Repository: aiogram/aiogram
Pull request #1161 (2023-04-08 15:33:32)

Forum topic in FSM
# Description

Added basic support of forum topics in FSM

## Type of change

- [x] New feature (non-breaking change which adds functionality)

# Checklist:

- [x] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes


github-actions[bot]: # :x: Changelog is required!

You need to add a brief description of the changes to the `CHANGES` directory.

For example, you can run `towncrier create <issue>.<type>` to create a file in the change directory and then write a description on that file.

Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/quickstart.html#creating-news-fragments)
github-actions[bot]: # :x: Changelog is required!

You need to add a brief description of the changes to the `CHANGES` directory.

For example, you can run `towncrier create <issue>.<type>` to create a file in the change directory and then write a description on that file.

Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/quickstart.html#creating-news-fragments)

Patch:
diff --git a/.butcher/methods/answerInlineQuery/entity.json b/.butcher/methods/answerInlineQuery/entity.json
index 410c1ef4..0a15ee68 100644
--- a/.butcher/methods/answerInlineQuery/entity.json
+++ b/.butcher/methods/answerInlineQuery/entity.json
@@ -38,9 +38,9 @@
       {
         "type": "Boolean",
         "required": false,
-        "description": "Pass True if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query",
-        "html_description": "<td>Pass <em>True</em> if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query</td>",
-        "rst_description": "Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query\n",
+        "description": "Pass True if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query.",
+        "html_description": "<td>Pass <em>True</em> if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query.</td>",
+        "rst_description": "Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query.\n",
         "name": "is_personal"
       },
       {
@@ -52,12 +52,12 @@
         "name": "next_offset"
       },
       {
-        "type": "String",
+        "type": "InlineQueryResultsButton",
         "required": false,
-        "description": "If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter switch_pm_parameter",
-        "html_description": "<td>If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter <em>switch_pm_parameter</em></td>",
-        "rst_description": "If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*\n",
-        "name": "switch_pm_text"
+        "description": "A JSON-serialized object describing a button to be shown above inline query results",
+        "html_description": "<td>A JSON-serialized object describing a button to be shown above inline query results</td>",
+        "rst_description": "A JSON-serialized object describing a button to be shown above inline query results\n",
+        "name": "button"
       },
       {
         "type": "String",
@@ -65,7 +65,23 @@
         "description": "Deep-linking parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.\n\nExample: An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a switch_inline button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.",
         "html_description": "<td><a href=\"/bots/features#deep-linking\">Deep-linking</a> parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only <code>A-Z</code>, <code>a-z</code>, <code>0-9</code>, <code>_</code> and <code>-</code> are allowed.<br/>\n<br/>\n<em>Example:</em> An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a <a href=\"#inlinekeyboardmarkup\"><em>switch_inline</em></a> button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.</td>",
         "rst_description": "`Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed.\n\n\n\n*Example:* An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a `https://core.telegram.org/bots/api#inlinekeyboardmarkup <https://core.telegram.org/bots/api#inlinekeyboardmarkup>`_ *switch_inline* button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.\n",
-        "name": "switch_pm_parameter"
+        "name": "switch_pm_parameter",
+        "deprecated": {
+          "version": "6.7",
+          "release_date": "2023-04-21"
+        }
+      },
+      {
+        "type": "String",
+        "required": false,
+        "description": "If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter switch_pm_parameter",
+        "html_description": "<td>If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter <em>switch_pm_parameter</em></td>",
+        "rst_description": "If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*\n",
+        "name": "switch_pm_text",
+        "deprecated": {
+          "version": "6.7",
+          "release_date": "2023-04-21"
+        }
       }
     ],
     "category": "methods"
diff --git a/.butcher/methods/getMyName/entity.json b/.butcher/methods/getMyName/entity.json
new file mode 100644
index 00000000..848fcb86
--- /dev/null
+++ b/.butcher/methods/getMyName/entity.json
@@ -0,0 +1,25 @@
+{
+  "meta": {},
+  "group": {
+    "title": "Available methods",
+    "anchor": "available-methods"
+  },
+  "object": {
+    "anchor": "getmyname",
+    "name": "getMyName",
+    "description": "Use this method to get the current bot name for the given user language. Returns BotName on success.",
+    "html_description": "<p>Use this method to get the current bot name for the given user language. Returns <a href=\"#botname\">BotName</a> on success.</p>",
+    "rst_description": "Use this method to get the current bot name for the given user language. Returns :class:`aiogram.types.bot_name.BotName` on success.",
+    "annotations": [
+      {
+        "type": "String",
+        "required": false,
+        "description": "A two-letter ISO 639-1 language code or an empty string",
+        "html_description": "<td>A two-letter ISO 639-1 language code or an empty string</td>",
+        "rst_description": "A two-letter ISO 639-1 language code or an empty string\n",
+        "name": "language_code"
+      }
+    ],
+    "category": "methods"
+  }
+}
diff --git a/.butcher/methods/setMyName/entity.json b/.butcher/methods/setMyName/entity.json
new file mode 100644
index 00000000..e6461251
--- /dev/null
+++ b/.butcher/methods/setMyName/entity.json
@@ -0,0 +1,33 @@
+{
+  "meta": {},
+  "group": {
+    "title": "Available methods",
+    "anchor": "available-methods"
+  },
+  "object": {
+    "anchor": "setmyname",
+    "name": "setMyName",
+    "description": "Use this method to change the bot's name. Returns True on success.",
+    "html_description": "<p>Use this method to change the bot's name. Returns <em>True</em> on success.</p>",
+    "rst_description": "Use this method to change the bot's name. Returns :code:`True` on success.",
+    "annotations": [
+      {
+        "type": "String",
+        "required": false,
+        "description": "New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language.",
+        "html_description": "<td>New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language.</td>",
+        "rst_description": "New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language.\n",
+        "name": "name"
+      },
+      {
+        "type": "String",
+        "required": false,
+        "description": "A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose language there is no dedicated name.",
+        "html_description": "<td>A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose language there is no dedicated name.</td>",
+        "rst_description": "A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose language there is no dedicated name.\n",
+        "name": "language_code"
+      }
+    ],
+    "category": "methods"
+  }
+}
diff --git a/.butcher/schema/schema.json b/.butcher/schema/schema.json
index 0005cd55..c3638d34 100644
--- a/.butcher/schema/schema.json
+++ b/.butcher/schema/schema.json
@@ -1,7 +1,7 @@
 {
   "api": {
-    "version": "6.6",
-    "release_date": "2023-03-09"
+    "version": "6.7",
+    "release_date": "2023-04-21"
   },
   "items": [
     {
@@ -2421,10 +2421,19 @@
         {
           "anchor": "writeaccessallowed",
           "name": "WriteAccessAllowed",
-          "description": "This object represents a service message about a user allowing a bot added to the attachment menu to write messages. Currently holds no information.",
-          "html_description": "<p>This object represents a service message about a user allowing a bot added to the attachment menu to write messages. Currently holds no information.</p>",
-          "rst_description": "This object represents a service message about a user allowing a bot added to the attachment menu to write messages. Currently holds no information.",
-          "annotations": [],
+          "description": "This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.",
+          "html_description": "<p>This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.</p>",
+          "rst_description": "This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.",
+          "annotations": [
+            {
+              "type": "String",
+              "description": "Name of the Web App which was launched from a link",
+              "html_description": "<td><em>Optional</em>. Name of the Web App which was launched from a link</td>",
+              "rst_description": "*Optional*. Name of the Web App which was launched from a link\n",
+              "name": "web_app_name",
+              "required": false
+            }
+          ],
           "category": "types"
         },
         {
@@ -2933,6 +2942,14 @@
               "name": "switch_inline_query_current_chat",
               "required": false
             },
+            {
+              "type": "SwitchInlineQueryChosenChat",
+              "description": "If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline query in the input field",
+              "html_description": "<td><em>Optional</em>. If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline query in the input field</td>",
+              "rst_description": "*Optional*. If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline query in the input field\n",
+              "name": "switch_inline_query_chosen_chat",
+              "required": false
+            },
             {
               "type": "CallbackGame",
               "description": "Description of the game that will be launched when the user presses the button.\n\nNOTE: This type of button must always be the first button in the first row.",
@@ -2994,6 +3011,56 @@
           ],
           "category": "types"
         },
+        {
+          "anchor": "switchinlinequerychosenchat",
+          "name": "SwitchInlineQueryChosenChat",
+          "description": "This object represents an inline button that switches the current user to inline mode in a chosen chat, with an optional default inline query.",
+          "html_description": "<p>This object represents an inline button that switches the current user to inline mode in a chosen chat, with an optional default inline query.</p>",
+          "rst_description": "This object represents an inline button that switches the current user to inline mode in a chosen chat, with an optional default inline query.",
+          "annotations": [
+            {
+              "type": "String",
+              "description": "The default inline query to be inserted in the input field. If left empty, only the bot's username will be inserted",
+              "html_description": "<td><em>Optional</em>. The default inline query to be inserted in the input field. If left empty, only the bot's username will be inserted</td>",
+              "rst_description": "*Optional*. The default inline query to be inserted in the input field. If left empty, only the bot's username will be inserted\n",
+              "name": "query",
+              "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if private chats with users can be chosen",
+              "html_description": "<td><em>Optional</em>. True, if private chats with users can be chosen</td>",
+              "rst_description": "*Optional*. True, if private chats with users can be chosen\n",
+              "name": "allow_user_chats",
+              "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if private chats with bots can be chosen",
+              "html_description": "<td><em>Optional</em>. True, if private chats with bots can be chosen</td>",
+              "rst_description": "*Optional*. True, if private chats with bots can be chosen\n",
+              "name": "allow_bot_chats",
+              "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if group and supergroup chats can be chosen",
+              "html_description": "<td><em>Optional</em>. True, if group and supergroup chats can be chosen</td>",
+              "rst_description": "*Optional*. True, if group and supergroup chats can be chosen\n",
+              "name": "allow_group_chats",
+              "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if channel chats can be chosen",
+              "html_description": "<td><em>Optional</em>. True, if channel chats can be chosen</td>",
+              "rst_description": "*Optional*. True, if channel chats can be chosen\n",
+              "name": "allow_channel_chats",
+              "required": false
+            }
+          ],
+          "category": "types"
+        },
         {
           "anchor": "callbackquery",
           "name": "CallbackQuery",
@@ -3807,6 +3874,14 @@
               "rst_description": "*Optional*. Chat invite link, which was used by the user to join the chat; for joining by invite link events only.\n",
               "name": "invite_link",
               "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if the user joined the chat via a chat folder invite link",
+              "html_description": "<td><em>Optional</em>. True, if the user joined the chat via a chat folder invite link</td>",
+              "rst_description": "*Optional*. True, if the user joined the chat via a chat folder invite link\n",
+              "name": "via_chat_folder_invite_link",
+              "required": false
             }
           ],
           "category": "types"
@@ -4252,6 +4327,24 @@
           ],
           "category": "types"
         },
