From 9e3e76c2467defb4e1d6a342e41195e93b405bb0 Mon Sep 17 00:00:00 2001 From: sliverp Date: Thu, 29 Jan 2026 16:41:12 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0=20token=20=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6=E5=92=8C=20URL=20?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E8=A7=84=E5=88=99=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gateway.ts | 62 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/src/gateway.ts b/src/gateway.ts index 94e258b..e0611b5 100644 --- a/src/gateway.ts +++ b/src/gateway.ts @@ -209,17 +209,37 @@ export async function startGateway(ctx: GatewayContext): Promise { QQGroupOpenid: event.groupOpenid, }); + // 发送消息的辅助函数,带 token 过期重试 + const sendWithTokenRetry = async (sendFn: (token: string) => Promise) => { + try { + const token = await getAccessToken(account.appId, account.clientSecret); + await sendFn(token); + } catch (err) { + const errMsg = String(err); + // 如果是 token 相关错误,清除缓存重试一次 + if (errMsg.includes("401") || errMsg.includes("token") || errMsg.includes("access_token")) { + log?.info(`[qqbot:${account.accountId}] Token may be expired, refreshing...`); + clearTokenCache(); + const newToken = await getAccessToken(account.appId, account.clientSecret); + await sendFn(newToken); + } else { + throw err; + } + } + }; + // 发送错误提示的辅助函数 const sendErrorMessage = async (errorText: string) => { try { - const token = await getAccessToken(account.appId, account.clientSecret); - if (event.type === "c2c") { - await sendC2CMessage(token, event.senderId, errorText, event.messageId); - } else if (event.type === "group" && event.groupOpenid) { - await sendGroupMessage(token, event.groupOpenid, errorText, event.messageId); - } else if (event.channelId) { - await sendChannelMessage(token, event.channelId, errorText, event.messageId); - } + await sendWithTokenRetry(async (token) => { + if (event.type === "c2c") { + await sendC2CMessage(token, event.senderId, errorText, event.messageId); + } else if (event.type === "group" && event.groupOpenid) { + await sendGroupMessage(token, event.groupOpenid, errorText, event.messageId); + } else if (event.channelId) { + await sendChannelMessage(token, event.channelId, errorText, event.messageId); + } + }); } catch (sendErr) { log?.error(`[qqbot:${account.accountId}] Failed to send error message: ${sendErr}`); } @@ -228,9 +248,6 @@ export async function startGateway(ctx: GatewayContext): Promise { try { const messagesConfig = pluginRuntime.channel.reply.resolveEffectiveMessagesConfig(cfg, route.agentId); - // 每次发消息前刷新 token - const freshToken = await getAccessToken(account.appId, account.clientSecret); - // 追踪是否有响应 let hasResponse = false; const responseTimeout = 30000; // 30秒超时 @@ -260,22 +277,27 @@ export async function startGateway(ctx: GatewayContext): Promise { if (!replyText.trim()) return; // 处理回复内容,避免被 QQ 识别为 URL - // 把文件扩展名中的点替换为下划线,如 README.md -> README_md const originalText = replyText; - replyText = replyText.replace(/(\w+)\.(\w{2,4})\b/g, "$1_$2"); + + // 把所有可能被识别为 URL 的点替换为下划线 + // 匹配:字母/数字.字母/数字 的模式 + replyText = replyText.replace(/([a-zA-Z0-9])\.([a-zA-Z0-9])/g, "$1_$2"); + const hasReplacement = replyText !== originalText; if (hasReplacement) { replyText += "\n\n(由于平台限制,回复中的部分符号已被替换)"; } try { - if (event.type === "c2c") { - await sendC2CMessage(freshToken, event.senderId, replyText, event.messageId); - } else if (event.type === "group" && event.groupOpenid) { - await sendGroupMessage(freshToken, event.groupOpenid, replyText, event.messageId); - } else if (event.channelId) { - await sendChannelMessage(freshToken, event.channelId, replyText, event.messageId); - } + await sendWithTokenRetry(async (token) => { + if (event.type === "c2c") { + await sendC2CMessage(token, event.senderId, replyText, event.messageId); + } else if (event.type === "group" && event.groupOpenid) { + await sendGroupMessage(token, event.groupOpenid, replyText, event.messageId); + } else if (event.channelId) { + await sendChannelMessage(token, event.channelId, replyText, event.messageId); + } + }); log?.info(`[qqbot:${account.accountId}] Sent reply`); pluginRuntime.channel.activity.record({