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.
This commit is contained in:
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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()
|
||||
|
||||
Reference in New Issue
Block a user