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 # 先生成回复(此时历史尚未包含本条,避免重复)