wechat-opencode-bot 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/bridge.js +61 -11
package/package.json
CHANGED
package/src/bridge.js
CHANGED
|
@@ -23,7 +23,7 @@ const CC_WECHAT_DIR = findCcWechatDir();
|
|
|
23
23
|
const { getActiveAccount, loadSyncBuf, saveSyncBuf } = await import(
|
|
24
24
|
join(CC_WECHAT_DIR, 'dist', 'store.js')
|
|
25
25
|
);
|
|
26
|
-
const { getUpdates, sendMessage, sendTyping, getConfig } = await import(
|
|
26
|
+
const { getUpdates, sendMessage, sendTyping, getConfig, buildBaseInfo: ccBuildBaseInfo, buildHeaders: ccBuildHeaders } = await import(
|
|
27
27
|
join(CC_WECHAT_DIR, 'dist', 'ilink-api.js')
|
|
28
28
|
);
|
|
29
29
|
const { stripMarkdown, chunkText } = await import(
|
|
@@ -222,7 +222,8 @@ async function fireTask(task) {
|
|
|
222
222
|
const plainText = stripMarkdown(chunk);
|
|
223
223
|
if (!plainText.trim()) return;
|
|
224
224
|
for (const c of chunkText(plainText, 3900)) {
|
|
225
|
-
await
|
|
225
|
+
await sendWechat(task.userId, c, '', undefined);
|
|
226
|
+
await sleep(1000);
|
|
226
227
|
}
|
|
227
228
|
});
|
|
228
229
|
} catch (err) {
|
|
@@ -324,6 +325,56 @@ async function api(path, options = {}, timeoutMs = 30_000) {
|
|
|
324
325
|
}
|
|
325
326
|
}
|
|
326
327
|
|
|
328
|
+
/**
|
|
329
|
+
* 发送微信消息并校验业务返回码(cc-wechat 的 sendMessage 只查 HTTP 状态,
|
|
330
|
+
* 微信限流时返回 HTTP 200 + ret!=0 会被静默吞掉,导致"回一半")。
|
|
331
|
+
* 失败自动重试(指数退避)。返回 true/false。
|
|
332
|
+
*/
|
|
333
|
+
async function sendWechat(userId, text, contextToken, refMsgId, { retries = 5 } = {}) {
|
|
334
|
+
const { randomBytes } = await import('node:crypto');
|
|
335
|
+
const clientId = `cc-wechat-${randomBytes(4).toString('hex')}`;
|
|
336
|
+
const textItem = { type: 1, text_item: { text } };
|
|
337
|
+
if (refMsgId) textItem.ref_msg = { title: text.slice(0, 40) };
|
|
338
|
+
const msg = {
|
|
339
|
+
from_user_id: '',
|
|
340
|
+
to_user_id: userId,
|
|
341
|
+
client_id: clientId,
|
|
342
|
+
message_type: 2,
|
|
343
|
+
message_state: 2,
|
|
344
|
+
item_list: [textItem],
|
|
345
|
+
context_token: contextToken,
|
|
346
|
+
};
|
|
347
|
+
if (refMsgId) msg.ref_message_id = refMsgId;
|
|
348
|
+
const body = JSON.stringify({ msg, base_info: ccBuildBaseInfo() });
|
|
349
|
+
let lastErr = '';
|
|
350
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
351
|
+
try {
|
|
352
|
+
const base = (account.baseUrl || '').replace(/\/+$/, '') + '/';
|
|
353
|
+
const resp = await fetch(`${base}ilink/bot/sendmessage`, {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
headers: ccBuildHeaders(account.token, body),
|
|
356
|
+
body,
|
|
357
|
+
});
|
|
358
|
+
const text = await resp.text();
|
|
359
|
+
let parsed = null;
|
|
360
|
+
try { parsed = JSON.parse(text); } catch {}
|
|
361
|
+
const ret = parsed?.ret ?? parsed?.errcode;
|
|
362
|
+
if (parsed && ret !== undefined && ret !== 0) {
|
|
363
|
+
lastErr = `ret=${ret} ${parsed.errmsg ?? ''}`;
|
|
364
|
+
// 限流或失败,指数退避重试(1s/2s/4s/8s...)
|
|
365
|
+
await sleep(1000 * 2 ** (attempt - 1));
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
return true;
|
|
369
|
+
} catch (err) {
|
|
370
|
+
lastErr = String(err.message ?? err);
|
|
371
|
+
await sleep(1000 * 2 ** (attempt - 1));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
log(`sendWechat 失败 ${userId}: ${lastErr}`);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
|
|
327
378
|
/** 获取或创建该微信用户的 opencode session */
|
|
328
379
|
async function getSessionForUser(userId) {
|
|
329
380
|
if (sessionMap[userId]) {
|
|
@@ -357,7 +408,8 @@ async function askOpenCodeStream(sessionId, text, onChunk) {
|
|
|
357
408
|
[schedule_list] 查看当前定时任务
|
|
358
409
|
[schedule_del: 任务ID] 删除指定定时任务
|
|
359
410
|
到点后系统会调用你生成内容并主动推送给用户。
|
|
360
|
-
回复要求:
|
|
411
|
+
回复要求: 请给出完整、详细、原样的回答,把你在命令行中会输出的全部内容直接发过来,不要缩略、不要只给总结或要点提示,确保用户看到完整答案。如果执行了代码或命令,把关键输出也一并附上。
|
|
412
|
+
微信格式: 不要使用 ASCII 艺术表格(┌─┬─┐ box-drawing 字符),微信会显示错乱。表格/列表改用纯文本(如 "编号 - 标题 (状态)"、"- 项目"、用缩进和符号组织),代码块用围栏标记即可。`;
|
|
361
413
|
|
|
362
414
|
// 确认 session 存在
|
|
363
415
|
await api(`/session/${sessionId}`, {}, 5000);
|
|
@@ -463,10 +515,10 @@ async function askOpenCodeStream(sessionId, text, onChunk) {
|
|
|
463
515
|
if (p.partID && reasoningPartIDs.has(p.partID)) break;
|
|
464
516
|
lastProgress = Date.now();
|
|
465
517
|
streamingBuffer += p.delta;
|
|
466
|
-
//
|
|
518
|
+
// 达到块大小或句子边界即推送(块放大到 400,避免高频小消息被微信限流吞掉)
|
|
467
519
|
if (
|
|
468
|
-
streamingBuffer.length >=
|
|
469
|
-
/[。!?\n]/.test(streamingBuffer.slice(-2))
|
|
520
|
+
streamingBuffer.length >= 400 ||
|
|
521
|
+
(/[。!?\n]/.test(streamingBuffer.slice(-2)) && streamingBuffer.length >= 40)
|
|
470
522
|
) {
|
|
471
523
|
await flushBuffer();
|
|
472
524
|
}
|
|
@@ -636,11 +688,9 @@ async function handleMessage(msg) {
|
|
|
636
688
|
if (!plainText.trim()) return;
|
|
637
689
|
const chunks = chunkText(plainText, 3900);
|
|
638
690
|
for (const c of chunks) {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
log(`发送失败: ${String(err)}`);
|
|
643
|
-
}
|
|
691
|
+
const ok = await sendWechat(fromUser, c, contextToken, messageId);
|
|
692
|
+
if (ok) log(`流式已发送 from=${fromUser} len=${c.length}`);
|
|
693
|
+
await sleep(1000); // 发送间隔,避免高频触发微信限流
|
|
644
694
|
}
|
|
645
695
|
});
|
|
646
696
|
} catch (err) {
|