Skip to content

Commit a5e86c8

Browse files
fix(telegram): preserve attachment captions (#7020)
* fix(telegram): preserve attachment captions * Update astrbot/core/platform/sources/telegram/tg_adapter.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent af6f9cf commit a5e86c8

File tree

3 files changed

+125
-11
lines changed

3 files changed

+125
-11
lines changed

astrbot/core/platform/sources/telegram/tg_adapter.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,18 @@ async def convert_message(
335335
logger.warning("Received an update without a message.")
336336
return None
337337

338+
def _apply_caption() -> None:
339+
if update.message.caption:
340+
message.message_str = update.message.caption
341+
message.message.append(Comp.Plain(message.message_str))
342+
if update.message.caption and update.message.caption_entities:
343+
for entity in update.message.caption_entities:
344+
if entity.type == "mention":
345+
name = update.message.caption[
346+
entity.offset + 1 : entity.offset + entity.length
347+
]
348+
message.message.append(Comp.At(qq=name, name=name))
349+
338350
message = AstrBotMessage()
339351
message.session_id = str(update.message.chat.id)
340352

@@ -454,16 +466,7 @@ async def convert_message(
454466
photo = update.message.photo[-1] # get the largest photo
455467
file = await photo.get_file()
456468
message.message.append(Comp.Image(file=file.file_path, url=file.file_path))
457-
if update.message.caption:
458-
message.message_str = update.message.caption
459-
message.message.append(Comp.Plain(message.message_str))
460-
if update.message.caption_entities:
461-
for entity in update.message.caption_entities:
462-
if entity.type == "mention":
463-
name = message.message_str[
464-
entity.offset + 1 : entity.offset + entity.length
465-
]
466-
message.message.append(Comp.At(qq=name, name=name))
469+
_apply_caption()
467470

468471
elif update.message.sticker:
469472
# 将sticker当作图片处理
@@ -486,6 +489,7 @@ async def convert_message(
486489
message.message.append(
487490
Comp.File(file=file_path, name=file_name, url=file_path)
488491
)
492+
_apply_caption()
489493

490494
elif update.message.video:
491495
file = await update.message.video.get_file()
@@ -497,6 +501,7 @@ async def convert_message(
497501
)
498502
else:
499503
message.message.append(Comp.Video(file=file_path, path=file.file_path))
504+
_apply_caption()
500505

501506
return message
502507

tests/fixtures/mocks/telegram.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def create_mock_telegram_modules():
3333

3434
mock_telegram_ext = MagicMock()
3535
mock_telegram_ext.ApplicationBuilder = MagicMock
36-
mock_telegram_ext.ContextTypes = MagicMock
36+
mock_telegram_ext.ContextTypes = MagicMock()
37+
mock_telegram_ext.ContextTypes.DEFAULT_TYPE = MagicMock
3738
mock_telegram_ext.ExtBot = MagicMock
3839
mock_telegram_ext.filters = MagicMock()
3940
mock_telegram_ext.filters.ALL = MagicMock()

tests/test_telegram_adapter.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import asyncio
2+
import importlib
3+
import sys
4+
from unittest.mock import MagicMock, patch
5+
6+
import pytest
7+
8+
import astrbot.api.message_components as Comp
9+
from tests.fixtures.helpers import (
10+
create_mock_file,
11+
create_mock_update,
12+
make_platform_config,
13+
)
14+
from tests.fixtures.mocks.telegram import create_mock_telegram_modules
15+
16+
_TELEGRAM_PLATFORM_ADAPTER = None
17+
18+
19+
def _load_telegram_adapter():
20+
global _TELEGRAM_PLATFORM_ADAPTER
21+
if _TELEGRAM_PLATFORM_ADAPTER is not None:
22+
return _TELEGRAM_PLATFORM_ADAPTER
23+
24+
mocks = create_mock_telegram_modules()
25+
patched_modules = {
26+
"telegram": mocks["telegram"],
27+
"telegram.constants": mocks["telegram"].constants,
28+
"telegram.error": mocks["telegram"].error,
29+
"telegram.ext": mocks["telegram.ext"],
30+
"telegramify_markdown": mocks["telegramify_markdown"],
31+
"apscheduler": mocks["apscheduler"],
32+
"apscheduler.schedulers": mocks["apscheduler"].schedulers,
33+
"apscheduler.schedulers.asyncio": mocks["apscheduler"].schedulers.asyncio,
34+
"apscheduler.schedulers.background": mocks["apscheduler"].schedulers.background,
35+
}
36+
with patch.dict(sys.modules, patched_modules):
37+
sys.modules.pop("astrbot.core.platform.sources.telegram.tg_adapter", None)
38+
module = importlib.import_module("astrbot.core.platform.sources.telegram.tg_adapter")
39+
_TELEGRAM_PLATFORM_ADAPTER = module.TelegramPlatformAdapter
40+
return _TELEGRAM_PLATFORM_ADAPTER
41+
42+
43+
def _build_context() -> MagicMock:
44+
context = MagicMock()
45+
context.bot.username = "test_bot"
46+
context.bot.id = 12345678
47+
return context
48+
49+
50+
@pytest.mark.asyncio
51+
async def test_telegram_document_caption_populates_message_text_and_plain():
52+
TelegramPlatformAdapter = _load_telegram_adapter()
53+
adapter = TelegramPlatformAdapter(
54+
make_platform_config("telegram"),
55+
{},
56+
asyncio.Queue(),
57+
)
58+
document = create_mock_file("https://api.telegram.org/file/test/report.md")
59+
document.file_name = "report.md"
60+
mention = MagicMock(type="mention", offset=0, length=6)
61+
update = create_mock_update(
62+
message_text=None,
63+
document=document,
64+
caption="@alice 请总结这份文档",
65+
caption_entities=[mention],
66+
)
67+
68+
result = await adapter.convert_message(update, _build_context())
69+
70+
assert result is not None
71+
assert result.message_str == "@alice 请总结这份文档"
72+
assert any(isinstance(component, Comp.File) for component in result.message)
73+
assert any(
74+
isinstance(component, Comp.Plain)
75+
and component.text == "@alice 请总结这份文档"
76+
for component in result.message
77+
)
78+
assert any(
79+
isinstance(component, Comp.At) and component.qq == "alice"
80+
for component in result.message
81+
)
82+
83+
84+
@pytest.mark.asyncio
85+
async def test_telegram_video_caption_populates_message_text_and_plain():
86+
TelegramPlatformAdapter = _load_telegram_adapter()
87+
adapter = TelegramPlatformAdapter(
88+
make_platform_config("telegram"),
89+
{},
90+
asyncio.Queue(),
91+
)
92+
video = create_mock_file("https://api.telegram.org/file/test/lesson.mp4")
93+
video.file_name = "lesson.mp4"
94+
update = create_mock_update(
95+
message_text=None,
96+
video=video,
97+
caption="这段视频讲了什么",
98+
)
99+
100+
result = await adapter.convert_message(update, _build_context())
101+
102+
assert result is not None
103+
assert result.message_str == "这段视频讲了什么"
104+
assert any(isinstance(component, Comp.Video) for component in result.message)
105+
assert any(
106+
isinstance(component, Comp.Plain) and component.text == "这段视频讲了什么"
107+
for component in result.message
108+
)

0 commit comments

Comments
 (0)