mozi-bot 1.1.1 → 1.2.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.
Files changed (50) hide show
  1. package/README.md +340 -219
  2. package/dist/agents/agent.d.ts.map +1 -1
  3. package/dist/agents/agent.js +28 -22
  4. package/dist/agents/agent.js.map +1 -1
  5. package/dist/channels/index.d.ts +4 -0
  6. package/dist/channels/index.d.ts.map +1 -1
  7. package/dist/channels/index.js +4 -0
  8. package/dist/channels/index.js.map +1 -1
  9. package/dist/channels/qq/api.d.ts +37 -0
  10. package/dist/channels/qq/api.d.ts.map +1 -0
  11. package/dist/channels/qq/api.js +145 -0
  12. package/dist/channels/qq/api.js.map +1 -0
  13. package/dist/channels/qq/index.d.ts +36 -0
  14. package/dist/channels/qq/index.d.ts.map +1 -0
  15. package/dist/channels/qq/index.js +145 -0
  16. package/dist/channels/qq/index.js.map +1 -0
  17. package/dist/channels/qq/websocket.d.ts +67 -0
  18. package/dist/channels/qq/websocket.d.ts.map +1 -0
  19. package/dist/channels/qq/websocket.js +372 -0
  20. package/dist/channels/qq/websocket.js.map +1 -0
  21. package/dist/channels/wecom/api.d.ts +30 -0
  22. package/dist/channels/wecom/api.d.ts.map +1 -0
  23. package/dist/channels/wecom/api.js +98 -0
  24. package/dist/channels/wecom/api.js.map +1 -0
  25. package/dist/channels/wecom/crypto.d.ts +25 -0
  26. package/dist/channels/wecom/crypto.d.ts.map +1 -0
  27. package/dist/channels/wecom/crypto.js +74 -0
  28. package/dist/channels/wecom/crypto.js.map +1 -0
  29. package/dist/channels/wecom/events.d.ts +34 -0
  30. package/dist/channels/wecom/events.d.ts.map +1 -0
  31. package/dist/channels/wecom/events.js +127 -0
  32. package/dist/channels/wecom/events.js.map +1 -0
  33. package/dist/channels/wecom/index.d.ts +39 -0
  34. package/dist/channels/wecom/index.d.ts.map +1 -0
  35. package/dist/channels/wecom/index.js +184 -0
  36. package/dist/channels/wecom/index.js.map +1 -0
  37. package/dist/cli/index.js +21 -6
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/config/index.d.ts +94 -0
  40. package/dist/config/index.d.ts.map +1 -1
  41. package/dist/config/index.js +49 -2
  42. package/dist/config/index.js.map +1 -1
  43. package/dist/gateway/server.d.ts +2 -0
  44. package/dist/gateway/server.d.ts.map +1 -1
  45. package/dist/gateway/server.js +47 -0
  46. package/dist/gateway/server.js.map +1 -1
  47. package/dist/types/index.d.ts +20 -1
  48. package/dist/types/index.d.ts.map +1 -1
  49. package/dist/types/index.js.map +1 -1
  50. package/package.json +4 -2
