From c3a61831ca5ffda4a8ac1d5ee63d6e7de1b04197 Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 31 Mar 2026 13:02:15 -0500 Subject: [PATCH] 's' --- app/routers/chatbot.py | 183 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 1 deletion(-) diff --git a/app/routers/chatbot.py b/app/routers/chatbot.py index a29b15c..b0d4e20 100644 --- a/app/routers/chatbot.py +++ b/app/routers/chatbot.py @@ -133,6 +133,29 @@ def _build_keyword_h5_obj(query: str, user_id: str) -> Optional[dict]: def _parse_decrypted_payload(decrypted_text: str) -> dict: text = (decrypted_text or "").strip() + # 兼容 BOM / NUL / 控制字符前缀 + text = text.lstrip("\ufeff\x00\r\n\t ") + if not text: + raise ValueError("empty decrypted payload") + + # 某些上游会把 JSON 再包一层字符串:"{\"UserId\":\"...\"}" + if text.startswith('"') and text.endswith('"'): + try: + unwrapped = json.loads(text) + if isinstance(unwrapped, str): + text = unwrapped.strip().lstrip("\ufeff\x00\r\n\t ") + except Exception: + pass + + # 若前面夹杂少量杂字符,尝试定位第一个结构起始位 + first_json = text.find("{") + first_xml = text.find("<") + starts = [x for x in [first_json, first_xml] if x >= 0] + if starts: + first = min(starts) + if first > 0: + text = text[first:] + if text.startswith("{"): obj = json.loads(text) obj["_payload_format"] = "json" @@ -161,6 +184,28 @@ def _parse_decrypted_payload(decrypted_text: str) -> dict: "from": get_text("from", "0"), "_payload_format": "xml", } + # 兼容 querystring 明文:UserId=xxx&Query=你好&appid=... + if "=" in text and "&" in text: + qs = parse_qs(text, keep_blank_values=True) + + def qv(*keys: str) -> str: + for key in keys: + vals = qs.get(key) or qs.get(key.lower()) or qs.get(key.upper()) or [] + if vals and str(vals[0]).strip(): + return str(vals[0]).strip() + return "" + + return { + "UserId": qv("UserId", "userid", "userId", "from_user"), + "Query": qv("Query", "query", "msg", "content"), + "Timestamp": qv("Timestamp", "timestamp", "createTime", "msg_time"), + "MsgId": qv("MsgId", "msgId", "msgid"), + "ChannelId": qv("ChannelId", "appid", "appId"), + "appid": qv("appid", "appId"), + "channel": qv("channel"), + "from": qv("from"), + "_payload_format": "querystring", + } raise ValueError("unsupported payload format") @@ -358,7 +403,19 @@ async def _handle_wechat_request(request: Request, app_id: Optional[str], backgr cipher_b64 = "".join(str(cipher_b64).split()).strip() try: decrypted = aes_decrypt_base64(cipher_b64, ENCODING_AES_KEY) + logger.info( + "chatbot decrypted preview app_id=%s preview=%s", + app_id or "", + (decrypted or "")[:200], + ) data = _parse_decrypted_payload(decrypted) + logger.info( + "chatbot payload parsed app_id=%s format=%s user=%s query=%s", + app_id or "", + data.get("_payload_format", ""), + str(data.get("UserId", "") or data.get("userId", ""))[:80], + str(data.get("Query", ""))[:80], + ) except Exception as error: logger.error( "chatbot decrypt failed app_id=%s content_type=%s body_prefix=%s err=%s", @@ -467,5 +524,129 @@ async def chatbot_private_message_analyze(payload: dict): @router.get("/private-message", response_class=HTMLResponse) def chatbot_private_message_page(): - return "

chatbot clone ready

请使用 /chatbot/api/private-message/query 调试接口

" + return """ + + + + + + Chatbot 私信调试台 + + + +
+
+

Chatbot 私信调试台

+
准备就绪
+ + + + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+

用户列表

+
+
+ +
+

原始结果

+
{}
+
+
+ + + + +"""