fix: 支持飞书语音消息识别

This commit is contained in:
jxxghp
2026-05-13 12:45:32 +08:00
parent b24127e66f
commit f8d096f476
5 changed files with 98 additions and 20 deletions

View File

@@ -2,6 +2,7 @@ import base64
import json
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock, patch
from urllib.parse import quote
@@ -14,6 +15,7 @@ from app.agent import MoviePilotAgent, AgentChain
from app.chain.message import MessageChain
from app.core.config import settings
from app.agent.llm import LLMHelper
from app.helper.voice import VoiceHelper
from app.modules.discord import DiscordModule
from app.modules.qqbot import QQBotModule
from app.modules.slack import SlackModule
@@ -278,16 +280,24 @@ class AgentImageSupportTest(unittest.TestCase):
"qq://file/" + quote("https://example.com/qq-voice.ogg", safe=""),
"vocechat://file/%2Fuploads%2Fvoice.ogg",
"synology://file/" + quote("https://example.com/synology-voice.wav", safe=""),
"feishu://file/om_audio/file_audio/voice.opus",
]
with patch.object(VoiceHelper, "is_available", return_value=True), patch.object(
chain,
"run_module",
side_effect=[b"slack", b"discord", b"qq", b"vocechat", b"synology"],
side_effect=[b"slack", b"discord", b"qq", b"vocechat", b"synology", b"feishu"],
) as run_module, patch.object(
VoiceHelper,
"transcribe_bytes",
side_effect=["slack text", "discord text", "qq text", "vocechat text", "synology text"],
side_effect=[
"slack text",
"discord text",
"qq text",
"vocechat text",
"synology text",
"feishu text",
],
) as transcribe_bytes:
result = chain._transcribe_audio_refs(
audio_refs=audio_refs,
@@ -297,7 +307,7 @@ class AgentImageSupportTest(unittest.TestCase):
self.assertEqual(
result,
"slack text\ndiscord text\nqq text\nvocechat text\nsynology text",
"slack text\ndiscord text\nqq text\nvocechat text\nsynology text\nfeishu text",
)
self.assertEqual(
[call.args[0] for call in run_module.call_args_list],
@@ -307,6 +317,7 @@ class AgentImageSupportTest(unittest.TestCase):
"download_qq_file_bytes",
"download_vocechat_file_bytes",
"download_synologychat_file_bytes",
"download_feishu_file_bytes",
],
)
self.assertEqual(
@@ -317,6 +328,7 @@ class AgentImageSupportTest(unittest.TestCase):
"qq-voice.ogg",
"voice.ogg",
"synology-voice.wav",
"voice.opus",
],
)
@@ -393,7 +405,7 @@ class AgentImageSupportTest(unittest.TestCase):
with patch.object(settings, "AI_AGENT_ENABLE", True), patch.object(
settings, "LLM_SUPPORT_IMAGE_INPUT", False
), patch.object(chain, "_get_or_create_session_id", return_value="session-1"), patch.object(
chain, "_download_images_to_base64"
chain, "_download_attachments_to_data_urls"
) as download_images, patch.object(
chain,
"_prepare_agent_files",
@@ -439,8 +451,8 @@ class AgentImageSupportTest(unittest.TestCase):
"run_module",
return_value="data:image/png;base64,abc123",
) as run_module:
images = chain._download_images_to_base64(
images=["https://files.slack.com/files-pri/T1-F1/test.png"],
images = chain._download_attachments_to_data_urls(
attachments=["https://files.slack.com/files-pri/T1-F1/test.png"],
channel=MessageChannel.Slack,
source="slack-test",
)
@@ -598,8 +610,8 @@ class AgentImageSupportTest(unittest.TestCase):
"run_module",
return_value="data:image/png;base64,wechat123",
) as run_module:
images = chain._download_images_to_base64(
images=["wxwork://media_id/media-1"],
images = chain._download_attachments_to_data_urls(
attachments=["wxwork://media_id/media-1"],
channel=MessageChannel.Wechat,
source="wechat-test",
)
@@ -964,7 +976,13 @@ class AgentImageSupportTest(unittest.TestCase):
with patch.object(
module,
"get_configs",
return_value={"discord-test": SimpleNamespace(name="discord-test")},
return_value={
"discord-test": SimpleNamespace(
name="discord-test",
type="discord",
enabled=True,
)
},
), patch.object(
module, "check_message", return_value=True
), patch.object(
@@ -1145,8 +1163,9 @@ class AgentImageSupportTest(unittest.TestCase):
def test_prepare_agent_files_saves_local_file(self):
chain = MessageChain()
with tempfile.TemporaryDirectory() as tempdir, patch.object(
settings, "TEMP_PATH", Path(tempdir)
with tempfile.TemporaryDirectory() as tempdir, patch(
"app.chain.message.settings",
SimpleNamespace(TEMP_PATH=Path(tempdir)),
), patch.object(
chain,
"_download_message_file_bytes",

View File

@@ -576,6 +576,24 @@ class TestFeishu(unittest.TestCase):
payload = forward.call_args.args[0]
self.assertEqual(payload["images"][0]["ref"], "feishu://image/om_img_evt/img_v2_evt")
def test_on_message_wraps_feishu_audio_ref_with_message_id(self):
client = self._build_client()
message = SimpleNamespace(
message_id="om_audio_evt",
chat_id="oc_chat_evt",
chat_type="p2p",
message_type="audio",
content=json.dumps({"file_key": "file_audio_evt", "file_name": "voice.opus"}),
)
sender = SimpleNamespace(sender_id=SimpleNamespace(open_id="ou_user_evt", user_id=None))
event = SimpleNamespace(sender=sender, message=message)
with patch.object(client, "_forward_to_message_chain") as forward:
client._on_message(SimpleNamespace(event=event))
payload = forward.call_args.args[0]
self.assertEqual(payload["audio_refs"], ["feishu://file/om_audio_evt/file_audio_evt/voice.opus"])
def test_feishu_channel_capabilities_enable_images_and_files(self):
self.assertTrue(
ChannelCapabilityManager.supports_capability(
@@ -784,14 +802,24 @@ class TestFeishu(unittest.TestCase):
):
data_url = module.download_feishu_image_to_data_url("feishu://image/om_msg/img_v2_xxx", "feishu-main")
file_bytes = module.download_feishu_file_bytes("feishu://file/file_xxx/note.txt", "feishu-main")
audio_bytes = module.download_feishu_file_bytes(
"feishu://file/om_audio/file_audio/voice.opus",
"feishu-main",
)
self.assertTrue(data_url.startswith("data:image/png;base64,"))
self.assertEqual(file_bytes, b"file")
client.download_message_resource_bytes.assert_called_once_with(
self.assertEqual(audio_bytes, b"image")
client.download_message_resource_bytes.assert_any_call(
message_id="om_msg",
file_key="img_v2_xxx",
resource_type="image",
)
client.download_message_resource_bytes.assert_any_call(
message_id="om_audio",
file_key="file_audio",
resource_type="audio",
)
def test_module_message_reaction_helpers_delegate_to_client(self):
module = FeishuModule()