@@ -0,0 +1,74 @@
1
+ /**
2
+ * 企业微信消息加解密工具
3
+ * 实现 AES-256-CBC 加解密和签名验证
4
+ */
5
+ import crypto from "crypto";
6
+ export class WeComCrypto {
7
+ token;
8
+ encodingAESKey;
9
+ corpId;
10
+ aesKey;
11
+ constructor(token, encodingAESKey, corpId) {
12
+ this.token = token;
13
+ this.encodingAESKey = encodingAESKey;
14
+ this.corpId = corpId;
15
+ this.aesKey = Buffer.from(encodingAESKey + "=", "base64");
16
+ }
17
+ /** 验证签名 */
18
+ verifySignature(msgSignature, timestamp, nonce, echostr) {
19
+ const signature = this.getSignature(timestamp, nonce, echostr);
20
+ return signature === msgSignature;
21
+ }
22
+ /** 生成签名 */
23
+ getSignature(timestamp, nonce, encryptedMsg) {
24
+ const arr = [this.token, timestamp, nonce, encryptedMsg].sort();
25
+ const sha1 = crypto.createHash("sha1");
26
+ sha1.update(arr.join(""));
27
+ return sha1.digest("hex");
28
+ }
29
+ /** 解密消息 */
30
+ decrypt(encrypted) {
31
+ const iv = this.aesKey.subarray(0, 16);
32
+ const decipher = crypto.createDecipheriv("aes-256-cbc", this.aesKey, iv);
33
+ decipher.setAutoPadding(false);
34
+ let decrypted = Buffer.concat([
35
+ decipher.update(encrypted, "base64"),
36
+ decipher.final(),
37
+ ]);
38
+ // PKCS#7 去填充
39
+ const pad = decrypted[decrypted.length - 1] ?? 0;
40
+ decrypted = decrypted.subarray(0, decrypted.length - pad);
41
+ // 去掉前 16 字节随机数
42
+ const content = decrypted.subarray(16);
43
+ // 读取消息长度 (4 字节 big-endian)
44
+ const msgLen = content.readUInt32BE(0);
45
+ const message = content.subarray(4, 4 + msgLen).toString("utf-8");
46
+ const corpId = content.subarray(4 + msgLen).toString("utf-8");
47
+ return { message, corpId };
48
+ }
49
+ /** 加密消息 */
50
+ encrypt(message) {
51
+ const randomBytes = crypto.randomBytes(16);
52
+ const msgBuf = Buffer.from(message, "utf-8");
53
+ const msgLenBuf = Buffer.alloc(4);
54
+ msgLenBuf.writeUInt32BE(msgBuf.length, 0);
55
+ const corpIdBuf = Buffer.from(this.corpId, "utf-8");
56
+ let plainBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, corpIdBuf]);
57
+ // PKCS#7 填充
58
+ const blockSize = 32;
59
+ const padLen = blockSize - (plainBuf.length % blockSize);
60
+ const padBuf = Buffer.alloc(padLen, padLen);
61
+ plainBuf = Buffer.concat([plainBuf, padBuf]);
62
+ const iv = this.aesKey.subarray(0, 16);
63
+ const cipher = crypto.createCipheriv("aes-256-cbc", this.aesKey, iv);
64
+ cipher.setAutoPadding(false);
65
+ const encrypted = Buffer.concat([cipher.update(plainBuf), cipher.final()]);
66
+ return encrypted.toString("base64");
67
+ }
68
+ /** 解密回调验证的 echostr */
69
+ decryptEchoStr(echostr) {
70
+ const { message } = this.decrypt(echostr);
71
+ return message;
72
+ }
73
+ }
74
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/channels/wecom/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 企业微信事件处理
3
+ * 处理回调消息和事件
4
+ */
5
+ import type { InboundMessageContext, WeComConfig } from "../../types/index.js";
6
+ /** 企业微信回调消息 */
7
+ export interface WeComCallbackMessage {
8
+ ToUserName: string;
9
+ FromUserName: string;
10
+ CreateTime: number;
11
+ MsgType: string;
12
+ Content?: string;
13
+ MsgId?: string;
14
+ AgentID?: number;
15
+ Event?: string;
16
+ EventKey?: string;
17
+ ChatId?: string;
18
+ }
19
+ export declare class WeComEventHandler {
20
+ private config;
21
+ private crypto;
22
+ constructor(config: WeComConfig);
23
+ /** 验证回调 URL */
24
+ verifyUrl(msgSignature: string, timestamp: string, nonce: string, echostr: string): string | null;
25
+ /** 解析回调消息 */
26
+ parseMessage(msgSignature: string, timestamp: string, nonce: string, encryptedXml: string): WeComCallbackMessage | null;
27
+ /** 解析 XML 为消息对象 */
28
+ private parseXml;
29
+ /** 转换为统一消息上下文 */
30
+ convertToMessageContext(message: WeComCallbackMessage): InboundMessageContext | null;
31
+ /** 生成回复消息 XML */
32
+ generateReplyXml(toUser: string, fromUser: string, content: string, timestamp: number): string;
33
+ }
34
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../src/channels/wecom/events.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAM/E,eAAe;AACf,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,WAAW;IAO/B,eAAe;IACf,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAcjG,aAAa;IACb,YAAY,CACV,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GACnB,oBAAoB,GAAG,IAAI;IAgC9B,mBAAmB;IACnB,OAAO,CAAC,QAAQ;IAsBhB,iBAAiB;IACjB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,GAAG,qBAAqB,GAAG,IAAI;IA6BpF,iBAAiB;IACjB,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,MAAM;CAwBV"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * 企业微信事件处理
3
+ * 处理回调消息和事件
4
+ */
5
+ import { WeComCrypto } from "./crypto.js";
6
+ import { getChildLogger } from "../../utils/logger.js";
7
+ const logger = getChildLogger("wecom-events");
8
+ export class WeComEventHandler {
9
+ config;
10
+ crypto = null;
11
+ constructor(config) {
12
+ this.config = config;
13
+ if (config.token && config.encodingAESKey) {
14
+ this.crypto = new WeComCrypto(config.token, config.encodingAESKey, config.corpId);
15
+ }
16
+ }
17
+ /** 验证回调 URL */
18
+ verifyUrl(msgSignature, timestamp, nonce, echostr) {
19
+ if (!this.crypto) {
20
+ logger.warn("Crypto not configured, skipping verification");
21
+ return null;
22
+ }
23
+ if (!this.crypto.verifySignature(msgSignature, timestamp, nonce, echostr)) {
24
+ logger.warn("Invalid signature for URL verification");
25
+ return null;
26
+ }
27
+ return this.crypto.decryptEchoStr(echostr);
28
+ }
29
+ /** 解析回调消息 */
30
+ parseMessage(msgSignature, timestamp, nonce, encryptedXml) {
31
+ if (!this.crypto) {
32
+ logger.warn("Crypto not configured, cannot parse message");
33
+ return null;
34
+ }
35
+ // 从 XML 中提取加密内容
36
+ const encryptMatch = encryptedXml.match(/<Encrypt><!\[CDATA\[(.*?)\]\]><\/Encrypt>/);
37
+ if (!encryptMatch) {
38
+ logger.warn("No Encrypt field in XML");
39
+ return null;
40
+ }
41
+ const encrypted = encryptMatch[1];
42
+ if (!encrypted) {
43
+ logger.warn("Empty Encrypt field in XML");
44
+ return null;
45
+ }
46
+ // 验证签名
47
+ if (!this.crypto.verifySignature(msgSignature, timestamp, nonce, encrypted)) {
48
+ logger.warn("Invalid signature for message");
49
+ return null;
50
+ }
51
+ // 解密消息
52
+ const { message } = this.crypto.decrypt(encrypted);
53
+ // 解析 XML
54
+ return this.parseXml(message);
55
+ }
56
+ /** 解析 XML 为消息对象 */
57
+ parseXml(xml) {
58
+ const getValue = (tag) => {
59
+ const match = xml.match(new RegExp(`<${tag}><!\[CDATA\[(.*?)\]\]><\/${tag}>`));
60
+ if (match)
61
+ return match[1];
62
+ const numMatch = xml.match(new RegExp(`<${tag}>(\\d+)<\/${tag}>`));
63
+ return numMatch ? numMatch[1] : undefined;
64
+ };
65
+ return {
66
+ ToUserName: getValue("ToUserName") || "",
67
+ FromUserName: getValue("FromUserName") || "",
68
+ CreateTime: parseInt(getValue("CreateTime") || "0", 10),
69
+ MsgType: getValue("MsgType") || "",
70
+ Content: getValue("Content"),
71
+ MsgId: getValue("MsgId"),
72
+ AgentID: getValue("AgentID") ? parseInt(getValue("AgentID"), 10) : undefined,
73
+ Event: getValue("Event"),
74
+ EventKey: getValue("EventKey"),
75
+ ChatId: getValue("ChatId"),
76
+ };
77
+ }
78
+ /** 转换为统一消息上下文 */
79
+ convertToMessageContext(message) {
80
+ // 只处理文本消息
81
+ if (message.MsgType !== "text") {
82
+ logger.debug({ msgType: message.MsgType }, "Ignoring non-text message");
83
+ return null;
84
+ }
85
+ if (!message.Content) {
86
+ logger.debug("Empty content, ignoring");
87
+ return null;
88
+ }
89
+ // 判断是私聊还是群聊
90
+ const chatType = message.ChatId ? "group" : "direct";
91
+ const chatId = message.ChatId || `direct:${message.FromUserName}`;
92
+ return {
93
+ channelId: "wecom",
94
+ messageId: message.MsgId || `${message.CreateTime}`,
95
+ chatId,
96
+ chatType,
97
+ senderId: message.FromUserName,
98
+ senderName: undefined, // 需要额外调用 API 获取用户名
99
+ content: message.Content,
100
+ timestamp: message.CreateTime * 1000,
101
+ raw: message,
102
+ };
103
+ }
104
+ /** 生成回复消息 XML */
105
+ generateReplyXml(toUser, fromUser, content, timestamp) {
106
+ const xml = `<xml>
107
+ <ToUserName><![CDATA[${toUser}]]></ToUserName>
108
+ <FromUserName><![CDATA[${fromUser}]]></FromUserName>
109
+ <CreateTime>${timestamp}</CreateTime>
110
+ <MsgType><![CDATA[text]]></MsgType>
111
+ <Content><![CDATA[${content}]]></Content>
112
+ </xml>`;
113
+ if (this.crypto) {
114
+ const encrypted = this.crypto.encrypt(xml);
115
+ const nonce = Math.random().toString(36).substring(2, 15);
116
+ const signature = this.crypto.getSignature(String(timestamp), nonce, encrypted);
117
+ return `<xml>
118
+ <Encrypt><![CDATA[${encrypted}]]></Encrypt>
119
+ <MsgSignature><![CDATA[${signature}]]></MsgSignature>
120
+ <TimeStamp>${timestamp}</TimeStamp>
121
+ <Nonce><![CDATA[${nonce}]]></Nonce>
122
+ </xml>`;
123
+ }
124
+ return xml;
125
+ }
126
+ }
127
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/channels/wecom/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 企业微信通道适配器
3
+ *
4
+ * 使用 HTTP 回调模式,需要公网部署
5
+ */
6
+ import { Router, type Request, type Response } from "express";
7
+ import type { WeComConfig, ChannelMeta, OutboundMessage, SendResult } from "../../types/index.js";
8
+ import { BaseChannelAdapter } from "../common/base.js";
9
+ import { WeComApiClient } from "./api.js";
10
+ export declare class WeComChannel extends BaseChannelAdapter {
11
+ readonly id: "wecom";
12
+ readonly meta: ChannelMeta;
13
+ private config;
14
+ private apiClient;
15
+ private eventHandler;
16
+ private initialized;
17
+ constructor(config: WeComConfig);
18
+ /** 初始化通道 */
19
+ initialize(): Promise<void>;
20
+ /** 关闭通道 */
21
+ shutdown(): Promise<void>;
22
+ /** 发送消息 */
23
+ sendMessage(message: OutboundMessage): Promise<SendResult>;
24
+ /** 发送文本消息 */
25
+ sendText(chatId: string, text: string, _replyToId?: string): Promise<SendResult>;
26
+ /** 检查通道状态 */
27
+ isHealthy(): Promise<boolean>;
28
+ /** 创建 Express 路由处理器 */
29
+ createRouter(): Router;
30
+ /** 处理 URL 验证 */
31
+ private handleUrlVerification;
32
+ /** 处理 Webhook 请求 */
33
+ handleWebhook(req: Request, res: Response): Promise<void>;
34
+ /** 获取 API 客户端 */
35
+ getApiClient(): WeComApiClient;
36
+ }
37
+ /** 创建企业微信通道 */
38
+ export declare function createWeComChannel(config: WeComConfig): WeComChannel;
39
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/wecom/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAqB1C,qBAAa,YAAa,SAAQ,kBAAkB;IAClD,QAAQ,CAAC,EAAE,EAAG,OAAO,CAAU;IAC/B,QAAQ,CAAC,IAAI,cAAc;IAE3B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,WAAW;IAQ/B,YAAY;IACN,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BjC,WAAW;IACL,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B,WAAW;IACL,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IAsBhE,aAAa;IACP,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAItF,aAAa;IACP,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IASnC,uBAAuB;IACvB,YAAY,IAAI,MAAM;IAmBtB,gBAAgB;IAChB,OAAO,CAAC,qBAAqB;IAyB7B,oBAAoB;IACd,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA8C/D,iBAAiB;IACjB,YAAY,IAAI,cAAc;CAG/B;AAED,eAAe;AACf,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAEpE"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * 企业微信通道适配器
3
+ *
4
+ * 使用 HTTP 回调模式,需要公网部署
5
+ */
6
+ import { Router } from "express";
7
+ import { BaseChannelAdapter } from "../common/base.js";
8
+ import { WeComApiClient } from "./api.js";
9
+ import { WeComEventHandler } from "./events.js";
10
+ import { getChildLogger } from "../../utils/logger.js";
11
+ /** 企业微信通道元数据 */
12
+ const WECOM_META = {
13
+ id: "wecom",
14
+ name: "企业微信",
15
+ description: "企业微信自建应用机器人",
16
+ capabilities: {
17
+ chatTypes: ["direct", "group"],
18
+ supportsMedia: true,
19
+ supportsReply: false, // 企业微信不支持回复特定消息
20
+ supportsMention: true,
21
+ supportsReaction: false,
22
+ supportsThread: false,
23
+ supportsEdit: false,
24
+ maxMessageLength: 2048,
25
+ },
26
+ };
27
+ export class WeComChannel extends BaseChannelAdapter {
28
+ id = "wecom";
29
+ meta = WECOM_META;
30
+ config;
31
+ apiClient;
32
+ eventHandler;
33
+ initialized = false;
34
+ constructor(config) {
35
+ super();
36
+ this.config = config;
37
+ this.apiClient = new WeComApiClient(config);
38
+ this.eventHandler = new WeComEventHandler(config);
39
+ this.logger = getChildLogger("wecom");
40
+ }
41
+ /** 初始化通道 */
42
+ async initialize() {
43
+ if (this.initialized)
44
+ return;
45
+ this.logger.info("Initializing WeCom channel");
46
+ // 验证配置
47
+ if (!this.config.corpId || !this.config.corpSecret) {
48
+ throw new Error("WeCom corpId and corpSecret are required");
49
+ }
50
+ if (!this.config.agentId) {
51
+ throw new Error("WeCom agentId is required");
52
+ }
53
+ // 测试获取 token
54
+ try {
55
+ await this.apiClient.getAccessToken();
56
+ this.logger.info("Successfully authenticated with WeCom");
57
+ }
58
+ catch (error) {
59
+ this.logger.error({ error }, "Failed to authenticate with WeCom");
60
+ throw error;
61
+ }
62
+ this.initialized = true;
63
+ }
64
+ /** 关闭通道 */
65
+ async shutdown() {
66
+ this.logger.info("Shutting down WeCom channel");
67
+ this.initialized = false;
68
+ }
69
+ /** 发送消息 */
70
+ async sendMessage(message) {
71
+ try {
72
+ const { chatId, content } = message;
73
+ // 根据 chatId 前缀判断消息类型
74
+ if (chatId.startsWith("direct:")) {
75
+ // 私聊消息
76
+ const userId = chatId.replace("direct:", "");
77
+ await this.apiClient.sendTextMessage(userId, content);
78
+ }
79
+ else {
80
+ // 群聊消息
81
+ await this.apiClient.sendGroupTextMessage(chatId, content);
82
+ }
83
+ return { success: true };
84
+ }
85
+ catch (error) {
86
+ const errorMessage = error instanceof Error ? error.message : String(error);
87
+ this.logger.error({ error, message }, "Failed to send message");
88
+ return { success: false, error: errorMessage };
89
+ }
90
+ }
91
+ /** 发送文本消息 */
92
+ async sendText(chatId, text, _replyToId) {
93
+ return this.sendMessage({ chatId, content: text });
94
+ }
95
+ /** 检查通道状态 */
96
+ async isHealthy() {
97
+ try {
98
+ await this.apiClient.getAccessToken();
99
+ return true;
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ }
105
+ /** 创建 Express 路由处理器 */
106
+ createRouter() {
107
+ const router = Router();
108
+ // URL 验证 (GET 请求)
109
+ router.get("/webhook", (req, res) => {
110
+ this.handleUrlVerification(req, res);
111
+ });
112
+ // 消息回调 (POST 请求)
113
+ router.post("/webhook", (req, res) => {
114
+ this.handleWebhook(req, res).catch((error) => {
115
+ this.logger.error({ error }, "Webhook handler error");
116
+ res.status(500).send("Internal server error");
117
+ });
118
+ });
119
+ return router;
120
+ }
121
+ /** 处理 URL 验证 */
122
+ handleUrlVerification(req, res) {
123
+ const { msg_signature, timestamp, nonce, echostr } = req.query;
124
+ if (!msg_signature || !timestamp || !nonce || !echostr) {
125
+ this.logger.warn("Missing verification parameters");
126
+ res.status(400).send("Missing parameters");
127
+ return;
128
+ }
129
+ const decrypted = this.eventHandler.verifyUrl(msg_signature, timestamp, nonce, echostr);
130
+ if (decrypted) {
131
+ this.logger.info("URL verification successful");
132
+ res.send(decrypted);
133
+ }
134
+ else {
135
+ this.logger.warn("URL verification failed");
136
+ res.status(403).send("Verification failed");
137
+ }
138
+ }
139
+ /** 处理 Webhook 请求 */
140
+ async handleWebhook(req, res) {
141
+ const { msg_signature, timestamp, nonce } = req.query;
142
+ if (!msg_signature || !timestamp || !nonce) {
143
+ this.logger.warn("Missing webhook parameters");
144
+ res.status(400).send("Missing parameters");
145
+ return;
146
+ }
147
+ // 获取原始 XML 内容
148
+ const rawBody = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
149
+ try {
150
+ const message = this.eventHandler.parseMessage(msg_signature, timestamp, nonce, rawBody);
151
+ if (!message) {
152
+ // 返回空响应
153
+ res.send("success");
154
+ return;
155
+ }
156
+ this.logger.debug({ message }, "Received message");
157
+ // 转换并处理消息
158
+ const context = this.eventHandler.convertToMessageContext(message);
159
+ if (context) {
160
+ // 异步处理消息,先返回响应
161
+ res.send("success");
162
+ this.handleInboundMessage(context).catch((error) => {
163
+ this.logger.error({ error, context }, "Failed to handle message");
164
+ });
165
+ }
166
+ else {
167
+ res.send("success");
168
+ }
169
+ }
170
+ catch (error) {
171
+ this.logger.error({ error }, "Failed to parse message");
172
+ res.send("success"); // 企业微信要求返回成功,否则会重试
173
+ }
174
+ }
175
+ /** 获取 API 客户端 */
176
+ getApiClient() {
177
+ return this.apiClient;
178
+ }
179
+ }
180
+ /** 创建企业微信通道 */
181
+ export function createWeComChannel(config) {
182
+ return new WeComChannel(config);
183
+ }
184
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/channels/wecom/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAA+B,MAAM,SAAS,CAAC;AAO9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,gBAAgB;AAChB,MAAM,UAAU,GAAgB;IAC9B,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,aAAa;IAC1B,YAAY,EAAE;QACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC9B,aAAa,EAAE,IAAI;QACnB,aAAa,EAAE,KAAK,EAAE,gBAAgB;QACtC,eAAe,EAAE,IAAI;QACrB,gBAAgB,EAAE,KAAK;QACvB,cAAc,EAAE,KAAK;QACrB,YAAY,EAAE,KAAK;QACnB,gBAAgB,EAAE,IAAI;KACvB;CACF,CAAC;AAEF,MAAM,OAAO,YAAa,SAAQ,kBAAkB;IACzC,EAAE,GAAG,OAAgB,CAAC;IACtB,IAAI,GAAG,UAAU,CAAC;IAEnB,MAAM,CAAc;IACpB,SAAS,CAAiB;IAC1B,YAAY,CAAoB;IAChC,WAAW,GAAG,KAAK,CAAC;IAE5B,YAAY,MAAmB;QAC7B,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,YAAY,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,YAAY;IACZ,KAAK,CAAC,UAAU;QACd,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QAE7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAE/C,OAAO;QACP,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC/C,CAAC;QAED,aAAa;QACb,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC;YACtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;QAC5D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,mCAAmC,CAAC,CAAC;YAClE,MAAM,KAAK,CAAC;QACd,CAAC;QAED,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC1B,CAAC;IAED,WAAW;IACX,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAChD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;IAC3B,CAAC;IAED,WAAW;IACX,KAAK,CAAC,WAAW,CAAC,OAAwB;QACxC,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;YAEpC,qBAAqB;YACrB,IAAI,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACjC,OAAO;gBACP,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;gBAC7C,MAAM,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACxD,CAAC;iBAAM,CAAC;gBACN,OAAO;gBACP,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC7D,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,wBAAwB,CAAC,CAAC;YAChE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QACjD,CAAC;IACH,CAAC;IAED,aAAa;IACb,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,IAAY,EAAE,UAAmB;QAC9D,OAAO,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,aAAa;IACb,KAAK,CAAC,SAAS;QACb,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,YAAY;QACV,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;QAExB,kBAAkB;QAClB,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YACrD,IAAI,CAAC,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,iBAAiB;QACjB,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YACtD,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC3C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,uBAAuB,CAAC,CAAC;gBACtD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;YAChD,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,gBAAgB;IACR,qBAAqB,CAAC,GAAY,EAAE,GAAa;QACvD,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;QAE/D,IAAI,CAAC,aAAa,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;YACvD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;YACpD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,SAAS,CAC3C,aAAuB,EACvB,SAAmB,EACnB,KAAe,EACf,OAAiB,CAClB,CAAC;QAEF,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAChD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;YAC5C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,KAAK,CAAC,aAAa,CAAC,GAAY,EAAE,GAAa;QAC7C,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;QAEtD,IAAI,CAAC,aAAa,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;YAC/C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,cAAc;QACd,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEnF,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAC5C,aAAuB,EACvB,SAAmB,EACnB,KAAe,EACf,OAAO,CACR,CAAC;YAEF,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,QAAQ;gBACR,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACpB,OAAO;YACT,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,EAAE,kBAAkB,CAAC,CAAC;YAEnD,UAAU;YACV,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC;YACnE,IAAI,OAAO,EAAE,CAAC;gBACZ,eAAe;gBACf,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAEpB,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBACjD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,0BAA0B,CAAC,CAAC;gBACpE,CAAC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,yBAAyB,CAAC,CAAC;YACxD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB;QAC1C,CAAC;IACH,CAAC;IAED,iBAAiB;IACjB,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;CACF;AAED,eAAe;AACf,MAAM,UAAU,kBAAkB,CAAC,MAAmB;IACpD,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
package/dist/cli/index.js CHANGED
@@ -17,14 +17,14 @@ const program = new Command();
17
17
  program
18
18
  .name("mozi")
19
19
  .description("Mozi - 支持国产模型和国产通讯软件的智能助手机器人")
20
- .version("1.1.1");
20
+ .version("1.2.0");
21
21
  // 启动命令
22
22
  program
23
23
  .command("start")
24
24
  .description("启动 Gateway 服务器")
25
25
  .option("-c, --config <path>", "配置文件路径")
26
26
  .option("-p, --port <port>", "服务器端口")
27
- .option("--web-only", "仅启用 WebChat (不需要配置飞书/钉钉)")
27
+ .option("--web-only", "仅启用 WebChat (不需要配置飞书/钉钉/QQ)")
28
28
  .action(async (options) => {
29
29
  try {
30
30
  const config = loadConfig({ configPath: options.config });
@@ -105,6 +105,7 @@ program
105
105
  const channels = [
106
106
  { id: "feishu", name: "飞书", config: config.channels.feishu },
107
107
  { id: "dingtalk", name: "钉钉", config: config.channels.dingtalk },
108
+ { id: "qq", name: "QQ", config: config.channels.qq },
108
109
  ];
109
110
  for (const channel of channels) {
110
111
  const status = channel.config ? "✅ 已配置" : "⬜ 未配置";
@@ -413,7 +414,7 @@ program
413
414
  }
414
415
  // 步骤 2: 通道配置
415
416
  console.log("\n📱 步骤 2/4: 配置通讯平台\n");
416
- console.log("支持的平台: 飞书, 钉钉");
417
+ console.log("支持的平台: 飞书, 钉钉, QQ");
417
418
  console.log("(可选配置,直接回车跳过)\n");
418
419
  const configFeishu = await question("是否配置飞书? (y/n): ");
419
420
  if (configFeishu.toLowerCase() === "y") {
@@ -437,6 +438,20 @@ program
437
438
  };
438
439
  }
439
440
  }
441
+ const configQQ = await question("是否配置 QQ 机器人? (y/n): ");
442
+ if (configQQ.toLowerCase() === "y") {
443
+ console.log("\n提示: 需要在 QQ 开放平台添加服务器 IP 到白名单");
444
+ const qqAppId = await question("QQ App ID: ");
445
+ const qqClientSecret = await question("QQ Client Secret: ");
446
+ if (qqAppId.trim() && qqClientSecret.trim()) {
447
+ const qqSandbox = await question("是否使用沙箱环境? (y/n,默认 n): ");
448
+ config.channels["qq"] = {
449
+ appId: qqAppId.trim(),
450
+ clientSecret: qqClientSecret.trim(),
451
+ sandbox: qqSandbox.toLowerCase() === "y",
452
+ };
453
+ }
454
+ }
440
455
  // 步骤 3: 服务器配置
441
456
  console.log("\n🌐 步骤 3/4: 配置服务器\n");
442
457
  const port = await question("服务器端口 (默认 3000): ");
@@ -504,8 +519,8 @@ program
504
519
  const hasChannels = Object.keys(config.channels || {}).length > 0;
505
520
  const startCmd = hasChannels ? "mozi start" : "mozi start --web-only";
506
521
  const startNote = hasChannels
507
- ? " (已配置飞书/钉钉,将同时启动)"
508
- : " (仅 WebChat,如需飞书/钉钉请配置 channels)";
522
+ ? " (已配置通讯平台,将同时启动)"
523
+ : " (仅 WebChat,如需通讯平台请配置 channels)";
509
524
  console.log(`
510
525
  ╔════════════════════════════════════════════════════════════╗
511
526
  ║ ║
@@ -519,7 +534,7 @@ ${startNote.padEnd(61)}║
519
534
  ║ 3. 测试聊天: mozi chat ║
520
535
  ║ ║
521
536
  ║ 启动选项: ║
522
- ║ - mozi start 完整服务 (WebChat+飞书+钉钉)
537
+ ║ - mozi start 完整服务 (WebChat+飞书+钉钉+QQ)
523
538
  ║ - mozi start --web-only 仅 WebChat ║
524
539
  ║ ║
525
540
  ║ 配置文件: ~/.mozi/config.local.json5 ║