增加JSON卡片解析

pull/67/head
foxplaying 2025-10-28 23:09:32 +08:00 committed by GitHub
parent c5d3f07824
commit fa0c04ae81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 243 additions and 3 deletions

View File

@ -14,6 +14,7 @@ from . import RealMessageType, MessageType, ACCEPT_FORMAT
import time import time
import json import json
import base64
import websockets as Server import websockets as Server
from typing import List, Tuple, Optional, Dict, Any from typing import List, Tuple, Optional, Dict, Any
import uuid import uuid
@ -312,12 +313,33 @@ class MessageHandler:
else: else:
logger.warning("at处理失败") logger.warning("at处理失败")
case RealMessageType.rps: case RealMessageType.rps:
logger.warning("暂时不支持猜拳魔法表情解析") message_data = sub_message.get("data", {})
result = message_data.get("result")
rps_map = {"1": "","2": "剪刀","3": "石头"}
if result in rps_map:
seg_message.append(Seg(type="text", data=f"[猜拳:{rps_map[result]}]"))
else:
logger.warning(f"收到未知猜拳结果: {result}")
seg_message.append(Seg(type="text", data="[猜拳]"))
case RealMessageType.dice: case RealMessageType.dice:
logger.warning("暂时不支持骰子表情解析") message_data = sub_message.get("data", {})
result = message_data.get("result")
if result is not None:
seg_message.append(Seg(type="text", data=f"[骰子:{result}点]"))
else:
logger.warning("收到骰子消息,但未包含结果")
seg_message.append(Seg(type="text", data="[骰子]"))
case RealMessageType.shake: case RealMessageType.shake:
# 预计等价于戳一戳 # 预计等价于戳一戳
logger.warning("暂时不支持窗口抖动解析") logger.warning("收到窗口抖动消息")
seg_message.append(Seg(type="text", data="[戳一戳]"))
case "poke":
# 在QQNT中的窗口抖动(等同于戳一戳)
logger.warning("收到戳一戳消息")
data = sub_message.get("data", {})
poke_type = data.get("type")
poke_id = data.get("id")
seg_message.append(Seg(type="text", data="[戳一戳]"))
case RealMessageType.share: case RealMessageType.share:
logger.warning("暂时不支持链接解析") logger.warning("暂时不支持链接解析")
case RealMessageType.forward: case RealMessageType.forward:
@ -332,6 +354,224 @@ class MessageHandler:
logger.warning("转发消息处理失败") logger.warning("转发消息处理失败")
case RealMessageType.node: case RealMessageType.node:
logger.warning("不支持转发消息节点解析") logger.warning("不支持转发消息节点解析")
case RealMessageType.json:
try:
data_field = sub_message.get("data", {})
raw_json_str = data_field.get("data", "")
if not raw_json_str:
logger.warning("Napcat JSON卡片中未找到 data 字段")
seg_message.append(Seg(type="text", data="[JSON卡片消息: 空]"))
break
# 二次解析 Napcat JSON
try:
parsed_data = json.loads(raw_json_str)
except Exception as e:
logger.error(f"Napcat JSON 二次解析失败: {e}")
seg_message.append(Seg(type="text", data=f"[JSON卡片原始字符串截断]: {raw_json_str[:200]}..."))
break
app_name = parsed_data.get("app", "未知应用")
view = parsed_data.get("view", "")
prompt = parsed_data.get("prompt", "")
meta = parsed_data.get("meta", {})
logger.debug(f"Napcat JSON卡片类型: {app_name} / {view}")
# 礼物卡片
if app_name == "com.tencent.giftmall.giftark":
gift = meta.get("giftData", {})
gift_name = gift.get("giftName", "未知礼物")
gift_msg = gift.get("giftMsg", "")
seg_message.append(Seg(type="text", data=f"[收到礼物] {gift_name}{gift_msg}"))
# 推荐联系人
elif app_name == "com.tencent.contact.lua":
contact_info = meta.get("contact", {})
name = contact_info.get("nickname", "未知联系人")
tag = contact_info.get("tag", "推荐联系人")
seg_message.append(Seg(type="text", data=f"[{tag}] {name}"))
# 推荐群聊
elif app_name == "com.tencent.troopsharecard":
contact_info = meta.get("contact", {})
name = contact_info.get("nickname", "未知群聊")
tag = contact_info.get("tag", "推荐群聊")
seg_message.append(Seg(type="text", data=f"[{tag}] {name}"))
# 图文分享(如 哔哩哔哩HD、网页、群精华等
elif app_name == "com.tencent.tuwen.lua":
news = meta.get("news", {})
title = news.get("title", "未知标题")
desc = (news.get("desc", "") or "").replace("[图片]", "").strip()
tag = news.get("tag", "图文分享")
preview_url = news.get("preview", "")
if tag and title and tag in title:
title = title.replace(tag, "", 1).strip(": -— ")
seg_message.append(Seg(type="text", data=f"[{tag}] {title}{desc}"))
if preview_url:
try:
image_base64 = await get_image_base64(preview_url)
seg_message.append(Seg(type="image", data=image_base64))
except Exception as e:
logger.error(f"图文图片下载失败: {e}")
# 群相册(含预览图)
elif app_name == "com.tencent.feed.lua":
feed = meta.get("feed", {})
title = feed.get("title", "群相册")
tag = feed.get("tagName", "群相册")
desc = feed.get("forwardMessage", "")
cover_url = feed.get("cover", "")
if tag and title and tag in title:
title = title.replace(tag, "", 1).strip(": -— ")
seg_message.append(Seg(type="text", data=f"[{tag}] {title}{desc}"))
if cover_url:
try:
image_base64 = await get_image_base64(cover_url)
seg_message.append(Seg(type="image", data=image_base64))
except Exception as e:
logger.error(f"群相册图片下载失败: {e}")
# 群公告由于图片URL是加密的因此无法读取
elif app_name == "com.tencent.mannounce":
mannounce = meta.get("mannounce", {})
title = mannounce.get("title", "")
text = mannounce.get("text", "")
encode_flag = mannounce.get("encode", 0)
if encode_flag == 1:
try:
if title:
title = base64.b64decode(title).decode("utf-8", errors="ignore")
if text:
text = base64.b64decode(text).decode("utf-8", errors="ignore")
except Exception as e:
logger.warning(f"群公告Base64解码失败: {e}")
if title and text:
content = f"[{title}]{text}"
elif title:
content = f"[{title}]"
elif text:
content = f"{text}"
else:
content = "[群公告]"
seg_message.append(Seg(type="text", data=content))
# QQ小程序分享含预览图
elif app_name == "com.tencent.miniapp_01":
detail = meta.get("detail_1", {})
title = detail.get("title", "未知小程序")
desc = detail.get("desc", "")
preview_url = detail.get("preview", "")
tag = "QQ小程序"
seg_message.append(Seg(type="text", data=f"[{tag}] {title}{desc}"))
if preview_url:
try:
image_base64 = await get_image_base64(preview_url)
seg_message.append(Seg(type="image", data=image_base64))
except Exception as e:
logger.error(f"QQ小程序图片下载失败: {e}")
# QQ收藏分享含预览图
elif app_name == "com.tencent.template.qqfavorite.share":
news = meta.get("news", {})
desc = news.get("desc", "").replace("[图片]", "").strip()
preview_url = news.get("preview", "")
tag = news.get("tag", "QQ收藏")
seg_message.append(Seg(type="text", data=f"[{tag}] {desc}"))
if preview_url:
try:
image_base64 = await get_image_base64(preview_url)
seg_message.append(Seg(type="image", data=image_base64))
except Exception as e:
logger.error(f"QQ收藏图片下载失败: {e}")
# QQ空间分享含预览图
elif app_name == "com.tencent.miniapp.lua":
miniapp = meta.get("miniapp", {})
title = miniapp.get("title", "未知标题")
tag = miniapp.get("tag", "QQ空间")
preview_url = miniapp.get("preview", "")
seg_message.append(Seg(type="text", data=f"[{tag}] {title}"))
if preview_url:
try:
image_base64 = await get_image_base64(preview_url)
seg_message.append(Seg(type="image", data=image_base64))
except Exception as e:
logger.error(f"QQ空间图片下载失败: {e}")
# QQ地图位置分享
elif app_name == "com.tencent.map":
location = meta.get("Location.Search", {})
name = location.get("name", "未知地点")
address = location.get("address", "")
seg_message.append(Seg(type="text", data=f"[位置] {address} · {name}"))
# QQ一起听歌
elif app_name == "com.tencent.together":
invite = (meta or {}).get("invite", {})
title = invite.get("title") or "一起听歌"
summary = invite.get("summary") or ""
seg_message.append(Seg(type="text", data=f"[{title}] {summary}"))
# 通用匹配(包括 music.lua、tuwen.lua 等)
elif app_name:
# 自动提取 meta 里的第一个子字段news/music/video/app...
if isinstance(meta, dict) and meta:
first_key = next(iter(meta))
news = meta.get(first_key, {})
else:
news = {}
title = news.get("title") or meta.get("title")
desc = news.get("desc") or meta.get("desc")
tag = news.get("tag") or meta.get("tag")
encode_flag = news.get("encode") or meta.get("encode") or 0
if encode_flag == 1:
try:
if title:
title = base64.b64decode(title).decode("utf-8", errors="ignore")
if desc:
desc = base64.b64decode(desc).decode("utf-8", errors="ignore")
except Exception as e:
logger.warning(f"Base64解码失败: {e}")
# 如果三者全都为空,记录错误并跳过输出
if not (title or desc or tag):
logger.error(f"[JSON解析失败] app_name={app_name}未识别的字段meta={meta}")
continue
# 三者中至少要有两个有内容,否则判定为未识别类型
non_empty_count = sum(bool(x) for x in [title, desc, tag])
if non_empty_count < 2:
logger.warning(f"[JSON卡片字段不足] app_name={app_name}字段过少title={title}, desc={desc}, tag={tag}字段meta={meta}")
continue
if not tag:
tag = "分享"
if not title:
title = "未知标题"
if not desc:
desc = ""
seg_message.append(Seg(type="text", data=f"[{tag}] {title}{desc}"))
# 未识别类型
else:
logger.warning(f"[未识别JSON卡片]: {prompt}")
except Exception as e:
logger.error(f"JSON卡片消息处理失败: {e}")
# 文件消息
case RealMessageType.file:
file_data = sub_message.get("data", {})
file_name = file_data.get("file", "未知文件")
file_size = file_data.get("file_size", "0")
try:
size_mb = round(int(file_size) / 1024 / 1024, 2)
except Exception:
size_mb = 0
seg_message.append(Seg(type="text", data=f"[文件] {file_name}{size_mb} MB"))
case _: case _:
logger.warning(f"未知消息类型: {sub_message_type}") logger.warning(f"未知消息类型: {sub_message_type}")
return seg_message, additional_config return seg_message, additional_config