Repository: python-telegram-bot/python-telegram-bot
Pull request: `Message.reply_*` to reply to same topic when in groups.
Closes #4139.

- [x] Added ``.. versionadded:: NEXT.VERSION``, ``.. versionchanged:: NEXT.VERSION`` or ``.. deprecated:: NEXT.VERSION`` to the docstrings for user facing changes (for methods/class descriptions, arguments and attributes)
- [x] Created new or adapted existing unit tests
- [ ] ~Documented code changes according to the `CSI standard <https://standards.mousepawmedia.com/en/stable/csi.html>`__~
- [ ] ~Added myself alphabetically to ``AUTHORS.rst`` (optional)~
- [ ] ~Added new classes & modules to the docs and all suitable ``__all__`` s~
- [ ] ~Checked the `Stability Policy <https://docs.python-telegram-bot.org/stability_policy.html>`_ in case of deprecations or changes to documented behavior~


Problem statement:
[FEATURE] Reply functions should reply to the same thread by default even without quoting
### What kind of feature are you missing? Where do you notice a shortcoming of PTB?

When replying to messages from a group that has topics enabled using reply methods such as `Message.reply_html`, the reply is sent to the General topic by default. This is because the `Message.reply_html` method does not preserve the original `message_thread_id`.

Upon closer inspection, I see that this is tied to the `do_quote` parameter - if this parameter is set to `True` or `None` then the message is sent to the correct topic and the original message is quoted. If this parameter is set to `False` then the message is sent to the General topic and the original message is not quoted. There are legitimate use cases where you don't want to quote the original message (e.g. for brevity) but still want the reply to end up in the same thread.

I am using version 20.8 of the library.

### Describe the solution you'd like

In my opinion, the intuitive solution is for the reply to go to the same `message_thread_id` that the message came from by default. This is consistent with the behavior of replying to the same `chat_id` by default. Of course, if the caller wants to pass a different `message_thread_id`, that possibility is still available.

### Describe alternatives you've considered

One can update their code base to pass the `message_thread_id` parameter manually to the reply methods, but this seems overkill.

### Additional context

I can implement this myself and send a PR, but I was wondering if there was a specific design intent behind this decision.

Hints from issue discussion:
Hi. This is not really tied to the `do_quote` parameter, this just sets the reply_to_message_id. The API however supports replying to messages without passing the thread id and it still is in the correct thread (partly because of backwards compatibility, partly in the way forums/topics work).

Anyhow, I fully agree with you implementing this. We did not add this because I missed it during the initial API update and since the default experience (quote is True in groups) does not break bots, this only comes up so sparingly that no one thought about setting this by default when a message object is available.

However, one can argue it changes the default behavior too much to not introduce this as a breaking change. I would argue it is fine, since it only ever can give users errors if they don't set this up correctly, and if we do honor the user passing manual thread ids there is no way to break existing setups. but that's up to the other maintainers to vote on.
> However, one can argue it changes the default behavior too much to not introduce this as a breaking change.

