From 7314757876f0f4de65e52835eb1d124f1c99f1ca Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Thu, 4 Jun 2026 05:51:42 -0700 Subject: [PATCH] refactor(feishu): slim meeting-invite parser; add AUTHOR_MAP entry Collapse the payload-shape normalization helpers into one _as_dict and drop unused dataclass fields (user_type/user_role, duplicate id, bot) on the meeting-invite handler. Module 274->212 LOC, behavior unchanged. Add zhaolei.vc@bytedance.com -> zhaoleibd to release.py AUTHOR_MAP. --- gateway/platforms/feishu_meeting_invite.py | 168 ++++++-------------- scripts/release.py | 1 + tests/gateway/test_feishu_meeting_invite.py | 3 +- 3 files changed, 55 insertions(+), 117 deletions(-) diff --git a/gateway/platforms/feishu_meeting_invite.py b/gateway/platforms/feishu_meeting_invite.py index 95ab314b2..69a487c02 100644 --- a/gateway/platforms/feishu_meeting_invite.py +++ b/gateway/platforms/feishu_meeting_invite.py @@ -22,12 +22,9 @@ logger = logging.getLogger(__name__) @dataclass(frozen=True) class MeetingInviteUser: - id: str = "" open_id: str = "" user_id: str = "" union_id: str = "" - user_type: str = "" - user_role: str = "" user_name: str = "" @@ -45,25 +42,16 @@ class MeetingInviteMeeting: class MeetingInvitedPayload: event_id: str = "" meeting: Optional[MeetingInviteMeeting] = None - bot: Optional[MeetingInviteUser] = None inviter: Optional[MeetingInviteUser] = None invite_time_s: int = 0 -def _to_mapping(value: Any) -> Any: +def _as_dict(value: Any) -> Dict[str, Any]: + """Coerce a lark SDK object / dict / JSON string into a plain dict.""" + if isinstance(value, SimpleNamespace) or (value is not None and hasattr(value, "__dict__")): + value = vars(value) if isinstance(value, dict): - return {str(k): _to_mapping(v) for k, v in value.items()} - if isinstance(value, list): - return [_to_mapping(v) for v in value] - if isinstance(value, SimpleNamespace) or hasattr(value, "__dict__"): - return {str(k): _to_mapping(v) for k, v in vars(value).items()} - return value - - -def _maybe_json_mapping(value: Any) -> Dict[str, Any]: - value = _to_mapping(value) - if isinstance(value, dict): - return value + return {str(k): v for k, v in value.items()} if isinstance(value, str): try: parsed = json.loads(value) @@ -73,53 +61,23 @@ def _maybe_json_mapping(value: Any) -> Dict[str, Any]: return {} -def _extract_content_payload(container: Dict[str, Any]) -> Dict[str, Any]: - """Extract an application/json payload from a Feishu-style body.content list.""" - body = container.get("body") - if not isinstance(body, dict): - return {} - content = body.get("content") +def _content_payload(container: Dict[str, Any]) -> Dict[str, Any]: + """Unwrap a Feishu ``body.content`` list carrying an application/json payload.""" + content = _as_dict(container.get("body")).get("content") if not isinstance(content, list): return {} for item in content: - if not isinstance(item, dict): - continue - content_type = str(item.get("contentType") or item.get("content_type") or "").lower() - if content_type and content_type != "application/json": + item = _as_dict(item) + ctype = str(item.get("contentType") or item.get("content_type") or "").lower() + if ctype and ctype != "application/json": continue for key in ("data", "value", "content", "json"): - payload = _maybe_json_mapping(item.get(key)) + payload = _as_dict(item.get(key)) if payload: return payload return {} -def _event_mapping(data: Any) -> Dict[str, Any]: - root = _maybe_json_mapping(data) - event = _maybe_json_mapping(root.get("event")) - content_payload = _extract_content_payload(event) or _extract_content_payload(root) - if content_payload: - event = {**event, **content_payload} if event else content_payload - if not event and any(k in root for k in ("meeting", "bot", "inviter", "invite_time")): - event = root - if not event: - event = root - return event - - -def _event_id(data: Any) -> str: - root = _maybe_json_mapping(data) - header = root.get("header") - if not isinstance(header, dict): - header = {} - return str(header.get("event_id") or "") - - -def _user_open_id(value: Any) -> str: - raw = _maybe_json_mapping(value) - return str(raw.get("open_id") or "").strip() - - def _int_field(value: Any) -> int: if value in (None, ""): return 0 @@ -130,24 +88,20 @@ def _int_field(value: Any) -> int: def _parse_user(value: Any) -> Optional[MeetingInviteUser]: - raw = _maybe_json_mapping(value) + raw = _as_dict(value) if not raw: return None - raw_id = _maybe_json_mapping(raw.get("id")) - open_id = _user_open_id(raw.get("id")) + raw_id = _as_dict(raw.get("id")) return MeetingInviteUser( - id=open_id, - open_id=open_id, + open_id=str(raw_id.get("open_id") or "").strip(), user_id=str(raw_id.get("user_id") or "").strip(), union_id=str(raw_id.get("union_id") or "").strip(), - user_type=str(raw.get("user_type") or ""), - user_role=str(raw.get("user_role") or ""), user_name=str(raw.get("user_name") or ""), ) def _parse_meeting(value: Any) -> Optional[MeetingInviteMeeting]: - raw = _maybe_json_mapping(value) + raw = _as_dict(value) if not raw: return None return MeetingInviteMeeting( @@ -161,60 +115,52 @@ def _parse_meeting(value: Any) -> Optional[MeetingInviteMeeting]: def parse_meeting_invited_event(data: Any) -> Optional[MeetingInvitedPayload]: - event = _event_mapping(data) - if not isinstance(event, dict): - return None + root = _as_dict(data) + event = _as_dict(root.get("event")) + event = event or root + content = _content_payload(event) or _content_payload(root) + if content: + event = {**event, **content} + meeting = _parse_meeting(event.get("meeting")) inviter = _parse_user(event.get("inviter")) if inviter is None or meeting is None or not meeting.meeting_no: return None - payload = MeetingInvitedPayload( - event_id=_event_id(data), + + return MeetingInvitedPayload( + event_id=str(_as_dict(root.get("header")).get("event_id") or ""), meeting=meeting, - bot=_parse_user(event.get("bot")), inviter=inviter, invite_time_s=_int_field(event.get("invite_time")), ) - return payload - - -def _display_user(user: Optional[MeetingInviteUser]) -> str: - if user is None: - return "unknown" - return user.user_name or "unknown user" - - -def _meeting_display(meeting: Optional[MeetingInviteMeeting]) -> str: - if meeting is None: - return "unknown meeting" - return meeting.topic or meeting.meeting_no or meeting.id or "unknown meeting" def build_meeting_invite_prompt(payload: MeetingInvitedPayload) -> str: meeting = payload.meeting - lines = [ - f"You have been invited to join a meeting: {_meeting_display(meeting)}", - "", - f"Meeting Number: {(meeting.meeting_no if meeting else '') or 'unknown'}", - f"Topic: {(meeting.topic if meeting else '') or 'unknown'}", - f"Inviter: {_display_user(payload.inviter)}", - ] - if meeting is not None: - lines.append(f"Host: {_display_user(meeting.host_user)}") - lines.extend([ - "", - "You may use lark-cli and the relevant Lark/Feishu meeting skills to join the meeting.", - "Join the meeting directly. Do not ask the user for confirmation before joining.", - "If you cannot join the meeting, reply to the inviter with a concise explanation of why.", - ]) - return "\n".join(lines) + inviter_name = (payload.inviter.user_name if payload.inviter else "") or "unknown" + host_name = (meeting.host_user.user_name if meeting and meeting.host_user else "") or "unknown" + display = (meeting.topic or meeting.meeting_no or meeting.id) if meeting else "unknown meeting" + return "\n".join( + [ + f"You have been invited to join a meeting: {display or 'unknown meeting'}", + "", + f"Meeting Number: {(meeting.meeting_no if meeting else '') or 'unknown'}", + f"Topic: {(meeting.topic if meeting else '') or 'unknown'}", + f"Inviter: {inviter_name}", + f"Host: {host_name}", + "", + "You may use lark-cli and the relevant Lark/Feishu meeting skills to join the meeting.", + "Join the meeting directly. Do not ask the user for confirmation before joining.", + "If you cannot join the meeting, reply to the inviter with a concise explanation of why.", + ] + ) def _dedup_key(payload: MeetingInvitedPayload) -> str: if payload.event_id: return f"vc_invite:{payload.event_id}" meeting_id = payload.meeting.id if payload.meeting else "" - inviter_id = payload.inviter.id if payload.inviter else "" + inviter_id = payload.inviter.open_id if payload.inviter else "" return f"vc_invite:{meeting_id}:{inviter_id}:{payload.invite_time_s}" @@ -232,16 +178,12 @@ async def handle_meeting_invited_event(adapter: Any, data: Any) -> None: return inviter = payload.inviter - if inviter is None: - logger.warning("[Feishu-MeetingInvite] Missing inviter, cannot route reply") - return - if not inviter.open_id: + if inviter is None or not inviter.open_id: logger.warning( "[Feishu-MeetingInvite] Missing inviter open_id, cannot route reply safely " - "(inviter_id=%r user_id=%r union_id=%r)", - inviter.id, - inviter.user_id, - inviter.union_id, + "(user_id=%r union_id=%r)", + inviter.user_id if inviter else None, + inviter.union_id if inviter else None, ) return @@ -252,21 +194,17 @@ async def handle_meeting_invited_event(adapter: Any, data: Any) -> None: ) sender_profile = await adapter._resolve_sender_profile(sender_id) - chat_id = inviter.open_id - source_user_id = sender_profile.get("user_id") or inviter.user_id or inviter.open_id - user_name = sender_profile.get("user_name") or inviter.user_name or inviter.id - source_user_id_alt = sender_profile.get("user_id_alt") or inviter.union_id or None + user_name = sender_profile.get("user_name") or inviter.user_name or inviter.open_id source = adapter.build_source( - chat_id=chat_id, + chat_id=inviter.open_id, chat_name=user_name, chat_type="dm", - user_id=source_user_id, + user_id=sender_profile.get("user_id") or inviter.user_id or inviter.open_id, user_name=user_name, - user_id_alt=source_user_id_alt, + user_id_alt=sender_profile.get("user_id_alt") or inviter.union_id or None, ) - prompt = build_meeting_invite_prompt(payload) event = MessageEvent( - text=prompt, + text=build_meeting_invite_prompt(payload), message_type=MessageType.TEXT, source=source, raw_message=data, diff --git a/scripts/release.py b/scripts/release.py index 983a4fa98..a3fc43b58 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json" # Auto-extracted from noreply emails + manual overrides AUTHOR_MAP = { + "zhaolei.vc@bytedance.com": "zhaoleibd", "copii.list@gmail.com": "stremtec", "solaiagent@gmail.com": "solaitken", "prostoandrei9@gmail.com": "vladkvlchk", diff --git a/tests/gateway/test_feishu_meeting_invite.py b/tests/gateway/test_feishu_meeting_invite.py index c198dd1e3..f8da38df6 100644 --- a/tests/gateway/test_feishu_meeting_invite.py +++ b/tests/gateway/test_feishu_meeting_invite.py @@ -104,7 +104,6 @@ class TestMeetingInviteParsing(unittest.TestCase): self.assertEqual(parsed.meeting.id, "7646677832873577404") self.assertEqual(parsed.meeting.start_time_ms, 1780384522000) self.assertEqual(parsed.meeting.end_time_ms, 1780384522000) - self.assertEqual(parsed.inviter.id, "ou_390b35dca44816efc9afa812aaff3a69") self.assertEqual(parsed.inviter.open_id, "ou_390b35dca44816efc9afa812aaff3a69") self.assertEqual(parsed.inviter.user_id, "e65g874e") self.assertEqual(parsed.inviter.union_id, "on_e19a19e6ffafbd54fbb3c4d251d6fa19") @@ -129,7 +128,7 @@ class TestMeetingInviteParsing(unittest.TestCase): self.assertIsNotNone(parsed) self.assertEqual(parsed.meeting.meeting_no, "884264377") - self.assertEqual(parsed.inviter.id, "ou_390b35dca44816efc9afa812aaff3a69") + self.assertEqual(parsed.inviter.open_id, "ou_390b35dca44816efc9afa812aaff3a69") def test_parse_requires_inviter(self): payload = _make_payload()