Repository: python-telegram-bot/python-telegram-bot
Pull request: API 6.3
Gathering all commits/fixes/PRs for the new and big 6.3 update.

- [x] Update tests for new forum methods - see [here](https://github.com/python-telegram-bot/python-telegram-bot/pull/3345/files#diff-16435365ee487202cae42fc85f2bcb3f6d3883545fbdce46cf68fd5c74abffbb)
- [x] revert changes to the test config

### Checklist for PRs

- [x] Added `.. versionadded:: version`, `.. versionchanged:: version` or `.. deprecated:: version` to the docstrings for user facing changes (for methods/class descriptions, arguments and attributes)
- [x] Created new or adapted existing unit tests
- [x] Documented code changes according to the [CSI standard](https://standards.mousepawmedia.com/en/stable/csi.html)
- [ ] ~Added myself alphabetically to `AUTHORS.rst` (optional)~
- [x] Added new classes & modules to the docs and all suitable `__all__` s


### If the PR contains API changes (otherwise, you can delete this passage)

* New classes:
    - [x] Added `self._id_attrs` and corresponding documentation
    - [x] `__init__` accepts `**_kwargs`
    
* Added new shortcuts:
    - [x] In `Chat` & `User` for all methods that accept `chat/user_id`
    - [x] In `Message` for all methods that accept `chat_id` and `message_id`
    - [ ] ~For new `Message` shortcuts: Added `quote` argument if methods accepts `reply_to_message_id`~
    - [ ] ~In `CallbackQuery` for all methods that accept either `chat_id` and `message_id` or `inline_message_id`~
    
* If relevant:

    - [x] Added new constants at `telegram.constants` and shortcuts to them as class variables
    - [x] Link new and existing constants in docstrings instead of hard coded number and strings
    - [ ] ~Add new message types to `Message.effective_attachment`~
    - [ ] ~Added new handlers for new update types~
      - [ ] ~Add the handlers to the warning loop in the `ConversationHandler`~
    - [x] Added new filters for new message (sub)types
    - [x] Added or updated documentation for the changed class(es) and/or method(s)
    - [x] Added or updated `bot_methods.rst`
    - [x] Updated the Bot API version number in all places: `README.rst` and `README_RAW.rst` (including the badge), as well as `telegram.constants.BOT_API_VERSION`
    - [ ] ~Added logic for arbitrary callback data in `tg.ext.Bot` for new methods that either accept a `reply_markup` in some form or have a return type that is/contains `telegram.Message`~


Problem statement:
[Feature] Bot API 6.3 update
This issue exists to keep track of the implementation of the 6.3 Update.

- [x] Topics (in groups):
  - [x] can_manage_topics in classes ChatAdministratorRights, ChatMemberAdministrator, ChatPermissions, ChatMemberRestricted 
  - [x] also in Bot function promoteChatMember
- [x] Message class
  - [x] new empty classes ForumTopicReopened, ForumTopicClosed 
  - [x] new class ForumTopicCreated
  - [x] add to all shortcuts thread_message_id, and add new shortcuts if needed (doubtful, but I did not check)
- [x] changed docstring: setChatTitle title attribute and deleteMessage
- [x] new param message_thread_id to too many methods to list
- [x] Chat
  - [x] needs shortcuts to all new methods except getForumTopicIconStickers
- [x] implement new methods
  - [x] new class ForumTopic

Hints from issue discussion:
I think I should be able to handle adding param `message_thread_id` to those numerous methods. Any pitfalls a newcomer wouldn't spot there?
@lemontree210 Doubt it. Think about adding it before the * in the param list, the docstring, the tests (if no fitting one exists create them, there should always be one mocking telegrams response and check all params), and you are good.
Should I do the Topics (in groups) part?
@clot27 If you want to, sure :)
I will do the docstring changes now and then a couple new methods

Commit message: New classes for API 6.3: `ForumTopic`, `ForumTopicCreated`, `ForumTopicClosed`, `ForumTopicReopened` (#3356)

Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>

Patch to review:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b62fa9d4880..ef168644966 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -3,6 +3,7 @@ on:
   pull_request:
     branches:
       - master
+      - api_6.3_update
   push:
     branches:
       - master
diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst
index df699e796aa..aff45cc164f 100644
--- a/docs/source/inclusions/bot_methods.rst
+++ b/docs/source/inclusions/bot_methods.rst
@@ -256,6 +256,35 @@
    </details>
    <br>
 
+.. raw:: html
+
+   <details>
+   <summary>Forum topic management</summary>
+
+.. list-table::
+    :align: left
+    :widths: 1 4
+
+    * - :meth:`~telegram.Bot.close_forum_topic`
+      - Used for closing a forum topic
+    * - :meth:`~telegram.Bot.create_forum_topic`
+      - Used to create a topic
+    * - :meth:`~telegram.Bot.delete_forum_topic`
+      - Used for deleting a forum topic
+    * - :meth:`~telegram.Bot.edit_forum_topic`
+      - Used to edit a topic
+    * - :meth:`~telegram.Bot.reopen_forum_topic`
+      - Used to reopen a topic
+    * - :meth:`~telegram.Bot.get_forum_topic_icon_stickers`
+      - Used to get custom emojis to use as topic icons
+    * - :meth:`~telegram.Bot.unpin_all_forum_topic_messages`
+      - Used to unpin all messages in a forum topic
+
+.. raw:: html
+
+   </details>
+   <br>
+
 .. raw:: html
 
    <details>
diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst
index 4f2a4ec1a97..41fd5e6385a 100644
--- a/docs/source/telegram.at-tree.rst
+++ b/docs/source/telegram.at-tree.rst
@@ -35,6 +35,10 @@ Available Types
     telegram.document
     telegram.file
     telegram.forcereply
+    telegram.forumtopic
+    telegram.forumtopicclosed
+    telegram.forumtopiccreated
+    telegram.forumtopicreopened
     telegram.inlinekeyboardbutton
     telegram.inlinekeyboardmarkup
     telegram.inputfile
diff --git a/docs/source/telegram.forumtopic.rst b/docs/source/telegram.forumtopic.rst
new file mode 100644
index 00000000000..a2dd6389c77
--- /dev/null
+++ b/docs/source/telegram.forumtopic.rst
@@ -0,0 +1,6 @@
+telegram.ForumTopic
+===================
+
+.. autoclass:: telegram.ForumTopic
+    :members:
+    :show-inheritance:
diff --git a/docs/source/telegram.forumtopicclosed.rst b/docs/source/telegram.forumtopicclosed.rst
new file mode 100644
index 00000000000..198a6737255
--- /dev/null
+++ b/docs/source/telegram.forumtopicclosed.rst
@@ -0,0 +1,6 @@
+telegram.ForumTopicClosed
+=========================
+
+.. autoclass:: telegram.ForumTopicClosed
+    :members:
+    :show-inheritance:
diff --git a/docs/source/telegram.forumtopiccreated.rst b/docs/source/telegram.forumtopiccreated.rst
new file mode 100644
index 00000000000..f1da6f81d37
--- /dev/null
+++ b/docs/source/telegram.forumtopiccreated.rst
@@ -0,0 +1,6 @@
+telegram.ForumTopicCreated
+==========================
+
+.. autoclass:: telegram.ForumTopicCreated
+    :members:
+    :show-inheritance:
diff --git a/docs/source/telegram.forumtopicreopened.rst b/docs/source/telegram.forumtopicreopened.rst
new file mode 100644
index 00000000000..b81c395d254
--- /dev/null
+++ b/docs/source/telegram.forumtopicreopened.rst
@@ -0,0 +1,6 @@
+telegram.ForumTopicReopened
+===========================
+
+.. autoclass:: telegram.ForumTopicReopened
+    :members:
+    :show-inheritance:
diff --git a/docs/substitutions/global.rst b/docs/substitutions/global.rst
index 46067cbc8a9..b06bb09db41 100644
--- a/docs/substitutions/global.rst
+++ b/docs/substitutions/global.rst
@@ -26,6 +26,8 @@
 
 .. |chat_id_group| replace:: Unique identifier for the target chat or username of the target supergroup (in the format ``@supergroupusername``).
 
+.. |message_thread_id| replace:: Unique identifier for the target message thread of the forum topic.
+
 .. |parse_mode| replace:: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message. See the constants in :class:`telegram.constants.ParseMode` for the available modes.
 
 .. |allow_sending_without_reply| replace:: Pass :obj:`True`, if the message should be sent even if the specified replied-to message is not found.
diff --git a/telegram/__init__.py b/telegram/__init__.py
index 11b5e9b720f..c0230f076c8 100644
--- a/telegram/__init__.py
+++ b/telegram/__init__.py
@@ -67,6 +67,10 @@
     "File",
     "FileCredentials",
     "ForceReply",
+    "ForumTopic",
+    "ForumTopicClosed",
+    "ForumTopicCreated",
+    "ForumTopicReopened",
     "Game",
     "GameHighScore",
     "helpers",
@@ -230,6 +234,7 @@
 from ._files.videonote import VideoNote
 from ._files.voice import Voice
 from ._forcereply import ForceReply
+from ._forumtopic import ForumTopic, ForumTopicClosed, ForumTopicCreated, ForumTopicReopened
 from ._games.callbackgame import CallbackGame
 from ._games.game import Game
 from ._games.gamehighscore import GameHighScore
diff --git a/telegram/_bot.py b/telegram/_bot.py
index d7e83362f51..9f74a6c8ac0 100644
--- a/telegram/_bot.py
+++ b/telegram/_bot.py
@@ -74,6 +74,7 @@
 from telegram._files.video import Video
 from telegram._files.videonote import VideoNote
 from telegram._files.voice import Voice
+from telegram._forumtopic import ForumTopic
 from telegram._games.gamehighscore import GameHighScore
 from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
 from telegram._menubutton import MenuButton
@@ -440,6 +441,7 @@ async def _send_message(
         reply_markup: ReplyMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -459,6 +461,9 @@ async def _send_message(
         if reply_markup is not None:
             data["reply_markup"] = reply_markup
 
+        if message_thread_id is not None:
+            data["message_thread_id"] = message_thread_id
+
         result = await self._post(
             endpoint,
             data,
@@ -660,6 +665,7 @@ async def send_message(
         reply_to_message_id: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -693,6 +699,10 @@ async def send_message(
                 :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
                 Additional interface options. An object for an inline keyboard, custom reply
                 keyboard, instructions to remove reply keyboard or to force a reply from the user.
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
 
         Returns:
             :class:`telegram.Message`: On success, the sent message is returned.
@@ -719,6 +729,7 @@ async def send_message(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -743,6 +754,7 @@ async def delete_message(
         limitations:
 
         - A message can only be deleted if it was sent less than 48 hours ago.
+        - Service messages about a supergroup, channel, or forum topic creation can't be deleted.
         - A dice message in a private chat can only be deleted if it was sent more than 24
           hours ago.
         - Bots can delete outgoing messages in private chats, groups, and supergroups.
@@ -787,6 +799,7 @@ async def forward_message(
         message_id: int,
         disable_notification: DVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -818,6 +831,10 @@ async def forward_message(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
 
         Returns:
             :class:`telegram.Message`: On success, the sent Message is returned.
@@ -838,6 +855,7 @@ async def forward_message(
             data,
             disable_notification=disable_notification,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -858,6 +876,7 @@ async def send_photo(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -893,6 +912,10 @@ async def send_photo(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -934,6 +957,7 @@ async def send_photo(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -958,6 +982,7 @@ async def send_audio(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1004,6 +1029,11 @@ async def send_audio(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -1062,6 +1092,7 @@ async def send_audio(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1084,6 +1115,7 @@ async def send_document(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1129,6 +1161,11 @@ async def send_document(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -1181,6 +1218,7 @@ async def send_document(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1198,6 +1236,7 @@ async def send_sticker(
         reply_markup: ReplyMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = 20,
@@ -1228,6 +1267,11 @@ async def send_sticker(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -1251,6 +1295,7 @@ async def send_sticker(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1276,6 +1321,7 @@ async def send_video(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1327,6 +1373,11 @@ async def send_video(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -1386,6 +1437,7 @@ async def send_video(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1406,6 +1458,7 @@ async def send_video_note(
         thumb: FileInput = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1450,6 +1503,11 @@ async def send_video_note(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -1500,6 +1558,7 @@ async def send_video_note(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1524,6 +1583,7 @@ async def send_animation(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1579,6 +1639,11 @@ async def send_animation(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -1627,6 +1692,7 @@ async def send_animation(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1648,6 +1714,7 @@ async def send_voice(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1693,6 +1760,11 @@ async def send_voice(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -1736,6 +1808,7 @@ async def send_voice(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1754,6 +1827,7 @@ async def send_media_group(
         reply_to_message_id: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = 20,
@@ -1784,6 +1858,11 @@ async def send_media_group(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
 
@@ -1847,6 +1926,9 @@ async def send_media_group(
         if reply_to_message_id:
             data["reply_to_message_id"] = reply_to_message_id
 
+        if message_thread_id:
+            data["message_thread_id"] = message_thread_id
+
         result = await self._post(
             "sendMediaGroup",
             data,
@@ -1874,6 +1956,7 @@ async def send_location(
         proximity_alert_radius: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         location: Location = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1910,6 +1993,11 @@ async def send_location(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -1960,6 +2048,7 @@ async def send_location(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2137,6 +2226,7 @@ async def send_venue(
         google_place_type: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         venue: Venue = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2176,6 +2266,10 @@ async def send_venue(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
 
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@@ -2242,6 +2336,7 @@ async def send_venue(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2262,6 +2357,7 @@ async def send_contact(
         vcard: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         contact: Contact = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2291,6 +2387,10 @@ async def send_contact(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
 
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@@ -2346,6 +2446,7 @@ async def send_contact(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2363,6 +2464,7 @@ async def send_game(
         reply_markup: InlineKeyboardMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2383,6 +2485,11 @@ async def send_game(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for a new
@@ -2406,6 +2513,7 @@ async def send_game(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -4057,6 +4165,7 @@ async def send_invoice(
         max_tip_amount: int = None,
         suggested_tip_amounts: List[int] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -4147,6 +4256,11 @@ async def send_invoice(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for an
@@ -4208,6 +4322,7 @@ async def send_invoice(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -4460,6 +4575,7 @@ async def promote_chat_member(
         is_anonymous: bool = None,
         can_manage_chat: bool = None,
         can_manage_video_chats: bool = None,
+        can_manage_topics: bool = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -4513,6 +4629,10 @@ async def promote_chat_member(
                 add new administrators with a subset of his own privileges or demote administrators
                 that he has promoted, directly or indirectly (promoted by administrators that were
                 appointed by him).
+            can_manage_topics (:obj:`bool`, optional): Pass :obj:`True`, if the administrator is
+                allowed to create, rename, close, and reopen forum topics; supergroups only.
+
+                .. versionadded:: 20.0
 
         Returns:
             :obj:`bool`: On success, :obj:`True` is returned.
@@ -4545,6 +4665,8 @@ async def promote_chat_member(
             data["can_manage_chat"] = can_manage_chat
         if can_manage_video_chats is not None:
             data["can_manage_video_chats"] = can_manage_video_chats
+        if can_manage_topics is not None:
+            data["can_manage_topics"] = can_manage_topics
 
         result = await self._post(
             "promoteChatMember",
@@ -5111,7 +5233,9 @@ async def set_chat_title(
 
         Args:
             chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
-            title (:obj:`str`): New chat title, 1-255 characters.
+            title (:obj:`str`): New chat title,
+                tg-const:`telegram.constants.ChatLimits.MIN_TITLE_LENGTH`-
+                tg-const:`telegram.constants.ChatLimits.MAX_TITLE_LENGTH` characters.
 
         Returns:
             :obj:`bool`: On success, :obj:`True` is returned.
@@ -5856,6 +5980,7 @@ async def send_poll(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         explanation_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -5908,6 +6033,11 @@ async def send_poll(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -5956,6 +6086,7 @@ async def send_poll(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -6020,6 +6151,7 @@ async def send_dice(
         emoji: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -6057,6 +6189,10 @@ async def send_dice(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
 
         Returns:
             :class:`telegram.Message`: On success, the sent Message is returned.
@@ -6077,6 +6213,7 @@ async def send_dice(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -6433,6 +6570,7 @@ async def copy_message(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -6464,6 +6602,11 @@ async def copy_message(
             protect_content (:obj:`bool`, optional): |protect_content|
 
                 .. versionadded:: 13.10
+            message_thread_id (:obj:`int`, optional): Unique identifier for the target message
+                thread (topic) of the forum; for forum supergroups only.
+
+                .. versionadded:: 20.0
+
             reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
             allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
             reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
@@ -6495,6 +6638,8 @@ async def copy_message(
             data["reply_to_message_id"] = reply_to_message_id
         if reply_markup:
             data["reply_markup"] = reply_markup
+        if message_thread_id:
+            data["message_thread_id"] = message_thread_id
 
         result = await self._post(
             "copyMessage",
@@ -6734,6 +6879,350 @@ async def create_invoice_link(
             api_kwargs=api_kwargs,
         )
 
+    @_log
+    async def get_forum_topic_icon_stickers(
+        self,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> List[Sticker]:
+        """Use this method to get custom emoji stickers, which can be used as a forum topic
+         icon by any user. Requires no parameters.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            List[:class:`telegram.Sticker`]
+
+        Raises:
+            :class:`telegram.error.TelegramError`
+
+        """
+        result = await self._post(
+            "getForumTopicIconStickers",
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+        return Sticker.de_list(result, self)  # type: ignore[return-value, arg-type]
+
+    @_log
+    async def create_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        name: str,
+        icon_color: int = None,
+        icon_custom_emoji_id: str = None,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> ForumTopic:
+        """
+        Use this method to create a topic in a forum supergroup chat. The bot must be
+        an administrator in the chat for this to work and must have
+        :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights.
+
+        .. seealso:: :meth:`telegram.Chat.create_forum_topic`,
+
+        .. versionadded:: 20.0
+
+        Args:
+            chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
+            name (:obj:`str`): New topic name,
+                :tg-const:`telegram.constants.TopicLimit.MIN_NAME_LENGTH`-
+                :tg-const:`telegram.constants.TopicLimit.MAX_NAME_LENGTH` characters.
+            icon_color (:obj:`int`, optional): Color of the topic icon in RGB format. Currently,
+                must be one of :attr:`telegram.constants.ForumIconColor.BLUE`,
+                :attr:`telegram.constants.ForumIconColor.YELLOW`,
+                :attr:`telegram.constants.ForumIconColor.PURPLE`,
+                :attr:`telegram.constants.ForumIconColor.GREEN`,
+                :attr:`telegram.constants.ForumIconColor.PINK`, or
+                :attr:`telegram.constants.ForumIconColor.RED`.
+            icon_custom_emoji_id (:obj:`str`, optional): New unique identifier of the custom emoji
+                shown as the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers`
+                to get all allowed custom emoji identifiers.
+
+        Returns:
+            :class:`telegram.ForumTopic`
+
+        Raises:
+            :class:`telegram.error.TelegramError`
+        """
+        data: JSONDict = {
+            "chat_id": chat_id,
+            "name": name,
+            "icon_color": icon_color,
+            "icon_custom_emoji_id": icon_custom_emoji_id,
+        }
+        result = await self._post(
+            "createForumTopic",
+            data,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+        return ForumTopic.de_json(result, self)  # type: ignore[return-value, arg-type]
+
+    @_log
+    async def edit_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        name: str,
+        icon_custom_emoji_id: str,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """
+        Use this method to edit name and icon of a topic in a forum supergroup chat. The bot must
+        be an administrator in the chat for this to work and must have
+        :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights,
+        unless it is the creator of the topic.
+
+        .. seealso:: :meth:`telegram.Message.edit_forum_topic`,
+            :meth:`telegram.Chat.edit_forum_topic`,
+
+        .. versionadded:: 20.0
+
+        Args:
+            chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
+            message_thread_id (:obj:`int`): |message_thread_id|
+            name (:obj:`str`): New topic name,
+                :tg-const:`telegram.constants.TopicLimit.MIN_NAME_LENGTH`-
+                :tg-const:`telegram.constants.TopicLimit.MAX_NAME_LENGTH` characters.
+            icon_custom_emoji_id (:obj:`str`): New unique identifier of the custom emoji shown as
+                the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers` to get all
+                allowed custom emoji identifiers.
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+
+        Raises:
+            :class:`telegram.error.TelegramError`
+
+        """
+        data: JSONDict = {
+            "chat_id": chat_id,
+            "message_thread_id": message_thread_id,
+            "name": name,
+            "icon_custom_emoji_id": icon_custom_emoji_id,
+        }
+        return await self._post(  # type: ignore[return-value]
+            "editForumTopic",
+            data,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    @_log
+    async def close_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """
+        Use this method to close an open topic in a forum supergroup chat. The bot must
+        be an administrator in the chat for this to work and must have
+        :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights,
+        unless it is the creator of the topic.
+
+        .. seealso:: :meth:`telegram.Message.close_forum_topic`,
+            :meth:`telegram.Chat.close_forum_topic`,
+
+        .. versionadded:: 20.0
+
+        Args:
+            chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
+            message_thread_id (:obj:`int`): |message_thread_id|
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+
+        Raises:
+            :class:`telegram.error.TelegramError`
+
+        """
+        data: JSONDict = {
+            "chat_id": chat_id,
+            "message_thread_id": message_thread_id,
+        }
+        return await self._post(  # type: ignore[return-value]
+            "closeForumTopic",
+            data,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    @_log
+    async def reopen_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """
+        Use this method to reopen a closed topic in a forum supergroup chat. The bot must
+        be an administrator in the chat for this to work and must have
+        :paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights,
+        unless it is the creator of the topic.
+
+        .. seealso:: :meth:`telegram.Message.reopen_forum_topic`,
+            :meth:`telegram.Chat.reopen_forum_topic`,
+
+        .. versionadded:: 20.0
+
+        Args:
+            chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
+            message_thread_id (:obj:`int`): |message_thread_id|
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+
+        Raises:
+            :class:`telegram.error.TelegramError`
+
+        """
+        data: JSONDict = {
+            "chat_id": chat_id,
+            "message_thread_id": message_thread_id,
+        }
+        return await self._post(  # type: ignore[return-value]
+            "reopenForumTopic",
+            data,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    @_log
+    async def delete_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """
+        Use this method to delete a forum topic along with all its messages in a forum supergroup
+        chat. The bot must be an administrator in the chat for this to work and must have
+        :paramref:`~telegram.ChatAdministratorRights.can_delete_messages` administrator rights,
+        unless it is the creator of the topic.
+
+        .. seealso:: :meth:`telegram.Message.delete_forum_topic`,
+            :meth:`telegram.Chat.delete_forum_topic`,
+
+        .. versionadded:: 20.0
+
+        Args:
+            chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
+            message_thread_id (:obj:`int`): |message_thread_id|
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+
+        Raises:
+            :class:`telegram.error.TelegramError`
+
+        """
+        data: JSONDict = {
+            "chat_id": chat_id,
+            "message_thread_id": message_thread_id,
+        }
+        return await self._post(  # type: ignore[return-value]
+            "deleteForumTopic",
+            data,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    @_log
+    async def unpin_all_forum_topic_messages(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """
+        Use this method to clear the list of pinned messages in a forum topic. The bot must
+        be an administrator in the chat for this to work and must have
+        :paramref:`~telegram.ChatAdministratorRights.can_pin_messages` administrator rights
+        in the supergroup, unless it is the creator of the topic.
+
+        .. seealso:: :meth:`telegram.Message.unpin_all_forum_topic_messages`,
+            :meth:`telegram.Chat.unpin_all_forum_topic_messages`,
+
+        .. versionadded:: 20.0
+
+        Args:
+            chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
+            message_thread_id (:obj:`int`): |message_thread_id|
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+
+        Raises:
+            :class:`telegram.error.TelegramError`
+
+        """
+        data: JSONDict = {
+            "chat_id": chat_id,
+            "message_thread_id": message_thread_id,
+        }
+        return await self._post(  # type: ignore[return-value]
+            "unpinAllForumTopicMessages",
+            data,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
     def to_dict(self, recursive: bool = True) -> JSONDict:  # skipcq: PYL-W0613
         """See :meth:`telegram.TelegramObject.to_dict`."""
         data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@@ -6926,3 +7415,17 @@ def __hash__(self) -> int:
     """Alias for :meth:`set_my_default_administrator_rights`"""
     createInvoiceLink = create_invoice_link
     """Alias for :meth:`create_invoice_link`"""
+    getForumTopicIconStickers = get_forum_topic_icon_stickers
+    """Alias for :meth:`get_forum_topic_icon_stickers`"""
+    createForumTopic = create_forum_topic
+    """Alias for :meth:`create_forum_topic`"""
+    editForumTopic = edit_forum_topic
+    """Alias for :meth:`edit_forum_topic`"""
+    closeForumTopic = close_forum_topic
+    """Alias for :meth:`close_forum_topic`"""
+    reopenForumTopic = reopen_forum_topic
+    """Alias for :meth:`reopen_forum_topic`"""
+    deleteForumTopic = delete_forum_topic
+    """Alias for :meth:`delete_forum_topic`"""
+    unpinAllForumTopicMessages = unpin_all_forum_topic_messages
+    """Alias for :meth:`unpin_all_forum_topic_messages`"""
diff --git a/telegram/_callbackquery.py b/telegram/_callbackquery.py
index 73674e0d491..2a6fd1ba682 100644
--- a/telegram/_callbackquery.py
+++ b/telegram/_callbackquery.py
@@ -724,6 +724,7 @@ async def copy_message(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -761,6 +762,7 @@ async def copy_message(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     MAX_ANSWER_TEXT_LENGTH: ClassVar[
diff --git a/telegram/_chat.py b/telegram/_chat.py
index 296a32cc4da..3acf2d9e34c 100644
--- a/telegram/_chat.py
+++ b/telegram/_chat.py
@@ -148,6 +148,22 @@ class Chat(TelegramObject):
             in the private chat. Returned only in :meth:`telegram.Bot.get_chat`.
 
             .. versionadded:: 20.0
+        is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum
+            (has topics_ enabled).
+
+            .. versionadded:: 20.0
+        active_usernames (List[:obj:`str`], optional):  If set, the list of all `active chat
+            usernames <https://telegram.org/blog/topics-in-groups-collectible-usernames\
+            #collectible-usernames>`_; for private chats, supergroups and channels. Returned
+            only in :meth:`telegram.Bot.get_chat`.
+
+            .. versionadded:: 20.0
+        emoji_status_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji
+            status of the other party in a private chat. Returned only in
+            :meth:`telegram.Bot.get_chat`.
+
+            .. versionadded:: 20.0
+
     Attributes:
         id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits
             and some programming languages may have difficulty/silent defects in interpreting it.
@@ -212,8 +228,24 @@ class Chat(TelegramObject):
             privacy settings of the other party restrict sending voice and video note messages
             in the private chat. Returned only in :meth:`telegram.Bot.get_chat`.
 
+            .. versionadded:: 20.0
+        is_forum (:obj:`bool`): Optional. :obj:`True`, if the supergroup chat is a forum
+            (has topics_ enabled).
+
+            .. versionadded:: 20.0
+        active_usernames (List[:obj:`str`]): Optional. If set, the list of all `active chat
+            usernames <https://telegram.org/blog/topics-in-groups-collectible-usernames\
+            #collectible-usernames>`_; for private chats, supergroups and channels. Returned
+            only in :meth:`telegram.Bot.get_chat`.
+
+            .. versionadded:: 20.0
+        emoji_status_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji
+            status of the other party in a private chat. Returned only in
+            :meth:`telegram.Bot.get_chat`.
+
             .. versionadded:: 20.0
 
+    .. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
     """
 
     __slots__ = (
@@ -240,6 +272,9 @@ class Chat(TelegramObject):
         "join_to_send_messages",
         "join_by_request",
         "has_restricted_voice_and_video_messages",
+        "is_forum",
+        "active_usernames",
+        "emoji_status_custom_emoji_id",
     )
 
     SENDER: ClassVar[str] = constants.ChatType.SENDER
@@ -281,6 +316,9 @@ def __init__(
         join_to_send_messages: bool = None,
         join_by_request: bool = None,
         has_restricted_voice_and_video_messages: bool = None,
+        is_forum: bool = None,
+        active_usernames: List[str] = None,
+        emoji_status_custom_emoji_id: str = None,
         *,
         api_kwargs: JSONDict = None,
     ):
@@ -312,6 +350,9 @@ def __init__(
         self.join_to_send_messages = join_to_send_messages
         self.join_by_request = join_by_request
         self.has_restricted_voice_and_video_messages = has_restricted_voice_and_video_messages
+        self.is_forum = is_forum
+        self.active_usernames = active_usernames
+        self.emoji_status_custom_emoji_id = emoji_status_custom_emoji_id
 
         self._id_attrs = (self.id,)
 
@@ -802,6 +843,7 @@ async def promote_member(
         is_anonymous: bool = None,
         can_manage_chat: bool = None,
         can_manage_video_chats: bool = None,
+        can_manage_topics: bool = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -845,6 +887,7 @@ async def promote_member(
             is_anonymous=is_anonymous,
             can_manage_chat=can_manage_chat,
             can_manage_video_chats=can_manage_video_chats,
+            can_manage_topics=can_manage_topics,
         )
 
     async def restrict_member(
@@ -1192,6 +1235,7 @@ async def send_message(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1220,6 +1264,7 @@ async def send_message(
             allow_sending_without_reply=allow_sending_without_reply,
             entities=entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1236,6 +1281,7 @@ async def send_media_group(
         reply_to_message_id: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = 20,
@@ -1268,6 +1314,7 @@ async def send_media_group(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
@@ -1317,6 +1364,7 @@ async def send_photo(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1347,6 +1395,7 @@ async def send_photo(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1365,6 +1414,7 @@ async def send_contact(
         vcard: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         contact: "Contact" = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1400,6 +1450,7 @@ async def send_contact(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_audio(
@@ -1417,6 +1468,7 @@ async def send_audio(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1451,6 +1503,7 @@ async def send_audio(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1471,6 +1524,7 @@ async def send_document(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1508,6 +1562,7 @@ async def send_document(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_dice(
@@ -1518,6 +1573,7 @@ async def send_dice(
         emoji: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1548,6 +1604,7 @@ async def send_dice(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_game(
@@ -1558,6 +1615,7 @@ async def send_game(
         reply_markup: "InlineKeyboardMarkup" = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1588,6 +1646,7 @@ async def send_game(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_invoice(
@@ -1618,6 +1677,7 @@ async def send_invoice(
         max_tip_amount: int = None,
         suggested_tip_amounts: List[int] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1678,6 +1738,7 @@ async def send_invoice(
             max_tip_amount=max_tip_amount,
             suggested_tip_amounts=suggested_tip_amounts,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_location(
@@ -1693,6 +1754,7 @@ async def send_location(
         proximity_alert_radius: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         location: "Location" = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1730,6 +1792,7 @@ async def send_location(
             proximity_alert_radius=proximity_alert_radius,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_animation(
@@ -1747,6 +1810,7 @@ async def send_animation(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1786,6 +1850,7 @@ async def send_animation(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_sticker(
@@ -1796,6 +1861,7 @@ async def send_sticker(
         reply_markup: ReplyMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = 20,
@@ -1826,6 +1892,7 @@ async def send_sticker(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_venue(
@@ -1843,6 +1910,7 @@ async def send_venue(
         google_place_type: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         venue: "Venue" = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1882,6 +1950,7 @@ async def send_venue(
             google_place_type=google_place_type,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_video(
@@ -1900,6 +1969,7 @@ async def send_video(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1940,6 +2010,7 @@ async def send_video(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_video_note(
@@ -1953,6 +2024,7 @@ async def send_video_note(
         thumb: FileInput = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1988,6 +2060,7 @@ async def send_video_note(
             allow_sending_without_reply=allow_sending_without_reply,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_voice(
@@ -2002,6 +2075,7 @@ async def send_voice(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2038,6 +2112,7 @@ async def send_voice(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_poll(
@@ -2059,6 +2134,7 @@ async def send_poll(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         explanation_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2100,6 +2176,7 @@ async def send_poll(
             allow_sending_without_reply=allow_sending_without_reply,
             explanation_entities=explanation_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_copy(
@@ -2114,6 +2191,7 @@ async def send_copy(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2148,6 +2226,7 @@ async def send_copy(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def copy_message(
@@ -2162,6 +2241,7 @@ async def copy_message(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2196,6 +2276,7 @@ async def copy_message(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def forward_from(
@@ -2204,6 +2285,7 @@ async def forward_from(
         message_id: int,
         disable_notification: DVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2236,6 +2318,7 @@ async def forward_from(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def forward_to(
@@ -2244,6 +2327,7 @@ async def forward_to(
         message_id: int,
         disable_notification: DVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2276,6 +2360,7 @@ async def forward_to(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def export_invite_link(
@@ -2532,6 +2617,207 @@ async def set_menu_button(
             api_kwargs=api_kwargs,
         )
 
+    async def create_forum_topic(
+        self,
+        name: str,
+        icon_color: int = None,
+        icon_custom_emoji_id: str = None,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.create_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs)
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.create_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().create_forum_topic(
+            chat_id=self.id,
+            name=name,
+            icon_color=icon_color,
+            icon_custom_emoji_id=icon_custom_emoji_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def edit_forum_topic(
+        self,
+        message_thread_id: int,
+        name: str,
+        icon_custom_emoji_id: str,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.edit_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs)
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.edit_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().edit_forum_topic(
+            chat_id=self.id,
+            message_thread_id=message_thread_id,
+            name=name,
+            icon_custom_emoji_id=icon_custom_emoji_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def close_forum_topic(
+        self,
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.close_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs)
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.close_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().close_forum_topic(
+            chat_id=self.id,
+            message_thread_id=message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def reopen_forum_topic(
+        self,
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.reopen_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs)
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.reopen_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().reopen_forum_topic(
+            chat_id=self.id,
+            message_thread_id=message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def delete_forum_topic(
+        self,
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.delete_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs)
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.delete_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().delete_forum_topic(
+            chat_id=self.id,
+            message_thread_id=message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def unpin_all_forum_topic_messages(
+        self,
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.unpin_all_forum_topic_messages(chat_id=update.effective_chat.id,
+                *args, **kwargs)
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.unpin_all_forum_topic_messages`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().unpin_all_forum_topic_messages(
+            chat_id=self.id,
+            message_thread_id=message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
     async def get_menu_button(
         self,
         *,
diff --git a/telegram/_chatadministratorrights.py b/telegram/_chatadministratorrights.py
index 185782373ca..877bd279f66 100644
--- a/telegram/_chatadministratorrights.py
+++ b/telegram/_chatadministratorrights.py
@@ -29,7 +29,12 @@ class ChatAdministratorRights(TelegramObject):
     considered equal, if their :attr:`is_anonymous`, :attr:`can_manage_chat`,
     :attr:`can_delete_messages`, :attr:`can_manage_video_chats`, :attr:`can_restrict_members`,
     :attr:`can_promote_members`, :attr:`can_change_info`, :attr:`can_invite_users`,
-    :attr:`can_post_messages`, :attr:`can_edit_messages`, :attr:`can_pin_messages` are equal.
+    :attr:`can_post_messages`, :attr:`can_edit_messages`, :attr:`can_pin_messages`,
+    :attr:`can_manage_topics` are equal.
+
+    .. versionchanged:: 20.0
+        :attr:`can_manage_topics` is considered as well when comparing objects of
+        this type in terms of equality.
 
     .. seealso: :meth:`Bot.set_my_default_administrator_rights`,
         :meth:`Bot.get_my_default_administrator_rights`
@@ -62,6 +67,10 @@ class ChatAdministratorRights(TelegramObject):
             messages of other users.
         can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to pin
             messages; groups and supergroups only.
+        can_manage_topics (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+            to create, rename, close, and reopen forum topics; supergroups only.
+
+            .. versionadded:: 20.0
 
     Attributes:
         is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
@@ -89,6 +98,10 @@ class ChatAdministratorRights(TelegramObject):
             messages of other users.
         can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to pin
             messages; groups and supergroups only.
+        can_manage_topics (:obj:`bool`) Optional. :obj:`True`, if the user is allowed
+            to create, rename, close, and reopen forum topics; supergroups only.
+
+                .. versionadded:: 20.0
     """
 
     __slots__ = (
@@ -103,6 +116,7 @@ class ChatAdministratorRights(TelegramObject):
         "can_post_messages",
         "can_edit_messages",
         "can_pin_messages",
+        "can_manage_topics",
     )
 
     def __init__(
@@ -118,6 +132,7 @@ def __init__(
         can_post_messages: bool = None,
         can_edit_messages: bool = None,
         can_pin_messages: bool = None,
+        can_manage_topics: bool = None,
         *,
         api_kwargs: JSONDict = None,
     ) -> None:
@@ -135,6 +150,7 @@ def __init__(
         self.can_post_messages = can_post_messages
         self.can_edit_messages = can_edit_messages
         self.can_pin_messages = can_pin_messages
+        self.can_manage_topics = can_manage_topics
 
         self._id_attrs = (
             self.is_anonymous,
@@ -148,6 +164,7 @@ def __init__(
             self.can_post_messages,
             self.can_edit_messages,
             self.can_pin_messages,
+            self.can_manage_topics,
         )
 
     @classmethod
@@ -159,7 +176,7 @@ def all_rights(cls) -> "ChatAdministratorRights":
 
         .. versionadded:: 20.0
         """
-        return cls(True, True, True, True, True, True, True, True, True, True, True)
+        return cls(True, True, True, True, True, True, True, True, True, True, True, True)
 
     @classmethod
     def no_rights(cls) -> "ChatAdministratorRights":
@@ -169,4 +186,6 @@ def no_rights(cls) -> "ChatAdministratorRights":
 
         .. versionadded:: 20.0
         """
-        return cls(False, False, False, False, False, False, False, False, False, False, False)
+        return cls(
+            False, False, False, False, False, False, False, False, False, False, False, False
+        )
diff --git a/telegram/_chatmember.py b/telegram/_chatmember.py
index 363009af607..fd7da6e6f2c 100644
--- a/telegram/_chatmember.py
+++ b/telegram/_chatmember.py
@@ -173,6 +173,8 @@ class ChatMemberAdministrator(ChatMember):
        * Argument and attribute ``can_manage_voice_chats`` were renamed to
          :paramref:`can_manage_video_chats` and  :attr:`can_manage_video_chats` in accordance to
          Bot API 6.0.
+       * The argument :paramref:`can_manage_topics` was added, which changes the position of the
+         optional argument :paramref:`custom_title`.
 
     Args:
         user (:class:`telegram.User`): Information about the user.
@@ -207,6 +209,10 @@ class ChatMemberAdministrator(ChatMember):
             messages; channels only.
         can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
             to pin messages; groups and supergroups only.
+        can_manage_topics (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+            to create, rename, close, and reopen forum topics; supergroups only.
+
+            .. versionadded:: 20.0
         custom_title (:obj:`str`, optional): Custom title for this user.
 
     Attributes:
@@ -244,6 +250,10 @@ class ChatMemberAdministrator(ChatMember):
             messages; channels only.
         can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
             to pin messages; groups and supergroups only.
+        can_manage_topics (:obj:`bool`) Optional. :obj:`True`, if the user is allowed
+            to create, rename, close, and reopen forum topics; supergroups only
+
+            .. versionadded:: 20.0
         custom_title (:obj:`str`): Optional. Custom title for this user.
     """
 
@@ -260,6 +270,7 @@ class ChatMemberAdministrator(ChatMember):
         "can_post_messages",
         "can_edit_messages",
         "can_pin_messages",
+        "can_manage_topics",
         "custom_title",
     )
 
@@ -278,6 +289,7 @@ def __init__(
         can_post_messages: bool = None,
         can_edit_messages: bool = None,
         can_pin_messages: bool = None,
+        can_manage_topics: bool = None,
         custom_title: str = None,
         *,
         api_kwargs: JSONDict = None,
@@ -295,6 +307,7 @@ def __init__(
         self.can_post_messages = can_post_messages
         self.can_edit_messages = can_edit_messages
         self.can_pin_messages = can_pin_messages
+        self.can_manage_topics = can_manage_topics
         self.custom_title = custom_title
 
 
@@ -332,6 +345,9 @@ class ChatMemberRestricted(ChatMember):
     in the chat. Supergroups only.
 
     .. versionadded:: 13.7
+    .. versionchanged:: 20.0
+       The argument :paramref:`can_manage_topics` was added, which changes the position of the
+       optional argument :paramref:`until_date`.
 
     Args:
         user (:class:`telegram.User`): Information about the user.
@@ -353,6 +369,10 @@ class ChatMemberRestricted(ChatMember):
             to send animations, games, stickers and use inline bots.
         can_add_web_page_previews (:obj:`bool`): :obj:`True`, if the user is
            allowed to add web page previews to their messages.
+        can_manage_topics (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+            to create, rename, close, and reopen forum topics; supergroups only
+
+            .. versionadded:: 20.0
         until_date (:class:`datetime.datetime`): Date when restrictions
            will be lifted for this user.
 
@@ -378,6 +398,10 @@ class ChatMemberRestricted(ChatMember):
             to send animations, games, stickers and use inline bots.
         can_add_web_page_previews (:obj:`bool`): :obj:`True`, if the user is
            allowed to add web page previews to their messages.
+        can_manage_topics (:obj:`bool`) Optional. :obj:`True`, if the user is allowed
+            to create, rename, close, and reopen forum topics; supergroups only
+
+            .. versionadded:: 20.0
         until_date (:class:`datetime.datetime`): Date when restrictions
            will be lifted for this user.
 
@@ -393,6 +417,7 @@ class ChatMemberRestricted(ChatMember):
         "can_send_polls",
         "can_send_other_messages",
         "can_add_web_page_previews",
+        "can_manage_topics",
         "until_date",
     )
 
@@ -408,6 +433,7 @@ def __init__(
         can_send_polls: bool,
         can_send_other_messages: bool,
         can_add_web_page_previews: bool,
+        can_manage_topics: bool,
         until_date: datetime.datetime,
         *,
         api_kwargs: JSONDict = None,
@@ -422,6 +448,7 @@ def __init__(
         self.can_send_polls = can_send_polls
         self.can_send_other_messages = can_send_other_messages
         self.can_add_web_page_previews = can_add_web_page_previews
+        self.can_manage_topics = can_manage_topics
         self.until_date = until_date
 
 
diff --git a/telegram/_chatpermissions.py b/telegram/_chatpermissions.py
index 416e3306a27..03c8bde8bde 100644
--- a/telegram/_chatpermissions.py
+++ b/telegram/_chatpermissions.py
@@ -28,12 +28,17 @@ class ChatPermissions(TelegramObject):
     Objects of this class are comparable in terms of equality. Two objects of this class are
     considered equal, if their :attr:`can_send_messages`, :attr:`can_send_media_messages`,
     :attr:`can_send_polls`, :attr:`can_send_other_messages`, :attr:`can_add_web_page_previews`,
-    :attr:`can_change_info`, :attr:`can_invite_users` and :attr:`can_pin_messages` are equal.
+    :attr:`can_change_info`, :attr:`can_invite_users` and :attr:`can_pin_messages`,
+    :attr:`can_manage_topics` are equal.
+
+    .. versionchanged:: 20.0
+        :attr:`can_manage_topics` is considered as well when comparing objects of
+        this type in terms of equality.
 
     Note:
         Though not stated explicitly in the official docs, Telegram changes not only the
         permissions that are set, but also sets all the others to :obj:`False`. However, since not
-        documented, this behaviour may change unbeknown to PTB.
+        documented, this behavior may change unbeknown to PTB.
 
     Args:
         can_send_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to send text
@@ -54,6 +59,11 @@ class ChatPermissions(TelegramObject):
             users to the chat.
         can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to pin
             messages. Ignored in public supergroups.
+        can_manage_topics (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+            to create forum topics. If omitted defaults to the value of
+            :attr:`can_pin_messages`.
+
+            .. versionadded:: 20.0
 
     Attributes:
         can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send text
@@ -74,6 +84,11 @@ class ChatPermissions(TelegramObject):
             new users to the chat.
         can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to pin
             messages. Ignored in public supergroups.
+        can_manage_topics (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
+            to create forum topics. If omitted defaults to the value of
+            :attr:`can_pin_messages`.
+
+            .. versionadded:: 20.0
 
     """
 
@@ -86,6 +101,7 @@ class ChatPermissions(TelegramObject):
         "can_change_info",
         "can_pin_messages",
         "can_add_web_page_previews",
+        "can_manage_topics",
     )
 
     def __init__(
@@ -98,6 +114,7 @@ def __init__(
         can_change_info: bool = None,
         can_invite_users: bool = None,
         can_pin_messages: bool = None,
+        can_manage_topics: bool = None,
         *,
         api_kwargs: JSONDict = None,
     ):
@@ -111,6 +128,7 @@ def __init__(
         self.can_change_info = can_change_info
         self.can_invite_users = can_invite_users
         self.can_pin_messages = can_pin_messages
+        self.can_manage_topics = can_manage_topics
 
         self._id_attrs = (
             self.can_send_messages,
@@ -121,6 +139,7 @@ def __init__(
             self.can_change_info,
             self.can_invite_users,
             self.can_pin_messages,
+            self.can_manage_topics,
         )
 
     @classmethod
@@ -133,7 +152,7 @@ def all_permissions(cls) -> "ChatPermissions":
         .. versionadded:: 20.0
 
         """
-        return cls(True, True, True, True, True, True, True, True)
+        return cls(True, True, True, True, True, True, True, True, True)
 
     @classmethod
     def no_permissions(cls) -> "ChatPermissions":
@@ -143,4 +162,4 @@ def no_permissions(cls) -> "ChatPermissions":
 
         .. versionadded:: 20.0
         """
-        return cls(False, False, False, False, False, False, False, False)
+        return cls(False, False, False, False, False, False, False, False, False)
diff --git a/telegram/_forumtopic.py b/telegram/_forumtopic.py
new file mode 100644
index 00000000000..68ea478831b
--- /dev/null
+++ b/telegram/_forumtopic.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2022
+# Leandro Toledo de Souza <devs@python-telegram-bot.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser Public License for more details.
+#
+# You should have received a copy of the GNU Lesser Public License
+# along with this program.  If not, see [http://www.gnu.org/licenses/].
+"""This module contains objects related to Telegram forum topics."""
+
+from telegram._telegramobject import TelegramObject
+from telegram._utils.types import JSONDict
+
+
+class ForumTopic(TelegramObject):
+    """
+    This object represents the content of a service message about a new forum topic created in
+    the chat.
+
+    Objects of this class are comparable in terms of equality. Two objects of this class are
+    considered equal, if their :attr:`message_thread_id`, :attr:`name` and :attr:`icon_color`
+    are equal.
+
+    .. versionadded:: 20.0
+
+    Args:
+        message_thread_id (:obj:`int`): Unique identifier of the forum topic
+        name (:obj:`str`): Name of the topic
+        icon_color (:obj:`int`): Color of the topic icon in RGB format
+        icon_custom_emoji_id (:obj:`str`, optional): Unique identifier of the custom emoji shown
+            as the topic icon.
+
+    Attributes:
+        message_thread_id (:obj:`int`): Unique identifier of the forum topic
+        name (:obj:`str`): Name of the topic
+        icon_color (:obj:`int`): Color of the topic icon in RGB format
+        icon_custom_emoji_id (:obj:`str`): Optional. Unique identifier of the custom emoji shown
+            as the topic icon.
+    """
+
+    __slots__ = ("message_thread_id", "name", "icon_color", "icon_custom_emoji_id")
+
+    def __init__(
+        self,
+        message_thread_id: int,
+        name: str,
+        icon_color: int,
+        icon_custom_emoji_id: str = None,
+        *,
+        api_kwargs: JSONDict = None,
+    ):
+        super().__init__(api_kwargs=api_kwargs)
+        self.message_thread_id = message_thread_id
+        self.name = name
+        self.icon_color = icon_color
+        self.icon_custom_emoji_id = icon_custom_emoji_id
+
+        self._id_attrs = (self.message_thread_id, self.name, self.icon_color)
+
+
+class ForumTopicCreated(TelegramObject):
+    """
+    This object represents the content of a service message about a new forum topic created in
+    the chat.
+
+    Objects of this class are comparable in terms of equality. Two objects of this class are
+    considered equal, if their :attr:`name` and :attr:`icon_color` are equal.
+
+    .. versionadded:: 20.0
+
+    Args:
+        name (:obj:`str`): Name of the topic
+        icon_color (:obj:`int`): Color of the topic icon in RGB format
+        icon_custom_emoji_id (:obj:`str`, optional): Unique identifier of the custom emoji shown
+            as the topic icon.
+
+    Attributes:
+        name (:obj:`str`): Name of the topic
+        icon_color (:obj:`int`): Color of the topic icon in RGB format
+        icon_custom_emoji_id (:obj:`str`): Optional. Unique identifier of the custom emoji shown
+            as the topic icon.
+    """
+
+    __slots__ = ("name", "icon_color", "icon_custom_emoji_id")
+
+    def __init__(
+        self,
+        name: str,
+        icon_color: int,
+        icon_custom_emoji_id: str = None,
+        *,
+        api_kwargs: JSONDict = None,
+    ):
+        super().__init__(api_kwargs=api_kwargs)
+        self.name = name
+        self.icon_color = icon_color
+        self.icon_custom_emoji_id = icon_custom_emoji_id
+
+        self._id_attrs = (self.name, self.icon_color)
+
+
+class ForumTopicClosed(TelegramObject):
+    """
+    This object represents a service message about a forum topic closed in the chat.
+    Currently holds no information.
+
+    .. versionadded:: 20.0
+    """
+
+    __slots__ = ()
+
+
+class ForumTopicReopened(TelegramObject):
+    """
+    This object represents a service message about a forum topic reopened in the chat.
+    Currently holds no information.
+
+    .. versionadded:: 20.0
+    """
+
+    __slots__ = ()
diff --git a/telegram/_message.py b/telegram/_message.py
index 9aad571c8c6..4a69d66934a 100644
--- a/telegram/_message.py
+++ b/telegram/_message.py
@@ -36,6 +36,7 @@
 from telegram._files.video import Video
 from telegram._files.videonote import VideoNote
 from telegram._files.voice import Voice
+from telegram._forumtopic import ForumTopicClosed, ForumTopicCreated, ForumTopicReopened
 from telegram._games.game import Game
 from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
 from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged
@@ -248,6 +249,25 @@ class Message(TelegramObject):
         reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
             to the message. :paramref:`~telegram.InlineKeyboardButton.login_url` buttons are
             represented as ordinary url buttons.
+        is_topic_message (:obj:`bool`, optional): True, if the message is sent to a forum topic.
+
+            .. versionadded:: 20.0
+        message_thread_id (:obj:`int`, optional): Unique identifier of a message thread to which
+            the message belongs; for supergroups only.
+
+            .. versionadded:: 20.0
+        forum_topic_created (:class:`telegram.ForumTopicCreated`, optional): Service message:
+            forum topic created
+
+            .. versionadded:: 20.0
+        forum_topic_closed (:class:`telegram.ForumTopicClosed`, optional): Service message:
+            forum topic closed
+
+            .. versionadded:: 20.0
+        forum_topic_reopened (:class:`telegram.ForumTopicReopened`, optional): Service message:
+            forum topic reopened
+
+            .. versionadded:: 20.0
 
     Attributes:
         message_id (:obj:`int`): Unique message identifier inside this chat.
@@ -400,6 +420,25 @@ class Message(TelegramObject):
         reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
             to the message. :paramref:`~telegram.InlineKeyboardButton.login_url` buttons are
             represented as ordinary url buttons.
+        is_topic_message (:obj:`bool`): Optional. True, if the message is sent to a forum topic.
+
+            .. versionadded:: 20.0
+        message_thread_id (:obj:`int`): Optional. Unique identifier of a message thread to which
+            the message belongs; for supergroups only.
+
+            .. versionadded:: 20.0
+        forum_topic_created (:class:`telegram.ForumTopicCreated`): Optional. Service message:
+            forum topic created
+
+            .. versionadded:: 20.0
+        forum_topic_closed (:class:`telegram.ForumTopicClosed`): Optional. Service message:
+            forum topic closed
+
+            .. versionadded:: 20.0
+        forum_topic_reopened (:class:`telegram.ForumTopicReopened`): Optional. Service message:
+            forum topic reopened
+
+            .. versionadded:: 20.0
 
     .. |custom_emoji_formatting_note| replace:: Custom emoji entities will currently be ignored
         by this function. Instead, the supplied replacement for the emoji will be used.
@@ -467,6 +506,11 @@ class Message(TelegramObject):
         "is_automatic_forward",
         "has_protected_content",
         "web_app_data",
+        "is_topic_message",
+        "message_thread_id",
+        "forum_topic_created",
+        "forum_topic_closed",
+        "forum_topic_reopened",
     )
 
     def __init__(
@@ -530,6 +574,11 @@ def __init__(
         is_automatic_forward: bool = None,
         has_protected_content: bool = None,
         web_app_data: WebAppData = None,
+        is_topic_message: bool = None,
+        message_thread_id: int = None,
+        forum_topic_created: ForumTopicCreated = None,
+        forum_topic_closed: ForumTopicClosed = None,
+        forum_topic_reopened: ForumTopicReopened = None,
         *,
         api_kwargs: JSONDict = None,
     ):
@@ -596,6 +645,11 @@ def __init__(
         self.video_chat_participants_invited = video_chat_participants_invited
         self.reply_markup = reply_markup
         self.web_app_data = web_app_data
+        self.is_topic_message = is_topic_message
+        self.message_thread_id = message_thread_id
+        self.forum_topic_created = forum_topic_created
+        self.forum_topic_closed = forum_topic_closed
+        self.forum_topic_reopened = forum_topic_reopened
 
         self._effective_attachment = DEFAULT_NONE
 
@@ -686,6 +740,13 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Message"]:
             data.get("video_chat_participants_invited"), bot
         )
         data["web_app_data"] = WebAppData.de_json(data.get("web_app_data"), bot)
+        data["forum_topic_closed"] = ForumTopicClosed.de_json(data.get("forum_topic_closed"), bot)
+        data["forum_topic_created"] = ForumTopicCreated.de_json(
+            data.get("forum_topic_created"), bot
+        )
+        data["forum_topic_reopened"] = ForumTopicReopened.de_json(
+            data.get("forum_topic_reopened"), bot
+        )
 
         return super().de_json(data=data, bot=bot)
 
@@ -784,6 +845,7 @@ async def reply_text(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -819,6 +881,7 @@ async def reply_text(
             allow_sending_without_reply=allow_sending_without_reply,
             entities=entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -836,6 +899,7 @@ async def reply_markdown(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -881,6 +945,7 @@ async def reply_markdown(
             allow_sending_without_reply=allow_sending_without_reply,
             entities=entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -898,6 +963,7 @@ async def reply_markdown_v2(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -939,6 +1005,7 @@ async def reply_markdown_v2(
             allow_sending_without_reply=allow_sending_without_reply,
             entities=entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -956,6 +1023,7 @@ async def reply_html(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -997,6 +1065,7 @@ async def reply_html(
             allow_sending_without_reply=allow_sending_without_reply,
             entities=entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1013,6 +1082,7 @@ async def reply_media_group(
         reply_to_message_id: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1055,6 +1125,7 @@ async def reply_media_group(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
@@ -1071,6 +1142,7 @@ async def reply_photo(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         quote: bool = None,
@@ -1108,6 +1180,7 @@ async def reply_photo(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1130,6 +1203,7 @@ async def reply_audio(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         quote: bool = None,
@@ -1171,6 +1245,7 @@ async def reply_audio(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1191,6 +1266,7 @@ async def reply_document(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         quote: bool = None,
@@ -1235,6 +1311,7 @@ async def reply_document(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_animation(
@@ -1252,6 +1329,7 @@ async def reply_animation(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         quote: bool = None,
@@ -1299,6 +1377,7 @@ async def reply_animation(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_sticker(
@@ -1309,6 +1388,7 @@ async def reply_sticker(
         reply_markup: ReplyMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1346,6 +1426,7 @@ async def reply_sticker(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_video(
@@ -1364,6 +1445,7 @@ async def reply_video(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         quote: bool = None,
@@ -1411,6 +1493,7 @@ async def reply_video(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_video_note(
@@ -1424,6 +1507,7 @@ async def reply_video_note(
         thumb: FileInput = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         quote: bool = None,
@@ -1467,6 +1551,7 @@ async def reply_video_note(
             allow_sending_without_reply=allow_sending_without_reply,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_voice(
@@ -1481,6 +1566,7 @@ async def reply_voice(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         quote: bool = None,
@@ -1525,6 +1611,7 @@ async def reply_voice(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_location(
@@ -1540,6 +1627,7 @@ async def reply_location(
         proximity_alert_radius: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         location: Location = None,
         quote: bool = None,
@@ -1584,6 +1672,7 @@ async def reply_location(
             proximity_alert_radius=proximity_alert_radius,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_venue(
@@ -1601,6 +1690,7 @@ async def reply_venue(
         google_place_type: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         venue: Venue = None,
         quote: bool = None,
@@ -1647,6 +1737,7 @@ async def reply_venue(
             google_place_type=google_place_type,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_contact(
@@ -1660,6 +1751,7 @@ async def reply_contact(
         vcard: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         contact: Contact = None,
         quote: bool = None,
@@ -1702,6 +1794,7 @@ async def reply_contact(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_poll(
@@ -1723,6 +1816,7 @@ async def reply_poll(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         explanation_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1771,6 +1865,7 @@ async def reply_poll(
             allow_sending_without_reply=allow_sending_without_reply,
             explanation_entities=explanation_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_dice(
@@ -1781,6 +1876,7 @@ async def reply_dice(
         emoji: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1818,6 +1914,7 @@ async def reply_dice(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_chat_action(
@@ -1860,6 +1957,7 @@ async def reply_game(
         reply_markup: "InlineKeyboardMarkup" = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1899,6 +1997,7 @@ async def reply_game(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_invoice(
@@ -1929,6 +2028,7 @@ async def reply_invoice(
         max_tip_amount: int = None,
         suggested_tip_amounts: List[int] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1998,6 +2098,7 @@ async def reply_invoice(
             max_tip_amount=max_tip_amount,
             suggested_tip_amounts=suggested_tip_amounts,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def forward(
@@ -2005,6 +2106,7 @@ async def forward(
         chat_id: Union[int, str],
         disable_notification: DVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2041,6 +2143,7 @@ async def forward(
             message_id=self.message_id,
             disable_notification=disable_notification,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2059,6 +2162,7 @@ async def copy(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2099,6 +2203,7 @@ async def copy(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def reply_copy(
@@ -2113,6 +2218,7 @@ async def reply_copy(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         quote: bool = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2161,6 +2267,7 @@ async def reply_copy(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def edit_text(
@@ -2646,6 +2753,180 @@ async def unpin(
             api_kwargs=api_kwargs,
         )
 
+    async def edit_forum_topic(
+        self,
+        name: str,
+        icon_custom_emoji_id: str,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.edit_forum_topic(
+                chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args,
+                **kwargs
+             )
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.edit_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().edit_forum_topic(
+            chat_id=self.chat_id,
+            message_thread_id=self.message_thread_id,
+            name=name,
+            icon_custom_emoji_id=icon_custom_emoji_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def close_forum_topic(
+        self,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.close_forum_topic(
+                chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args,
+                **kwargs
+             )
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.close_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().close_forum_topic(
+            chat_id=self.chat_id,
+            message_thread_id=self.message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def reopen_forum_topic(
+        self,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+            await bot.reopen_forum_topic(
+                chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args,
+                **kwargs
+             )
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.reopen_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().reopen_forum_topic(
+            chat_id=self.chat_id,
+            message_thread_id=self.message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def delete_forum_topic(
+        self,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.delete_forum_topic(
+                chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args,
+                **kwargs
+             )
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.delete_forum_topic`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().delete_forum_topic(
+            chat_id=self.chat_id,
+            message_thread_id=self.message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
+    async def unpin_all_forum_topic_messages(
+        self,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+    ) -> bool:
+        """Shortcut for::
+
+             await bot.unpin_all_forum_topic_messages(
+                chat_id=message.chat_id, message_thread_id=message.message_thread_id, *args,
+                **kwargs
+             )
+
+        For the documentation of the arguments, please see
+        :meth:`telegram.Bot.unpin_all_forum_topic_messages`.
+
+        .. versionadded:: 20.0
+
+        Returns:
+            :obj:`bool`: On success, :obj:`True` is returned.
+        """
+        return await self.get_bot().unpin_all_forum_topic_messages(
+            chat_id=self.chat_id,
+            message_thread_id=self.message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=api_kwargs,
+        )
+
     def parse_entity(self, entity: MessageEntity) -> str:
         """Returns the text from a given :class:`telegram.MessageEntity`.
 
diff --git a/telegram/_user.py b/telegram/_user.py
index 01bf0d93a87..015ec93234e 100644
--- a/telegram/_user.py
+++ b/telegram/_user.py
@@ -376,6 +376,7 @@ async def send_message(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -404,6 +405,7 @@ async def send_message(
             allow_sending_without_reply=allow_sending_without_reply,
             entities=entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -422,6 +424,7 @@ async def send_photo(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -452,6 +455,7 @@ async def send_photo(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -468,6 +472,7 @@ async def send_media_group(
         reply_to_message_id: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = 20,
@@ -500,6 +505,7 @@ async def send_media_group(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
@@ -520,6 +526,7 @@ async def send_audio(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -554,6 +561,7 @@ async def send_audio(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -605,6 +613,7 @@ async def send_contact(
         vcard: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         contact: "Contact" = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -640,6 +649,7 @@ async def send_contact(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_dice(
@@ -650,6 +660,7 @@ async def send_dice(
         emoji: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -680,6 +691,7 @@ async def send_dice(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_document(
@@ -695,6 +707,7 @@ async def send_document(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -732,6 +745,7 @@ async def send_document(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_game(
@@ -742,6 +756,7 @@ async def send_game(
         reply_markup: "InlineKeyboardMarkup" = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -772,6 +787,7 @@ async def send_game(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_invoice(
@@ -802,6 +818,7 @@ async def send_invoice(
         max_tip_amount: int = None,
         suggested_tip_amounts: List[int] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -862,6 +879,7 @@ async def send_invoice(
             max_tip_amount=max_tip_amount,
             suggested_tip_amounts=suggested_tip_amounts,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_location(
@@ -877,6 +895,7 @@ async def send_location(
         proximity_alert_radius: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         location: "Location" = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -914,6 +933,7 @@ async def send_location(
             proximity_alert_radius=proximity_alert_radius,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_animation(
@@ -931,6 +951,7 @@ async def send_animation(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -970,6 +991,7 @@ async def send_animation(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_sticker(
@@ -980,6 +1002,7 @@ async def send_sticker(
         reply_markup: ReplyMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = 20,
@@ -1010,6 +1033,7 @@ async def send_sticker(
             api_kwargs=api_kwargs,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_video(
@@ -1028,6 +1052,7 @@ async def send_video(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1068,6 +1093,7 @@ async def send_video(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_venue(
@@ -1085,6 +1111,7 @@ async def send_venue(
         google_place_type: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         venue: "Venue" = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1124,6 +1151,7 @@ async def send_venue(
             google_place_type=google_place_type,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_video_note(
@@ -1137,6 +1165,7 @@ async def send_video_note(
         thumb: FileInput = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1172,6 +1201,7 @@ async def send_video_note(
             allow_sending_without_reply=allow_sending_without_reply,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_voice(
@@ -1186,6 +1216,7 @@ async def send_voice(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1222,6 +1253,7 @@ async def send_voice(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_poll(
@@ -1243,6 +1275,7 @@ async def send_poll(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         explanation_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1284,6 +1317,7 @@ async def send_poll(
             allow_sending_without_reply=allow_sending_without_reply,
             explanation_entities=explanation_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def send_copy(
@@ -1298,6 +1332,7 @@ async def send_copy(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1332,6 +1367,7 @@ async def send_copy(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def copy_message(
@@ -1346,6 +1382,7 @@ async def copy_message(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1380,6 +1417,7 @@ async def copy_message(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
         )
 
     async def approve_join_request(
diff --git a/telegram/constants.py b/telegram/constants.py
index f5cd7b838f2..1a5bb3e9405 100644
--- a/telegram/constants.py
+++ b/telegram/constants.py
@@ -39,12 +39,14 @@
     "ChatAction",
     "ChatID",
     "ChatInviteLinkLimit",
+    "ChatLimit",
     "ChatMemberStatus",
     "ChatType",
     "CustomEmojiStickerLimit",
     "DiceEmoji",
     "FileSizeLimit",
     "FloodLimit",
+    "ForumIconColor",
     "InlineKeyboardMarkupLimit",
     "InlineQueryLimit",
     "InlineQueryResultType",
@@ -62,6 +64,7 @@
     "PollType",
     "SUPPORTED_WEBHOOK_PORTS",
     "StickerType",
+    "TopicLimit",
     "WebhookLimit",
     "UpdateType",
 ]
@@ -370,6 +373,66 @@ class FloodLimit(IntEnum):
     """
 
 
+class ForumIconColor(IntEnum):
+    """This enum contains the available colors for use in
+    :paramref:`telegram.Bot.create_forum_topic.icon_color`. The enum members of this enumeration
+    are instances of :class:`int` and can be treated as such.
+
+    .. versionadded:: 20.0
+    """
+
+    __slots__ = ()
+
+    BLUE = 0x6FB9F0
+    """:obj:`int`: An icon with a color which corresponds to blue.
+
+    .. raw:: html
+
+        <div style="height:15px; width:15px; background-color:#6FB9F0;"></div>
+
+    """
+    YELLOW = 0xFFD67E
+    """:obj:`int`: An icon with a color which corresponds to yellow.
+
+    .. raw:: html
+
+        <div style="height:15px; width:15px; background-color:#FFD67E;"></div>
+
+    """
+    PURPLE = 0xCB86DB
+    """:obj:`int`: An icon with a color which corresponds to purple.
+
+    .. raw:: html
+
+        <div style="height:15px; width:15px; background-color:#CB86DB;"></div>
+
+    """
+    GREEN = 0x8EEE98
+    """:obj:`int`: An icon with a color which corresponds to green.
+
+    .. raw:: html
+
+        <div style="height:15px; width:15px; background-color:#8EEE98;"></div>
+
+    """
+    PINK = 0xFF93B2
+    """:obj:`int`: An icon with a color which corresponds to pink.
+
+    .. raw:: html
+
+        <div style="height:15px; width:15px; background-color:#FF93B2;"></div>
+
+    """
+    RED = 0xFB6F5F
+    """:obj:`int`: An icon with a color which corresponds to red.
+
+    .. raw:: html
+
+        <div style="height:15px; width:15px; background-color:#FB6F5F;"></div>
+
+    """
+
+
 class InlineKeyboardMarkupLimit(IntEnum):
     """This enum contains limitations for :class:`telegram.InlineKeyboardMarkup`/
     :meth:`telegram.Bot.send_message` & friends. The enum
@@ -901,3 +964,33 @@ class WebhookLimit(IntEnum):
     """:obj:`int`: Minimum length of the secret token."""
     MAX_SECRET_TOKEN_LENGTH = 256
     """:obj:`int`: Maximum length of the secret token."""
+
+
+class ChatLimit(IntEnum):
+    """This enum contains limitations for :paramref:`telegram.Bot.set_chat_title.title`. The
+    enum members of this enumeration are instances of :class:`int` and can be treated as such.
+
+    .. versionadded:: 20.0
+    """
+
+    __slots__ = ()
+
+    MIN_TITLE_LENGTH = 1
+    """:obj:`int`: Minimum length of a new chat title."""
+    MAX_TITLE_LENGTH = 128
+    """:obj:`int`: Maximum length of a new chat title."""
+
+
+class TopicLimit(IntEnum):
+    """This enum contains limitations for :paramref:`telegram.Bot.edit_forum_topic.name`. The
+    enum members of this enumeration are instances of :class:`int` and can be treated as such.
+
+    .. versionadded:: 20.0
+    """
+
+    __slots__ = ()
+
+    MIN_NAME_LENGTH = 1
+    """:obj:`int`: Minimum length of the topic name."""
+    MAX_NAME_LENGTH = 128
+    """:obj:`int`: Maximum length of the topic name."""
diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py
index b6622108f02..0fd9713068b 100644
--- a/telegram/ext/_extbot.py
+++ b/telegram/ext/_extbot.py
@@ -53,6 +53,7 @@
     Contact,
     Document,
     File,
+    ForumTopic,
     GameHighScore,
     InlineKeyboardMarkup,
     InputMedia,
@@ -467,6 +468,7 @@ async def _send_message(
         reply_markup: ReplyMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -484,6 +486,7 @@ async def _send_message(
             reply_markup=self._replace_keyboard(reply_markup),
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -631,6 +634,7 @@ async def copy_message(
         allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -652,6 +656,7 @@ async def copy_message(
             allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=self._replace_keyboard(reply_markup),
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1103,6 +1108,28 @@ async def delete_chat_sticker_set(
             api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
         )
 
+    async def delete_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+        rate_limit_args: RLARGS = None,
+    ) -> bool:
+        return await super().delete_forum_topic(
+            chat_id=chat_id,
+            message_thread_id=message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
+        )
+
     async def delete_message(
         self,
         chat_id: Union[str, int],
@@ -1217,6 +1244,32 @@ async def edit_chat_invite_link(
             api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
         )
 
+    async def edit_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        name: str,
+        icon_custom_emoji_id: str,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+        rate_limit_args: RLARGS = None,
+    ) -> bool:
+        return await super().edit_forum_topic(
+            chat_id=chat_id,
+            message_thread_id=message_thread_id,
+            name=name,
+            icon_custom_emoji_id=icon_custom_emoji_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
+        )
+
     async def edit_message_caption(
         self,
         chat_id: Union[str, int] = None,
@@ -1402,6 +1455,7 @@ async def forward_message(
         message_id: int,
         disable_notification: DVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1416,6 +1470,7 @@ async def forward_message(
             message_id=message_id,
             disable_notification=disable_notification,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1527,6 +1582,24 @@ async def get_file(
             api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
         )
 
+    async def get_forum_topic_icon_stickers(
+        self,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+        rate_limit_args: RLARGS = None,
+    ) -> List[Sticker]:
+        return await super().get_forum_topic_icon_stickers(
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
+        )
+
     async def get_game_high_scores(
         self,
         user_id: Union[int, str],
@@ -1751,6 +1824,54 @@ async def close(
             api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
         )
 
+    async def close_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+        rate_limit_args: RLARGS = None,
+    ) -> bool:
+        return await super().close_forum_topic(
+            chat_id=chat_id,
+            message_thread_id=message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
+        )
+
+    async def create_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        name: str,
+        icon_color: int = None,
+        icon_custom_emoji_id: str = None,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+        rate_limit_args: RLARGS = None,
+    ) -> ForumTopic:
+        return await super().create_forum_topic(
+            chat_id=chat_id,
+            name=name,
+            icon_color=icon_color,
+            icon_custom_emoji_id=icon_custom_emoji_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
+        )
+
     async def pin_chat_message(
         self,
         chat_id: Union[str, int],
@@ -1790,6 +1911,7 @@ async def promote_chat_member(
         is_anonymous: bool = None,
         can_manage_chat: bool = None,
         can_manage_video_chats: bool = None,
+        can_manage_topics: bool = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1812,6 +1934,29 @@ async def promote_chat_member(
             is_anonymous=is_anonymous,
             can_manage_chat=can_manage_chat,
             can_manage_video_chats=can_manage_video_chats,
+            can_manage_topics=can_manage_topics,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
+        )
+
+    async def reopen_forum_topic(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+        rate_limit_args: RLARGS = None,
+    ) -> bool:
+        return await super().reopen_forum_topic(
+            chat_id=chat_id,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1883,6 +2028,7 @@ async def send_animation(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1907,6 +2053,7 @@ async def send_animation(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             filename=filename,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -1931,6 +2078,7 @@ async def send_audio(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1955,6 +2103,7 @@ async def send_audio(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             filename=filename,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -1997,6 +2146,7 @@ async def send_contact(
         vcard: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         contact: Contact = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2017,6 +2167,7 @@ async def send_contact(
             vcard=vcard,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             contact=contact,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -2034,6 +2185,7 @@ async def send_dice(
         emoji: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2050,6 +2202,7 @@ async def send_dice(
             emoji=emoji,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2071,6 +2224,7 @@ async def send_document(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2093,6 +2247,7 @@ async def send_document(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             filename=filename,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -2110,6 +2265,7 @@ async def send_game(
         reply_markup: InlineKeyboardMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2126,6 +2282,7 @@ async def send_game(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2162,6 +2319,7 @@ async def send_invoice(
         max_tip_amount: int = None,
         suggested_tip_amounts: List[int] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2198,6 +2356,7 @@ async def send_invoice(
             max_tip_amount=max_tip_amount,
             suggested_tip_amounts=suggested_tip_amounts,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2219,6 +2378,7 @@ async def send_location(
         proximity_alert_radius: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         location: Location = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2241,6 +2401,7 @@ async def send_location(
             proximity_alert_radius=proximity_alert_radius,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             location=location,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -2259,6 +2420,7 @@ async def send_media_group(
         reply_to_message_id: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = 20,
@@ -2277,6 +2439,7 @@ async def send_media_group(
             reply_to_message_id=reply_to_message_id,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2299,6 +2462,7 @@ async def send_message(
         reply_to_message_id: int = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         reply_markup: ReplyMarkup = None,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2315,6 +2479,7 @@ async def send_message(
             disable_web_page_preview=disable_web_page_preview,
             disable_notification=disable_notification,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             reply_to_message_id=reply_to_message_id,
             allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
@@ -2337,6 +2502,7 @@ async def send_photo(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2357,6 +2523,7 @@ async def send_photo(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             filename=filename,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -2385,6 +2552,7 @@ async def send_poll(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         explanation_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2412,6 +2580,7 @@ async def send_poll(
             allow_sending_without_reply=allow_sending_without_reply,
             explanation_entities=explanation_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2428,6 +2597,7 @@ async def send_sticker(
         reply_markup: ReplyMarkup = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
         write_timeout: ODVInput[float] = 20,
@@ -2444,6 +2614,7 @@ async def send_sticker(
             reply_markup=reply_markup,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2467,6 +2638,7 @@ async def send_venue(
         google_place_type: str = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         venue: Venue = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2491,6 +2663,7 @@ async def send_venue(
             google_place_type=google_place_type,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             venue=venue,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -2516,6 +2689,7 @@ async def send_video(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2541,6 +2715,7 @@ async def send_video(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             filename=filename,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -2561,6 +2736,7 @@ async def send_video_note(
         thumb: FileInput = None,
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2581,6 +2757,7 @@ async def send_video_note(
             thumb=thumb,
             allow_sending_without_reply=allow_sending_without_reply,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             filename=filename,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -2602,6 +2779,7 @@ async def send_voice(
         allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
         caption_entities: Union[List["MessageEntity"], Tuple["MessageEntity", ...]] = None,
         protect_content: ODVInput[bool] = DEFAULT_NONE,
+        message_thread_id: int = None,
         *,
         filename: str = None,
         read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2623,6 +2801,7 @@ async def send_voice(
             allow_sending_without_reply=allow_sending_without_reply,
             caption_entities=caption_entities,
             protect_content=protect_content,
+            message_thread_id=message_thread_id,
             filename=filename,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -3079,6 +3258,28 @@ async def unpin_chat_message(
             api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
         )
 
+    async def unpin_all_forum_topic_messages(
+        self,
+        chat_id: Union[str, int],
+        message_thread_id: int,
+        *,
+        read_timeout: ODVInput[float] = DEFAULT_NONE,
+        write_timeout: ODVInput[float] = DEFAULT_NONE,
+        connect_timeout: ODVInput[float] = DEFAULT_NONE,
+        pool_timeout: ODVInput[float] = DEFAULT_NONE,
+        api_kwargs: JSONDict = None,
+        rate_limit_args: RLARGS = None,
+    ) -> bool:
+        return await super().unpin_all_forum_topic_messages(
+            chat_id=chat_id,
+            message_thread_id=message_thread_id,
+            read_timeout=read_timeout,
+            write_timeout=write_timeout,
+            connect_timeout=connect_timeout,
+            pool_timeout=pool_timeout,
+            api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
+        )
+
     async def upload_sticker_file(
         self,
         user_id: Union[str, int],
@@ -3190,3 +3391,10 @@ async def upload_sticker_file(
     getMyDefaultAdministratorRights = get_my_default_administrator_rights
     setMyDefaultAdministratorRights = set_my_default_administrator_rights
     createInvoiceLink = create_invoice_link
+    getForumTopicIconStickers = get_forum_topic_icon_stickers
+    createForumTopic = create_forum_topic
+    editForumTopic = edit_forum_topic
+    closeForumTopic = close_forum_topic
+    reopenForumTopic = reopen_forum_topic
+    deleteForumTopic = delete_forum_topic
+    unpinAllForumTopicMessages = unpin_all_forum_topic_messages
diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py
index 5ad236153cd..00f3744a73f 100644
--- a/telegram/ext/filters.py
+++ b/telegram/ext/filters.py
@@ -1705,6 +1705,9 @@ def filter(self, update: Update) -> bool:
                 or StatusUpdate.VIDEO_CHAT_ENDED.check_update(update)
                 or StatusUpdate.VIDEO_CHAT_PARTICIPANTS_INVITED.check_update(update)
                 or StatusUpdate.WEB_APP_DATA.check_update(update)
+                or StatusUpdate.FORUM_TOPIC_CREATED.check_update(update)
+                or StatusUpdate.FORUM_TOPIC_CLOSED.check_update(update)
+                or StatusUpdate.FORUM_TOPIC_REOPENED.check_update(update)
             )
 
     ALL = _All(name="filters.StatusUpdate.ALL")
@@ -1893,6 +1896,42 @@ def filter(self, message: Message) -> bool:
     .. versionadded:: 20.0
     """
 
+    class _ForumTopicCreated(MessageFilter):
+        __slots__ = ()
+
+        def filter(self, message: Message) -> bool:
+            return bool(message.forum_topic_created)
+
+    FORUM_TOPIC_CREATED = _ForumTopicCreated(name="filters.StatusUpdate.FORUM_TOPIC_CREATED")
+    """Messages that contain :attr:`telegram.Message.forum_topic_created`.
+
+    .. versionadded:: 20.0
+    """
+
+    class _ForumTopicClosed(MessageFilter):
+        __slots__ = ()
+
+        def filter(self, message: Message) -> bool:
+            return bool(message.forum_topic_closed)
+
+    FORUM_TOPIC_CLOSED = _ForumTopicClosed(name="filters.StatusUpdate.FORUM_TOPIC_CLOSED")
+    """Messages that contain :attr:`telegram.Message.forum_topic_closed`.
+
+    .. versionadded:: 20.0
+    """
+
+    class _ForumTopicReopened(MessageFilter):
+        __slots__ = ()
+
+        def filter(self, message: Message) -> bool:
+            return bool(message.forum_topic_reopened)
+
+    FORUM_TOPIC_REOPENED = _ForumTopicReopened(name="filters.StatusUpdate.FORUM_TOPIC_REOPENED")
+    """Messages that contain :attr:`telegram.Message.forum_topic_reopened`.
+
+    .. versionadded:: 20.0
+    """
+
 
 class Sticker:
     """Filters messages which contain a sticker.
diff --git a/tests/bots.py b/tests/bots.py
index 69927a32cdc..4ed5160a5ac 100644
--- a/tests/bots.py
+++ b/tests/bots.py
@@ -28,13 +28,14 @@
 FALLBACKS = (
     "W3sidG9rZW4iOiAiNTc5Njk0NzE0OkFBRnBLOHc2emtrVXJENHhTZVl3RjNNTzhlLTRHcm1jeTdjIiwgInBheW1lbnRfc"
     "HJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpRME5qWmxOekk1WWpKaSIsICJjaGF0X2lkIjogIjY3NTY2Nj"
-    "IyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTMxMDkxMTEzNSIsICJjaGFubmVsX2lkIjogIkBweXRob250ZWxlZ3J"
-    "hbWJvdHRlc3RzIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBmYWxsYmFjayAxIiwgImJvdF91c2VybmFtZSI6ICJAcHRi"
-    "X2ZhbGxiYWNrXzFfYm90In0sIHsidG9rZW4iOiAiNTU4MTk0MDY2OkFBRndEUElGbHpHVWxDYVdIdFRPRVg0UkZyWDh1O"
-    "URNcWZvIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WWpFd09EUXdNVEZtTkRjeSIsIC"
-    "JjaGF0X2lkIjogIjY3NTY2NjIyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTIyMTIxNjgzMCIsICJjaGFubmVsX2l"
-    "kIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBmYWxsYmFjayAyIiwgImJv"
-    "dF91c2VybmFtZSI6ICJAcHRiX2ZhbGxiYWNrXzJfYm90In1d"
+    "IyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTMxMDkxMTEzNSIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTYxOTE"
+    "1OTQwNCIsICJjaGFubmVsX2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0"
+    "cyBmYWxsYmFjayAxIiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2ZhbGxiYWNrXzFfYm90In0sIHsidG9rZW4iOiAiNTU4M"
+    "Tk0MDY2OkFBRndEUElGbHpHVWxDYVdIdFRPRVg0UkZyWDh1OURNcWZvIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOi"
+    "AiMjg0Njg1MDYzOlRFU1Q6WWpFd09EUXdNVEZtTkRjeSIsICJjaGF0X2lkIjogIjY3NTY2NjIyNCIsICJzdXBlcl9ncm9"
+    "1cF9pZCI6ICItMTAwMTIyMTIxNjgzMCIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTYxOTE1OTQwNCIsICJjaGFubmVs"
+    "X2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBmYWxsYmFjayAyIiwgI"
+    "mJvdF91c2VybmFtZSI6ICJAcHRiX2ZhbGxiYWNrXzJfYm90In1d "
 )
 
 GITHUB_ACTION = os.getenv("GITHUB_ACTION", None)
diff --git a/tests/conftest.py b/tests/conftest.py
index 81b611b1da6..9d56aab7619 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -207,6 +207,11 @@ def super_group_id(bot_info):
     return bot_info["super_group_id"]
 
 
+@pytest.fixture(scope="session")
+def forum_group_id(bot_info):
+    return int(bot_info["forum_group_id"])
+
+
 @pytest.fixture(scope="session")
 def channel_id(bot_info):
     return bot_info["channel_id"]
diff --git a/tests/test_bot.py b/tests/test_bot.py
index ebfbb5b7c72..501ea6170ef 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -2259,6 +2259,7 @@ async def test_promote_chat_member(self, bot, channel_id, monkeypatch):
                 can_promote_members=True,
                 can_manage_chat=True,
                 can_manage_video_chats=True,
+                can_manage_topics=True,
             )
 
         # Test that we pass the correct params to TG
@@ -2278,6 +2279,7 @@ async def make_assertion(*args, **_):
                 and data.get("can_promote_members") == 9
                 and data.get("can_manage_chat") == 10
                 and data.get("can_manage_video_chats") == 11
+                and data.get("can_manage_topics") == 12
             )
 
         monkeypatch.setattr(bot, "_post", make_assertion)
@@ -2295,6 +2297,7 @@ async def make_assertion(*args, **_):
             can_promote_members=9,
             can_manage_chat=10,
             can_manage_video_chats=11,
+            can_manage_topics=12,
         )
 
     @pytest.mark.flaky(3, 1)
@@ -2565,6 +2568,9 @@ async def test_pin_and_unpin_message(self, bot, super_group_id):
     # set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers
     # are tested in the test_sticker module.
 
+    # get_forum_topic_icon_stickers, edit_forum_topic, etc...
+    # are tested in the test_forum module.
+
     async def test_timeout_propagation_explicit(self, monkeypatch, bot, chat_id):
         # Use BaseException that's not a subclass of Exception such that
         # OkException should not be caught anywhere
@@ -2709,6 +2715,7 @@ async def test_get_set_my_default_administrator_rights(self, bot):
         assert my_admin_rights_ch.can_promote_members is my_rights.can_promote_members
         assert my_admin_rights_ch.can_restrict_members is my_rights.can_restrict_members
         assert my_admin_rights_ch.can_pin_messages is None  # Not returned for channels
+        assert my_admin_rights_ch.can_manage_topics is None  # Not returned for channels
 
     @pytest.mark.asyncio
     async def test_get_set_chat_menu_button(self, bot, chat_id):
diff --git a/tests/test_chat.py b/tests/test_chat.py
index ddc9d3cdbd8..e8871deeedd 100644
--- a/tests/test_chat.py
+++ b/tests/test_chat.py
@@ -44,6 +44,9 @@ def chat(bot):
         join_to_send_messages=True,
         join_by_request=True,
         has_restricted_voice_and_video_messages=True,
+        is_forum=True,
+        active_usernames=TestChat.active_usernames,
+        emoji_status_custom_emoji_id=TestChat.emoji_status_custom_emoji_id,
     )
     chat.set_bot(bot)
     return chat
@@ -71,6 +74,9 @@ class TestChat:
     join_to_send_messages = True
     join_by_request = True
     has_restricted_voice_and_video_messages = True
+    is_forum = True
+    active_usernames = ["These", "Are", "Usernames!"]
+    emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID"
 
     def test_slot_behaviour(self, chat, mro_slots):
         for attr in chat.__slots__:
@@ -98,6 +104,9 @@ def test_de_json(self, bot):
             "has_restricted_voice_and_video_messages": (
                 self.has_restricted_voice_and_video_messages
             ),
+            "is_forum": self.is_forum,
+            "active_usernames": self.active_usernames,
+            "emoji_status_custom_emoji_id": self.emoji_status_custom_emoji_id,
         }
         chat = Chat.de_json(json_dict, bot)
 
@@ -124,6 +133,9 @@ def test_de_json(self, bot):
         assert chat.api_kwargs == {
             "all_members_are_administrators": self.all_members_are_administrators
         }
+        assert chat.is_forum == self.is_forum
+        assert chat.active_usernames == self.active_usernames
+        assert chat.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id
 
     def test_to_dict(self, chat):
         chat_dict = chat.to_dict()
@@ -146,6 +158,9 @@ def test_to_dict(self, chat):
             chat_dict["has_restricted_voice_and_video_messages"]
             == chat.has_restricted_voice_and_video_messages
         )
+        assert chat_dict["is_forum"] == chat.is_forum
+        assert chat_dict["active_usernames"] == chat.active_usernames
+        assert chat_dict["emoji_status_custom_emoji_id"] == chat.emoji_status_custom_emoji_id
 
     def test_enum_init(self):
         chat = Chat(id=1, type="foo")
@@ -881,6 +896,128 @@ async def make_assertion(*_, **kwargs):
         monkeypatch.setattr(chat.get_bot(), "decline_chat_join_request", make_assertion)
         assert await chat.decline_join_request(user_id=42)
 
+    async def test_create_forum_topic(self, monkeypatch, chat):
+        async def make_assertion(*_, **kwargs):
+            return (
+                kwargs["chat_id"] == chat.id
+                and kwargs["name"] == "New Name"
+                and kwargs["icon_color"] == 0x6FB9F0
+                and kwargs["icon_custom_emoji_id"] == "12345"
+            )
+
+        assert check_shortcut_signature(
+            Chat.create_forum_topic, Bot.create_forum_topic, ["chat_id"], []
+        )
+        assert await check_shortcut_call(
+            chat.create_forum_topic,
+            chat.get_bot(),
+            "create_forum_topic",
+            shortcut_kwargs=["chat_id"],
+        )
+        assert await check_defaults_handling(chat.create_forum_topic, chat.get_bot())
+
+        monkeypatch.setattr(chat.get_bot(), "create_forum_topic", make_assertion)
+        assert await chat.create_forum_topic(
+            name="New Name", icon_color=0x6FB9F0, icon_custom_emoji_id="12345"
+        )
+
+    async def test_edit_forum_topic(self, monkeypatch, chat):
+        async def make_assertion(*_, **kwargs):
+            return (
+                kwargs["chat_id"] == chat.id
+                and kwargs["message_thread_id"] == 42
+                and kwargs["name"] == "New Name"
+                and kwargs["icon_custom_emoji_id"] == "12345"
+            )
+
+        assert check_shortcut_signature(
+            Chat.edit_forum_topic, Bot.edit_forum_topic, ["chat_id"], []
+        )
+        assert await check_shortcut_call(
+            chat.edit_forum_topic, chat.get_bot(), "edit_forum_topic", shortcut_kwargs=["chat_id"]
+        )
+        assert await check_defaults_handling(chat.edit_forum_topic, chat.get_bot())
+
+        monkeypatch.setattr(chat.get_bot(), "edit_forum_topic", make_assertion)
+        assert await chat.edit_forum_topic(
+            message_thread_id=42, name="New Name", icon_custom_emoji_id="12345"
+        )
+
+    async def test_close_forum_topic(self, monkeypatch, chat):
+        async def make_assertion(*_, **kwargs):
+            return kwargs["chat_id"] == chat.id and kwargs["message_thread_id"] == 42
+
+        assert check_shortcut_signature(
+            Chat.close_forum_topic, Bot.close_forum_topic, ["chat_id"], []
+        )
+        assert await check_shortcut_call(
+            chat.close_forum_topic,
+            chat.get_bot(),
+            "close_forum_topic",
+            shortcut_kwargs=["chat_id"],
+        )
+        assert await check_defaults_handling(chat.close_forum_topic, chat.get_bot())
+
+        monkeypatch.setattr(chat.get_bot(), "close_forum_topic", make_assertion)
+        assert await chat.close_forum_topic(message_thread_id=42)
+
+    async def test_reopen_forum_topic(self, monkeypatch, chat):
+        async def make_assertion(*_, **kwargs):
+            return kwargs["chat_id"] == chat.id and kwargs["message_thread_id"] == 42
+
+        assert check_shortcut_signature(
+            Chat.reopen_forum_topic, Bot.reopen_forum_topic, ["chat_id"], []
+        )
+        assert await check_shortcut_call(
+            chat.reopen_forum_topic,
+            chat.get_bot(),
+            "reopen_forum_topic",
+            shortcut_kwargs=["chat_id"],
+        )
+        assert await check_defaults_handling(chat.reopen_forum_topic, chat.get_bot())
+
+        monkeypatch.setattr(chat.get_bot(), "reopen_forum_topic", make_assertion)
+        assert await chat.reopen_forum_topic(message_thread_id=42)
+
+    async def test_delete_forum_topic(self, monkeypatch, chat):
+        async def make_assertion(*_, **kwargs):
+            return kwargs["chat_id"] == chat.id and kwargs["message_thread_id"] == 42
+
+        assert check_shortcut_signature(
+            Chat.delete_forum_topic, Bot.delete_forum_topic, ["chat_id"], []
+        )
+        assert await check_shortcut_call(
+            chat.delete_forum_topic,
+            chat.get_bot(),
+            "delete_forum_topic",
+            shortcut_kwargs=["chat_id"],
+        )
+        assert await check_defaults_handling(chat.delete_forum_topic, chat.get_bot())
+
+        monkeypatch.setattr(chat.get_bot(), "delete_forum_topic", make_assertion)
+        assert await chat.delete_forum_topic(message_thread_id=42)
+
+    async def test_unpin_all_forum_topic_messages(self, monkeypatch, chat):
+        async def make_assertion(*_, **kwargs):
+            return kwargs["chat_id"] == chat.id and kwargs["message_thread_id"] == 42
+
+        assert check_shortcut_signature(
+            Chat.unpin_all_forum_topic_messages,
+            Bot.unpin_all_forum_topic_messages,
+            ["chat_id"],
+            [],
+        )
+        assert await check_shortcut_call(
+            chat.unpin_all_forum_topic_messages,
+            chat.get_bot(),
+            "unpin_all_forum_topic_messages",
+            shortcut_kwargs=["chat_id"],
+        )
+        assert await check_defaults_handling(chat.unpin_all_forum_topic_messages, chat.get_bot())
+
+        monkeypatch.setattr(chat.get_bot(), "unpin_all_forum_topic_messages", make_assertion)
+        assert await chat.unpin_all_forum_topic_messages(message_thread_id=42)
+
     def test_mention_html(self):
         with pytest.raises(TypeError, match="Can not create a mention to a private group chat"):
             chat = Chat(id=1, type="foo")
diff --git a/tests/test_chatadministratorrights.py b/tests/test_chatadministratorrights.py
index 16175f252a6..83826f74bac 100644
--- a/tests/test_chatadministratorrights.py
+++ b/tests/test_chatadministratorrights.py
@@ -34,6 +34,7 @@ def chat_admin_rights():
         can_edit_messages=True,
         can_manage_chat=True,
         can_manage_video_chats=True,
+        can_manage_topics=True,
         is_anonymous=True,
     )
 
@@ -57,6 +58,7 @@ def test_de_json(self, bot, chat_admin_rights):
             "can_edit_messages": True,
             "can_manage_chat": True,
             "can_manage_video_chats": True,
+            "can_manage_topics": True,
             "is_anonymous": True,
         }
         chat_administrator_rights_de = ChatAdministratorRights.de_json(json_dict, bot)
@@ -80,13 +82,14 @@ def test_to_dict(self, chat_admin_rights):
         assert admin_rights_dict["can_manage_chat"] == car.can_manage_chat
         assert admin_rights_dict["is_anonymous"] == car.is_anonymous
         assert admin_rights_dict["can_manage_video_chats"] == car.can_manage_video_chats
+        assert admin_rights_dict["can_manage_topics"] == car.can_manage_topics
 
     def test_equality(self):
-        a = ChatAdministratorRights(True, False, False, False, False, False, False, False)
-        b = ChatAdministratorRights(True, False, False, False, False, False, False, False)
-        c = ChatAdministratorRights(False, False, False, False, False, False, False, False)
-        d = ChatAdministratorRights(True, True, False, False, False, False, False, False)
-        e = ChatAdministratorRights(True, True, False, False, False, False, False, False)
+        a = ChatAdministratorRights(True, False, False, False, False, False, False, False, False)
+        b = ChatAdministratorRights(True, False, False, False, False, False, False, False, False)
+        c = ChatAdministratorRights(False, False, False, False, False, False, False, False, False)
+        d = ChatAdministratorRights(True, True, False, False, False, False, False, False, False)
+        e = ChatAdministratorRights(True, True, False, False, False, False, False, False, False)
 
         assert a == b
         assert hash(a) == hash(b)
@@ -102,7 +105,7 @@ def test_equality(self):
         assert hash(d) == hash(e)
 
     def test_all_rights(self):
-        f = ChatAdministratorRights(True, True, True, True, True, True, True, True)
+        f = ChatAdministratorRights(True, True, True, True, True, True, True, True, True)
         t = ChatAdministratorRights.all_rights()
         # if the dirs are the same, the attributes will all be there
         assert dir(f) == dir(t)
@@ -114,7 +117,7 @@ def test_all_rights(self):
         assert f != t
 
     def test_no_rights(self):
-        f = ChatAdministratorRights(False, False, False, False, False, False, False, False)
+        f = ChatAdministratorRights(False, False, False, False, False, False, False, False, False)
         t = ChatAdministratorRights.no_rights()
         # if the dirs are the same, the attributes will all be there
         assert dir(f) == dir(t)
diff --git a/tests/test_chatmember.py b/tests/test_chatmember.py
index f2a1d0854c8..ab7e7d23da2 100644
--- a/tests/test_chatmember.py
+++ b/tests/test_chatmember.py
@@ -60,6 +60,7 @@ class CMDefaults:
     is_member: bool = True
     can_manage_chat: bool = True
     can_manage_video_chats: bool = True
+    can_manage_topics: bool = True
 
 
 def chat_member_owner():
@@ -81,6 +82,7 @@ def chat_member_administrator():
         CMDefaults.can_post_messages,
         CMDefaults.can_edit_messages,
         CMDefaults.can_pin_messages,
+        CMDefaults.can_manage_topics,
         CMDefaults.custom_title,
     )
 
@@ -101,6 +103,7 @@ def chat_member_restricted():
         CMDefaults.can_send_polls,
         CMDefaults.can_send_other_messages,
         CMDefaults.can_add_web_page_previews,
+        CMDefaults.can_manage_topics,
         CMDefaults.until_date,
     )
 
diff --git a/tests/test_chatpermissions.py b/tests/test_chatpermissions.py
index 64f4da6c269..5498bae24ca 100644
--- a/tests/test_chatpermissions.py
+++ b/tests/test_chatpermissions.py
@@ -33,6 +33,7 @@ def chat_permissions():
         can_change_info=True,
         can_invite_users=True,
         can_pin_messages=True,
+        can_manage_topics=True,
     )
 
 
@@ -45,6 +46,7 @@ class TestChatPermissions:
     can_change_info = False
     can_invite_users = None
     can_pin_messages = None
+    can_manage_topics = None
 
     def test_slot_behaviour(self, chat_permissions, mro_slots):
         inst = chat_permissions
@@ -62,6 +64,7 @@ def test_de_json(self, bot):
             "can_change_info": self.can_change_info,
             "can_invite_users": self.can_invite_users,
             "can_pin_messages": self.can_pin_messages,
+            "can_manage_topics": self.can_manage_topics,
         }
         permissions = ChatPermissions.de_json(json_dict, bot)
         assert permissions.api_kwargs == {}
@@ -74,6 +77,7 @@ def test_de_json(self, bot):
         assert permissions.can_change_info == self.can_change_info
         assert permissions.can_invite_users == self.can_invite_users
         assert permissions.can_pin_messages == self.can_pin_messages
+        assert permissions.can_manage_topics == self.can_manage_topics
 
     def test_to_dict(self, chat_permissions):
         permissions_dict = chat_permissions.to_dict()
@@ -94,6 +98,7 @@ def test_to_dict(self, chat_permissions):
         assert permissions_dict["can_change_info"] == chat_permissions.can_change_info
         assert permissions_dict["can_invite_users"] == chat_permissions.can_invite_users
         assert permissions_dict["can_pin_messages"] == chat_permissions.can_pin_messages
+        assert permissions_dict["can_manage_topics"] == chat_permissions.can_manage_topics
 
     def test_equality(self):
         a = ChatPermissions(
diff --git a/tests/test_filters.py b/tests/test_filters.py
index a3fcb5c3280..e37411a1d01 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -980,6 +980,21 @@ def test_filters_status_update(self, update):
         assert filters.StatusUpdate.WEB_APP_DATA.check_update(update)
         update.message.web_app_data = None
 
+        update.message.forum_topic_created = "topic"
+        assert filters.StatusUpdate.ALL.check_update(update)
+        assert filters.StatusUpdate.FORUM_TOPIC_CREATED.check_update(update)
+        update.message.forum_topic_created = None
+
+        update.message.forum_topic_closed = "topic"
+        assert filters.StatusUpdate.ALL.check_update(update)
+        assert filters.StatusUpdate.FORUM_TOPIC_CLOSED.check_update(update)
+        update.message.forum_topic_closed = None
+
+        update.message.forum_topic_reopened = "topic"
+        assert filters.StatusUpdate.ALL.check_update(update)
+        assert filters.StatusUpdate.FORUM_TOPIC_REOPENED.check_update(update)
+        update.message.forum_topic_reopened = None
+
     def test_filters_forwarded(self, update):
         assert not filters.FORWARDED.check_update(update)
         update.message.forward_date = datetime.datetime.utcnow()
diff --git a/tests/test_forum.py b/tests/test_forum.py
new file mode 100644
index 00000000000..5487ca7feaa
--- /dev/null
+++ b/tests/test_forum.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2022
+# Leandro Toledo de Souza <devs@python-telegram-bot.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser Public License for more details.
+#
+# You should have received a copy of the GNU Lesser Public License
+# along with this program.  If not, see [http://www.gnu.org/licenses/].
+import pytest
+
+from telegram import ForumTopic, ForumTopicClosed, ForumTopicCreated, ForumTopicReopened, Sticker
+
+TEST_MSG_TEXT = "Topics are forever"
+TEST_TOPIC_ICON_COLOR = 0x6FB9F0
+TEST_TOPIC_NAME = "Sad bot true: real stories"
+
+
+@pytest.fixture(scope="module")
+async def emoji_id(bot):
+    emoji_sticker_list = await bot.get_forum_topic_icon_stickers()
+    first_sticker = emoji_sticker_list[0]
+    return first_sticker.custom_emoji_id
+
+
+@pytest.fixture
+async def forum_topic_object(forum_group_id, emoji_id):
+    return ForumTopic(
+        message_thread_id=forum_group_id,
+        name=TEST_TOPIC_NAME,
+        icon_color=TEST_TOPIC_ICON_COLOR,
+        icon_custom_emoji_id=emoji_id,
+    )
+
+
+@pytest.fixture
+async def real_topic(bot, emoji_id, forum_group_id):
+    result = await bot.create_forum_topic(
+        chat_id=forum_group_id,
+        name=TEST_TOPIC_NAME,
+        icon_color=TEST_TOPIC_ICON_COLOR,
+        icon_custom_emoji_id=emoji_id,
+    )
+
+    yield result
+
+    result = await bot.delete_forum_topic(
+        chat_id=forum_group_id, message_thread_id=result.message_thread_id
+    )
+    assert result is True, "Topic was not deleted"
+
+
+class TestForumTopic:
+    def test_slot_behaviour(self, mro_slots, forum_topic_object):
+        for attr in forum_topic_object.__slots__:
+            assert getattr(forum_topic_object, attr, "err") != "err", f"got extra slot '{attr}'"
+        assert len(mro_slots(forum_topic_object)) == len(
+            set(mro_slots(forum_topic_object))
+        ), "duplicate slot"
+
+    async def test_expected_values(self, emoji_id, forum_group_id, forum_topic_object):
+        assert forum_topic_object.message_thread_id == forum_group_id
+        assert forum_topic_object.icon_color == TEST_TOPIC_ICON_COLOR
+        assert forum_topic_object.name == TEST_TOPIC_NAME
+        assert forum_topic_object.icon_custom_emoji_id == emoji_id
+
+    def test_de_json(self, bot, emoji_id, forum_group_id):
+        assert ForumTopic.de_json(None, bot=bot) is None
+
+        json_dict = {
+            "message_thread_id": forum_group_id,
+            "name": TEST_TOPIC_NAME,
+            "icon_color": TEST_TOPIC_ICON_COLOR,
+            "icon_custom_emoji_id": emoji_id,
+        }
+        topic = ForumTopic.de_json(json_dict, bot)
+        assert topic.api_kwargs == {}
+
+        assert topic.message_thread_id == forum_group_id
+        assert topic.icon_color == TEST_TOPIC_ICON_COLOR
+        assert topic.name == TEST_TOPIC_NAME
+        assert topic.icon_custom_emoji_id == emoji_id
+
+    def test_to_dict(self, emoji_id, forum_group_id, forum_topic_object):
+        topic_dict = forum_topic_object.to_dict()
+
+        assert isinstance(topic_dict, dict)
+        assert topic_dict["message_thread_id"] == forum_group_id
+        assert topic_dict["name"] == TEST_TOPIC_NAME
+        assert topic_dict["icon_color"] == TEST_TOPIC_ICON_COLOR
+        assert topic_dict["icon_custom_emoji_id"] == emoji_id
+
+    def test_equality(self, emoji_id, forum_group_id):
+        a = ForumTopic(
+            message_thread_id=forum_group_id,
+            name=TEST_TOPIC_NAME,
+            icon_color=TEST_TOPIC_ICON_COLOR,
+        )
+        b = ForumTopic(
+            message_thread_id=forum_group_id,
+            name=TEST_TOPIC_NAME,
+            icon_color=TEST_TOPIC_ICON_COLOR,
+            icon_custom_emoji_id=emoji_id,
+        )
+        c = ForumTopic(
+            message_thread_id=forum_group_id,
+            name=f"{TEST_TOPIC_NAME}!",
+            icon_color=TEST_TOPIC_ICON_COLOR,
+        )
+        d = ForumTopic(
+            message_thread_id=forum_group_id + 1,
+            name=TEST_TOPIC_NAME,
+            icon_color=TEST_TOPIC_ICON_COLOR,
+        )
+        e = ForumTopic(
+            message_thread_id=forum_group_id,
+            name=TEST_TOPIC_NAME,
+            icon_color=0xFFD67E,
+        )
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+        assert a != e
+        assert hash(a) != hash(e)
+
+    @pytest.mark.flaky(3, 1)
+    async def test_create_forum_topic(self, real_topic):
+        # TODO test with str identifier of a chat too?
+        result = real_topic
+        assert isinstance(result, ForumTopic)
+        assert result.name == TEST_TOPIC_NAME
+        assert result.message_thread_id
+        assert isinstance(result.icon_color, int)
+        assert isinstance(result.icon_custom_emoji_id, str)
+
+    async def test_create_forum_topic_with_only_required_args(self, bot, forum_group_id):
+        result = await bot.create_forum_topic(chat_id=forum_group_id, name=TEST_TOPIC_NAME)
+        assert isinstance(result, ForumTopic)
+        assert result.name == TEST_TOPIC_NAME
+        assert result.message_thread_id
+        assert isinstance(result.icon_color, int)  # color is still there though it was not passed
+        assert result.icon_custom_emoji_id is None
+
+        result = await bot.delete_forum_topic(
+            chat_id=forum_group_id, message_thread_id=result.message_thread_id
+        )
+        assert result is True, "Failed to delete forum topic"
+
+    @pytest.mark.flaky(3, 1)
+    async def test_get_forum_topic_icon_stickers(self, bot):
+        emoji_sticker_list = await bot.get_forum_topic_icon_stickers()
+        first_sticker = emoji_sticker_list[0]
+
+        assert first_sticker.emoji == "📰"
+        assert first_sticker.height == 512
+        assert first_sticker.width == 512
+        assert first_sticker.is_animated
+        assert not first_sticker.is_video
+        assert first_sticker.set_name == "Topics"
+        assert first_sticker.type == Sticker.CUSTOM_EMOJI
+        assert first_sticker.thumb.width == 128
+        assert first_sticker.thumb.height == 128
+
+        # The following data of first item returned has changed in the past already,
+        # so check sizes loosely and ID's only by length of string
+        assert first_sticker.thumb.file_size in range(2000, 7000)
+        assert first_sticker.file_size in range(20000, 70000)
+        assert len(first_sticker.custom_emoji_id) == 19
+        assert len(first_sticker.thumb.file_unique_id) == 16
+        assert len(first_sticker.file_unique_id) == 15
+
+    async def test_edit_forum_topic(self, emoji_id, forum_group_id, bot, real_topic):
+        result = await bot.edit_forum_topic(
+            chat_id=forum_group_id,
+            message_thread_id=real_topic.message_thread_id,
+            name=f"{TEST_TOPIC_NAME}_EDITED",
+            icon_custom_emoji_id=emoji_id,
+        )
+        assert result is True, "Failed to edit forum topic"
+        # no way of checking the edited name, just the boolean result
+
+    @pytest.mark.flaky(3, 1)
+    async def test_send_message_to_topic(self, bot, forum_group_id, real_topic):
+        message_thread_id = real_topic.message_thread_id
+
+        message = await bot.send_message(
+            chat_id=forum_group_id, text=TEST_MSG_TEXT, message_thread_id=message_thread_id
+        )
+
+        assert message.text == TEST_MSG_TEXT
+        assert message.is_topic_message is True
+        assert message.message_thread_id == message_thread_id
+
+    async def test_close_and_reopen_forum_topic(self, bot, forum_group_id, real_topic):
+        message_thread_id = real_topic.message_thread_id
+
+        result = await bot.close_forum_topic(
+            chat_id=forum_group_id,
+            message_thread_id=message_thread_id,
+        )
+        assert result is True, "Failed to close forum topic"
+        # bot will still be able to send a message to a closed topic, so can't test anything like
+        # the inability to post to the topic
+
+        result = await bot.reopen_forum_topic(
+            chat_id=forum_group_id,
+            message_thread_id=message_thread_id,
+        )
+        assert result is True, "Failed to reopen forum topic"
+
+    async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_topic):
+        message_thread_id = real_topic.message_thread_id
+
+        msgs = [
+            await (
+                await bot.send_message(
+                    chat_id=forum_group_id, text=TEST_MSG_TEXT, message_thread_id=message_thread_id
+                )
+            ).pin()
+            for _ in range(2)
+        ]
+
+        assert all(msgs) is True, "Message(s) were not pinned"
+
+        # We need 2 or more pinned msgs for this to work, else we get Chat_not_modified error
+        result = await bot.unpin_all_forum_topic_messages(
+            chat_id=forum_group_id, message_thread_id=message_thread_id
+        )
+        assert result is True, "Failed to unpin all the messages in forum topic"
+
+
+@pytest.fixture
+def topic_created():
+    return ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=TEST_TOPIC_ICON_COLOR)
+
+
+class TestForumTopicCreated:
+    def test_slot_behaviour(self, topic_created, mro_slots):
+        for attr in topic_created.__slots__:
+            assert getattr(topic_created, attr, "err") != "err", f"got extra slot '{attr}'"
+        assert len(mro_slots(topic_created)) == len(
+            set(mro_slots(topic_created))
+        ), "duplicate slot"
+
+    def test_expected_values(self, topic_created):
+        assert topic_created.icon_color == TEST_TOPIC_ICON_COLOR
+        assert topic_created.name == TEST_TOPIC_NAME
+
+    def test_de_json(self, bot):
+        assert ForumTopicCreated.de_json(None, bot=bot) is None
+
+        json_dict = {"icon_color": TEST_TOPIC_ICON_COLOR, "name": TEST_TOPIC_NAME}
+        action = ForumTopicCreated.de_json(json_dict, bot)
+        assert action.api_kwargs == {}
+
+        assert action.icon_color == TEST_TOPIC_ICON_COLOR
+        assert action.name == TEST_TOPIC_NAME
+
+    def test_to_dict(self, topic_created):
+        action_dict = topic_created.to_dict()
+
+        assert isinstance(action_dict, dict)
+        assert action_dict["name"] == TEST_TOPIC_NAME
+        assert action_dict["icon_color"] == TEST_TOPIC_ICON_COLOR
+
+    def test_equality(self, emoji_id):
+        a = ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=TEST_TOPIC_ICON_COLOR)
+        b = ForumTopicCreated(
+            name=TEST_TOPIC_NAME,
+            icon_color=TEST_TOPIC_ICON_COLOR,
+            icon_custom_emoji_id=emoji_id,
+        )
+        c = ForumTopicCreated(name=f"{TEST_TOPIC_NAME}!", icon_color=TEST_TOPIC_ICON_COLOR)
+        d = ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=0xFFD67E)
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+class TestForumTopicClosed:
+    def test_slot_behaviour(self, mro_slots):
+        action = ForumTopicClosed()
+        for attr in action.__slots__:
+            assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
+        assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
+
+    def test_de_json(self):
+        action = ForumTopicClosed.de_json({}, None)
+        assert action.api_kwargs == {}
+        assert isinstance(action, ForumTopicClosed)
+
+    def test_to_dict(self):
+        action = ForumTopicClosed()
+        action_dict = action.to_dict()
+        assert action_dict == {}
+
+
+class TestForumTopicReopened:
+    def test_slot_behaviour(self, mro_slots):
+        action = ForumTopicReopened()
+        for attr in action.__slots__:
+            assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
+        assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
+
+    def test_de_json(self):
+        action = ForumTopicReopened.de_json({}, None)
+        assert action.api_kwargs == {}
+        assert isinstance(action, ForumTopicReopened)
+
+    def test_to_dict(self):
+        action = ForumTopicReopened()
+        action_dict = action.to_dict()
+        assert action_dict == {}
diff --git a/tests/test_message.py b/tests/test_message.py
index 3ad03e386cc..bb8aa8519c6 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -198,6 +198,7 @@ def message(bot):
             ]
         },
         {"web_app_data": WebAppData("some_data", "some_button_text")},
+        {"message_thread_id": 123},
     ],
     ids=[
         "forwarded_user",
@@ -251,6 +252,7 @@ def message(bot):
         "has_protected_content",
         "entities",
         "web_app_data",
+        "message_thread_id",
     ],
 )
 def message_params(bot, request):
@@ -1693,6 +1695,124 @@ def test_default_quote(self, message):
         finally:
             message.get_bot()._defaults = None
 
+    async def test_edit_forum_topic(self, monkeypatch, message):
+        async def make_assertion(*_, **kwargs):
+            return (
+                kwargs["chat_id"] == message.chat_id
+                and kwargs["message_thread_id"] == message.message_thread_id
+                and kwargs["name"] == "New Name"
+                and kwargs["icon_custom_emoji_id"] == "12345"
+            )
+
+        assert check_shortcut_signature(
+            Message.edit_forum_topic, Bot.edit_forum_topic, ["chat_id", "message_thread_id"], []
+        )
+        assert await check_shortcut_call(
+            message.edit_forum_topic,
+            message.get_bot(),
+            "edit_forum_topic",
+            shortcut_kwargs=["chat_id", "message_thread_id"],
+        )
+        assert await check_defaults_handling(message.edit_forum_topic, message.get_bot())
+
+        monkeypatch.setattr(message.get_bot(), "edit_forum_topic", make_assertion)
+        assert await message.edit_forum_topic(name="New Name", icon_custom_emoji_id="12345")
+
+    async def test_close_forum_topic(self, monkeypatch, message):
+        async def make_assertion(*_, **kwargs):
+            return (
+                kwargs["chat_id"] == message.chat_id
+                and kwargs["message_thread_id"] == message.message_thread_id
+            )
+
+        assert check_shortcut_signature(
+            Message.close_forum_topic, Bot.close_forum_topic, ["chat_id", "message_thread_id"], []
+        )
+        assert await check_shortcut_call(
+            message.close_forum_topic,
+            message.get_bot(),
+            "close_forum_topic",
+            shortcut_kwargs=["chat_id", "message_thread_id"],
+        )
+        assert await check_defaults_handling(message.close_forum_topic, message.get_bot())
+
+        monkeypatch.setattr(message.get_bot(), "close_forum_topic", make_assertion)
+        assert await message.close_forum_topic()
+
+    async def test_reopen_forum_topic(self, monkeypatch, message):
+        async def make_assertion(*_, **kwargs):
+            return (
+                kwargs["chat_id"] == message.chat_id
+                and kwargs["message_thread_id"] == message.message_thread_id
+            )
+
+        assert check_shortcut_signature(
+            Message.reopen_forum_topic,
+            Bot.reopen_forum_topic,
+            ["chat_id", "message_thread_id"],
+            [],
+        )
+        assert await check_shortcut_call(
+            message.reopen_forum_topic,
+            message.get_bot(),
+            "reopen_forum_topic",
+            shortcut_kwargs=["chat_id", "message_thread_id"],
+        )
+        assert await check_defaults_handling(message.reopen_forum_topic, message.get_bot())
+
+        monkeypatch.setattr(message.get_bot(), "reopen_forum_topic", make_assertion)
+        assert await message.reopen_forum_topic()
+
+    async def test_delete_forum_topic(self, monkeypatch, message):
+        async def make_assertion(*_, **kwargs):
+            return (
+                kwargs["chat_id"] == message.chat_id
+                and kwargs["message_thread_id"] == message.message_thread_id
+            )
+
+        assert check_shortcut_signature(
+            Message.delete_forum_topic,
+            Bot.delete_forum_topic,
+            ["chat_id", "message_thread_id"],
+            [],
+        )
+        assert await check_shortcut_call(
+            message.delete_forum_topic,
+            message.get_bot(),
+            "delete_forum_topic",
+            shortcut_kwargs=["chat_id", "message_thread_id"],
+        )
+        assert await check_defaults_handling(message.delete_forum_topic, message.get_bot())
+
+        monkeypatch.setattr(message.get_bot(), "delete_forum_topic", make_assertion)
+        assert await message.delete_forum_topic()
+
+    async def test_unpin_all_forum_topic_messages(self, monkeypatch, message):
+        async def make_assertion(*_, **kwargs):
+            return (
+                kwargs["chat_id"] == message.chat_id
+                and kwargs["message_thread_id"] == message.message_thread_id
+            )
+
+        assert check_shortcut_signature(
+            Message.unpin_all_forum_topic_messages,
+            Bot.unpin_all_forum_topic_messages,
+            ["chat_id", "message_thread_id"],
+            [],
+        )
+        assert await check_shortcut_call(
+            message.unpin_all_forum_topic_messages,
+            message.get_bot(),
+            "unpin_all_forum_topic_messages",
+            shortcut_kwargs=["chat_id", "message_thread_id"],
+        )
+        assert await check_defaults_handling(
+            message.unpin_all_forum_topic_messages, message.get_bot()
+        )
+
+        monkeypatch.setattr(message.get_bot(), "unpin_all_forum_topic_messages", make_assertion)
+        assert await message.unpin_all_forum_topic_messages()
+
     def test_equality(self):
         id_ = 1
         a = Message(


Code review comments:
diff_hunk: @@ -4513,6 +4629,10 @@ async def promote_chat_member(
                 add new administrators with a subset of his own privileges or demote administrators
                 that he has promoted, directly or indirectly (promoted by administrators that were
                 appointed by him).
+            can_manage_topics (:obj:`bool`, optional): Pass :obj:`True`, if the administrator is
path: telegram/_bot.py
text: @author:
```suggestion
            can_manage_topics (:obj:`bool`, optional): Pass :obj:`True`, if the user is
```