refactor(feishu): promote download helper methods to public, update call sites and tests

This commit is contained in:
jxxghp
2026-05-13 08:19:16 +08:00
parent b6062a9ce2
commit 7b4cb2097b
4 changed files with 123 additions and 115 deletions
+3 -4
View File
@@ -663,7 +663,7 @@ class MoviePilotAgent:
and self.should_dispatch_reply and self.should_dispatch_reply
and not self._tool_context.get("user_reply_sent") and not self._tool_context.get("user_reply_sent")
): ):
await self.send_agent_message(remaining_text, is_streaming_fallback=True) await self.send_agent_message(remaining_text)
elif ( elif (
remaining_text remaining_text
and self.persist_output_message and self.persist_output_message
@@ -743,16 +743,15 @@ class MoviePilotAgent:
# 确保停止流式输出 # 确保停止流式输出
await self.stream_handler.stop_streaming() await self.stream_handler.stop_streaming()
async def send_agent_message(self, message: str, title: str = "", is_streaming_fallback: bool = False): async def send_agent_message(self, message: str, title: str = ""):
""" """
通过原渠道发送消息给用户 通过原渠道发送消息给用户
""" """
mtype = NotificationType.System if is_streaming_fallback else NotificationType.Agent
await AgentChain().async_post_message( await AgentChain().async_post_message(
Notification( Notification(
channel=self.channel, channel=self.channel,
source=self.source, source=self.source,
mtype=mtype, mtype=NotificationType.Agent,
userid=self.user_id, userid=self.user_id,
username=self.username, username=self.username,
original_message_id=self.original_message_id, original_message_id=self.original_message_id,
+3 -3
View File
@@ -243,13 +243,13 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
image_key = image_key.strip() image_key = image_key.strip()
downloaded = None downloaded = None
if message_id: if message_id:
downloaded = client._download_message_resource_bytes( downloaded = client.download_message_resource_bytes(
message_id=message_id, message_id=message_id,
file_key=image_key, file_key=image_key,
resource_type="image", resource_type="image",
) )
if not downloaded: if not downloaded:
downloaded = client._download_image_bytes(image_key) downloaded = client.download_image_bytes(image_key)
if not downloaded: if not downloaded:
return None return None
content, _, content_type = downloaded content, _, content_type = downloaded
@@ -271,7 +271,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
file_key = parts[0].strip() if parts else "" file_key = parts[0].strip() if parts else ""
if not file_key: if not file_key:
return None return None
downloaded = client._download_file_bytes(file_key) downloaded = client.download_file_bytes(file_key)
if not downloaded: if not downloaded:
return None return None
content, _, _ = downloaded content, _, _ = downloaded
+23 -14
View File
@@ -1,7 +1,5 @@
import asyncio import asyncio
import base64
import json import json
import mimetypes
import threading import threading
import uuid import uuid
from pathlib import Path from pathlib import Path
@@ -202,7 +200,9 @@ class Feishu:
threading.Thread(target=_run, daemon=True).start() threading.Thread(target=_run, daemon=True).start()
@staticmethod @staticmethod
def _parse_message_content(message) -> Tuple[str, Optional[List[CommingMessage.MessageImage]], Optional[List[str]], Optional[List[CommingMessage.MessageAttachment]]]: def _parse_message_content(message) -> Tuple[
str, Optional[List[CommingMessage.MessageImage]], Optional[List[str]], Optional[
List[CommingMessage.MessageAttachment]]]:
"""从飞书事件消息体中提取文本、图片、音频和文件引用。""" """从飞书事件消息体中提取文本、图片、音频和文件引用。"""
raw_content = getattr(message, "content", None) raw_content = getattr(message, "content", None)
if not raw_content: if not raw_content:
@@ -608,7 +608,8 @@ class Feishu:
card_rows.append({"tag": "action", "actions": elements}) card_rows.append({"tag": "action", "actions": elements})
return card_rows return card_rows
def _build_card(self, title: Optional[str], text: Optional[str], link: Optional[str], buttons: Optional[List[List[dict]]]) -> Dict[str, Any]: def _build_card(self, title: Optional[str], text: Optional[str], link: Optional[str],
buttons: Optional[List[List[dict]]]) -> Dict[str, Any]:
"""构建飞书交互卡片结构。""" """构建飞书交互卡片结构。"""
elements: List[dict] = [] elements: List[dict] = []
title_section = self._build_markdown_section(title, text_size="heading") title_section = self._build_markdown_section(title, text_size="heading")
@@ -923,7 +924,8 @@ class Feishu:
data = getattr(response, "data", None) data = getattr(response, "data", None)
return getattr(data, "image_key", None) return getattr(data, "image_key", None)
def _upload_file(self, file_path: Path, file_name: Optional[str] = None, duration: Optional[int] = None) -> Optional[str]: def _upload_file(self, file_path: Path, file_name: Optional[str] = None, duration: Optional[int] = None) -> \
Optional[str]:
if not self._api_client: if not self._api_client:
return None return None
with file_path.open("rb") as fp: with file_path.open("rb") as fp:
@@ -949,7 +951,7 @@ class Feishu:
data = getattr(response, "data", None) data = getattr(response, "data", None)
return getattr(data, "file_key", None) return getattr(data, "file_key", None)
def _download_image_bytes(self, image_key: str) -> Optional[Tuple[bytes, Optional[str], Optional[str]]]: def download_image_bytes(self, image_key: str) -> Optional[Tuple[bytes, Optional[str], Optional[str]]]:
if not self._api_client or not image_key: if not self._api_client or not image_key:
return None return None
response = self._api_client.im.v1.image.get( response = self._api_client.im.v1.image.get(
@@ -962,7 +964,7 @@ class Feishu:
content_type = response.raw.headers.get("Content-Type") content_type = response.raw.headers.get("Content-Type")
return response.file.read(), response.file_name, content_type return response.file.read(), response.file_name, content_type
def _download_file_bytes(self, file_key: str) -> Optional[Tuple[bytes, Optional[str], Optional[str]]]: def download_file_bytes(self, file_key: str) -> Optional[Tuple[bytes, Optional[str], Optional[str]]]:
if not self._api_client or not file_key: if not self._api_client or not file_key:
return None return None
response = self._api_client.im.v1.file.get( response = self._api_client.im.v1.file.get(
@@ -975,7 +977,8 @@ class Feishu:
content_type = response.raw.headers.get("Content-Type") content_type = response.raw.headers.get("Content-Type")
return response.file.read(), response.file_name, content_type return response.file.read(), response.file_name, content_type
def _download_message_resource_bytes(self, message_id: str, file_key: str, resource_type: str) -> Optional[Tuple[bytes, Optional[str], Optional[str]]]: def download_message_resource_bytes(self, message_id: str, file_key: str, resource_type: str) -> Optional[
Tuple[bytes, Optional[str], Optional[str]]]:
if not self._api_client or not message_id or not file_key: if not self._api_client or not message_id or not file_key:
return None return None
response = self._api_client.im.v1.message_resource.get( response = self._api_client.im.v1.message_resource.get(
@@ -1026,7 +1029,8 @@ class Feishu:
if not result: if not result:
return {"success": False} return {"success": False}
result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(userid or "") or self._default_chat_id result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(
userid or "") or self._default_chat_id
return result return result
def send_file( def send_file(
@@ -1107,7 +1111,8 @@ class Feishu:
if not result: if not result:
return {"success": False} return {"success": False}
result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(userid or "") or self._default_chat_id result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(
userid or "") or self._default_chat_id
return result return result
def send_voice( def send_voice(
@@ -1161,7 +1166,8 @@ class Feishu:
if not result: if not result:
return {"success": False} return {"success": False}
result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(userid or "") or self._default_chat_id result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(
userid or "") or self._default_chat_id
return result return result
def send_notification( def send_notification(
@@ -1193,7 +1199,8 @@ class Feishu:
return {"success": False} return {"success": False}
if not result: if not result:
return {"success": False} return {"success": False}
result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(userid or "") or self._default_chat_id result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(
userid or "") or self._default_chat_id
return result return result
payload = self._build_card( payload = self._build_card(
@@ -1227,10 +1234,12 @@ class Feishu:
if not result: if not result:
return {"success": False} return {"success": False}
result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(userid or "") or self._default_chat_id result["chat_id"] = result.get("chat_id") or chat_id or self._user_chat_mapping.get(
userid or "") or self._default_chat_id
return result return result
def edit_message(self, message_id: str, title: Optional[str] = None, text: Optional[str] = None, buttons: Optional[List[List[dict]]] = None, metadata: Optional[dict] = None) -> bool: def edit_message(self, message_id: str, title: Optional[str] = None, text: Optional[str] = None,
buttons: Optional[List[List[dict]]] = None, metadata: Optional[dict] = None) -> bool:
"""编辑已发送的飞书交互卡片消息。""" """编辑已发送的飞书交互卡片消息。"""
if not self._api_client: if not self._api_client:
return False return False
+7 -7
View File
@@ -543,9 +543,9 @@ class TestFeishu(unittest.TestCase):
message_resource_response=self._resource_response(b"resource-bytes", file_name="voice.opus", content_type="audio/ogg"), message_resource_response=self._resource_response(b"resource-bytes", file_name="voice.opus", content_type="audio/ogg"),
) )
image_download = client._download_image_bytes("img_v2_test") image_download = client.download_image_bytes("img_v2_test")
file_download = client._download_file_bytes("file_test") file_download = client.download_file_bytes("file_test")
resource_download = client._download_message_resource_bytes("om_test", "file_test", "audio") resource_download = client.download_message_resource_bytes("om_test", "file_test", "audio")
self.assertEqual(image_download[0], b"image-bytes") self.assertEqual(image_download[0], b"image-bytes")
self.assertEqual(file_download[0], b"file-bytes") self.assertEqual(file_download[0], b"file-bytes")
@@ -633,9 +633,9 @@ class TestFeishu(unittest.TestCase):
def test_module_download_helpers_delegate_to_client(self): def test_module_download_helpers_delegate_to_client(self):
module = FeishuModule() module = FeishuModule()
client = MagicMock() client = MagicMock()
client._download_image_bytes.return_value = (b"image", "poster.png", "image/png") client.download_image_bytes.return_value = (b"image", "poster.png", "image/png")
client._download_file_bytes.return_value = (b"file", "note.txt", "text/plain") client.download_file_bytes.return_value = (b"file", "note.txt", "text/plain")
client._download_message_resource_bytes.return_value = (b"image", "poster.png", "image/png") client.download_message_resource_bytes.return_value = (b"image", "poster.png", "image/png")
with patch.object(module, "get_config", return_value=SimpleNamespace(name="feishu-main")), patch.object( with patch.object(module, "get_config", return_value=SimpleNamespace(name="feishu-main")), patch.object(
module, "get_instance", return_value=client module, "get_instance", return_value=client
@@ -645,7 +645,7 @@ class TestFeishu(unittest.TestCase):
self.assertTrue(data_url.startswith("data:image/png;base64,")) self.assertTrue(data_url.startswith("data:image/png;base64,"))
self.assertEqual(file_bytes, b"file") self.assertEqual(file_bytes, b"file")
client._download_message_resource_bytes.assert_called_once_with( client.download_message_resource_bytes.assert_called_once_with(
message_id="om_msg", message_id="om_msg",
file_key="img_v2_xxx", file_key="img_v2_xxx",
resource_type="image", resource_type="image",