Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion ductor_bot/messenger/telegram/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
handle_command,
handle_interrupt,
handle_new_session,
merge_reply_context,
strip_mention,
)
from ductor_bot.messenger.telegram.media import (
Expand Down Expand Up @@ -1407,7 +1408,10 @@ async def _resolve_text(self, message: Message) -> str | None:
)
if not message.text:
return None
return strip_mention(message.text, self._bot_username)
text = strip_mention(message.text, self._bot_username)
reply = message.reply_to_message
reply_text = None if reply is None else (reply.text or reply.caption)
return merge_reply_context(text, reply_text)

async def _handle_streaming(
self, message: Message, key: SessionKey, text: str, *, thread_id: int | None = None
Expand Down
9 changes: 9 additions & 0 deletions ductor_bot/messenger/telegram/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,12 @@ def strip_mention(text: str, bot_username: str | None) -> str:
stripped = (text[:idx] + text[idx + len(tag) :]).strip()
return stripped or text
return text


def merge_reply_context(text: str, reply_text: str | None) -> str:
"""Combine the user's message with replied-to text when present."""
message_text = text.strip()
quoted = (reply_text or "").strip()
if not quoted:
return message_text
return f"[REPLY TO]\n{quoted}\n\n[USER MESSAGE]\n{message_text}"
26 changes: 24 additions & 2 deletions tests/messenger/telegram/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ def _make_message(
type(msg).message_id = PropertyMock(return_value=message_id)
msg.text = text
msg.answer = AsyncMock(return_value=msg)
msg.reply_to_message = None
msg.entities = None
msg.caption = None
msg.caption_entities = None

user = MagicMock(spec=User)
user.id = user_id
Expand Down Expand Up @@ -501,7 +505,7 @@ async def test_returns_early_for_none_text(self) -> None:
@patch("ductor_bot.messenger.telegram.app.strip_mention", return_value="clean text")
async def test_strips_mention_from_text(self, mock_strip: MagicMock) -> None:
tg_bot, _ = _make_tg_bot()
tg_bot.bot_instance_username = "testbot"
tg_bot._bot_username = "testbot"
orch = _make_orchestrator()
tg_bot._orchestrator = orch

Expand All @@ -525,12 +529,30 @@ async def test_strips_mention_from_text(self, mock_strip: MagicMock) -> None:
class TestResolveText:
async def test_plain_text_message(self) -> None:
tg_bot, _ = _make_tg_bot()
tg_bot.bot_instance_username = "mybot"
tg_bot._bot_username = "mybot"
tg_bot._orchestrator = _make_orchestrator()
msg = _make_message(text="Hello")
result = await tg_bot._resolve_text(msg)
assert result == "Hello"

async def test_reply_includes_replied_message_text(self) -> None:
tg_bot, _ = _make_tg_bot()
tg_bot._bot_username = "mybot"
tg_bot._orchestrator = _make_orchestrator()

msg = _make_message(text="@mybot what do you think?", chat_type="group")
reply = MagicMock(spec=Message)
reply.text = "Original message text"
reply.caption = None
msg.reply_to_message = reply

result = await tg_bot._resolve_text(msg)

assert result == (
"[REPLY TO]\nOriginal message text\n\n"
"[USER MESSAGE]\nwhat do you think?"
)

async def test_none_when_no_text_and_no_media(self) -> None:
tg_bot, _ = _make_tg_bot()
tg_bot._orchestrator = _make_orchestrator()
Expand Down