Skip to content

Commit b24127e

Browse files
committed
fix: map feishu user identity for agent subscribe
1 parent 35eb8c5 commit b24127e

3 files changed

Lines changed: 122 additions & 42 deletions

File tree

app/modules/feishu/feishu.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
from app.core.config import settings
5353
from app.core.context import Context, MediaInfo
54+
from app.db.user_oper import UserOper
5455
from app.log import logger
5556
from app.schemas import CommingMessage, Notification
5657
from app.schemas.types import MessageChannel, NotificationType
@@ -269,6 +270,27 @@ def _remember_user_id_type(
269270
if normalized_user_id:
270271
self._user_receive_id_type_mapping[normalized_user_id] = "user_id"
271272

273+
@staticmethod
274+
def _resolve_username(
275+
open_id: Optional[str],
276+
user_id: Optional[str],
277+
fallback: Optional[str],
278+
) -> Optional[str]:
279+
"""根据飞书绑定 ID 映射 MoviePilot 用户名,未绑定时保留渠道名称。"""
280+
binding_ids = {}
281+
if open_id:
282+
binding_ids["feishu_openid"] = open_id
283+
if user_id:
284+
binding_ids["feishu_userid"] = user_id
285+
if binding_ids:
286+
try:
287+
mapped_username = UserOper().get_name(**binding_ids)
288+
if mapped_username:
289+
return mapped_username
290+
except Exception as err:
291+
logger.debug(f"解析飞书用户绑定失败:{err}")
292+
return fallback
293+
272294
def _on_message(self, data: P2ImMessageReceiveV1) -> None:
273295
"""处理飞书长连接收到的普通消息事件。"""
274296
event = getattr(data, "event", None)
@@ -454,7 +476,11 @@ def parse_message(self, body: Any) -> Optional[CommingMessage]:
454476
sender = message.get("sender") or {}
455477
open_id = sender.get("open_id")
456478
user_id = sender.get("user_id")
457-
username = sender.get("name") or open_id or user_id
479+
username = self._resolve_username(
480+
open_id=open_id,
481+
user_id=user_id,
482+
fallback=sender.get("name") or open_id or user_id,
483+
)
458484
userid = open_id or user_id
459485
if not userid:
460486
return None

tests/test_agent_add_subscribe_tool.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,32 @@ def test_subscription_falls_back_to_channel_username_when_no_binding_exists(self
6060
self.assertEqual(async_add.await_args.kwargs["username"], "tg_display_name")
6161
self.assertIn("成功添加订阅:The Matrix (1999)", result)
6262

63+
def test_feishu_subscription_uses_pre_resolved_username_when_openid_lookup_misses(self):
64+
tool = AddSubscribeTool(session_id="session-1", user_id="ou_feishu_user")
65+
tool.set_message_attr(
66+
channel=MessageChannel.Feishu.value,
67+
source="feishu-main",
68+
username="moviepilot-user",
69+
)
70+
71+
with patch(
72+
"app.agent.tools.impl.add_subscribe.SubscribeChain.async_add",
73+
new=AsyncMock(return_value=(1, "")),
74+
) as async_add, patch(
75+
"app.agent.tools.impl.add_subscribe.UserOper.get_name",
76+
return_value=None,
77+
):
78+
result = asyncio.run(
79+
tool.run(
80+
title="The Matrix",
81+
year="1999",
82+
media_type="movie",
83+
)
84+
)
85+
86+
self.assertEqual(async_add.await_args.kwargs["username"], "moviepilot-user")
87+
self.assertIn("成功添加订阅:The Matrix (1999)", result)
88+
6389

6490
if __name__ == "__main__":
6591
unittest.main()

tests/test_feishu.py

Lines changed: 69 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -123,19 +123,20 @@ def _resource_response(content: bytes, file_name: str = "resource.bin", content_
123123
def test_parse_message_returns_callback_message(self):
124124
client = self._build_client()
125125

126-
result = client.parse_message(
127-
{
128-
"type": "cardAction",
129-
"callback_data": "approve",
130-
"message_id": "om_123",
131-
"chat_id": "oc_123",
132-
"sender": {
133-
"open_id": "ou_user_1",
134-
"user_id": "u_user_1",
135-
"name": "tester",
136-
},
137-
}
138-
)
126+
with patch("app.modules.feishu.feishu.UserOper.get_name", return_value=None):
127+
result = client.parse_message(
128+
{
129+
"type": "cardAction",
130+
"callback_data": "approve",
131+
"message_id": "om_123",
132+
"chat_id": "oc_123",
133+
"sender": {
134+
"open_id": "ou_user_1",
135+
"user_id": "u_user_1",
136+
"name": "tester",
137+
},
138+
}
139+
)
139140

140141
self.assertIsNotNone(result)
141142
self.assertEqual(result.channel, MessageChannel.Feishu)
@@ -195,7 +196,9 @@ def build(self):
195196
def test_parse_message_blocks_non_admin_command(self):
196197
client = self._build_client(FEISHU_ADMINS="ou_admin")
197198

198-
with patch.object(client, "send_text", return_value={"success": True}) as send_text:
199+
with patch("app.modules.feishu.feishu.UserOper.get_name", return_value=None), patch.object(
200+
client, "send_text", return_value={"success": True}
201+
) as send_text:
199202
result = client.parse_message(
200203
{
201204
"type": "message",
@@ -217,6 +220,30 @@ def test_parse_message_blocks_non_admin_command(self):
217220
receive_id_type="open_id",
218221
)
219222

223+
def test_parse_message_maps_feishu_ids_to_moviepilot_username(self):
224+
client = self._build_client()
225+
226+
with patch("app.modules.feishu.feishu.UserOper.get_name", return_value="moviepilot-user") as get_name:
227+
result = client.parse_message(
228+
{
229+
"type": "message",
230+
"text": "/ai 添加黑客帝国订阅",
231+
"sender": {
232+
"open_id": "ou_bound_user",
233+
"user_id": "u_bound_user",
234+
"name": "ou_bound_user",
235+
},
236+
}
237+
)
238+
239+
self.assertIsNotNone(result)
240+
self.assertEqual(result.userid, "ou_bound_user")
241+
self.assertEqual(result.username, "moviepilot-user")
242+
get_name.assert_called_once_with(
243+
feishu_openid="ou_bound_user",
244+
feishu_userid="u_bound_user",
245+
)
246+
220247
def test_send_notification_uses_direct_card_content(self):
221248
client = self._build_client()
222249
client._api_client, message_api = self._build_message_api(
@@ -499,33 +526,34 @@ def test_close_streaming_card_updates_card_settings(self):
499526
def test_parse_message_supports_image_and_file_payloads(self):
500527
client = self._build_client()
501528

502-
image_message = client.parse_message(
503-
{
504-
"type": "message",
505-
"text": "",
506-
"images": [{"ref": "feishu://image/img_v2_test"}],
507-
"message_id": "om_img",
508-
"chat_id": "oc_chat",
509-
"sender": {
510-
"open_id": "ou_user_5",
511-
"name": "tester",
512-
},
513-
}
514-
)
515-
516-
file_message = client.parse_message(
517-
{
518-
"type": "message",
519-
"text": "",
520-
"files": [{"ref": "feishu://file/file_key/report.pdf", "name": "report.pdf"}],
521-
"message_id": "om_file",
522-
"chat_id": "oc_chat",
523-
"sender": {
524-
"open_id": "ou_user_6",
525-
"name": "tester",
526-
},
527-
}
528-
)
529+
with patch("app.modules.feishu.feishu.UserOper.get_name", return_value=None):
530+
image_message = client.parse_message(
531+
{
532+
"type": "message",
533+
"text": "",
534+
"images": [{"ref": "feishu://image/img_v2_test"}],
535+
"message_id": "om_img",
536+
"chat_id": "oc_chat",
537+
"sender": {
538+
"open_id": "ou_user_5",
539+
"name": "tester",
540+
},
541+
}
542+
)
543+
544+
file_message = client.parse_message(
545+
{
546+
"type": "message",
547+
"text": "",
548+
"files": [{"ref": "feishu://file/file_key/report.pdf", "name": "report.pdf"}],
549+
"message_id": "om_file",
550+
"chat_id": "oc_chat",
551+
"sender": {
552+
"open_id": "ou_user_6",
553+
"name": "tester",
554+
},
555+
}
556+
)
529557

530558
self.assertEqual(image_message.images[0].ref, "feishu://image/img_v2_test")
531559
self.assertEqual(file_message.files[0].ref, "feishu://file/file_key/report.pdf")

0 commit comments

Comments
 (0)