From c758908745f740d5e6e2a63d8fed9b03639c666a Mon Sep 17 00:00:00 2001 From: sheetung <755855262@qq.com> Date: Mon, 18 May 2026 04:55:18 +0000 Subject: [PATCH 1/3] feat(aiocqhttp): handle json type messages in message converter Add support for parsing OneBot JSON message segments (QQ mini-program, Bilibili share cards, etc.) in the target2yiri converter. Parses the card metadata and converts it to plain text to avoid silently dropping these message types. Co-Authored-By: Claude Opus 4.7 --- src/langbot/pkg/platform/sources/aiocqhttp.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/langbot/pkg/platform/sources/aiocqhttp.py b/src/langbot/pkg/platform/sources/aiocqhttp.py index 50e7922c..287b4b04 100644 --- a/src/langbot/pkg/platform/sources/aiocqhttp.py +++ b/src/langbot/pkg/platform/sources/aiocqhttp.py @@ -3,6 +3,7 @@ import typing import asyncio import traceback import datetime +import json import aiocqhttp import pydantic @@ -293,6 +294,41 @@ class AiocqhttpMessageConverter(abstract_platform_adapter.AbstractMessageConvert elif msg.type == 'dice': face_id = msg.data['result'] yiri_msg_list.append(platform_message.Face(face_type='dice', face_id=int(face_id), face_name='骰子')) + elif msg.type == 'json': + try: + # `msg.data['data']` may already be a dict in some implementations, or a JSON string in others + raw = msg.data.get('data', {}) + if isinstance(raw, (dict, list)): + inner_data = raw + else: + try: + inner_data = json.loads(raw or '{}') + except Exception: + inner_data = {} + + # Try to parse QQ mini-program / Bilibili share cards + app_name = inner_data.get('app', '') if isinstance(inner_data, dict) else '' + if app_name == 'com.tencent.miniapp_01': + detail = inner_data.get('meta', {}) + # Some implementations nest details under detail_1 + detail_1 = detail.get('detail_1') if isinstance(detail, dict) else None + detail_block = detail_1 if isinstance(detail_1, dict) else detail + title = ( + detail_block.get('desc', '分享小程序') if isinstance(detail_block, dict) else '分享小程序' + ) + qqdocurl = detail_block.get('qqdocurl', '') if isinstance(detail_block, dict) else '' + + if qqdocurl: + clean_url = qqdocurl.split('?')[0] + text_content = f'[小程序:{title}] {clean_url}' + yiri_msg_list.append(platform_message.Plain(text=text_content)) + else: + yiri_msg_list.append(platform_message.Plain(text=f'[小程序:{title}]')) + else: + # Fallback for unknown JSON card types + yiri_msg_list.append(platform_message.Plain(text='[收到一张JSON卡片]')) + except Exception as e: + print(f'解析 JSON 消息失败: {e}') chain = platform_message.MessageChain(yiri_msg_list) From 0963fd54439dfaa6cd78703cc1f3d2848482cbf3 Mon Sep 17 00:00:00 2001 From: sheetung <755855262@qq.com> Date: Mon, 18 May 2026 07:22:14 +0000 Subject: [PATCH 2/3] feat(aiocqhttp): unify json card message parsing with standard field extraction Unify JSON card message parsing across mini-program, music, and article/video types. Extract app, preview, title, and url fields using the standard QQ JSON card structure (meta.detail_1 / music / news) instead of app-name hardcoding. Co-Authored-By: Claude Opus 4.7 --- src/langbot/pkg/platform/sources/aiocqhttp.py | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/src/langbot/pkg/platform/sources/aiocqhttp.py b/src/langbot/pkg/platform/sources/aiocqhttp.py index 287b4b04..3cb55d89 100644 --- a/src/langbot/pkg/platform/sources/aiocqhttp.py +++ b/src/langbot/pkg/platform/sources/aiocqhttp.py @@ -296,39 +296,27 @@ class AiocqhttpMessageConverter(abstract_platform_adapter.AbstractMessageConvert yiri_msg_list.append(platform_message.Face(face_type='dice', face_id=int(face_id), face_name='骰子')) elif msg.type == 'json': try: - # `msg.data['data']` may already be a dict in some implementations, or a JSON string in others raw = msg.data.get('data', {}) - if isinstance(raw, (dict, list)): - inner_data = raw - else: - try: - inner_data = json.loads(raw or '{}') - except Exception: - inner_data = {} - - # Try to parse QQ mini-program / Bilibili share cards - app_name = inner_data.get('app', '') if isinstance(inner_data, dict) else '' - if app_name == 'com.tencent.miniapp_01': - detail = inner_data.get('meta', {}) - # Some implementations nest details under detail_1 - detail_1 = detail.get('detail_1') if isinstance(detail, dict) else None - detail_block = detail_1 if isinstance(detail_1, dict) else detail - title = ( - detail_block.get('desc', '分享小程序') if isinstance(detail_block, dict) else '分享小程序' - ) - qqdocurl = detail_block.get('qqdocurl', '') if isinstance(detail_block, dict) else '' - - if qqdocurl: - clean_url = qqdocurl.split('?')[0] - text_content = f'[小程序:{title}] {clean_url}' - yiri_msg_list.append(platform_message.Plain(text=text_content)) + if isinstance(raw, str): + raw = json.loads(raw) + if isinstance(raw, dict): + _meta = raw.get('meta', {}) or {} + if isinstance(_meta, dict): + _detail = _meta.get('detail_1') or _meta.get('music') or _meta.get('news') or {} else: - yiri_msg_list.append(platform_message.Plain(text=f'[小程序:{title}]')) + _detail = {} + if isinstance(_detail, dict): + preview = _detail.get('preview', '') + title = _detail.get('desc', '') or _detail.get('title', '') + url = _detail.get('qqdocurl', '') or _detail.get('jumpUrl', '') + else: + preview = title = url = '' + text = ' '.join([f'[{raw.get("app", "")}]', preview, title, url]).strip() + yiri_msg_list.append(platform_message.Plain(text=text or '[收到一张JSON卡片]')) else: - # Fallback for unknown JSON card types - yiri_msg_list.append(platform_message.Plain(text='[收到一张JSON卡片]')) - except Exception as e: - print(f'解析 JSON 消息失败: {e}') + yiri_msg_list.append(platform_message.Plain(text=str(raw))) + except Exception: + yiri_msg_list.append(platform_message.Plain(text='[收到一张JSON卡片]')) chain = platform_message.MessageChain(yiri_msg_list) From d46b762d034a2ba91c61dd9effe28e4a12904779 Mon Sep 17 00:00:00 2001 From: sheetung <755855262@qq.com> Date: Mon, 18 May 2026 07:32:49 +0000 Subject: [PATCH 3/3] ci: trigger re-run