From f975672b0ecf261bc596ad7ede8650e7ef0018b7 Mon Sep 17 00:00:00 2001 From: qihu-bot Date: Tue, 18 Aug 2026 11:30:15 +0800 Subject: [PATCH] =?UTF-8?q?#974=20=E5=BE=AE=E4=BF=A1Bot=20sendtyping?= =?UTF-8?q?=E5=BD=BB=E5=BA=95=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: sendtyping三个bug - 1.ilink_user_id误用bot ID(应from_user_id用户ID) 2.请求缺base_info 3.未检查ret==0。另发现账号session已过期(-14 session timeout)是sendtyping失效+文本失败的根源。修复: getconfig/sendtyping补base_info+正确ilink_user_id+ret==0判断, -14时写session_expired日志。 --- backend/python/app/api/im_bot_user.py | 48 ++++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/backend/python/app/api/im_bot_user.py b/backend/python/app/api/im_bot_user.py index e6fb53c..bd138a1 100644 --- a/backend/python/app/api/im_bot_user.py +++ b/backend/python/app/api/im_bot_user.py @@ -319,7 +319,11 @@ def _wechat_send_message(baseurl: str, bot_token: str, to_user_id: str, context_ def _wechat_get_typing_ticket(baseurl: str, bot_token: str, ilink_user_id: str, context_token: str) -> str: - """getconfig 获取 typing_ticket(按 ilink_user_id 缓存 24h)。""" + """getconfig 获取 typing_ticket(按 ilink_user_id 缓存 24h)。 + + #974 修复:ilink_user_id 必须传【用户 ID】(from_user_id),不是 bot ID; + 请求体必须带 base_info;响应必须 ret==0 才算成功。 + """ global _typing_ticket_cache _now = time.time() _hit = _typing_ticket_cache.get(ilink_user_id) @@ -329,14 +333,30 @@ def _wechat_get_typing_ticket(baseurl: str, bot_token: str, ilink_user_id: str, resp = requests.post( baseurl.rstrip("/") + "/ilink/bot/getconfig", headers=_wechat_headers(bot_token), - json={"ilink_user_id": ilink_user_id, "context_token": context_token}, + json={ + "ilink_user_id": ilink_user_id, + "context_token": context_token, + "base_info": {"channel_version": "1.0.3"}, + }, timeout=10, ) body = resp.json() if resp.content else {} - ticket = body.get("typing_ticket", "") if isinstance(body, dict) else "" - if ticket: - _typing_ticket_cache[ilink_user_id] = (ticket, _now) - return ticket + # ret==0 才算成功;-14 会话过期 / -4 RPC 失败均无 ticket + ret = body.get("ret") + errcode = body.get("errcode") + if ret == 0 or (ret is None and errcode is None): + ticket = body.get("typing_ticket", "") if isinstance(body, dict) else "" + if ticket: + _typing_ticket_cache[ilink_user_id] = (ticket, _now) + return ticket + # 会话过期:记录,供上层标记 expired + if errcode == -14 or ret == -14: + try: + with open("/tmp/wechat_session_expired.log", "a", encoding="utf-8") as _f: + _f.write(_beijing_now().isoformat() + " GETCONFIG_SESSION_EXPIRED user=%s\n" % ilink_user_id) + except Exception: + pass + return "" except Exception: return "" @@ -349,7 +369,12 @@ def _wechat_send_typing(baseurl: str, bot_token: str, ilink_user_id: str, typing resp = requests.post( baseurl.rstrip("/") + "/ilink/bot/sendtyping", headers=_wechat_headers(bot_token), - json={"ilink_user_id": ilink_user_id, "typing_ticket": typing_ticket, "status": status}, + json={ + "ilink_user_id": ilink_user_id, + "typing_ticket": typing_ticket, + "status": status, + "base_info": {"channel_version": "1.0.3"}, + }, timeout=10, ) return resp.ok @@ -633,12 +658,11 @@ def _wechat_receiver_loop(account_id: int): + " ctx=" + repr(ctx) + " raw=" + json.dumps(m, ensure_ascii=False) + "\n") except Exception: pass - # 发送"正在输入"状态(尽力而为,失败静默) + # 发送"正在输入"状态(#974:ilink_user_id 用用户 ID = sender,非 bot ID) try: - _bot_id = m.get("to_user_id") or d.get("ilink_bot_id") or "" - _ticket = _wechat_get_typing_ticket(baseurl, bot_token, _bot_id, ctx) - if _bot_id and _ticket: - _wechat_send_typing(baseurl, bot_token, _bot_id, _ticket, 1) + _ticket = _wechat_get_typing_ticket(baseurl, bot_token, sender, ctx) + if sender and _ticket: + _wechat_send_typing(baseurl, bot_token, sender, _ticket, 1) except Exception: pass # 先生成回复(此时历史尚未包含本条,避免重复)