@wu529778790/open-im 1.11.4-beta.2 → 1.11.4-beta.20
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/README.md +45 -23
- package/dist/clawbot/client.js +44 -11
- package/dist/clawbot/message-sender.js +4 -16
- package/dist/clawbot/types.d.ts +3 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ open-im 把 Claude Code、Codex、CodeBuddy 接入 Telegram、飞书、企业微
|
|
|
10
10
|
- **无缝接力** — 和 Claude Code CLI 共享 session,手机聊一半,电脑接着来
|
|
11
11
|
- **完整能力** — 流式输出、会话管理、模型切换,全靠聊天命令
|
|
12
12
|
- **一个桥接,多个平台** — 同一个 bot 支持 7 个 IM 平台
|
|
13
|
+
- **交互式选择** — AI 问"选 1/2/3"时,IM 显示按钮(Telegram/飞书/钉钉)
|
|
13
14
|
|
|
14
15
|
## 快速开始
|
|
15
16
|
|
|
@@ -17,19 +18,11 @@ open-im 把 Claude Code、Codex、CodeBuddy 接入 Telegram、飞书、企业微
|
|
|
17
18
|
# 安装
|
|
18
19
|
npm install -g @wu529778790/open-im
|
|
19
20
|
|
|
20
|
-
# 配置(交互式向导)
|
|
21
|
-
open-im init
|
|
22
|
-
|
|
23
21
|
# 启动
|
|
24
22
|
open-im start
|
|
25
23
|
```
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npx @wu529778790/open-im init
|
|
31
|
-
npx @wu529778790/open-im start
|
|
32
|
-
```
|
|
25
|
+
首次启动会自动打开 Web 控制台,引导你完成配置。
|
|
33
26
|
|
|
34
27
|
### 最小配置
|
|
35
28
|
|
|
@@ -46,20 +39,22 @@ npx @wu529778790/open-im start
|
|
|
46
39
|
|
|
47
40
|
## 平台支持
|
|
48
41
|
|
|
49
|
-
| 平台 | 流式输出 | 接入指南 |
|
|
50
|
-
|
|
51
|
-
| Telegram | ✅ | [Bot 文档](https://core.telegram.org/bots#creating-a-new-bot) |
|
|
52
|
-
| 飞书 | ✅ | [开放平台](https://open.feishu.cn/) |
|
|
53
|
-
| QQ 机器人 | ✅ | [开放平台](https://bot.q.qq.com/) |
|
|
54
|
-
| 企业微信 | ✅ | [管理后台](https://work.weixin.qq.com/) |
|
|
55
|
-
| 钉钉机器人 | ⚠️ 部分 | [开放平台](https://open-dev.dingtalk.com/) |
|
|
56
|
-
| 微信助理(WorkBuddy) | ✅ | [接入指南](https://www.codebuddy.cn/docs/workbuddy/Claw) |
|
|
57
|
-
| 微信客服号(ClawBot) | ✅ | [接入指南](https://www.codebuddy.cn/docs/workbuddy/Claw) |
|
|
42
|
+
| 平台 | 流式输出 | 图片 | 交互按钮 | 接入指南 |
|
|
43
|
+
|------|---------|------|---------|---------|
|
|
44
|
+
| Telegram | ✅ | ✅ | ✅ | [Bot 文档](https://core.telegram.org/bots#creating-a-new-bot) |
|
|
45
|
+
| 飞书 | ✅ | ✅ | ✅ | [开放平台](https://open.feishu.cn/) |
|
|
46
|
+
| QQ 机器人 | ✅ | ✅ | ❌ | [开放平台](https://bot.q.qq.com/) |
|
|
47
|
+
| 企业微信 | ✅ | ✅ | ❌ | [管理后台](https://work.weixin.qq.com/) |
|
|
48
|
+
| 钉钉机器人 | ⚠️ 部分 | ✅ | ✅ | [开放平台](https://open-dev.dingtalk.com/) |
|
|
49
|
+
| 微信助理(WorkBuddy) | ✅ | ❌ | ❌ | [接入指南](https://www.codebuddy.cn/docs/workbuddy/Claw) |
|
|
50
|
+
| 微信客服号(ClawBot) | ✅ | ⚠️ 下载中 | ❌ | [接入指南](https://www.codebuddy.cn/docs/workbuddy/Claw) |
|
|
58
51
|
|
|
59
|
-
每个平台可单独配置 AI 后端(`claude` / `codex` / `codebuddy`),默认 `claude`。
|
|
52
|
+
每个平台可单独配置 AI 后端(`claude` / `codex` / `codebuddy` / `opencode`),默认 `claude`。
|
|
60
53
|
|
|
61
54
|
## 聊天命令
|
|
62
55
|
|
|
56
|
+
### 会话管理
|
|
57
|
+
|
|
63
58
|
| 命令 | 说明 |
|
|
64
59
|
|------|------|
|
|
65
60
|
| `/help` | 显示所有命令 |
|
|
@@ -70,12 +65,35 @@ npx @wu529778790/open-im start
|
|
|
70
65
|
| `/delete <序号>` | 删除会话 |
|
|
71
66
|
| `/rename <标题>` | 重命名会话 |
|
|
72
67
|
| `/fork [序号]` | 分支会话 |
|
|
68
|
+
|
|
69
|
+
### 信息查看
|
|
70
|
+
|
|
71
|
+
| 命令 | 说明 |
|
|
72
|
+
|------|------|
|
|
73
73
|
| `/models` | 查看可用模型 |
|
|
74
74
|
| `/context` | 查看上下文用量 |
|
|
75
|
+
| `/plugins` | 查看已安装插件 |
|
|
75
76
|
| `/status` | 显示状态信息 |
|
|
76
77
|
| `/cd <路径>` / `/pwd` | 切换/查看工作目录 |
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
### 快捷命令
|
|
80
|
+
|
|
81
|
+
| 命令 | 说明 |
|
|
82
|
+
|------|------|
|
|
83
|
+
| `/git commit` | 提交代码 |
|
|
84
|
+
| `/git push` | 推送到远程 |
|
|
85
|
+
| `/git pull` | 拉取远程更新 |
|
|
86
|
+
| `/test` | 运行测试 |
|
|
87
|
+
| `/build` | 构建项目 |
|
|
88
|
+
| `/review` | 代码审查 |
|
|
89
|
+
| `/explain` | 解释项目结构 |
|
|
90
|
+
|
|
91
|
+
### 权限控制
|
|
92
|
+
|
|
93
|
+
| 命令 | 说明 |
|
|
94
|
+
|------|------|
|
|
95
|
+
| `/allow` `/y` | 允许操作 |
|
|
96
|
+
| `/deny` `/n` | 拒绝操作 |
|
|
79
97
|
|
|
80
98
|
## 会话接力
|
|
81
99
|
|
|
@@ -102,6 +120,8 @@ claude -c # 接上手机端的对话
|
|
|
102
120
|
- 启动/停止桥接服务
|
|
103
121
|
- 编辑配置文件
|
|
104
122
|
- 首次运行自动弹出设置向导
|
|
123
|
+
- 平台卡片支持展开/折叠
|
|
124
|
+
- 一键保存并启动
|
|
105
125
|
|
|
106
126
|
局域网访问:`export OPEN_IM_WEB_HOST=0.0.0.0`
|
|
107
127
|
|
|
@@ -114,6 +134,7 @@ claude -c # 接上手机端的对话
|
|
|
114
134
|
| `open-im stop` | 停止服务 |
|
|
115
135
|
| `open-im restart` | 重启 |
|
|
116
136
|
| `open-im dashboard` | 仅启动 Web 配置服务 |
|
|
137
|
+
| `open-im --version` | 查看版本号 |
|
|
117
138
|
|
|
118
139
|
## 配置
|
|
119
140
|
|
|
@@ -143,10 +164,11 @@ claude -c # 接上手机端的对话
|
|
|
143
164
|
- **`TELEGRAM_BOT_TOKEN`** — Telegram Bot Token
|
|
144
165
|
- **`OPEN_IM_WEB_PORT`** — Web 控制台端口(默认 39282)
|
|
145
166
|
- **`OPEN_IM_WEB_HOST`** — Web 控制台监听地址
|
|
167
|
+
- **`OPEN_IM_SENTRY_DSN`** — Sentry 错误追踪(可选)
|
|
146
168
|
|
|
147
|
-
###
|
|
169
|
+
### 错误追踪
|
|
148
170
|
|
|
149
|
-
|
|
171
|
+
默认启用 Sentry 收集错误日志(不含聊天内容)。关闭:`OPEN_IM_TELEMETRY=false`
|
|
150
172
|
|
|
151
173
|
## 平台配置详情
|
|
152
174
|
|
package/dist/clawbot/client.js
CHANGED
|
@@ -12,7 +12,8 @@ import { createLogger } from '../logger.js';
|
|
|
12
12
|
import { jitteredDelay, isFatalReconnectError, SLOW_PROBE_MS } from '../shared/reconnect.js';
|
|
13
13
|
import { cacheContextToken } from './message-sender.js';
|
|
14
14
|
import { setClawbotContextToken, clearClawbotContextToken } from '../shared/active-chats.js';
|
|
15
|
-
import {
|
|
15
|
+
import { createMediaTargetPath } from '../shared/media-storage.js';
|
|
16
|
+
import { createDecipheriv } from 'node:crypto';
|
|
16
17
|
import { CLAWBOT_POLL_INTERVAL_MS } from '../constants.js';
|
|
17
18
|
const log = createLogger('ClawBot');
|
|
18
19
|
const RECONNECT_DELAYS_MS = [3000, 5000, 10000, 20000, 30000];
|
|
@@ -132,6 +133,10 @@ function startPolling() {
|
|
|
132
133
|
cacheContextToken(chatId, msg.context_token);
|
|
133
134
|
setClawbotContextToken(msg.context_token);
|
|
134
135
|
}
|
|
136
|
+
// Debug: log raw item_list for image messages
|
|
137
|
+
if (extracted === '[图片]') {
|
|
138
|
+
log.info(`Image message raw item_list: ${JSON.stringify(msg.item_list).substring(0, 2000)}`);
|
|
139
|
+
}
|
|
135
140
|
// Extract and download images from message
|
|
136
141
|
const imagePaths = await extractImages(msg);
|
|
137
142
|
userMessages.push({ chatId, msgId, content: extracted, imagePaths: imagePaths.length > 0 ? imagePaths : undefined });
|
|
@@ -249,28 +254,56 @@ async function extractImages(msg) {
|
|
|
249
254
|
for (const item of msg.item_list) {
|
|
250
255
|
if (item.type !== 2 /* MessageItemType.IMAGE */)
|
|
251
256
|
continue;
|
|
252
|
-
const
|
|
253
|
-
|
|
257
|
+
const imageItem = item.image_item;
|
|
258
|
+
const media = imageItem?.media;
|
|
259
|
+
// iLink API 使用 full_url 而非 cdn_url
|
|
260
|
+
const imageUrl = media?.full_url || media?.cdn_url;
|
|
261
|
+
if (!imageUrl) {
|
|
262
|
+
log.warn('Image item missing full_url/cdn_url');
|
|
254
263
|
continue;
|
|
264
|
+
}
|
|
255
265
|
try {
|
|
256
|
-
//
|
|
257
|
-
const response = await fetch(
|
|
266
|
+
// 直接从 full_url 下载图片(CDN 返回的是已解密的图片数据)
|
|
267
|
+
const response = await fetch(imageUrl, { signal: AbortSignal.timeout(30_000) });
|
|
258
268
|
if (!response.ok) {
|
|
259
269
|
log.warn(`Image download failed: HTTP ${response.status}`);
|
|
260
270
|
continue;
|
|
261
271
|
}
|
|
262
272
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
273
|
+
// 检查是否是有效图片
|
|
274
|
+
const first4 = buffer.subarray(0, 4).toString('hex');
|
|
275
|
+
const isJpeg = first4 === 'ffd8ffe0' || first4 === 'ffd8ffe1';
|
|
276
|
+
const isPng = first4 === '89504e47';
|
|
277
|
+
let finalBuffer;
|
|
278
|
+
if (isJpeg || isPng) {
|
|
279
|
+
// CDN 返回的是有效图片,直接使用
|
|
280
|
+
finalBuffer = buffer;
|
|
267
281
|
}
|
|
268
282
|
else {
|
|
269
|
-
|
|
283
|
+
// CDN 返回的是加密数据,尝试解密
|
|
284
|
+
const aesKeyHex = imageItem?.aeskey;
|
|
285
|
+
if (aesKeyHex && aesKeyHex.length === 32) {
|
|
286
|
+
try {
|
|
287
|
+
const keyBuf = Buffer.from(aesKeyHex, 'hex');
|
|
288
|
+
const iv = keyBuf.subarray(0, 16);
|
|
289
|
+
const decipher = createDecipheriv('aes-128-cbc', keyBuf, iv);
|
|
290
|
+
finalBuffer = Buffer.concat([decipher.update(buffer), decipher.final()]);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
log.info('AES decryption failed, using raw image data');
|
|
294
|
+
finalBuffer = buffer;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
finalBuffer = buffer;
|
|
299
|
+
}
|
|
270
300
|
}
|
|
271
301
|
// Save to disk
|
|
272
302
|
const targetPath = createMediaTargetPath('.jpg', `clawbot-${Date.now()}`);
|
|
273
|
-
await
|
|
303
|
+
const { writeFile } = await import('node:fs/promises');
|
|
304
|
+
const { mkdir } = await import('node:fs/promises');
|
|
305
|
+
await mkdir('/tmp/t/open-im-images', { recursive: true });
|
|
306
|
+
await writeFile(targetPath, finalBuffer);
|
|
274
307
|
paths.push(targetPath);
|
|
275
308
|
log.info(`ClawBot image saved: ${targetPath}`);
|
|
276
309
|
}
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { randomBytes } from 'node:crypto';
|
|
7
7
|
import { createLogger } from '../logger.js';
|
|
8
|
-
import {
|
|
9
|
-
import { MAX_CLAWBOT_MESSAGE_LENGTH } from '../constants.js';
|
|
8
|
+
import { toReplyPlainText } from '../shared/utils.js';
|
|
10
9
|
import { getChannelState } from './client.js';
|
|
11
10
|
import { getActiveChatId, getClawbotContextToken } from '../shared/active-chats.js';
|
|
12
11
|
const log = createLogger('ClawBotSender');
|
|
@@ -94,20 +93,9 @@ async function postMessage(chatId, text, contextToken) {
|
|
|
94
93
|
*/
|
|
95
94
|
export async function sendTextReply(chatId, text, contextToken) {
|
|
96
95
|
const plainText = toReplyPlainText(text);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
await postMessage(chatId, plainText, contextToken);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
log.info(`Sending ClawBot reply in ${parts.length} parts to chatId=${chatId}, totalLen=${plainText.length}`);
|
|
104
|
-
for (let i = 0; i < parts.length; i++) {
|
|
105
|
-
const partText = i === 0
|
|
106
|
-
? `${parts[i]}\n\n_(1/${parts.length})_`
|
|
107
|
-
: `_(续 ${i + 1}/${parts.length})_\n\n${parts[i]}`;
|
|
108
|
-
await postMessage(chatId, partText, contextToken);
|
|
109
|
-
log.info(`ClawBot part ${i + 1}/${parts.length} sent`);
|
|
110
|
-
}
|
|
96
|
+
// 发送文字消息
|
|
97
|
+
log.info(`Sending ClawBot reply to chatId=${chatId}, len=${plainText.length}`);
|
|
98
|
+
await postMessage(chatId, plainText, contextToken);
|
|
111
99
|
}
|
|
112
100
|
/**
|
|
113
101
|
* Send error reply to a ClawBot chat.
|
package/dist/clawbot/types.d.ts
CHANGED
|
@@ -21,9 +21,12 @@ export interface TextItem {
|
|
|
21
21
|
}
|
|
22
22
|
/** Image content item */
|
|
23
23
|
export interface ImageItem {
|
|
24
|
+
aeskey?: string;
|
|
24
25
|
media?: {
|
|
25
26
|
aes_key?: string;
|
|
26
27
|
cdn_url?: string;
|
|
28
|
+
full_url?: string;
|
|
29
|
+
encrypt_query_param?: string;
|
|
27
30
|
};
|
|
28
31
|
width?: number;
|
|
29
32
|
height?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/open-im",
|
|
3
|
-
"version": "1.11.4-beta.
|
|
3
|
+
"version": "1.11.4-beta.20",
|
|
4
4
|
"description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -58,9 +58,13 @@
|
|
|
58
58
|
"@sentry/node": "^10.58.0",
|
|
59
59
|
"centrifuge": "^5.5.3",
|
|
60
60
|
"dingtalk-stream": "^2.1.4",
|
|
61
|
+
"edge-tts": "^1.0.1",
|
|
62
|
+
"https-proxy-agent": "^9.1.0",
|
|
63
|
+
"node-edge-tts": "^1.2.10",
|
|
61
64
|
"prompts": "^2.4.2",
|
|
65
|
+
"say": "^0.16.0",
|
|
62
66
|
"telegraf": "^4.16.3",
|
|
63
|
-
"ws": "^8.
|
|
67
|
+
"ws": "^8.21.0"
|
|
64
68
|
},
|
|
65
69
|
"devDependencies": {
|
|
66
70
|
"@eslint/js": "^9.15.0",
|