diff --git a/mcp_server.py b/mcp_server.py index 619af79..d604271 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -666,6 +666,31 @@ def _parse_app_message_outer(content): return root +def _format_namecard_text(content): + """Parse type=42 (名片) XML into a compact human-readable line. + + Source XML carries dozens of fields (antispamticket, biznamecardinfo, + brand URLs, image MD5s) but the useful signal is just three attrs: + ``nickname`` (display name), ``username`` (wxid; ``gh_*`` for 公众号), + and ``certinfo`` (the user-authored bio). Everything else is either + auth tokens that should not be piped to downstream systems, or + rendering metadata that bloats the chat log without helping a human + or an LLM understand the conversation. + """ + root = _parse_xml_root(content) + if root is None: + return None + nickname = (root.get("nickname") or "").strip() + username = (root.get("username") or "").strip() + certinfo = _collapse_text(root.get("certinfo") or "") + if not nickname and not username: + return None + head = nickname or username + if username.startswith("gh_"): + head = f"{head} (公众号 {username})" + return f"[名片] {head}: {certinfo}" if certinfo else f"[名片] {head}" + + def _format_app_message_text(content, local_type, is_group, chat_username, chat_display_name, names): if not content or '`, dumping the full `` element including +antispamticket, biznamecardinfo and head-image URLs. Those tokens are PII that +should not be piped to downstream LLM / log systems. + +These tests pin the new behaviour: a compact `[名片] : ` line, +without any source-only XML fields. +""" +import unittest + +import mcp_server + + +# Realistic-shape sample with the noisy / sensitive attrs that used to leak. +_REAL_NAMECARD = ( + '' +) + + +class FormatNamecardTextTests(unittest.TestCase): + def test_compact_line_for_real_namecard(self): + out = mcp_server._format_namecard_text(_REAL_NAMECARD) + self.assertEqual(out, "[名片] 李雷: 搬砖工人 / 业余摄影") + + def test_no_pii_or_url_in_output(self): + out = mcp_server._format_namecard_text(_REAL_NAMECARD) + self.assertNotIn("antispamticket", out) + self.assertNotIn("v2_abc123def456", out) + self.assertNotIn("qlogo.cn", out) + self.assertNotIn("brandIconUrl", out) + self.assertNotIn("headimgurl", out) + + def test_official_account_marked(self): + xml = ( + '' + ) + out = mcp_server._format_namecard_text(xml) + self.assertEqual( + out, "[名片] Some Official Account (公众号 gh_some_official): 一个公众号" + ) + + def test_no_certinfo_falls_back_to_head_only(self): + xml = '' + out = mcp_server._format_namecard_text(xml) + self.assertEqual(out, "[名片] 韩梅梅") + + def test_only_username_when_nickname_missing(self): + xml = '' + out = mcp_server._format_namecard_text(xml) + self.assertEqual(out, "[名片] wxid_demo") + + def test_missing_both_identifiers_returns_none(self): + xml = '' + self.assertIsNone(mcp_server._format_namecard_text(xml)) + + def test_broken_xml_returns_none(self): + self.assertIsNone(mcp_server._format_namecard_text("")) + self.assertIsNone(mcp_server._format_namecard_text("