Since the `Message.reply_*` explicitly document which arguments they pass (

> Shortcut for:
>
> `await bot.send_message(update.effective_message.chat_id, *args, **kwargs)`

) and notably do not list `message_thread_id` here, one can certainly view this as "breaking documented behavior".
We do however have a sentence in our [stability policy](https://docs.python-telegram-bot.org/en/v20.8/stability_policy.html#what-doesn-t-this-policy-cover) saying that

> Introduction of […] changes of flavors of comparable behavior (e.g. the default for the HTTP protocol version being used) are not covered by this policy.

A rather broad interpretation of this sentence, this sentence would cover such a change IMO. Since I agree that replying to the same thread is the expected use case, I would be fine with using such an interpretation.
+1 😃 
yeah, the default behaviour should be changed 👍🏽 
Alrighz @andrei-livadariu if you want to go ahead with implementing it

Commit message: Review comments

Patch to review:
diff --git a/telegram/_message.py b/telegram/_message.py
index eac0c748486..693338fcbf4 100644
--- a/telegram/_message.py
+++ b/telegram/_message.py
@@ -824,6 +824,9 @@ class Message(MaybeInaccessibleMessage):
     .. |blockquote_no_md1_support| replace:: Since block quotation entities are not supported
        by :attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a
        :exc:`ValueError` when encountering a block quotation.
+
+    .. |reply_same_thread| replace:: If :paramref:`message_thread_id` is not provided,
+       this will reply to the same thread (topic) of the original message.
     """
 
     # fmt: on
@@ -1560,10 +1563,18 @@ async def reply_text(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_message(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_message(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -1593,7 +1604,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1627,6 +1638,7 @@ async def reply_markdown(
 
             await bot.send_message(
                 update.effective_message.chat_id,
+                message_thread_id=update.effective_message.message_thread_id,
                 parse_mode=ParseMode.MARKDOWN,
                 *args,
                 **kwargs,
@@ -1636,6 +1648,9 @@ async def reply_markdown(
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Note:
             :tg-const:`telegram.constants.ParseMode.MARKDOWN` is a legacy mode, retained by
             Telegram for backward compatibility. You should use :meth:`reply_markdown_v2` instead.
@@ -1668,7 +1683,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1702,6 +1717,7 @@ async def reply_markdown_v2(
 
             await bot.send_message(
                 update.effective_message.chat_id,
+                message_thread_id=update.effective_message.message_thread_id,
                 parse_mode=ParseMode.MARKDOWN_V2,
                 *args,
                 **kwargs,
@@ -1711,6 +1727,9 @@ async def reply_markdown_v2(
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -1739,7 +1758,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1773,6 +1792,7 @@ async def reply_html(
 
             await bot.send_message(
                 update.effective_message.chat_id,
+                message_thread_id=update.effective_message.message_thread_id,
                 parse_mode=ParseMode.HTML,
                 *args,
                 **kwargs,
@@ -1782,6 +1802,9 @@ async def reply_html(
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -1810,7 +1833,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1843,10 +1866,18 @@ async def reply_media_group(
     ) -> Tuple["Message", ...]:
         """Shortcut for::
 
-             await bot.send_media_group(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_media_group(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_media_group`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -1878,7 +1909,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
@@ -1910,10 +1941,18 @@ async def reply_photo(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_photo(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_photo(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_photo`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -1943,7 +1982,7 @@ async def reply_photo(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
-            message_thread_id=message_thread_id,
+            message_thread_id=message_thread_id or self.message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -1981,10 +2020,18 @@ async def reply_audio(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_audio(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_audio(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_audio`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2017,7 +2064,7 @@ async def reply_audio(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
-            message_thread_id=message_thread_id,
+            message_thread_id=message_thread_id or self.message_thread_id,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
             connect_timeout=connect_timeout,
@@ -2053,10 +2100,18 @@ async def reply_document(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_document(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_document(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_document`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2092,7 +2147,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
             thumbnail=thumbnail,
         )
 
@@ -2126,10 +2181,18 @@ async def reply_animation(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_animation(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_animation(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_animation`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2167,7 +2230,7 @@ async def reply_animation(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
-            message_thread_id=message_thread_id,
+            message_thread_id=message_thread_id or self.message_thread_id,
             has_spoiler=has_spoiler,
             thumbnail=thumbnail,
         )
@@ -2194,10 +2257,18 @@ async def reply_sticker(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_sticker(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_sticker(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_sticker`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2228,7 +2299,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
             emoji=emoji,
         )
 
@@ -2263,10 +2334,18 @@ async def reply_video(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_video(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_video(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_video`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2305,7 +2384,7 @@ async def reply_video(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
-            message_thread_id=message_thread_id,
+            message_thread_id=message_thread_id or self.message_thread_id,
             has_spoiler=has_spoiler,
             thumbnail=thumbnail,
         )
@@ -2335,10 +2414,18 @@ async def reply_video_note(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_video_note(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_video_note(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_video_note`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2372,7 +2459,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
             thumbnail=thumbnail,
         )
 
@@ -2402,10 +2489,18 @@ async def reply_voice(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_voice(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_voice(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_voice`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2441,7 +2536,7 @@ async def reply_voice(
             caption_entities=caption_entities,
             filename=filename,
             protect_content=protect_content,
-            message_thread_id=message_thread_id,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def reply_location(
@@ -2471,10 +2566,18 @@ async def reply_location(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_location(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_location(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_location`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2511,7 +2614,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def reply_venue(
@@ -2543,10 +2646,18 @@ async def reply_venue(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_venue(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_venue(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_venue`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2585,7 +2696,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def reply_contact(
@@ -2613,10 +2724,18 @@ async def reply_contact(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_contact(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_contact(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_contact`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2651,7 +2770,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def reply_poll(
@@ -2686,10 +2805,18 @@ async def reply_poll(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_poll(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_poll(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_poll`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2731,7 +2858,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def reply_dice(
@@ -2755,10 +2882,18 @@ async def reply_dice(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_dice(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_dice(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_dice`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2789,7 +2924,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def reply_chat_action(
@@ -2805,10 +2940,18 @@ async def reply_chat_action(
     ) -> bool:
         """Shortcut for::
 
-             await bot.send_chat_action(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_chat_action(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_chat_action`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         .. versionadded:: 13.2
 
         Returns:
@@ -2817,7 +2960,7 @@ async def reply_chat_action(
         """
         return await self.get_bot().send_chat_action(
             chat_id=self.chat_id,
-            message_thread_id=message_thread_id,
+            message_thread_id=message_thread_id or self.message_thread_id,
             action=action,
             read_timeout=read_timeout,
             write_timeout=write_timeout,
@@ -2847,10 +2990,18 @@ async def reply_game(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_game(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_game(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_game`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -2883,7 +3034,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def reply_invoice(
@@ -2927,10 +3078,18 @@ async def reply_invoice(
     ) -> "Message":
         """Shortcut for::
 
-             await bot.send_invoice(update.effective_message.chat_id, *args, **kwargs)
+             await bot.send_invoice(
+                 update.effective_message.chat_id,
+                 message_thread_id=update.effective_message.message_thread_id,
+                 *args,
+                 **kwargs,
+             )
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.send_invoice`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Warning:
             As of API 5.2 :paramref:`start_parameter <telegram.Bot.send_invoice.start_parameter>`
             is an optional argument and therefore the
@@ -2993,7 +3152,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def forward(
@@ -3130,6 +3289,7 @@ async def reply_copy(
 
              await bot.copy_message(
                  chat_id=message.chat.id,
+                 message_thread_id=update.effective_message.message_thread_id,
                  message_id=message_id,
                  *args,
                  **kwargs
@@ -3137,6 +3297,9 @@ async def reply_copy(
 
         For the documentation of the arguments, please see :meth:`telegram.Bot.copy_message`.
 
+        .. versionchanged:: NEXT.VERSION
+                |reply_same_thread|
+
         Keyword Args:
             quote (:obj:`bool`, optional): |reply_quote|
 
@@ -3172,7 +3335,7 @@ async def reply_copy(
             pool_timeout=pool_timeout,
             api_kwargs=api_kwargs,
             protect_content=protect_content,
-            message_thread_id=message_thread_id,
+            message_thread_id=message_thread_id or self.message_thread_id,
         )
 
     async def edit_text(


Code review comments:
diff_hunk: @@ -1593,7 +1604,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,
+            message_thread_id=message_thread_id or self.message_thread_id,
path: telegram/_message.py
text: @user1:
```suggestion
            message_thread_id=message_thread_id or (self.message_thread_id if chat_id in {self.chat_id, self.username} else None),
```

@author:
Makes much sense.
Only in my branch, in master if user passed message_thread_id by themselves but the PS. cleared it :)