+        {
+          "anchor": "botname",
+          "name": "BotName",
+          "description": "This object represents the bot's name.",
+          "html_description": "<p>This object represents the bot's name.</p>",
+          "rst_description": "This object represents the bot's name.",
+          "annotations": [
+            {
+              "type": "String",
+              "description": "The bot's name",
+              "html_description": "<td>The bot's name</td>",
+              "rst_description": "The bot's name\n",
+              "name": "name",
+              "required": true
+            }
+          ],
+          "category": "types"
+        },
         {
           "anchor": "botdescription",
           "name": "BotDescription",
@@ -7988,6 +8081,50 @@
           ],
           "category": "methods"
         },
+        {
+          "anchor": "setmyname",
+          "name": "setMyName",
+          "description": "Use this method to change the bot's name. Returns True on success.",
+          "html_description": "<p>Use this method to change the bot's name. Returns <em>True</em> on success.</p>",
+          "rst_description": "Use this method to change the bot's name. Returns :code:`True` on success.",
+          "annotations": [
+            {
+              "type": "String",
+              "required": false,
+              "description": "New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language.",
+              "html_description": "<td>New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language.</td>",
+              "rst_description": "New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language.\n",
+              "name": "name"
+            },
+            {
+              "type": "String",
+              "required": false,
+              "description": "A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose language there is no dedicated name.",
+              "html_description": "<td>A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose language there is no dedicated name.</td>",
+              "rst_description": "A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose language there is no dedicated name.\n",
+              "name": "language_code"
+            }
+          ],
+          "category": "methods"
+        },
+        {
+          "anchor": "getmyname",
+          "name": "getMyName",
+          "description": "Use this method to get the current bot name for the given user language. Returns BotName on success.",
+          "html_description": "<p>Use this method to get the current bot name for the given user language. Returns <a href=\"#botname\">BotName</a> on success.</p>",
+          "rst_description": "Use this method to get the current bot name for the given user language. Returns :class:`aiogram.types.bot_name.BotName` on success.",
+          "annotations": [
+            {
+              "type": "String",
+              "required": false,
+              "description": "A two-letter ISO 639-1 language code or an empty string",
+              "html_description": "<td>A two-letter ISO 639-1 language code or an empty string</td>",
+              "rst_description": "A two-letter ISO 639-1 language code or an empty string\n",
+              "name": "language_code"
+            }
+          ],
+          "category": "methods"
+        },
         {
           "anchor": "setmydescription",
           "name": "setMyDescription",
@@ -9451,9 +9588,9 @@
             {
               "type": "Boolean",
               "required": false,
-              "description": "Pass True if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query",
-              "html_description": "<td>Pass <em>True</em> if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query</td>",
-              "rst_description": "Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query\n",
+              "description": "Pass True if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query.",
+              "html_description": "<td>Pass <em>True</em> if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query.</td>",
+              "rst_description": "Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query.\n",
               "name": "is_personal"
             },
             {
@@ -9465,23 +9602,49 @@
               "name": "next_offset"
             },
             {
-              "type": "String",
+              "type": "InlineQueryResultsButton",
               "required": false,
-              "description": "If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter switch_pm_parameter",
-              "html_description": "<td>If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter <em>switch_pm_parameter</em></td>",
-              "rst_description": "If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*\n",
-              "name": "switch_pm_text"
+              "description": "A JSON-serialized object describing a button to be shown above inline query results",
+              "html_description": "<td>A JSON-serialized object describing a button to be shown above inline query results</td>",
+              "rst_description": "A JSON-serialized object describing a button to be shown above inline query results\n",
+              "name": "button"
+            }
+          ],
+          "category": "methods"
+        },
+        {
+          "anchor": "inlinequeryresultsbutton",
+          "name": "InlineQueryResultsButton",
+          "description": "This object represents a button to be shown above inline query results. You must use exactly one of the optional fields.",
+          "html_description": "<p>This object represents a button to be shown above inline query results. You <strong>must</strong> use exactly one of the optional fields.</p>",
+          "rst_description": "This object represents a button to be shown above inline query results. You **must** use exactly one of the optional fields.",
+          "annotations": [
+            {
+              "type": "String",
+              "description": "Label text on the button",
+              "html_description": "<td>Label text on the button</td>",
+              "rst_description": "Label text on the button\n",
+              "name": "text",
+              "required": true
+            },
+            {
+              "type": "WebAppInfo",
+              "description": "Description of the Web App that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method web_app_switch_inline_query inside the Web App.",
+              "html_description": "<td><em>Optional</em>. Description of the <a href=\"/bots/webapps\">Web App</a> that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method <em>web_app_switch_inline_query</em> inside the Web App.</td>",
+              "rst_description": "*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method *web_app_switch_inline_query* inside the Web App.\n",
+              "name": "web_app",
+              "required": false
             },
             {
               "type": "String",
-              "required": false,
-              "description": "Deep-linking parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.\n\nExample: An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a switch_inline button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.",
-              "html_description": "<td><a href=\"/bots/features#deep-linking\">Deep-linking</a> parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only <code>A-Z</code>, <code>a-z</code>, <code>0-9</code>, <code>_</code> and <code>-</code> are allowed.<br/>\n<br/>\n<em>Example:</em> An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a <a href=\"#inlinekeyboardmarkup\"><em>switch_inline</em></a> button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.</td>",
-              "rst_description": "`Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed.\n\n\n\n*Example:* An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a `https://core.telegram.org/bots/api#inlinekeyboardmarkup <https://core.telegram.org/bots/api#inlinekeyboardmarkup>`_ *switch_inline* button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.\n",
-              "name": "switch_pm_parameter"
+              "description": "Deep-linking parameter for the /start message sent to the bot when a user presses the button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.\n\nExample: An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a switch_inline button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.",
+              "html_description": "<td><em>Optional</em>. <a href=\"/bots/features#deep-linking\">Deep-linking</a> parameter for the /start message sent to the bot when a user presses the button. 1-64 characters, only <code>A-Z</code>, <code>a-z</code>, <code>0-9</code>, <code>_</code> and <code>-</code> are allowed.<br/>\n<br/>\n<em>Example:</em> An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a <a href=\"#inlinekeyboardmarkup\"><em>switch_inline</em></a> button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.</td>",
+              "rst_description": "*Optional*. `Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when a user presses the button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed.\n\n\n\n*Example:* An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a `https://core.telegram.org/bots/api#inlinekeyboardmarkup <https://core.telegram.org/bots/api#inlinekeyboardmarkup>`_ *switch_inline* button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.\n",
+              "name": "start_parameter",
+              "required": false
             }
           ],
-          "category": "methods"
+          "category": "types"
         },
         {
           "anchor": "inlinequeryresult",
diff --git a/.butcher/types/BotName/entity.json b/.butcher/types/BotName/entity.json
new file mode 100644
index 00000000..d18b4902
--- /dev/null
+++ b/.butcher/types/BotName/entity.json
@@ -0,0 +1,25 @@
+{
+  "meta": {},
+  "group": {
+    "title": "Available types",
+    "anchor": "available-types"
+  },
+  "object": {
+    "anchor": "botname",
+    "name": "BotName",
+    "description": "This object represents the bot's name.",
+    "html_description": "<p>This object represents the bot's name.</p>",
+    "rst_description": "This object represents the bot's name.",
+    "annotations": [
+      {
+        "type": "String",
+        "description": "The bot's name",
+        "html_description": "<td>The bot's name</td>",
+        "rst_description": "The bot's name\n",
+        "name": "name",
+        "required": true
+      }
+    ],
+    "category": "types"
+  }
+}
diff --git a/.butcher/types/ChatMemberUpdated/entity.json b/.butcher/types/ChatMemberUpdated/entity.json
index 7454b512..fbec5160 100644
--- a/.butcher/types/ChatMemberUpdated/entity.json
+++ b/.butcher/types/ChatMemberUpdated/entity.json
@@ -58,6 +58,14 @@
         "rst_description": "*Optional*. Chat invite link, which was used by the user to join the chat; for joining by invite link events only.\n",
         "name": "invite_link",
         "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if the user joined the chat via a chat folder invite link",
+        "html_description": "<td><em>Optional</em>. True, if the user joined the chat via a chat folder invite link</td>",
+        "rst_description": "*Optional*. True, if the user joined the chat via a chat folder invite link\n",
+        "name": "via_chat_folder_invite_link",
+        "required": false
       }
     ],
     "category": "types"
diff --git a/.butcher/types/InlineKeyboardButton/entity.json b/.butcher/types/InlineKeyboardButton/entity.json
index e73ff4dc..c7919e99 100644
--- a/.butcher/types/InlineKeyboardButton/entity.json
+++ b/.butcher/types/InlineKeyboardButton/entity.json
@@ -67,6 +67,14 @@
         "name": "switch_inline_query_current_chat",
         "required": false
       },
