mozi-bot 1.1.2 → 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 (47) hide show
  1. package/README.md +251 -31
  2. package/dist/channels/index.d.ts +4 -0
  3. package/dist/channels/index.d.ts.map +1 -1
  4. package/dist/channels/index.js +4 -0
  5. package/dist/channels/index.js.map +1 -1
  6. package/dist/channels/qq/api.d.ts +37 -0
  7. package/dist/channels/qq/api.d.ts.map +1 -0
  8. package/dist/channels/qq/api.js +145 -0
  9. package/dist/channels/qq/api.js.map +1 -0
  10. package/dist/channels/qq/index.d.ts +36 -0
  11. package/dist/channels/qq/index.d.ts.map +1 -0
  12. package/dist/channels/qq/index.js +145 -0
  13. package/dist/channels/qq/index.js.map +1 -0
  14. package/dist/channels/qq/websocket.d.ts +67 -0
  15. package/dist/channels/qq/websocket.d.ts.map +1 -0
  16. package/dist/channels/qq/websocket.js +372 -0
  17. package/dist/channels/qq/websocket.js.map +1 -0
  18. package/dist/channels/wecom/api.d.ts +30 -0
  19. package/dist/channels/wecom/api.d.ts.map +1 -0
  20. package/dist/channels/wecom/api.js +98 -0
  21. package/dist/channels/wecom/api.js.map +1 -0
  22. package/dist/channels/wecom/crypto.d.ts +25 -0
  23. package/dist/channels/wecom/crypto.d.ts.map +1 -0
  24. package/dist/channels/wecom/crypto.js +74 -0
  25. package/dist/channels/wecom/crypto.js.map +1 -0
  26. package/dist/channels/wecom/events.d.ts +34 -0
  27. package/dist/channels/wecom/events.d.ts.map +1 -0
  28. package/dist/channels/wecom/events.js +127 -0
  29. package/dist/channels/wecom/events.js.map +1 -0
  30. package/dist/channels/wecom/index.d.ts +39 -0
  31. package/dist/channels/wecom/index.d.ts.map +1 -0
  32. package/dist/channels/wecom/index.js +184 -0
  33. package/dist/channels/wecom/index.js.map +1 -0
  34. package/dist/cli/index.js +21 -6
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/config/index.d.ts +94 -0
  37. package/dist/config/index.d.ts.map +1 -1
  38. package/dist/config/index.js +49 -2
  39. package/dist/config/index.js.map +1 -1
  40. package/dist/gateway/server.d.ts +2 -0
  41. package/dist/gateway/server.d.ts.map +1 -1
  42. package/dist/gateway/server.js +47 -0
  43. package/dist/gateway/server.js.map +1 -1
  44. package/dist/types/index.d.ts +20 -1
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/types/index.js.map +1 -1
  47. package/package.json +4 -2
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 企业微信 API 客户端
3
+ * 基于企业微信服务端 API
4
+ */
5
+ import axios from "axios";
6
+ import { getChildLogger } from "../../utils/logger.js";
7
+ const logger = getChildLogger("wecom-api");
8
+ /** 企业微信 API 基础地址 */
9
+ const WECOM_API_BASE = "https://qyapi.weixin.qq.com/cgi-bin";
10
+ export class WeComApiClient {
11
+ config;
12
+ client;
13
+ tokenCache = null;
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.client = axios.create({
17
+ baseURL: WECOM_API_BASE,
18
+ timeout: 30000,
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ },
22
+ });
23
+ }
24
+ /** 获取 Access Token */
25
+ async getAccessToken() {
26
+ // 检查缓存
27
+ if (this.tokenCache && this.tokenCache.expiresAt > Date.now() + 60000) {
28
+ return this.tokenCache.accessToken;
29
+ }
30
+ try {
31
+ const response = await this.client.get("/gettoken", {
32
+ params: {
33
+ corpid: this.config.corpId,
34
+ corpsecret: this.config.corpSecret,
35
+ },
36
+ });
37
+ const { access_token, expires_in, errcode, errmsg } = response.data;
38
+ if (errcode && errcode !== 0) {
39
+ throw new Error(`WeCom API error: ${errcode} - ${errmsg}`);
40
+ }
41
+ this.tokenCache = {
42
+ accessToken: access_token,
43
+ expiresAt: Date.now() + expires_in * 1000,
44
+ };
45
+ logger.debug("Access token refreshed");
46
+ return access_token;
47
+ }
48
+ catch (error) {
49
+ logger.error({ error }, "Failed to get access token");
50
+ throw error;
51
+ }
52
+ }
53
+ /** 发送应用消息 (文本) */
54
+ async sendTextMessage(userId, content) {
55
+ const token = await this.getAccessToken();
56
+ const data = {
57
+ touser: userId,
58
+ msgtype: "text",
59
+ agentid: this.config.agentId,
60
+ text: {
61
+ content,
62
+ },
63
+ };
64
+ const response = await this.client.post(`/message/send?access_token=${token}`, data);
65
+ if (response.data.errcode !== 0) {
66
+ throw new Error(`WeCom send error: ${response.data.errcode} - ${response.data.errmsg}`);
67
+ }
68
+ logger.debug({ userId, msgid: response.data.msgid }, "Text message sent");
69
+ return response.data;
70
+ }
71
+ /** 发送群聊消息 */
72
+ async sendGroupTextMessage(chatId, content) {
73
+ const token = await this.getAccessToken();
74
+ const data = {
75
+ chatid: chatId,
76
+ msgtype: "text",
77
+ text: {
78
+ content,
79
+ },
80
+ };
81
+ const response = await this.client.post(`/appchat/send?access_token=${token}`, data);
82
+ if (response.data.errcode !== 0) {
83
+ throw new Error(`WeCom group send error: ${response.data.errcode} - ${response.data.errmsg}`);
84
+ }
85
+ logger.debug({ chatId }, "Group text message sent");
86
+ return response.data;
87
+ }
88
+ /** 获取用户信息 */
89
+ async getUserInfo(userId) {
90
+ const token = await this.getAccessToken();
91
+ const response = await this.client.get(`/user/get?access_token=${token}&userid=${userId}`);
92
+ if (response.data.errcode !== 0) {
93
+ throw new Error(`WeCom get user error: ${response.data.errcode} - ${response.data.errmsg}`);
94
+ }
95
+ return response.data;
96
+ }
97
+ }
98
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/channels/wecom/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 企业微信消息加解密工具
3
+ * 实现 AES-256-CBC 加解密和签名验证
4
+ */
5
+ export declare class WeComCrypto {
6
+ private token;
7
+ private encodingAESKey;
8
+ private corpId;
9
+ private aesKey;
10
+ constructor(token: string, encodingAESKey: string, corpId: string);
11
+ /** 验证签名 */
12
+ verifySignature(msgSignature: string, timestamp: string, nonce: string, echostr: string): boolean;
13
+ /** 生成签名 */
14
+ getSignature(timestamp: string, nonce: string, encryptedMsg: string): string;
15
+ /** 解密消息 */
16
+ decrypt(encrypted: string): {
17
+ message: string;
18
+ corpId: string;
19
+ };
20
+ /** 加密消息 */
21
+ encrypt(message: string): string;
22
+ /** 解密回调验证的 echostr */
23
+ decryptEchoStr(echostr: string): string;
24
+ }
25
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/channels/wecom/crypto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;gBAEX,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAOjE,WAAW;IACX,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAKjG,WAAW;IACX,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAO5E,WAAW;IACX,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAyB/D,WAAW;IACX,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAuBhC,sBAAsB;IACtB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAIxC"}
@@ -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":"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"}