@neomei/opencode-qiwei 0.1.0
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 +38 -0
- package/bin/opencode-qiwei +96 -0
- package/dist/core/config.d.ts +62 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +47 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/group-policy.d.ts +12 -0
- package/dist/core/group-policy.d.ts.map +1 -0
- package/dist/core/group-policy.js +26 -0
- package/dist/core/group-policy.js.map +1 -0
- package/dist/core/logger.d.ts +8 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +20 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/media-handler.d.ts +22 -0
- package/dist/core/media-handler.d.ts.map +1 -0
- package/dist/core/media-handler.js +51 -0
- package/dist/core/media-handler.js.map +1 -0
- package/dist/core/message-handler.d.ts +38 -0
- package/dist/core/message-handler.d.ts.map +1 -0
- package/dist/core/message-handler.js +257 -0
- package/dist/core/message-handler.js.map +1 -0
- package/dist/core/session-manager.d.ts +33 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +75 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/template-card-manager.d.ts +32 -0
- package/dist/core/template-card-manager.d.ts.map +1 -0
- package/dist/core/template-card-manager.js +46 -0
- package/dist/core/template-card-manager.js.map +1 -0
- package/dist/core/types.d.ts +79 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/opencode/client.d.ts +27 -0
- package/dist/opencode/client.d.ts.map +1 -0
- package/dist/opencode/client.js +65 -0
- package/dist/opencode/client.js.map +1 -0
- package/dist/opencode/event-handler.d.ts +25 -0
- package/dist/opencode/event-handler.d.ts.map +1 -0
- package/dist/opencode/event-handler.js +167 -0
- package/dist/opencode/event-handler.js.map +1 -0
- package/dist/plugin.d.ts +14 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +58 -0
- package/dist/plugin.js.map +1 -0
- package/dist/standalone.d.ts +8 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +84 -0
- package/dist/standalone.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# opencode-qiwei
|
|
2
|
+
|
|
3
|
+
OpenCode 企业微信桥接 — 让 OpenCode 接入企业微信
|
|
4
|
+
|
|
5
|
+
基于企业微信智能机器人 WebSocket 长连接通道,提供:
|
|
6
|
+
- 消息收发(文本、图片、语音、文件)
|
|
7
|
+
- 流式回复(Markdown 渲染)
|
|
8
|
+
- 模板卡片交互(按钮、投票、选择)
|
|
9
|
+
- 自动权限批准
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @neomei/opencode-qiwei
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 配置
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
// ~/.config/opencode/qiwei.json
|
|
21
|
+
{
|
|
22
|
+
"botId": "your-bot-id",
|
|
23
|
+
"corpSecret": "your-secret",
|
|
24
|
+
"agentId": 1000001,
|
|
25
|
+
"token": "your-token",
|
|
26
|
+
"encodingAESKey": "your-43-char-aes-key",
|
|
27
|
+
"opencodeUrl": "http://localhost:19876",
|
|
28
|
+
"streaming": true,
|
|
29
|
+
"showProcess": "tools",
|
|
30
|
+
"autoApprove": true
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 使用
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
opencode-qiwei start
|
|
38
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* opencode-qiwei CLI — 企业微信桥接命令行工具
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { startStandalone } from '../dist/standalone.js';
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { createInterface } from 'readline';
|
|
11
|
+
|
|
12
|
+
const QIWEI_CONFIG = join(homedir(), '.config', 'opencode', 'qiwei.json');
|
|
13
|
+
|
|
14
|
+
async function runSetup() {
|
|
15
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
16
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
17
|
+
|
|
18
|
+
console.log('\n📋 企业微信智能机器人配置向导\n');
|
|
19
|
+
console.log('请在企业微信管理后台获取以下信息:');
|
|
20
|
+
console.log(' 应用管理 → 智能机器人 → 接收消息\n');
|
|
21
|
+
|
|
22
|
+
const botId = await ask('机器人 ID (botId): ');
|
|
23
|
+
const secret = await ask('机器人 Secret: ');
|
|
24
|
+
|
|
25
|
+
rl.close();
|
|
26
|
+
|
|
27
|
+
const config = {
|
|
28
|
+
botId: botId.trim(),
|
|
29
|
+
secret: secret.trim(),
|
|
30
|
+
corpId: 'placeholder',
|
|
31
|
+
corpSecret: 'placeholder',
|
|
32
|
+
agentId: 1000001,
|
|
33
|
+
token: 'placeholder-token',
|
|
34
|
+
encodingAESKey: 'placeholder-aes-key-43-characters-here',
|
|
35
|
+
opencodeUrl: 'http://localhost:19876',
|
|
36
|
+
streaming: true,
|
|
37
|
+
requireMention: true,
|
|
38
|
+
groupPolicy: 'open',
|
|
39
|
+
showProcess: 'tools',
|
|
40
|
+
autoApprove: true,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
mkdirSync(join(homedir(), '.config', 'opencode'), { recursive: true });
|
|
44
|
+
writeFileSync(QIWEI_CONFIG, JSON.stringify(config, null, 2));
|
|
45
|
+
console.log(`\n✅ 配置已保存到 ${QIWEI_CONFIG}`);
|
|
46
|
+
console.log('现在可以运行 opencode-qiwei start 启动桥接\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function runDoctor() {
|
|
50
|
+
console.log('\n🔍 opencode-qiwei 连接预检\n');
|
|
51
|
+
|
|
52
|
+
if (!existsSync(QIWEI_CONFIG)) {
|
|
53
|
+
console.log('❌ 配置文件不存在');
|
|
54
|
+
console.log(' 运行 opencode-qiwei setup 创建配置\n');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const config = JSON.parse(readFileSync(QIWEI_CONFIG, 'utf-8'));
|
|
59
|
+
|
|
60
|
+
console.log(`✅ 配置文件: ${QIWEI_CONFIG}`);
|
|
61
|
+
console.log(` botId: ${config.botId}`);
|
|
62
|
+
console.log(` OpenCode: ${config.opencodeUrl}`);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(config.opencodeUrl);
|
|
66
|
+
console.log(`✅ OpenCode 可达 (${res.status})`);
|
|
67
|
+
} catch {
|
|
68
|
+
console.log('❌ OpenCode 不可达');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await import('@wecom/aibot-node-sdk');
|
|
73
|
+
console.log('✅ 企业微信 SDK 已加载');
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.log(`❌ SDK 加载失败: ${e.message}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function main() {
|
|
82
|
+
const cmd = process.argv[2] || 'start';
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
if (cmd === 'setup') return await runSetup();
|
|
86
|
+
if (cmd === 'doctor') return await runDoctor();
|
|
87
|
+
if (cmd === 'status') { console.log('opencode-qiwei v0.1.0'); return; }
|
|
88
|
+
if (cmd === 'start') return await startStandalone();
|
|
89
|
+
console.log('用法: opencode-qiwei <start|setup|doctor|status>');
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error('错误:', err.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { WecomConfig } from './types.js';
|
|
3
|
+
declare const WecomConfigSchema: z.ZodObject<{
|
|
4
|
+
botId: z.ZodString;
|
|
5
|
+
secret: z.ZodString;
|
|
6
|
+
corpId: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
7
|
+
corpSecret: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
8
|
+
agentId: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
9
|
+
token: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
10
|
+
encodingAESKey: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
11
|
+
opencodeUrl: z.ZodDefault<z.ZodString>;
|
|
12
|
+
streaming: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
+
requireMention: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
groupPolicy: z.ZodDefault<z.ZodEnum<["open", "allowlist", "disabled"]>>;
|
|
15
|
+
allowlist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
16
|
+
showProcess: z.ZodDefault<z.ZodEnum<["none", "tools", "thinking", "full"]>>;
|
|
17
|
+
autoApprove: z.ZodDefault<z.ZodBoolean>;
|
|
18
|
+
scene: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
plugVersion: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, "strip", z.ZodTypeAny, {
|
|
21
|
+
botId: string;
|
|
22
|
+
secret: string;
|
|
23
|
+
corpId: string;
|
|
24
|
+
corpSecret: string;
|
|
25
|
+
agentId: number;
|
|
26
|
+
token: string;
|
|
27
|
+
encodingAESKey: string;
|
|
28
|
+
opencodeUrl: string;
|
|
29
|
+
streaming: boolean;
|
|
30
|
+
requireMention: boolean;
|
|
31
|
+
groupPolicy: "open" | "allowlist" | "disabled";
|
|
32
|
+
showProcess: "none" | "tools" | "thinking" | "full";
|
|
33
|
+
autoApprove: boolean;
|
|
34
|
+
allowlist?: string[] | undefined;
|
|
35
|
+
scene?: number | undefined;
|
|
36
|
+
plugVersion?: string | undefined;
|
|
37
|
+
}, {
|
|
38
|
+
botId: string;
|
|
39
|
+
secret: string;
|
|
40
|
+
allowlist?: string[] | undefined;
|
|
41
|
+
corpId?: string | undefined;
|
|
42
|
+
corpSecret?: string | undefined;
|
|
43
|
+
agentId?: number | undefined;
|
|
44
|
+
token?: string | undefined;
|
|
45
|
+
encodingAESKey?: string | undefined;
|
|
46
|
+
opencodeUrl?: string | undefined;
|
|
47
|
+
streaming?: boolean | undefined;
|
|
48
|
+
requireMention?: boolean | undefined;
|
|
49
|
+
groupPolicy?: "open" | "allowlist" | "disabled" | undefined;
|
|
50
|
+
showProcess?: "none" | "tools" | "thinking" | "full" | undefined;
|
|
51
|
+
autoApprove?: boolean | undefined;
|
|
52
|
+
scene?: number | undefined;
|
|
53
|
+
plugVersion?: string | undefined;
|
|
54
|
+
}>;
|
|
55
|
+
export declare class ConfigManager {
|
|
56
|
+
private configPath;
|
|
57
|
+
constructor(configPath?: string);
|
|
58
|
+
load(): WecomConfig;
|
|
59
|
+
save(config: WecomConfig): void;
|
|
60
|
+
}
|
|
61
|
+
export { WecomConfigSchema };
|
|
62
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,QAAA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiBrB,CAAC;AAIH,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,CAAC,EAAE,MAAM;IAI/B,IAAI,IAAI,WAAW;IAUnB,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;CAKhC;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
const WecomConfigSchema = z.object({
|
|
6
|
+
botId: z.string().min(1),
|
|
7
|
+
secret: z.string().min(1),
|
|
8
|
+
corpId: z.string().optional().default(''),
|
|
9
|
+
corpSecret: z.string().optional().default(''),
|
|
10
|
+
agentId: z.number().int().positive().optional().default(1000001),
|
|
11
|
+
token: z.string().optional().default(''),
|
|
12
|
+
encodingAESKey: z.string().optional().default(''),
|
|
13
|
+
opencodeUrl: z.string().url().default('http://localhost:19876'),
|
|
14
|
+
streaming: z.boolean().default(true),
|
|
15
|
+
requireMention: z.boolean().default(true),
|
|
16
|
+
groupPolicy: z.enum(['open', 'allowlist', 'disabled']).default('allowlist'),
|
|
17
|
+
allowlist: z.array(z.string()).optional(),
|
|
18
|
+
showProcess: z.enum(['none', 'tools', 'thinking', 'full']).default('tools'),
|
|
19
|
+
autoApprove: z.boolean().default(false),
|
|
20
|
+
scene: z.number().int().optional(),
|
|
21
|
+
plugVersion: z.string().optional(),
|
|
22
|
+
});
|
|
23
|
+
const DEFAULT_CONFIG_PATH = join(homedir(), '.config', 'opencode', 'qiwei.json');
|
|
24
|
+
export class ConfigManager {
|
|
25
|
+
configPath;
|
|
26
|
+
constructor(configPath) {
|
|
27
|
+
this.configPath = configPath || DEFAULT_CONFIG_PATH;
|
|
28
|
+
}
|
|
29
|
+
load() {
|
|
30
|
+
const dir = dirname(this.configPath);
|
|
31
|
+
if (!existsSync(dir))
|
|
32
|
+
mkdirSync(dir, { recursive: true });
|
|
33
|
+
if (!existsSync(this.configPath)) {
|
|
34
|
+
throw new Error(`配置文件不存在: ${this.configPath}\n请先配置企业微信凭证`);
|
|
35
|
+
}
|
|
36
|
+
const raw = JSON.parse(readFileSync(this.configPath, 'utf-8'));
|
|
37
|
+
return WecomConfigSchema.parse(raw);
|
|
38
|
+
}
|
|
39
|
+
save(config) {
|
|
40
|
+
const dir = dirname(this.configPath);
|
|
41
|
+
if (!existsSync(dir))
|
|
42
|
+
mkdirSync(dir, { recursive: true });
|
|
43
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export { WecomConfigSchema };
|
|
47
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 群聊策略处理
|
|
3
|
+
*/
|
|
4
|
+
export declare class GroupPolicy {
|
|
5
|
+
/**
|
|
6
|
+
* 检查是否需要 @ 机器人才回复
|
|
7
|
+
* 企微文本消息中,@ 机器人会以 @机器人名 的形式出现在内容开头
|
|
8
|
+
* 群聊消息中如果不 @ 机器人,则不回复
|
|
9
|
+
*/
|
|
10
|
+
static shouldReply(text: string, chatType: 'single' | 'group', requireMention: boolean, botName?: string): boolean;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=group-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group-policy.d.ts","sourceRoot":"","sources":["../../src/core/group-policy.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,qBAAa,WAAW;IACtB;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,GAAG,OAAO,EAC5B,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO;CAeX"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 群聊策略处理
|
|
3
|
+
*/
|
|
4
|
+
export class GroupPolicy {
|
|
5
|
+
/**
|
|
6
|
+
* 检查是否需要 @ 机器人才回复
|
|
7
|
+
* 企微文本消息中,@ 机器人会以 @机器人名 的形式出现在内容开头
|
|
8
|
+
* 群聊消息中如果不 @ 机器人,则不回复
|
|
9
|
+
*/
|
|
10
|
+
static shouldReply(text, chatType, requireMention, botName) {
|
|
11
|
+
// 单聊永远回复
|
|
12
|
+
if (chatType === 'single')
|
|
13
|
+
return true;
|
|
14
|
+
// 群聊:不需要 @ 则直接回复
|
|
15
|
+
if (!requireMention)
|
|
16
|
+
return true;
|
|
17
|
+
// 群聊需要 @ — 检查是否 @ 了机器人
|
|
18
|
+
if (botName && text.includes(`@${botName}`))
|
|
19
|
+
return true;
|
|
20
|
+
// 通用 @ 检测
|
|
21
|
+
if (text.startsWith('@') || text.includes('@'))
|
|
22
|
+
return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=group-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group-policy.js","sourceRoot":"","sources":["../../src/core/group-policy.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,OAAO,WAAW;IACtB;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAChB,IAAY,EACZ,QAA4B,EAC5B,cAAuB,EACvB,OAAgB;QAEhB,SAAS;QACT,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAEvC,iBAAiB;QACjB,IAAI,CAAC,cAAc;YAAE,OAAO,IAAI,CAAC;QAEjC,uBAAuB;QACvB,IAAI,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,OAAO,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QAEzD,UAAU;QACV,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAE5D,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** 轻量日志工具 */
|
|
2
|
+
export declare function createLogger(module: string): {
|
|
3
|
+
info(msg: string, data?: any): void;
|
|
4
|
+
warn(msg: string, data?: any): void;
|
|
5
|
+
error(msg: string, data?: any): void;
|
|
6
|
+
debug(msg: string, data?: any): void;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;cAG7B,MAAM,SAAS,GAAG;cAGlB,MAAM,SAAS,GAAG;eAGjB,MAAM,SAAS,GAAG;eAGlB,MAAM,SAAS,GAAG;EAIhC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** 轻量日志工具 */
|
|
2
|
+
export function createLogger(module) {
|
|
3
|
+
const prefix = `[${module}]`;
|
|
4
|
+
return {
|
|
5
|
+
info(msg, data) {
|
|
6
|
+
console.log(`${prefix} ${msg}`, data ? JSON.stringify(data).slice(0, 200) : '');
|
|
7
|
+
},
|
|
8
|
+
warn(msg, data) {
|
|
9
|
+
console.warn(`${prefix} ⚠️ ${msg}`, data ? JSON.stringify(data).slice(0, 200) : '');
|
|
10
|
+
},
|
|
11
|
+
error(msg, data) {
|
|
12
|
+
console.error(`${prefix} ❌ ${msg}`, data ? JSON.stringify(data).slice(0, 200) : '');
|
|
13
|
+
},
|
|
14
|
+
debug(msg, data) {
|
|
15
|
+
if (process.env.DEBUG)
|
|
16
|
+
console.log(`${prefix} 🔍 ${msg}`, data ? JSON.stringify(data).slice(0, 200) : '');
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,MAAM,GAAG,IAAI,MAAM,GAAG,CAAC;IAC7B,OAAO;QACL,IAAI,CAAC,GAAW,EAAE,IAAU;YAC1B,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,CAAC,GAAW,EAAE,IAAU;YAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,QAAQ,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,KAAK,CAAC,GAAW,EAAE,IAAU;YAC3B,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,KAAK,CAAC,GAAW,EAAE,IAAU;YAC3B,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,OAAO,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5G,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 企微媒体文件处理
|
|
3
|
+
* 下载并解密企微发送的图片/文件/语音/视频
|
|
4
|
+
*/
|
|
5
|
+
import type { WSClient } from '@wecom/aibot-node-sdk';
|
|
6
|
+
export interface DownloadedMedia {
|
|
7
|
+
buffer: Buffer;
|
|
8
|
+
filename: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class MediaHandler {
|
|
12
|
+
/**
|
|
13
|
+
* 从企微消息中下载并解密文件
|
|
14
|
+
* @param url - 加密的文件 URL(5分钟有效)
|
|
15
|
+
* @param aesKey - Base64 编码的 AES 解密密钥
|
|
16
|
+
*/
|
|
17
|
+
static download(wsClient: WSClient, url: string, aesKey?: string): Promise<DownloadedMedia | null>;
|
|
18
|
+
/** 上传文件获取 media_id(用于主动发送) */
|
|
19
|
+
static upload(wsClient: WSClient, fileBuffer: Buffer, type: 'image' | 'file' | 'voice' | 'video', filename: string): Promise<string | null>;
|
|
20
|
+
private static guessMimeType;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=media-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-handler.d.ts","sourceRoot":"","sources":["../../src/core/media-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAKtD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,YAAY;IACvB;;;;OAIG;WACU,QAAQ,CACnB,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAclC,8BAA8B;WACjB,MAAM,CACjB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,EAC1C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUzB,OAAO,CAAC,MAAM,CAAC,aAAa;CAY7B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 企微媒体文件处理
|
|
3
|
+
* 下载并解密企微发送的图片/文件/语音/视频
|
|
4
|
+
*/
|
|
5
|
+
import { createLogger } from './logger.js';
|
|
6
|
+
const log = createLogger('MediaHandler');
|
|
7
|
+
export class MediaHandler {
|
|
8
|
+
/**
|
|
9
|
+
* 从企微消息中下载并解密文件
|
|
10
|
+
* @param url - 加密的文件 URL(5分钟有效)
|
|
11
|
+
* @param aesKey - Base64 编码的 AES 解密密钥
|
|
12
|
+
*/
|
|
13
|
+
static async download(wsClient, url, aesKey) {
|
|
14
|
+
try {
|
|
15
|
+
const result = await wsClient.downloadFile(url, aesKey);
|
|
16
|
+
return {
|
|
17
|
+
buffer: result.buffer,
|
|
18
|
+
filename: result.filename || 'download',
|
|
19
|
+
mimeType: MediaHandler.guessMimeType(result.filename || ''),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
log.error('文件下载失败', { err });
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** 上传文件获取 media_id(用于主动发送) */
|
|
28
|
+
static async upload(wsClient, fileBuffer, type, filename) {
|
|
29
|
+
try {
|
|
30
|
+
const result = await wsClient.uploadMedia(fileBuffer, { type, filename });
|
|
31
|
+
return result.media_id;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
log.error('文件上传失败', { err });
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
static guessMimeType(filename) {
|
|
39
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
40
|
+
const map = {
|
|
41
|
+
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
|
|
42
|
+
gif: 'image/gif', webp: 'image/webp', bmp: 'image/bmp',
|
|
43
|
+
mp4: 'video/mp4', mov: 'video/quicktime',
|
|
44
|
+
mp3: 'audio/mpeg', wav: 'audio/wav', amr: 'audio/amr',
|
|
45
|
+
pdf: 'application/pdf', doc: 'application/msword',
|
|
46
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
47
|
+
};
|
|
48
|
+
return map[ext] || 'application/octet-stream';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=media-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-handler.js","sourceRoot":"","sources":["../../src/core/media-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,GAAG,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;AAQzC,MAAM,OAAO,YAAY;IACvB;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,CACnB,QAAkB,EAClB,GAAW,EACX,MAAe;QAEf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACxD,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,UAAU;gBACvC,QAAQ,EAAE,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;aAC5D,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,MAAM,CAAC,KAAK,CAAC,MAAM,CACjB,QAAkB,EAClB,UAAkB,EAClB,IAA0C,EAC1C,QAAgB;QAEhB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC1E,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,aAAa,CAAC,QAAgB;QAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC3D,MAAM,GAAG,GAA2B;YAClC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW;YACvD,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW;YACtD,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,iBAAiB;YACxC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW;YACrD,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,oBAAoB;YACjD,IAAI,EAAE,yEAAyE;SAChF,CAAC;QACF,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;IAChD,CAAC;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息处理器 — 企微消息 ↔ OpenCode 流式回复
|
|
3
|
+
*/
|
|
4
|
+
import type { WsFrame, TextMessage, ImageMessage, MixedMessage, WSClient } from '@wecom/aibot-node-sdk';
|
|
5
|
+
import type { WecomConfig } from './types.js';
|
|
6
|
+
import { SessionManager } from './session-manager.js';
|
|
7
|
+
import { OpenCodeClient } from '../opencode/client.js';
|
|
8
|
+
export declare class MessageHandler {
|
|
9
|
+
private config;
|
|
10
|
+
private sessionManager;
|
|
11
|
+
private wsClient;
|
|
12
|
+
private opencode;
|
|
13
|
+
/** 挂起的流式回调:sessionId → { frame, streamId, lastContent } */
|
|
14
|
+
private streams;
|
|
15
|
+
constructor(config: WecomConfig, sessionManager: SessionManager, wsClient: WSClient, opencode: OpenCodeClient);
|
|
16
|
+
/** 处理文本消息 */
|
|
17
|
+
handleMessage(frame: WsFrame<TextMessage>): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* 尝试将用户消息解析为挂起交互的回复。
|
|
20
|
+
* 返回 true 表示已处理。
|
|
21
|
+
*/
|
|
22
|
+
private handleInteractionReply;
|
|
23
|
+
/** 通知用户有挂起的交互(由 EventHandler 调用) */
|
|
24
|
+
notifyPending(chatId: string, message: string): Promise<void>;
|
|
25
|
+
/** 流式推送文本增量到企微 */
|
|
26
|
+
pushStreamDelta(sessionId: string, delta: string): Promise<void>;
|
|
27
|
+
/** 流式结束 */
|
|
28
|
+
finishStream(sessionId: string): Promise<void>;
|
|
29
|
+
/** 处理图片消息 */
|
|
30
|
+
handleImageMessage(frame: WsFrame<ImageMessage>): Promise<void>;
|
|
31
|
+
/** 处理图文混排消息 */
|
|
32
|
+
handleMixedMessage(frame: WsFrame<MixedMessage>): Promise<void>;
|
|
33
|
+
/** 进入会话事件 */
|
|
34
|
+
handleEnterChat(frame: WsFrame<any>): Promise<void>;
|
|
35
|
+
/** 模板卡片事件 */
|
|
36
|
+
handleCardEvent(frame: WsFrame<any>): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=message-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-handler.d.ts","sourceRoot":"","sources":["../../src/core/message-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACxG,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAKvD,qBAAa,cAAc;IAKvB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ;IAPlB,2DAA2D;IAC3D,OAAO,CAAC,OAAO,CAAqF;gBAG1F,MAAM,EAAE,WAAW,EACnB,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,cAAc;IAGlC,aAAa;IACP,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC;IAgE/C;;;OAGG;YACW,sBAAsB;IAoGpC,oCAAoC;IAC9B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBnE,kBAAkB;IACZ,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAkBtD,WAAW;IACL,YAAY,CAAC,SAAS,EAAE,MAAM;IAiBpC,aAAa;IACP,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC;IASrD,eAAe;IACT,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC;IAOrD,aAAa;IACP,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC;IAWzC,aAAa;IACP,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC;CAI1C"}
|