+      {
+        "type": "SwitchInlineQueryChosenChat",
+        "description": "If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline query in the input field",
+        "html_description": "<td><em>Optional</em>. If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline query in the input field</td>",
+        "rst_description": "*Optional*. If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline query in the input field\n",
+        "name": "switch_inline_query_chosen_chat",
+        "required": false
+      },
       {
         "type": "CallbackGame",
         "description": "Description of the game that will be launched when the user presses the button.\n\nNOTE: This type of button must always be the first button in the first row.",
diff --git a/.butcher/types/InlineQueryResultsButton/entity.json b/.butcher/types/InlineQueryResultsButton/entity.json
new file mode 100644
index 00000000..169b1c2c
--- /dev/null
+++ b/.butcher/types/InlineQueryResultsButton/entity.json
@@ -0,0 +1,41 @@
+{
+  "meta": {},
+  "group": {
+    "title": "Inline mode",
+    "anchor": "inline-mode"
+  },
+  "object": {
+    "anchor": "inlinequeryresultsbutton",
+    "name": "InlineQueryResultsButton",
+    "description": "This object represents a button to be shown above inline query results. You must use exactly one of the optional fields.",
+    "html_description": "<p>This object represents a button to be shown above inline query results. You <strong>must</strong> use exactly one of the optional fields.</p>",
+    "rst_description": "This object represents a button to be shown above inline query results. You **must** use exactly one of the optional fields.",
+    "annotations": [
+      {
+        "type": "String",
+        "description": "Label text on the button",
+        "html_description": "<td>Label text on the button</td>",
+        "rst_description": "Label text on the button\n",
+        "name": "text",
+        "required": true
+      },
+      {
+        "type": "WebAppInfo",
+        "description": "Description of the Web App that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method web_app_switch_inline_query inside the Web App.",
+        "html_description": "<td><em>Optional</em>. Description of the <a href=\"/bots/webapps\">Web App</a> that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method <em>web_app_switch_inline_query</em> inside the Web App.</td>",
+        "rst_description": "*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method *web_app_switch_inline_query* inside the Web App.\n",
+        "name": "web_app",
+        "required": false
+      },
+      {
+        "type": "String",
+        "description": "Deep-linking parameter for the /start message sent to the bot when a user presses the button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.\n\nExample: An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a switch_inline button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.",
+        "html_description": "<td><em>Optional</em>. <a href=\"/bots/features#deep-linking\">Deep-linking</a> parameter for the /start message sent to the bot when a user presses the button. 1-64 characters, only <code>A-Z</code>, <code>a-z</code>, <code>0-9</code>, <code>_</code> and <code>-</code> are allowed.<br/>\n<br/>\n<em>Example:</em> An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a <a href=\"#inlinekeyboardmarkup\"><em>switch_inline</em></a> button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.</td>",
+        "rst_description": "*Optional*. `Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when a user presses the button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed.\n\n\n\n*Example:* An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an OAuth link. Once done, the bot can offer a `https://core.telegram.org/bots/api#inlinekeyboardmarkup <https://core.telegram.org/bots/api#inlinekeyboardmarkup>`_ *switch_inline* button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities.\n",
+        "name": "start_parameter",
+        "required": false
+      }
+    ],
+    "category": "types"
+  }
+}
diff --git a/.butcher/types/SwitchInlineQueryChosenChat/entity.json b/.butcher/types/SwitchInlineQueryChosenChat/entity.json
new file mode 100644
index 00000000..f36f3c33
--- /dev/null
+++ b/.butcher/types/SwitchInlineQueryChosenChat/entity.json
@@ -0,0 +1,57 @@
+{
+  "meta": {},
+  "group": {
+    "title": "Available types",
+    "anchor": "available-types"
+  },
+  "object": {
+    "anchor": "switchinlinequerychosenchat",
+    "name": "SwitchInlineQueryChosenChat",
+    "description": "This object represents an inline button that switches the current user to inline mode in a chosen chat, with an optional default inline query.",
+    "html_description": "<p>This object represents an inline button that switches the current user to inline mode in a chosen chat, with an optional default inline query.</p>",
+    "rst_description": "This object represents an inline button that switches the current user to inline mode in a chosen chat, with an optional default inline query.",
+    "annotations": [
+      {
+        "type": "String",
+        "description": "The default inline query to be inserted in the input field. If left empty, only the bot's username will be inserted",
+        "html_description": "<td><em>Optional</em>. The default inline query to be inserted in the input field. If left empty, only the bot's username will be inserted</td>",
+        "rst_description": "*Optional*. The default inline query to be inserted in the input field. If left empty, only the bot's username will be inserted\n",
+        "name": "query",
+        "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if private chats with users can be chosen",
+        "html_description": "<td><em>Optional</em>. True, if private chats with users can be chosen</td>",
+        "rst_description": "*Optional*. True, if private chats with users can be chosen\n",
+        "name": "allow_user_chats",
+        "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if private chats with bots can be chosen",
+        "html_description": "<td><em>Optional</em>. True, if private chats with bots can be chosen</td>",
+        "rst_description": "*Optional*. True, if private chats with bots can be chosen\n",
+        "name": "allow_bot_chats",
+        "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if group and supergroup chats can be chosen",
+        "html_description": "<td><em>Optional</em>. True, if group and supergroup chats can be chosen</td>",
+        "rst_description": "*Optional*. True, if group and supergroup chats can be chosen\n",
+        "name": "allow_group_chats",
+        "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if channel chats can be chosen",
+        "html_description": "<td><em>Optional</em>. True, if channel chats can be chosen</td>",
+        "rst_description": "*Optional*. True, if channel chats can be chosen\n",
+        "name": "allow_channel_chats",
+        "required": false
+      }
+    ],
+    "category": "types"
+  }
+}
diff --git a/.butcher/types/WriteAccessAllowed/entity.json b/.butcher/types/WriteAccessAllowed/entity.json
index 2ddeba40..62d83fdd 100644
--- a/.butcher/types/WriteAccessAllowed/entity.json
+++ b/.butcher/types/WriteAccessAllowed/entity.json
@@ -7,10 +7,19 @@
   "object": {
     "anchor": "writeaccessallowed",
     "name": "WriteAccessAllowed",
-    "description": "This object represents a service message about a user allowing a bot added to the attachment menu to write messages. Currently holds no information.",
-    "html_description": "<p>This object represents a service message about a user allowing a bot added to the attachment menu to write messages. Currently holds no information.</p>",
-    "rst_description": "This object represents a service message about a user allowing a bot added to the attachment menu to write messages. Currently holds no information.",
-    "annotations": [],
+    "description": "This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.",
+    "html_description": "<p>This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.</p>",
+    "rst_description": "This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.",
+    "annotations": [
+      {
+        "type": "String",
+        "description": "Name of the Web App which was launched from a link",
+        "html_description": "<td><em>Optional</em>. Name of the Web App which was launched from a link</td>",
+        "rst_description": "*Optional*. Name of the Web App which was launched from a link\n",
+        "name": "web_app_name",
+        "required": false
+      }
+    ],
     "category": "types"
   }
 }
