fix(im_bot): segment long WeChat text replies + enhance send diagnostics (#878)\n\niLink returns HTTP 200 but WeChat silently drops long messages (1698-char\nHTML image link, 3011-char skill list). Add WECHAT_TEXT_MAX_LEN=1500 and\nsegment long text into multiple items. Also log payload item lengths and\nmedia URL lengths for future diagnosis.
This commit is contained in:
1 parent
f4e18bafe4
commit
34648e4810
2 files changed
+68
-3
No files matched your search
@@ -47,6 +47,10 @@ def _beijing_now() -> datetime:
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
ILINK_BASE = "https://ilinkai.weixin.qq.com"
|
ILINK_BASE = "https://ilinkai.weixin.qq.com"
|
||||||
|
|
||||||
|
# 微信/iLink 单条文本消息长度软上限。超过则分段发送,避免 iLink 返回 200
|
||||||
|
# 但用户侧静默收不到(实测 1698 字符 HTML 链接、3011 字符 Markdown 均丢失)。
|
||||||
|
WECHAT_TEXT_MAX_LEN = 1500
|
||||||
|
|
||||||
# 每个已连接账号一个后台接收线程,key = 账号表主键 id
|
# 每个已连接账号一个后台接收线程,key = 账号表主键 id
|
||||||
_wechat_receiver_threads: dict[int, threading.Thread] = {}
|
_wechat_receiver_threads: dict[int, threading.Thread] = {}
|
||||||
|
|
||||||
@@ -212,10 +216,19 @@ def _wechat_post_msg(baseurl: str, bot_token: str, to_user_id: str, context_toke
|
|||||||
# 07-30 diagnostic
|
# 07-30 diagnostic
|
||||||
import json as _json3
|
import json as _json3
|
||||||
try:
|
try:
|
||||||
|
# 增强诊断:记录每条消息类型、长度、内容摘要,用于定位“iLink 200 但用户收不到”
|
||||||
|
_item_summaries = []
|
||||||
|
for _it in item_list:
|
||||||
|
_t = _it.get("type")
|
||||||
|
_txt = ""
|
||||||
|
if _t == 1 and _it.get("text_item"):
|
||||||
|
_txt = _it["text_item"].get("text", "")
|
||||||
|
_item_summaries.append(f"type={_t} len={len(_txt)} text={repr(_txt[:80])}")
|
||||||
with open("/tmp/wechat_send_debug.log", "a", encoding="utf-8") as _f:
|
with open("/tmp/wechat_send_debug.log", "a", encoding="utf-8") as _f:
|
||||||
_f.write(_beijing_now().isoformat() + " SEND_DEBUG token=" + repr(bot_token[:20] if bot_token else "NULL")
|
_f.write(_beijing_now().isoformat() + " SEND_DEBUG token=" + repr(bot_token[:20] if bot_token else "NULL")
|
||||||
+ " to=" + repr(to_user_id[:30]) + " ctx=" + repr(context_token[:30]) + "\n")
|
+ " to=" + repr(to_user_id[:30]) + " ctx=" + repr(context_token[:30]) + "\n")
|
||||||
_f.write(" payload_keys=" + str(list(payload.get("msg", {}).keys())) + "\n")
|
_f.write(" payload_keys=" + str(list(payload.get("msg", {}).keys()))
|
||||||
|
+ " items=[" + "; ".join(_item_summaries) + "]\n")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -249,9 +262,49 @@ def _wechat_post_msg(baseurl: str, bot_token: str, to_user_id: str, context_toke
|
|||||||
|
|
||||||
|
|
||||||
def _wechat_send_message(baseurl: str, bot_token: str, to_user_id: str, context_token: str, text: str):
|
def _wechat_send_message(baseurl: str, bot_token: str, to_user_id: str, context_token: str, text: str):
|
||||||
"""发送纯文本回复。"""
|
"""发送纯文本回复。长文本按 WECHAT_TEXT_MAX_LEN 分段,避免 iLink 200 但微信不显示。"""
|
||||||
|
if not text:
|
||||||
return _wechat_post_msg(baseurl, bot_token, to_user_id, context_token,
|
return _wechat_post_msg(baseurl, bot_token, to_user_id, context_token,
|
||||||
[{"type": 1, "text_item": {"text": text}}])
|
[{"type": 1, "text_item": {"text": "(无内容)"}}])
|
||||||
|
# 单条不超过软上限;保留完整段落优先在换行处切分
|
||||||
|
chunks = []
|
||||||
|
start = 0
|
||||||
|
while start < len(text):
|
||||||
|
end = min(start + WECHAT_TEXT_MAX_LEN, len(text))
|
||||||
|
if end < len(text):
|
||||||
|
# 尽量在换行处切断
|
||||||
|
_nl = text.rfind("\n", start, end)
|
||||||
|
if _nl > start + WECHAT_TEXT_MAX_LEN // 2:
|
||||||
|
end = _nl + 1
|
||||||
|
chunks.append(text[start:end])
|
||||||
|
start = end
|
||||||
|
|
||||||
|
all_ok = True
|
||||||
|
last_ret = None
|
||||||
|
for idx, chunk in enumerate(chunks):
|
||||||
|
suffix = ""
|
||||||
|
if len(chunks) > 1:
|
||||||
|
if idx == 0:
|
||||||
|
suffix = "\n(内容较长,分条发送)"
|
||||||
|
if idx == len(chunks) - 1:
|
||||||
|
suffix = "\n(已发送完毕;完整内容请访问平台 AI 聊天的 WeXin Bot 窗口)"
|
||||||
|
ok, ret, _ = _wechat_post_msg(
|
||||||
|
baseurl, bot_token, to_user_id, context_token,
|
||||||
|
[{"type": 1, "text_item": {"text": chunk + suffix}}]
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
all_ok = False
|
||||||
|
last_ret = ret
|
||||||
|
# 记录失败但不中断,尽量把已切好的消息发完
|
||||||
|
try:
|
||||||
|
with open("/tmp/wechat_send.log", "a", encoding="utf-8") as _f:
|
||||||
|
_f.write(_beijing_now().isoformat() + " SEGMENT_FAIL idx=%s ret=%s\n" % (idx, ret))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# 分段之间稍作间隔,降低频率/风控影响
|
||||||
|
if idx < len(chunks) - 1:
|
||||||
|
time.sleep(0.5)
|
||||||
|
return all_ok, last_ret, {}
|
||||||
|
|
||||||
|
|
||||||
def _platform_relay_url(raw_url: str) -> str:
|
def _platform_relay_url(raw_url: str) -> str:
|
||||||
@@ -276,6 +329,11 @@ def _wechat_send_media(baseurl: str, bot_token: str, to_user_id: str, context_to
|
|||||||
确保用户可点击访问且不会破坏会话;待拿到 iLink 媒体协议文档后再恢复内联 item。
|
确保用户可点击访问且不会破坏会话;待拿到 iLink 媒体协议文档后再恢复内联 item。
|
||||||
"""
|
"""
|
||||||
relay_url = _platform_relay_url(url)
|
relay_url = _platform_relay_url(url)
|
||||||
|
try:
|
||||||
|
with open("/tmp/wechat_send_debug.log", "a", encoding="utf-8") as _f:
|
||||||
|
_f.write(_beijing_now().isoformat() + " MEDIA_URL_LEN=%s type=%s\n" % (len(relay_url), media_type))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
if media_type == "image":
|
if media_type == "image":
|
||||||
caption = "🖼️ 图片已生成,点击查看:" + relay_url
|
caption = "🖼️ 图片已生成,点击查看:" + relay_url
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -191,6 +191,13 @@ async def lifespan(app: FastAPI):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[EventBus] 启动失败(不影响正常服务): {e}")
|
logger.warning(f"[EventBus] 启动失败(不影响正常服务): {e}")
|
||||||
|
|
||||||
|
# 启动个人微信接收线程(之前仅在扫码确认时启动,重启后丢失)
|
||||||
|
try:
|
||||||
|
from app.api.im_bot_user import start_all_wechat_receivers
|
||||||
|
start_all_wechat_receivers()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[IM个人微信] 启动恢复失败(不影响正常服务): {e}")
|
||||||
|
|
||||||
logger.info("Qihu-AIFlow v2.0.0 启动完成")
|
logger.info("Qihu-AIFlow v2.0.0 启动完成")
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|||||||
Reference in new issue
Block a user