From 187d820bb01618c3dbaf2800421048410dc5bafa Mon Sep 17 00:00:00 2001 From: Belugary <53219544+Belugary@users.noreply.github.com> Date: Wed, 13 May 2026 13:31:18 +0800 Subject: [PATCH] feat(mcp): render voice messages with duration in chat history (#97) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Voice messages in `_format_message_text` previously rendered as a bare `[语音] (local_id=N, ts=T)` because msg_type=34 fell through to the generic non-text branch with no schema-aware summarizer. LLMs reading chat history had no way to judge whether a voice clip was worth calling `decode_voice` on without first inspecting it. ## Fix New helper `_format_voice_text(content)` parses the embedded `` and renders `[语音 N.Ns]` (duration to one decimal, milliseconds → seconds). Type=34 dispatches through it, then appends the existing `_id_suffix()` so the local_id annotation is preserved end-to-end: [语音 3.3s] (local_id=72481, ts=1700000000) Falls back to `[语音]` (still with `_id_suffix()`) when content is empty, `` is absent, XML parse fails, or `voicelength` is missing / zero / non-numeric. XML parsing routes through the existing `_parse_xml_root` so the `_XML_UNSAFE_RE` DOCTYPE/ENTITY filter and 200KB size cap are reused — no new XXE surface. ## Tests 12 new cases in `tests/test_voice_format.py`: happy path, subsecond, multi-second, missing / zero / non-numeric voicelength, empty / None content, missing `` tag, malformed XML, XXE payload, and two end-to-end cases through `_format_message_text` (with and without voicelength) to pin the full rendered output including `_id_suffix()`. Baseline 183 → 195 passing, 0 regressions. ## Scope - `mcp_server.py`: adds `_format_voice_text` helper and one branch in `_format_message_text` (base_type == 34). No public surface change — this only affects formatting of messages that previously rendered as the bare `[语音]` fallback. - `tests/test_voice_format.py`: new file, synthetic fixtures only (no real PII). --- mcp_server.py | 17 ++++++++ tests/test_voice_format.py | 82 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 tests/test_voice_format.py diff --git a/mcp_server.py b/mcp_server.py index 35e93a3..98bad55 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -973,6 +973,21 @@ def _format_voip_message_text(content): return f"[通话] {status_map.get(raw_text, raw_text)}" +def _format_voice_text(content): + if not content or '` is parseable, with graceful +fallback to `[语音]` on missing / zero / malformed length. +""" +import unittest + +import mcp_server + + +def _voice_xml(length_ms): + return ( + f'' + ) + + +class FormatVoiceTextTests(unittest.TestCase): + def test_renders_duration_with_one_decimal(self): + self.assertEqual(mcp_server._format_voice_text(_voice_xml(3300)), "[语音 3.3s]") + + def test_subsecond_voice(self): + self.assertEqual(mcp_server._format_voice_text(_voice_xml(800)), "[语音 0.8s]") + + def test_long_clip(self): + self.assertEqual(mcp_server._format_voice_text(_voice_xml(62000)), "[语音 62.0s]") + + def test_missing_voicelength_falls_back(self): + xml = '' + self.assertEqual(mcp_server._format_voice_text(xml), "[语音]") + + def test_zero_voicelength_falls_back(self): + self.assertEqual(mcp_server._format_voice_text(_voice_xml(0)), "[语音]") + + def test_non_numeric_voicelength_falls_back(self): + xml = '' + self.assertEqual(mcp_server._format_voice_text(xml), "[语音]") + + def test_empty_content(self): + self.assertEqual(mcp_server._format_voice_text(""), "[语音]") + self.assertEqual(mcp_server._format_voice_text(None), "[语音]") + + def test_missing_voicemsg_tag(self): + self.assertEqual(mcp_server._format_voice_text(""), "[语音]") + + def test_malformed_xml(self): + self.assertEqual(mcp_server._format_voice_text("]>' + '' + ) + self.assertEqual(mcp_server._format_voice_text(xxe), "[语音]") + + def test_end_to_end_format_message_text_with_voicelength(self): + xml = _voice_xml(3300) + _, text = mcp_server._format_message_text( + local_id=72481, local_type=34, content=xml, is_group=False, + chat_username="wxid_synth_a", chat_display_name="A", names={}, + create_time=1700000000, + ) + self.assertEqual(text, "[语音 3.3s] (local_id=72481, ts=1700000000)") + + def test_end_to_end_without_voicelength(self): + _, text = mcp_server._format_message_text( + local_id=99, local_type=34, content="", is_group=False, + chat_username="wxid_synth_a", chat_display_name="A", names={}, + create_time=0, + ) + self.assertEqual(text, "[语音] (local_id=99)") + + +if __name__ == "__main__": + unittest.main()