diff --git a/CHANGES/1160.bugfix b/CHANGES/1160.bugfix
new file mode 100644
index 00000000..68e82835
--- /dev/null
+++ b/CHANGES/1160.bugfix
@@ -0,0 +1,1 @@
+Added missing FORUM_TOPIC_EDITED value to content_type property
diff --git a/CHANGES/1161.feature.rst b/CHANGES/1161.feature.rst
new file mode 100644
index 00000000..819c697c
--- /dev/null
+++ b/CHANGES/1161.feature.rst
@@ -0,0 +1,17 @@
+Added support for FSM in Forum topics.
+
+The strategy can be changed in dispatcher:
+
+.. code-block:: python
+
+    from aiogram.fsm.strategy import FSMStrategy
+    ...
+    dispatcher = Dispatcher(
+        fsm_strategy=FSMStrategy.USER_IN_THREAD,
+        storage=...,  # Any persistent storage
+    )
+
+.. note::
+
+    If you have implemented you own storages you should extend record key generation
+    with new one attribute - `thread_id`
diff --git a/CHANGES/1162.bugfix.rst b/CHANGES/1162.bugfix.rst
new file mode 100644
index 00000000..16d5be16
--- /dev/null
+++ b/CHANGES/1162.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed compatibility with Python 3.8-3.9
diff --git a/CHANGES/1163.feature.rst b/CHANGES/1163.feature.rst
new file mode 100644
index 00000000..06e97c3d
--- /dev/null
+++ b/CHANGES/1163.feature.rst
@@ -0,0 +1,4 @@
+Improved CallbackData serialization.
+
+- Minimized UUID (hex without dashes)
+- Replaced bool values with int (true=1, false=0)
diff --git a/CHANGES/1168.misc.rst b/CHANGES/1168.misc.rst
new file mode 100644
index 00000000..0bce876f
--- /dev/null
+++ b/CHANGES/1168.misc.rst
@@ -0,0 +1,6 @@
+Added full support of `Bot API 6.7 <https://core.telegram.org/bots/api-changelog#april-21-2023>`_
+
+.. warning::
+
+    Note that arguments *switch_pm_parameter* and *switch_pm_text* was deprecated
+    and should be changed to *button* argument as described in API docs.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ada02324..93f96099 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,1 +1,1 @@
-Please read the [Contributing](https://docs.aiogram.dev/en/dev-3.x/contributing/) guidelines in the documentation site.
+Please read the [Contributing](https://docs.aiogram.dev/en/dev-3.x/contributing.html) guidelines in the documentation site.
diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py
index acb99aae..a5258eaa 100644
--- a/aiogram/client/bot.py
+++ b/aiogram/client/bot.py
@@ -71,6 +71,7 @@ from ..methods import (
     GetMyCommands,
     GetMyDefaultAdministratorRights,
     GetMyDescription,
+    GetMyName,
     GetMyShortDescription,
     GetStickerSet,
     GetUpdates,
@@ -115,6 +116,7 @@ from ..methods import (
     SetMyCommands,
     SetMyDefaultAdministratorRights,
     SetMyDescription,
+    SetMyName,
     SetMyShortDescription,
     SetPassportDataErrors,
     SetStickerEmojiList,
@@ -140,6 +142,7 @@ from ..types import (
     BotCommand,
     BotCommandScope,
     BotDescription,
+    BotName,
     BotShortDescription,
     Chat,
     ChatAdministratorRights,
@@ -158,6 +161,7 @@ from ..types import (
     GameHighScore,
     InlineKeyboardMarkup,
     InlineQueryResult,
+    InlineQueryResultsButton,
     InputFile,
     InputMedia,
     InputMediaAudio,
@@ -481,8 +485,9 @@ class Bot(ContextInstanceMixin["Bot"]):
         cache_time: Optional[int] = None,
         is_personal: Optional[bool] = None,
         next_offset: Optional[str] = None,
-        switch_pm_text: Optional[str] = None,
+        button: Optional[InlineQueryResultsButton] = None,
         switch_pm_parameter: Optional[str] = None,
+        switch_pm_text: Optional[str] = None,
         request_timeout: Optional[int] = None,
     ) -> bool:
         """
@@ -495,10 +500,11 @@ class Bot(ContextInstanceMixin["Bot"]):
         :param inline_query_id: Unique identifier for the answered query
         :param results: A JSON-serialized array of results for the inline query
         :param cache_time: The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300.
-        :param is_personal: Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query
+        :param is_personal: Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query.
         :param next_offset: Pass the offset that a client should send in the next query with the same text to receive more results. Pass an empty string if there are no more results or if you don't support pagination. Offset length can't exceed 64 bytes.
-        :param switch_pm_text: If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*
+        :param button: A JSON-serialized object describing a button to be shown above inline query results
         :param switch_pm_parameter: `Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed.
+        :param switch_pm_text: If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*
         :param request_timeout: Request timeout
         :return: On success, :code:`True` is returned.
         """
@@ -509,8 +515,9 @@ class Bot(ContextInstanceMixin["Bot"]):
             cache_time=cache_time,
             is_personal=is_personal,
             next_offset=next_offset,
-            switch_pm_text=switch_pm_text,
+            button=button,
             switch_pm_parameter=switch_pm_parameter,
+            switch_pm_text=switch_pm_text,
         )
         return await self(call, request_timeout=request_timeout)
 
@@ -3913,3 +3920,46 @@ class Bot(ContextInstanceMixin["Bot"]):
             title=title,
         )
         return await self(call, request_timeout=request_timeout)
+
+    async def get_my_name(
+        self,
+        language_code: Optional[str] = None,
+        request_timeout: Optional[int] = None,
+    ) -> BotName:
+        """
+        Use this method to get the current bot name for the given user language. Returns :class:`aiogram.types.bot_name.BotName` on success.
+
+        Source: https://core.telegram.org/bots/api#getmyname
+
+        :param language_code: A two-letter ISO 639-1 language code or an empty string
+        :param request_timeout: Request timeout
+        :return: Returns :class:`aiogram.types.bot_name.BotName` on success.
+        """
+
+        call = GetMyName(
+            language_code=language_code,
+        )
+        return await self(call, request_timeout=request_timeout)
+
+    async def set_my_name(
+        self,
+        name: Optional[str] = None,
+        language_code: Optional[str] = None,
+        request_timeout: Optional[int] = None,
+    ) -> bool:
+        """
+        Use this method to change the bot's name. Returns :code:`True` on success.
+
+        Source: https://core.telegram.org/bots/api#setmyname
+
+        :param name: New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language.
+        :param language_code: A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose language there is no dedicated name.
+        :param request_timeout: Request timeout
+        :return: Returns :code:`True` on success.
+        """
+
+        call = SetMyName(
+            name=name,
+            language_code=language_code,
+        )
+        return await self(call, request_timeout=request_timeout)
diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py
index 8485d83c..9c61a447 100644
--- a/aiogram/dispatcher/dispatcher.py
+++ b/aiogram/dispatcher/dispatcher.py
@@ -92,8 +92,8 @@ class Dispatcher(Router):
 
         self.workflow_data: Dict[str, Any] = kwargs
         self._running_lock = Lock()
-        self._stop_signal = Event()
-        self._stopped_signal = Event()
+        self._stop_signal: Optional[Event] = None
+        self._stopped_signal: Optional[Event] = None
 
     def __getitem__(self, item: str) -> Any:
         return self.workflow_data[item]
@@ -430,6 +430,8 @@ class Dispatcher(Router):
         """
         if not self._running_lock.locked():
             raise RuntimeError("Polling is not started")
+        if not self._stop_signal or not self._stopped_signal:
+            return
         self._stop_signal.set()
         await self._stopped_signal.wait()
 
@@ -438,6 +440,8 @@ class Dispatcher(Router):
             return
 
         loggers.dispatcher.warning("Received %s signal", sig.name)
+        if not self._stop_signal:
+            return
         self._stop_signal.set()
 
     async def start_polling(
@@ -473,6 +477,11 @@ class Dispatcher(Router):
             )
 
         async with self._running_lock:  # Prevent to run this method twice at a once
+            if self._stop_signal is None:
+                self._stop_signal = Event()
+            if self._stopped_signal is None:
+                self._stopped_signal = Event()
+
             self._stop_signal.clear()
             self._stopped_signal.clear()
 
diff --git a/aiogram/dispatcher/middlewares/user_context.py b/aiogram/dispatcher/middlewares/user_context.py
index 3531beb7..9ede4334 100644
--- a/aiogram/dispatcher/middlewares/user_context.py
+++ b/aiogram/dispatcher/middlewares/user_context.py
@@ -4,6 +4,10 @@ from typing import Any, Awaitable, Callable, Dict, Iterator, Optional, Tuple
 from aiogram.dispatcher.middlewares.base import BaseMiddleware
 from aiogram.types import Chat, TelegramObject, Update, User
 
+EVENT_FROM_USER_KEY = "event_from_user"
+EVENT_CHAT_KEY = "event_chat"
+EVENT_THREAD_ID_KEY = "event_thread_id"
+
 
 class UserContextMiddleware(BaseMiddleware):
     async def __call__(
@@ -14,61 +18,64 @@ class UserContextMiddleware(BaseMiddleware):
     ) -> Any:
         if not isinstance(event, Update):
             raise RuntimeError("UserContextMiddleware got an unexpected event type!")
-        chat, user = self.resolve_event_context(event=event)
-        with self.context(chat=chat, user=user):
-            if user is not None:
-                data["event_from_user"] = user
-            if chat is not None:
-                data["event_chat"] = chat
-            return await handler(event, data)
-
-    @contextmanager
-    def context(self, chat: Optional[Chat] = None, user: Optional[User] = None) -> Iterator[None]:
-        chat_token = None
-        user_token = None
-        if chat:
-            chat_token = chat.set_current(chat)
-        if user:
-            user_token = user.set_current(user)
-        try:
-            yield
-        finally:
-            if chat and chat_token:
-                chat.reset_current(chat_token)
-            if user and user_token:
-                user.reset_current(user_token)
+        chat, user, thread_id = self.resolve_event_context(event=event)
+        if user is not None:
+            data[EVENT_FROM_USER_KEY] = user
+        if chat is not None:
+            data[EVENT_CHAT_KEY] = chat
+        if thread_id is not None:
+            data[EVENT_THREAD_ID_KEY] = thread_id
+        return await handler(event, data)
 
     @classmethod
-    def resolve_event_context(cls, event: Update) -> Tuple[Optional[Chat], Optional[User]]:
+    def resolve_event_context(
+        cls, event: Update
+    ) -> Tuple[Optional[Chat], Optional[User], Optional[int]]:
         """
         Resolve chat and user instance from Update object
         """
         if event.message:
-            return event.message.chat, event.message.from_user
+            return (
+                event.message.chat,
+                event.message.from_user,
+                event.message.message_thread_id if event.message.is_topic_message else None,
+            )
         if event.edited_message:
-            return event.edited_message.chat, event.edited_message.from_user
+            return (
+                event.edited_message.chat,
+                event.edited_message.from_user,
+                event.edited_message.message_thread_id
+                if event.edited_message.is_topic_message
+                else None,
+            )
         if event.channel_post:
-            return event.channel_post.chat, None
+            return event.channel_post.chat, None, None
         if event.edited_channel_post:
-            return event.edited_channel_post.chat, None
+            return event.edited_channel_post.chat, None, None
         if event.inline_query:
-            return None, event.inline_query.from_user
+            return None, event.inline_query.from_user, None
         if event.chosen_inline_result:
-            return None, event.chosen_inline_result.from_user
+            return None, event.chosen_inline_result.from_user, None
         if event.callback_query:
             if event.callback_query.message:
-                return event.callback_query.message.chat, event.callback_query.from_user
-            return None, event.callback_query.from_user
+                return (
+                    event.callback_query.message.chat,
+                    event.callback_query.from_user,
+                    event.callback_query.message.message_thread_id
+                    if event.callback_query.message.is_topic_message
+                    else None,
+                )
+            return None, event.callback_query.from_user, None
         if event.shipping_query:
-            return None, event.shipping_query.from_user
+            return None, event.shipping_query.from_user, None
         if event.pre_checkout_query:
-            return None, event.pre_checkout_query.from_user
+            return None, event.pre_checkout_query.from_user, None
         if event.poll_answer:
-            return None, event.poll_answer.user
+            return None, event.poll_answer.user, None
         if event.my_chat_member:
-            return event.my_chat_member.chat, event.my_chat_member.from_user
+            return event.my_chat_member.chat, event.my_chat_member.from_user, None
         if event.chat_member:
-            return event.chat_member.chat, event.chat_member.from_user
+            return event.chat_member.chat, event.chat_member.from_user, None
         if event.chat_join_request:
-            return event.chat_join_request.chat, event.chat_join_request.from_user
-        return None, None
+            return event.chat_join_request.chat, event.chat_join_request.from_user, None
+        return None, None, None
diff --git a/aiogram/filters/callback_data.py b/aiogram/filters/callback_data.py
index 84ec1e88..05d7783d 100644
--- a/aiogram/filters/callback_data.py
+++ b/aiogram/filters/callback_data.py
@@ -67,7 +67,11 @@ class CallbackData(BaseModel):
             return ""
         if isinstance(value, Enum):
             return str(value.value)
-        if isinstance(value, (int, str, float, Decimal, Fraction, UUID)):
+        if isinstance(value, UUID):
+            return value.hex
+        if isinstance(value, bool):
+            return str(int(value))
+        if isinstance(value, (int, str, float, Decimal, Fraction)):
             return str(value)
         raise ValueError(
             f"Attribute {key}={value!r} of type {type(value).__name__!r}"
diff --git a/aiogram/fsm/middleware.py b/aiogram/fsm/middleware.py
index 0232ff0a..6de91a83 100644
--- a/aiogram/fsm/middleware.py
+++ b/aiogram/fsm/middleware.py
@@ -47,25 +47,42 @@ class FSMContextMiddleware(BaseMiddleware):
     ) -> Optional[FSMContext]:
         user = data.get("event_from_user")
         chat = data.get("event_chat")
+        thread_id = data.get("event_thread_id")
         chat_id = chat.id if chat else None
         user_id = user.id if user else None
-        return self.resolve_context(bot=bot, chat_id=chat_id, user_id=user_id, destiny=destiny)
+        return self.resolve_context(
+            bot=bot,
+            chat_id=chat_id,
+            user_id=user_id,
+            thread_id=thread_id,
+            destiny=destiny,
+        )
 
     def resolve_context(
         self,
         bot: Bot,
         chat_id: Optional[int],
         user_id: Optional[int],
+        thread_id: Optional[int] = None,
         destiny: str = DEFAULT_DESTINY,
     ) -> Optional[FSMContext]:
         if chat_id is None:
             chat_id = user_id
 
         if chat_id is not None and user_id is not None:
-            chat_id, user_id = apply_strategy(
-                chat_id=chat_id, user_id=user_id, strategy=self.strategy
+            chat_id, user_id, thread_id = apply_strategy(
+                chat_id=chat_id,
+                user_id=user_id,
+                thread_id=thread_id,
+                strategy=self.strategy,
+            )
+            return self.get_context(
+                bot=bot,
+                chat_id=chat_id,
+                user_id=user_id,
+                thread_id=thread_id,
+                destiny=destiny,
             )
-            return self.get_context(bot=bot, chat_id=chat_id, user_id=user_id, destiny=destiny)
         return None
 
     def get_context(
@@ -73,6 +90,7 @@ class FSMContextMiddleware(BaseMiddleware):
         bot: Bot,
         chat_id: int,
         user_id: int,
+        thread_id: Optional[int] = None,
         destiny: str = DEFAULT_DESTINY,
     ) -> FSMContext:
         return FSMContext(
@@ -81,6 +99,7 @@ class FSMContextMiddleware(BaseMiddleware):
                 user_id=user_id,
                 chat_id=chat_id,
                 bot_id=bot.id,
+                thread_id=thread_id,
                 destiny=destiny,
             ),
         )
diff --git a/aiogram/fsm/storage/base.py b/aiogram/fsm/storage/base.py
index b3551060..52cb62f2 100644
--- a/aiogram/fsm/storage/base.py
+++ b/aiogram/fsm/storage/base.py
@@ -15,6 +15,7 @@ class StorageKey:
     bot_id: int
     chat_id: int
     user_id: int
+    thread_id: Optional[int] = None
     destiny: str = DEFAULT_DESTINY
 
 
diff --git a/aiogram/fsm/storage/redis.py b/aiogram/fsm/storage/redis.py
index 76450f86..6a55d881 100644
--- a/aiogram/fsm/storage/redis.py
+++ b/aiogram/fsm/storage/redis.py
@@ -70,7 +70,10 @@ class DefaultKeyBuilder(KeyBuilder):
         parts = [self.prefix]
         if self.with_bot_id:
             parts.append(str(key.bot_id))
-        parts.extend([str(key.chat_id), str(key.user_id)])
+        parts.append(str(key.chat_id))
+        if key.thread_id:
+            parts.append(str(key.thread_id))
+        parts.append(str(key.user_id))
         if self.with_destiny:
             parts.append(key.destiny)
         elif key.destiny != DEFAULT_DESTINY:
diff --git a/aiogram/fsm/strategy.py b/aiogram/fsm/strategy.py
index 4f540a4a..227924cb 100644
--- a/aiogram/fsm/strategy.py
+++ b/aiogram/fsm/strategy.py
@@ -1,16 +1,24 @@
 from enum import Enum, auto
-from typing import Tuple
+from typing import Optional, Tuple
 
 
 class FSMStrategy(Enum):
     USER_IN_CHAT = auto()
     CHAT = auto()
     GLOBAL_USER = auto()
+    USER_IN_THREAD = auto()
 
 
-def apply_strategy(chat_id: int, user_id: int, strategy: FSMStrategy) -> Tuple[int, int]:
+def apply_strategy(
+    strategy: FSMStrategy,
+    chat_id: int,
+    user_id: int,
+    thread_id: Optional[int] = None,
+) -> Tuple[int, int, Optional[int]]:
     if strategy == FSMStrategy.CHAT:
-        return chat_id, chat_id
+        return chat_id, chat_id, None
     if strategy == FSMStrategy.GLOBAL_USER:
-        return user_id, user_id
-    return chat_id, user_id
+        return user_id, user_id, None
+    if strategy == FSMStrategy.USER_IN_THREAD:
+        return chat_id, user_id, thread_id
+    return chat_id, user_id, None
diff --git a/aiogram/methods/__init__.py b/aiogram/methods/__init__.py
index 5bf0edf5..fd887d7e 100644
--- a/aiogram/methods/__init__.py
+++ b/aiogram/methods/__init__.py
@@ -48,6 +48,7 @@ from .get_me import GetMe
 from .get_my_commands import GetMyCommands
 from .get_my_default_administrator_rights import GetMyDefaultAdministratorRights
 from .get_my_description import GetMyDescription
+from .get_my_name import GetMyName
 from .get_my_short_description import GetMyShortDescription
 from .get_sticker_set import GetStickerSet
 from .get_updates import GetUpdates
@@ -92,6 +93,7 @@ from .set_game_score import SetGameScore
 from .set_my_commands import SetMyCommands
 from .set_my_default_administrator_rights import SetMyDefaultAdministratorRights
 from .set_my_description import SetMyDescription
+from .set_my_name import SetMyName
 from .set_my_short_description import SetMyShortDescription
 from .set_passport_data_errors import SetPassportDataErrors
 from .set_sticker_emoji_list import SetStickerEmojiList
@@ -161,6 +163,7 @@ __all__ = (
     "GetMyCommands",
     "GetMyDefaultAdministratorRights",
     "GetMyDescription",
+    "GetMyName",
     "GetMyShortDescription",
     "GetStickerSet",
     "GetUpdates",
@@ -207,6 +210,7 @@ __all__ = (
     "SetMyCommands",
     "SetMyDefaultAdministratorRights",
     "SetMyDescription",
+    "SetMyName",
     "SetMyShortDescription",
     "SetPassportDataErrors",
     "SetStickerEmojiList",
diff --git a/aiogram/methods/answer_inline_query.py b/aiogram/methods/answer_inline_query.py
index f7760cb0..7b6b6d88 100644
--- a/aiogram/methods/answer_inline_query.py
+++ b/aiogram/methods/answer_inline_query.py
@@ -2,7 +2,9 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING, List, Optional
 
-from ..types import InlineQueryResult
+from pydantic import Field
+
+from ..types import InlineQueryResult, InlineQueryResultsButton
 from .base import TelegramMethod
 
 
@@ -25,10 +27,18 @@ class AnswerInlineQuery(TelegramMethod[bool]):
     cache_time: Optional[int] = None
     """The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300."""
     is_personal: Optional[bool] = None
-    """Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query"""
+    """Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query."""
     next_offset: Optional[str] = None
     """Pass the offset that a client should send in the next query with the same text to receive more results. Pass an empty string if there are no more results or if you don't support pagination. Offset length can't exceed 64 bytes."""
-    switch_pm_text: Optional[str] = None
-    """If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*"""
-    switch_pm_parameter: Optional[str] = None
-    """`Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed."""
+    button: Optional[InlineQueryResultsButton] = None
+    """A JSON-serialized object describing a button to be shown above inline query results"""
+    switch_pm_parameter: Optional[str] = Field(None, deprecated=True)
+    """`Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed.
+
+.. deprecated:: API:6.7
+   https://core.telegram.org/bots/api-changelog#april-21-2023"""
+    switch_pm_text: Optional[str] = Field(None, deprecated=True)
+    """If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*
+
+.. deprecated:: API:6.7
+   https://core.telegram.org/bots/api-changelog#april-21-2023"""
diff --git a/aiogram/methods/get_my_name.py b/aiogram/methods/get_my_name.py
new file mode 100644
index 00000000..202b21ff
--- /dev/null
+++ b/aiogram/methods/get_my_name.py
@@ -0,0 +1,18 @@
+from typing import Optional
+
+from ..types import BotName
+from .base import TelegramMethod
+
+
+class GetMyName(TelegramMethod[BotName]):
+    """
+    Use this method to get the current bot name for the given user language. Returns :class:`aiogram.types.bot_name.BotName` on success.
+
+    Source: https://core.telegram.org/bots/api#getmyname
+    """
+
+    __returning__ = BotName
+    __api_method__ = "getMyName"
+
+    language_code: Optional[str] = None
+    """A two-letter ISO 639-1 language code or an empty string"""
diff --git a/aiogram/methods/set_my_name.py b/aiogram/methods/set_my_name.py
new file mode 100644
index 00000000..9b392281
--- /dev/null
+++ b/aiogram/methods/set_my_name.py
@@ -0,0 +1,19 @@
+from typing import Optional
+
+from .base import TelegramMethod
+
+
+class SetMyName(TelegramMethod[bool]):
+    """
+    Use this method to change the bot's name. Returns :code:`True` on success.
+
+    Source: https://core.telegram.org/bots/api#setmyname
+    """
+
+    __returning__ = bool
+    __api_method__ = "setMyName"
+
+    name: Optional[str] = None
+    """New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language."""
+    language_code: Optional[str] = None
+    """A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose language there is no dedicated name."""
diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py
index 85fd9338..4808f7fe 100644
--- a/aiogram/types/__init__.py
+++ b/aiogram/types/__init__.py
@@ -15,6 +15,7 @@ from .bot_command_scope_chat_administrators import BotCommandScopeChatAdministra
 from .bot_command_scope_chat_member import BotCommandScopeChatMember
 from .bot_command_scope_default import BotCommandScopeDefault
 from .bot_description import BotDescription
+from .bot_name import BotName
 from .bot_short_description import BotShortDescription
 from .callback_game import CallbackGame
 from .callback_query import CallbackQuery
@@ -77,6 +78,7 @@ from .inline_query_result_photo import InlineQueryResultPhoto
 from .inline_query_result_venue import InlineQueryResultVenue
 from .inline_query_result_video import InlineQueryResultVideo
 from .inline_query_result_voice import InlineQueryResultVoice
+from .inline_query_results_button import InlineQueryResultsButton
 from .input_contact_message_content import InputContactMessageContent
 from .input_file import BufferedInputFile, FSInputFile, InputFile, URLInputFile
 from .input_invoice_message_content import InputInvoiceMessageContent
@@ -139,6 +141,7 @@ from .shipping_query import ShippingQuery
 from .sticker import Sticker
 from .sticker_set import StickerSet
 from .successful_payment import SuccessfulPayment
+from .switch_inline_query_chosen_chat import SwitchInlineQueryChosenChat
 from .update import Update
 from .user import User
 from .user_profile_photos import UserProfilePhotos
@@ -169,6 +172,7 @@ __all__ = (
     "BotCommandScopeChatMember",
     "BotCommandScopeDefault",
     "BotDescription",
+    "BotName",
     "BotShortDescription",
     "BufferedInputFile",
     "CallbackGame",
@@ -234,6 +238,7 @@ __all__ = (
     "InlineQueryResultVenue",
     "InlineQueryResultVideo",
     "InlineQueryResultVoice",
+    "InlineQueryResultsButton",
     "InputContactMessageContent",
     "InputFile",
     "InputInvoiceMessageContent",
@@ -294,6 +299,7 @@ __all__ = (
     "Sticker",
     "StickerSet",
     "SuccessfulPayment",
+    "SwitchInlineQueryChosenChat",
     "TelegramObject",
     "UNSET_PARSE_MODE",
     "URLInputFile",
diff --git a/aiogram/types/bot_name.py b/aiogram/types/bot_name.py
new file mode 100644
index 00000000..103f1fde
--- /dev/null
+++ b/aiogram/types/bot_name.py
@@ -0,0 +1,12 @@
+from .base import TelegramObject
+
+
+class BotName(TelegramObject):
+    """
+    This object represents the bot's name.
+
+    Source: https://core.telegram.org/bots/api#botname
+    """
+
+    name: str
+    """The bot's name"""
diff --git a/aiogram/types/chat_member_updated.py b/aiogram/types/chat_member_updated.py
index e4e4a340..5931da6f 100644
--- a/aiogram/types/chat_member_updated.py
+++ b/aiogram/types/chat_member_updated.py
@@ -52,3 +52,5 @@ class ChatMemberUpdated(TelegramObject):
     """New information about the chat member"""
     invite_link: Optional[ChatInviteLink] = None
     """*Optional*. Chat invite link, which was used by the user to join the chat; for joining by invite link events only."""
+    via_chat_folder_invite_link: Optional[bool] = None
+    """*Optional*. True, if the user joined the chat via a chat folder invite link"""
diff --git a/aiogram/types/inline_keyboard_button.py b/aiogram/types/inline_keyboard_button.py
index c4208b70..977fae8d 100644
--- a/aiogram/types/inline_keyboard_button.py
+++ b/aiogram/types/inline_keyboard_button.py
@@ -7,6 +7,7 @@ from .base import MutableTelegramObject
 if TYPE_CHECKING:
     from .callback_game import CallbackGame
     from .login_url import LoginUrl
+    from .switch_inline_query_chosen_chat import SwitchInlineQueryChosenChat
     from .web_app_info import WebAppInfo
 
 
@@ -31,6 +32,8 @@ class InlineKeyboardButton(MutableTelegramObject):
     """*Optional*. If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. May be empty, in which case just the bot's username will be inserted."""
     switch_inline_query_current_chat: Optional[str] = None
     """*Optional*. If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. May be empty, in which case only the bot's username will be inserted."""
+    switch_inline_query_chosen_chat: Optional[SwitchInlineQueryChosenChat] = None
+    """*Optional*. If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline query in the input field"""
     callback_game: Optional[CallbackGame] = None
     """*Optional*. Description of the game that will be launched when the user presses the button."""
     pay: Optional[bool] = None
diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py
index 1feb6f7f..55eab72f 100644
--- a/aiogram/types/inline_query.py
+++ b/aiogram/types/inline_query.py
@@ -9,6 +9,7 @@ from .base import TelegramObject
 if TYPE_CHECKING:
     from ..methods import AnswerInlineQuery
     from .inline_query_result import InlineQueryResult
+    from .inline_query_results_button import InlineQueryResultsButton
     from .location import Location
     from .user import User
 
@@ -39,8 +40,9 @@ class InlineQuery(TelegramObject):
         cache_time: Optional[int] = None,
         is_personal: Optional[bool] = None,
         next_offset: Optional[str] = None,
-        switch_pm_text: Optional[str] = None,
+        button: Optional[InlineQueryResultsButton] = None,
         switch_pm_parameter: Optional[str] = None,
+        switch_pm_text: Optional[str] = None,
         **kwargs: Any,
     ) -> AnswerInlineQuery:
         """
@@ -57,10 +59,11 @@ class InlineQuery(TelegramObject):
 
         :param results: A JSON-serialized array of results for the inline query
         :param cache_time: The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300.
-        :param is_personal: Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query
+        :param is_personal: Pass :code:`True` if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query.
         :param next_offset: Pass the offset that a client should send in the next query with the same text to receive more results. Pass an empty string if there are no more results or if you don't support pagination. Offset length can't exceed 64 bytes.
-        :param switch_pm_text: If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*
+        :param button: A JSON-serialized object describing a button to be shown above inline query results
         :param switch_pm_parameter: `Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed.
+        :param switch_pm_text: If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter *switch_pm_parameter*
         :return: instance of method :class:`aiogram.methods.answer_inline_query.AnswerInlineQuery`
         """
         # DO NOT EDIT MANUALLY!!!
@@ -74,7 +77,8 @@ class InlineQuery(TelegramObject):
             cache_time=cache_time,
             is_personal=is_personal,
             next_offset=next_offset,
-            switch_pm_text=switch_pm_text,
+            button=button,
             switch_pm_parameter=switch_pm_parameter,
+            switch_pm_text=switch_pm_text,
             **kwargs,
         )
diff --git a/aiogram/types/inline_query_results_button.py b/aiogram/types/inline_query_results_button.py
new file mode 100644
index 00000000..4a8f59df
--- /dev/null
+++ b/aiogram/types/inline_query_results_button.py
@@ -0,0 +1,23 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Optional
+
+from .base import TelegramObject
+
+if TYPE_CHECKING:
+    from .web_app_info import WebAppInfo
+
+
+class InlineQueryResultsButton(TelegramObject):
+    """
+    This object represents a button to be shown above inline query results. You **must** use exactly one of the optional fields.
+
+    Source: https://core.telegram.org/bots/api#inlinequeryresultsbutton
+    """
+
+    text: str
+    """Label text on the button"""
+    web_app: Optional[WebAppInfo] = None
+    """*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method *web_app_switch_inline_query* inside the Web App."""
+    start_parameter: Optional[str] = None
+    """*Optional*. `Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when a user presses the button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed."""
diff --git a/aiogram/types/message.py b/aiogram/types/message.py
index c74ccb97..a057ef97 100644
--- a/aiogram/types/message.py
+++ b/aiogram/types/message.py
@@ -317,6 +317,8 @@ class Message(TelegramObject):
             return ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED
         if self.forum_topic_created:
             return ContentType.FORUM_TOPIC_CREATED
+        if self.forum_topic_edited:
+            return ContentType.FORUM_TOPIC_EDITED
         if self.forum_topic_closed:
             return ContentType.FORUM_TOPIC_CLOSED
         if self.forum_topic_reopened:
diff --git a/aiogram/types/switch_inline_query_chosen_chat.py b/aiogram/types/switch_inline_query_chosen_chat.py
new file mode 100644
index 00000000..f0a2e84f
--- /dev/null
+++ b/aiogram/types/switch_inline_query_chosen_chat.py
@@ -0,0 +1,22 @@
+from typing import Optional
+
+from .base import TelegramObject
+
+
+class SwitchInlineQueryChosenChat(TelegramObject):
+    """
+    This object represents an inline button that switches the current user to inline mode in a chosen chat, with an optional default inline query.
+
+    Source: https://core.telegram.org/bots/api#switchinlinequerychosenchat
+    """
+
+    query: Optional[str] = None
+    """*Optional*. The default inline query to be inserted in the input field. If left empty, only the bot's username will be inserted"""
+    allow_user_chats: Optional[bool] = None
+    """*Optional*. True, if private chats with users can be chosen"""
+    allow_bot_chats: Optional[bool] = None
+    """*Optional*. True, if private chats with bots can be chosen"""
+    allow_group_chats: Optional[bool] = None
+    """*Optional*. True, if group and supergroup chats can be chosen"""
+    allow_channel_chats: Optional[bool] = None
+    """*Optional*. True, if channel chats can be chosen"""
diff --git a/aiogram/types/write_access_allowed.py b/aiogram/types/write_access_allowed.py
index 859280db..32924410 100644
--- a/aiogram/types/write_access_allowed.py
+++ b/aiogram/types/write_access_allowed.py
@@ -1,9 +1,14 @@
+from typing import Optional
+
 from aiogram.types import TelegramObject
 
 
 class WriteAccessAllowed(TelegramObject):
     """
-    This object represents a service message about a user allowing a bot added to the attachment menu to write messages. Currently holds no information.
+    This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.
 
     Source: https://core.telegram.org/bots/api#writeaccessallowed
     """
+
+    web_app_name: Optional[str] = None
+    """*Optional*. Name of the Web App which was launched from a link"""
diff --git a/docs/api/methods/get_my_name.rst b/docs/api/methods/get_my_name.rst
new file mode 100644
index 00000000..656c41ed
--- /dev/null
+++ b/docs/api/methods/get_my_name.rst
@@ -0,0 +1,37 @@
+#########
+getMyName
+#########
+
+Returns: :obj:`BotName`
+
+.. automodule:: aiogram.methods.get_my_name
+    :members:
+    :member-order: bysource
+    :undoc-members: True
+
+
+Usage
+=====
+
+As bot method
+-------------
+
+.. code-block::
+
+    result: BotName = await bot.get_my_name(...)
+
+
+Method as object
+----------------
+
+Imports:
+
+- :code:`from aiogram.methods.get_my_name import GetMyName`
+- alias: :code:`from aiogram.methods import GetMyName`
+
+With specific bot
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    result: BotName = await bot(GetMyName(...))
diff --git a/docs/api/methods/index.rst b/docs/api/methods/index.rst
index 17626855..4a7f4b01 100644
--- a/docs/api/methods/index.rst
+++ b/docs/api/methods/index.rst
@@ -42,6 +42,7 @@ Available methods
     get_my_commands
     get_my_default_administrator_rights
     get_my_description
+    get_my_name
     get_my_short_description
     get_user_profile_photos
     hide_general_forum_topic
@@ -78,6 +79,7 @@ Available methods
     set_my_commands
     set_my_default_administrator_rights
     set_my_description
+    set_my_name
     set_my_short_description
     unban_chat_member
     unban_chat_sender_chat
diff --git a/docs/api/methods/set_my_name.rst b/docs/api/methods/set_my_name.rst
new file mode 100644
index 00000000..f08f6fee
--- /dev/null
+++ b/docs/api/methods/set_my_name.rst
@@ -0,0 +1,44 @@
+#########
+setMyName
+#########
+
+Returns: :obj:`bool`
+
+.. automodule:: aiogram.methods.set_my_name
+    :members:
+    :member-order: bysource
+    :undoc-members: True
+
+
+Usage
+=====
+
+As bot method
+-------------
+
+.. code-block::
+
+    result: bool = await bot.set_my_name(...)
+
+
+Method as object
+----------------
+
+Imports:
+
+- :code:`from aiogram.methods.set_my_name import SetMyName`
+- alias: :code:`from aiogram.methods import SetMyName`
+
+With specific bot
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    result: bool = await bot(SetMyName(...))
+
+As reply into Webhook in handler
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    return SetMyName(...)
diff --git a/docs/api/types/bot_name.rst b/docs/api/types/bot_name.rst
new file mode 100644
index 00000000..794667fa
--- /dev/null
+++ b/docs/api/types/bot_name.rst
@@ -0,0 +1,9 @@
+#######
+BotName
+#######
+
+
+.. automodule:: aiogram.types.bot_name
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/index.rst b/docs/api/types/index.rst
index 304eae6b..930e9354 100644
--- a/docs/api/types/index.rst
+++ b/docs/api/types/index.rst
@@ -34,6 +34,7 @@ Inline mode
     inline_query_result_venue
     inline_query_result_video
     inline_query_result_voice
+    inline_query_results_button
     input_contact_message_content
     input_invoice_message_content
     input_location_message_content
@@ -60,6 +61,7 @@ Available types
     bot_command_scope_chat_member
     bot_command_scope_default
     bot_description
+    bot_name
     bot_short_description
     callback_query
     chat
@@ -121,6 +123,7 @@ Available types
     reply_keyboard_markup
     reply_keyboard_remove
     response_parameters
+    switch_inline_query_chosen_chat
     user
     user_profile_photos
     user_shared
diff --git a/docs/api/types/inline_query_results_button.rst b/docs/api/types/inline_query_results_button.rst
new file mode 100644
index 00000000..c4b1fc05
--- /dev/null
+++ b/docs/api/types/inline_query_results_button.rst
@@ -0,0 +1,9 @@
+########################
+InlineQueryResultsButton
+########################
+
+
+.. automodule:: aiogram.types.inline_query_results_button
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/switch_inline_query_chosen_chat.rst b/docs/api/types/switch_inline_query_chosen_chat.rst
new file mode 100644
index 00000000..961534d6
--- /dev/null
+++ b/docs/api/types/switch_inline_query_chosen_chat.rst
@@ -0,0 +1,9 @@
+###########################
+SwitchInlineQueryChosenChat
+###########################
+
+
+.. automodule:: aiogram.types.switch_inline_query_chosen_chat
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/examples/echo_bot.py b/examples/echo_bot.py
index d51a0eeb..e18201af 100644
--- a/examples/echo_bot.py
+++ b/examples/echo_bot.py
@@ -5,7 +5,7 @@ from aiogram import Bot, Dispatcher, Router, types
 from aiogram.filters import Command
 from aiogram.types import Message
 
-# Bot token can be obtained via https://t.me/BotFahter
+# Bot token can be obtained via https://t.me/BotFather
 TOKEN = "42:TOKEN"
 
 # All handlers should be attached to the Router (or Dispatcher)
diff --git a/pyproject.toml b/pyproject.toml
index 5a203c92..cf0f81ac 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -148,7 +148,14 @@ features = [
     "test",
 ]
 extra-dependencies = [
-    "butcher @ git+https://github.com/aiogram/butcher.git@v0.1.13"
+    "butcher @ git+https://github.com/aiogram/butcher.git@v0.1.14"
+]
+
+[tool.hatch.envs.dev.scripts]
+update = [
+    "butcher parse",
+    "butcher refresh",
+    "butcher apply all",
 ]
 
 [tool.hatch.envs.test]


Test patch:
diff --git a/tests/test_api/test_methods/test_get_my_name.py b/tests/test_api/test_methods/test_get_my_name.py
new file mode 100644
index 00000000..085ac0db
--- /dev/null
+++ b/tests/test_api/test_methods/test_get_my_name.py
@@ -0,0 +1,11 @@
+from aiogram.methods import GetMyName
+from aiogram.types import BotDescription, BotName
+from tests.mocked_bot import MockedBot
+
+
+class TestGetMyName:
+    async def test_bot_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(GetMyName, ok=True, result=BotName(name="Test"))
+
+        response: BotName = await bot.get_my_name()
+        assert response == prepare_result.result
diff --git a/tests/test_api/test_methods/test_set_my_name.py b/tests/test_api/test_methods/test_set_my_name.py
new file mode 100644
index 00000000..3b511f2f
--- /dev/null
+++ b/tests/test_api/test_methods/test_set_my_name.py
@@ -0,0 +1,10 @@
+from aiogram.methods import SetMyName
+from tests.mocked_bot import MockedBot
+
+
+class TestSetMyName:
+    async def test_bot_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(SetMyName, ok=True, result=True)
+
+        response: bool = await bot.set_my_name()
+        assert response == prepare_result.result
diff --git a/tests/test_api/test_types/test_message.py b/tests/test_api/test_types/test_message.py
index 7eec99c7..1b15327f 100644
--- a/tests/test_api/test_types/test_message.py
+++ b/tests/test_api/test_types/test_message.py
@@ -44,6 +44,7 @@ from aiogram.types import (
     EncryptedCredentials,
     ForumTopicClosed,
     ForumTopicCreated,
+    ForumTopicEdited,
     ForumTopicReopened,
     Game,
     InlineKeyboardButton,
@@ -414,6 +415,16 @@ TEST_FORUM_TOPIC_CREATED = Message(
         icon_color=0xFFD67E,
     ),
 )
+TEST_FORUM_TOPIC_EDITED = Message(
+    message_id=42,
+    date=datetime.datetime.now(),
+    chat=Chat(id=42, type="private"),
+    from_user=User(id=42, is_bot=False, first_name="Test"),
+    forum_topic_edited=ForumTopicEdited(
+        name="test_edited",
+        icon_color=0xFFD67E,
+    ),
+)
 TEST_FORUM_TOPIC_CLOSED = Message(
     message_id=42,
     date=datetime.datetime.now(),
@@ -484,6 +495,7 @@ class TestMessage:
             [TEST_MESSAGE_DICE, ContentType.DICE],
             [TEST_MESSAGE_WEB_APP_DATA, ContentType.WEB_APP_DATA],
             [TEST_FORUM_TOPIC_CREATED, ContentType.FORUM_TOPIC_CREATED],
+            [TEST_FORUM_TOPIC_EDITED, ContentType.FORUM_TOPIC_EDITED],
             [TEST_FORUM_TOPIC_CLOSED, ContentType.FORUM_TOPIC_CLOSED],
             [TEST_FORUM_TOPIC_REOPENED, ContentType.FORUM_TOPIC_REOPENED],
             [TEST_MESSAGE_UNKNOWN, ContentType.UNKNOWN],
diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py
index 4fb2b113..41ecef1b 100644
--- a/tests/test_dispatcher/test_dispatcher.py
+++ b/tests/test_dispatcher/test_dispatcher.py
@@ -14,7 +14,7 @@ from aiogram import Bot
 from aiogram.dispatcher.dispatcher import Dispatcher
 from aiogram.dispatcher.event.bases import UNHANDLED, SkipHandler
 from aiogram.dispatcher.router import Router
-from aiogram.methods import GetMe, GetUpdates, Request, SendMessage, TelegramMethod
+from aiogram.methods import GetMe, GetUpdates, SendMessage, TelegramMethod
 from aiogram.types import (
     CallbackQuery,
     Chat,
@@ -462,9 +462,9 @@ class TestDispatcher:
         async def my_handler(event: Any, **kwargs: Any):
             assert event == getattr(update, event_type)
             if has_chat:
-                assert Chat.get_current(False)
+                assert kwargs["event_chat"]
             if has_user:
-                assert User.get_current(False)
+                assert kwargs["event_from_user"]
             return kwargs
 
         result = await router.feed_update(bot, update, test="PASS")
@@ -708,10 +708,15 @@ class TestDispatcher:
         with pytest.raises(RuntimeError):
             await dispatcher.stop_polling()
 
-        assert not dispatcher._stop_signal.is_set()
-        assert not dispatcher._stopped_signal.is_set()
+        assert not dispatcher._stop_signal
+        assert not dispatcher._stopped_signal
         with patch("asyncio.locks.Event.wait", new_callable=AsyncMock) as mocked_wait:
             async with dispatcher._running_lock:
+                await dispatcher.stop_polling()
+                assert not dispatcher._stop_signal
+
+                dispatcher._stop_signal = Event()
+                dispatcher._stopped_signal = Event()
                 await dispatcher.stop_polling()
                 assert dispatcher._stop_signal.is_set()
                 mocked_wait.assert_awaited()
@@ -723,6 +728,11 @@ class TestDispatcher:
             mocked_set.assert_not_called()
 
             async with dispatcher._running_lock:
+                dispatcher._signal_stop_polling(signal.SIGINT)
+                mocked_set.assert_not_called()
+
+                dispatcher._stop_signal = Event()
+                dispatcher._stopped_signal = Event()
                 dispatcher._signal_stop_polling(signal.SIGINT)
                 mocked_set.assert_called()
 
@@ -764,12 +774,29 @@ class TestDispatcher:
 
     def test_run_polling(self, bot: MockedBot):
         dispatcher = Dispatcher()
+
+        async def stop():
+            await asyncio.sleep(0.5)
+            await dispatcher.stop_polling()
+
+        start_called = False
+
+        @dispatcher.startup()
+        async def startup():
+            nonlocal start_called
+            start_called = True
+            asyncio.create_task(stop())
+
+        original_start_polling = dispatcher.start_polling
         with patch(
-            "aiogram.dispatcher.dispatcher.Dispatcher.start_polling"
+            "aiogram.dispatcher.dispatcher.Dispatcher.start_polling",
+            side_effect=original_start_polling,
         ) as patched_start_polling:
             dispatcher.run_polling(bot)
             patched_start_polling.assert_awaited_once()
 
+        assert start_called
+
     async def test_feed_webhook_update_fast_process(self, bot: MockedBot):
         dispatcher = Dispatcher()
         dispatcher.message.register(simple_message_handler)
diff --git a/tests/test_dispatcher/test_middlewares/test_user_context.py b/tests/test_dispatcher/test_middlewares/test_user_context.py
index ca2abb2d..54c09ce2 100644
--- a/tests/test_dispatcher/test_middlewares/test_user_context.py
+++ b/tests/test_dispatcher/test_middlewares/test_user_context.py
@@ -1,6 +1,9 @@
+from unittest.mock import patch
+
 import pytest
 
 from aiogram.dispatcher.middlewares.user_context import UserContextMiddleware
+from aiogram.types import Update
 
 
 async def next_handler(*args, **kwargs):
@@ -11,3 +14,13 @@ class TestUserContextMiddleware:
     async def test_unexpected_event_type(self):
         with pytest.raises(RuntimeError):
             await UserContextMiddleware()(next_handler, object(), {})
+
+    async def test_call(self):
+        middleware = UserContextMiddleware()
+        data = {}
+        with patch.object(UserContextMiddleware, "resolve_event_context", return_value=[1, 2, 3]):
+            await middleware(next_handler, Update(update_id=42), data)
+
+        assert data["event_chat"] == 1
+        assert data["event_from_user"] == 2
+        assert data["event_thread_id"] == 3
diff --git a/tests/test_filters/test_callback_data.py b/tests/test_filters/test_callback_data.py
index 70388689..e8721a41 100644
--- a/tests/test_filters/test_callback_data.py
+++ b/tests/test_filters/test_callback_data.py
@@ -49,34 +49,38 @@ class TestCallbackData:
                 pass
 
     @pytest.mark.parametrize(
-        "value,success,expected",
+        "value,expected",
         [
-            [None, True, ""],
-            [42, True, "42"],
-            ["test", True, "test"],
-            [9.99, True, "9.99"],
-            [Decimal("9.99"), True, "9.99"],
-            [Fraction("3/2"), True, "3/2"],
-            [
-                UUID("123e4567-e89b-12d3-a456-426655440000"),
-                True,
-                "123e4567-e89b-12d3-a456-426655440000",
-            ],
-            [MyIntEnum.FOO, True, "1"],
-            [MyStringEnum.FOO, True, "FOO"],
-            [..., False, "..."],
-            [object, False, "..."],
-            [object(), False, "..."],
-            [User(id=42, is_bot=False, first_name="test"), False, "..."],
+            [None, ""],
+            [True, "1"],
+            [False, "0"],
+            [42, "42"],
+            ["test", "test"],
+            [9.99, "9.99"],
+            [Decimal("9.99"), "9.99"],
+            [Fraction("3/2"), "3/2"],
+            [UUID("123e4567-e89b-12d3-a456-426655440000"), "123e4567e89b12d3a456426655440000"],
+            [MyIntEnum.FOO, "1"],
+            [MyStringEnum.FOO, "FOO"],
         ],
     )
-    def test_encode_value(self, value, success, expected):
+    def test_encode_value_positive(self, value, expected):
         callback = MyCallback(foo="test", bar=42)
-        if success:
-            assert callback._encode_value("test", value) == expected
-        else:
-            with pytest.raises(ValueError):
-                assert callback._encode_value("test", value) == expected
+        assert callback._encode_value("test", value) == expected
+
+    @pytest.mark.parametrize(
+        "value",
+        [
+            ...,
+            object,
+            object(),
+            User(id=42, is_bot=False, first_name="test"),
+        ],
+    )
+    def test_encode_value_negative(self, value):
+        callback = MyCallback(foo="test", bar=42)
+        with pytest.raises(ValueError):
+            callback._encode_value("test", value)
 
     def test_pack(self):
         with pytest.raises(ValueError, match="Separator symbol .+"):
diff --git a/tests/test_fsm/storage/test_redis.py b/tests/test_fsm/storage/test_redis.py
index 6e42eb48..adca384a 100644
--- a/tests/test_fsm/storage/test_redis.py
+++ b/tests/test_fsm/storage/test_redis.py
@@ -11,6 +11,7 @@ PREFIX = "test"
 BOT_ID = 42
 CHAT_ID = -1
 USER_ID = 2
+THREAD_ID = 3
 FIELD = "data"
 
 
@@ -46,6 +47,19 @@ class TestRedisDefaultKeyBuilder:
         with pytest.raises(ValueError):
             key_builder.build(key, FIELD)
 
+    def test_thread_id(self):
+        key_builder = DefaultKeyBuilder(
+            prefix=PREFIX,
+        )
+        key = StorageKey(
+            chat_id=CHAT_ID,
+            user_id=USER_ID,
+            bot_id=BOT_ID,
+            thread_id=THREAD_ID,
+            destiny=DEFAULT_DESTINY,
+        )
+        assert key_builder.build(key, FIELD) == f"{PREFIX}:{CHAT_ID}:{THREAD_ID}:{USER_ID}:{FIELD}"
+
     def test_create_isolation(self):
         fake_redis = object()
         storage = RedisStorage(redis=fake_redis)
diff --git a/tests/test_fsm/test_strategy.py b/tests/test_fsm/test_strategy.py
index b00a7b98..3dab2b3d 100644
--- a/tests/test_fsm/test_strategy.py
+++ b/tests/test_fsm/test_strategy.py
@@ -2,19 +2,41 @@ import pytest
 
 from aiogram.fsm.strategy import FSMStrategy, apply_strategy
 
+CHAT_ID = -42
+USER_ID = 42
+THREAD_ID = 1
+
+PRIVATE = (USER_ID, USER_ID, None)
+CHAT = (CHAT_ID, USER_ID, None)
+THREAD = (CHAT_ID, USER_ID, THREAD_ID)
+
 
 class TestStrategy:
     @pytest.mark.parametrize(
         "strategy,case,expected",
         [
-            [FSMStrategy.USER_IN_CHAT, (-42, 42), (-42, 42)],
-            [FSMStrategy.CHAT, (-42, 42), (-42, -42)],
-            [FSMStrategy.GLOBAL_USER, (-42, 42), (42, 42)],
-            [FSMStrategy.USER_IN_CHAT, (42, 42), (42, 42)],
-            [FSMStrategy.CHAT, (42, 42), (42, 42)],
-            [FSMStrategy.GLOBAL_USER, (42, 42), (42, 42)],
+            [FSMStrategy.USER_IN_CHAT, CHAT, CHAT],
+            [FSMStrategy.USER_IN_CHAT, PRIVATE, PRIVATE],
+            [FSMStrategy.USER_IN_CHAT, THREAD, CHAT],
+            [FSMStrategy.CHAT, CHAT, (CHAT_ID, CHAT_ID, None)],
+            [FSMStrategy.CHAT, PRIVATE, (USER_ID, USER_ID, None)],
+            [FSMStrategy.CHAT, THREAD, (CHAT_ID, CHAT_ID, None)],
+            [FSMStrategy.GLOBAL_USER, CHAT, PRIVATE],
+            [FSMStrategy.GLOBAL_USER, PRIVATE, PRIVATE],
+            [FSMStrategy.GLOBAL_USER, THREAD, PRIVATE],
+            [FSMStrategy.USER_IN_THREAD, CHAT, CHAT],
+            [FSMStrategy.USER_IN_THREAD, PRIVATE, PRIVATE],
+            [FSMStrategy.USER_IN_THREAD, THREAD, THREAD],
         ],
     )
     def test_strategy(self, strategy, case, expected):
-        chat_id, user_id = case
-        assert apply_strategy(chat_id=chat_id, user_id=user_id, strategy=strategy) == expected
+        chat_id, user_id, thread_id = case
+        assert (
+            apply_strategy(
+                chat_id=chat_id,
+                user_id=user_id,
+                thread_id=thread_id,
+                strategy=strategy,
+            )
+            == expected
+        )