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.
- package/README.md +340 -219
- package/dist/agents/agent.d.ts.map +1 -1
- package/dist/agents/agent.js +28 -22
- package/dist/agents/agent.js.map +1 -1
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +4 -0
- package/dist/channels/index.js.map +1 -1
- package/dist/channels/qq/api.d.ts +37 -0
- package/dist/channels/qq/api.d.ts.map +1 -0
- package/dist/channels/qq/api.js +145 -0
- package/dist/channels/qq/api.js.map +1 -0
- package/dist/channels/qq/index.d.ts +36 -0
- package/dist/channels/qq/index.d.ts.map +1 -0
- package/dist/channels/qq/index.js +145 -0
- package/dist/channels/qq/index.js.map +1 -0
- package/dist/channels/qq/websocket.d.ts +67 -0
- package/dist/channels/qq/websocket.d.ts.map +1 -0
- package/dist/channels/qq/websocket.js +372 -0
- package/dist/channels/qq/websocket.js.map +1 -0
- package/dist/channels/wecom/api.d.ts +30 -0
- package/dist/channels/wecom/api.d.ts.map +1 -0
- package/dist/channels/wecom/api.js +98 -0
- package/dist/channels/wecom/api.js.map +1 -0
- package/dist/channels/wecom/crypto.d.ts +25 -0
- package/dist/channels/wecom/crypto.d.ts.map +1 -0
- package/dist/channels/wecom/crypto.js +74 -0
- package/dist/channels/wecom/crypto.js.map +1 -0
- package/dist/channels/wecom/events.d.ts +34 -0
- package/dist/channels/wecom/events.d.ts.map +1 -0
- package/dist/channels/wecom/events.js +127 -0
- package/dist/channels/wecom/events.js.map +1 -0
- package/dist/channels/wecom/index.d.ts +39 -0
- package/dist/channels/wecom/index.d.ts.map +1 -0
- package/dist/channels/wecom/index.js +184 -0
- package/dist/channels/wecom/index.js.map +1 -0
- package/dist/cli/index.js +21 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +94 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +49 -2
- package/dist/config/index.js.map +1 -1
- package/dist/gateway/server.d.ts +2 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +47 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/types/index.d.ts +20 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQ 机器人 WebSocket 客户端
|
|
3
|
+
* 实现与 QQ 机器人网关的长连接
|
|
4
|
+
*/
|
|
5
|
+
import type { QQConfig, InboundMessageContext } from "../../types/index.js";
|
|
6
|
+
/** 事件处理器类型 */
|
|
7
|
+
type EventHandler = (context: InboundMessageContext) => Promise<void>;
|
|
8
|
+
export declare class QQWebSocketClient {
|
|
9
|
+
private config;
|
|
10
|
+
private apiClient;
|
|
11
|
+
private ws;
|
|
12
|
+
private sessionId;
|
|
13
|
+
private sequence;
|
|
14
|
+
private heartbeatInterval;
|
|
15
|
+
private heartbeatTimer;
|
|
16
|
+
private reconnectAttempts;
|
|
17
|
+
private maxReconnectAttempts;
|
|
18
|
+
private eventHandler;
|
|
19
|
+
private isConnected;
|
|
20
|
+
constructor(config: QQConfig);
|
|
21
|
+
/** 设置事件处理器 */
|
|
22
|
+
setEventHandler(handler: EventHandler): void;
|
|
23
|
+
/** 启动连接 */
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
/** 连接到网关 */
|
|
26
|
+
private connect;
|
|
27
|
+
/** 处理 WebSocket 消息 */
|
|
28
|
+
private handlePayload;
|
|
29
|
+
/** 处理 Hello 消息 */
|
|
30
|
+
private handleHello;
|
|
31
|
+
/** 发送鉴权请求 */
|
|
32
|
+
private identify;
|
|
33
|
+
/** 恢复连接 */
|
|
34
|
+
private resume;
|
|
35
|
+
/** 获取订阅的事件 intents */
|
|
36
|
+
private getIntents;
|
|
37
|
+
/** 处理事件分发 */
|
|
38
|
+
private handleDispatch;
|
|
39
|
+
/** 处理频道消息 */
|
|
40
|
+
private handleChannelMessage;
|
|
41
|
+
/** 处理频道私信 */
|
|
42
|
+
private handleDirectMessage;
|
|
43
|
+
/** 处理 QQ 群消息 */
|
|
44
|
+
private handleGroupMessage;
|
|
45
|
+
/** 处理 QQ 私聊消息 */
|
|
46
|
+
private handleC2CMessage;
|
|
47
|
+
/** 清理消息内容 (去除 @ 标记等) */
|
|
48
|
+
private cleanContent;
|
|
49
|
+
/** 开始心跳 */
|
|
50
|
+
private startHeartbeat;
|
|
51
|
+
/** 停止心跳 */
|
|
52
|
+
private stopHeartbeat;
|
|
53
|
+
/** 发送心跳 */
|
|
54
|
+
private sendHeartbeat;
|
|
55
|
+
/** 发送消息 */
|
|
56
|
+
private send;
|
|
57
|
+
/** 处理断开连接 */
|
|
58
|
+
private handleDisconnect;
|
|
59
|
+
/** 重新连接 */
|
|
60
|
+
private reconnect;
|
|
61
|
+
/** 停止连接 */
|
|
62
|
+
stop(): Promise<void>;
|
|
63
|
+
/** 检查是否已连接 */
|
|
64
|
+
checkConnected(): boolean;
|
|
65
|
+
}
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../../src/channels/qq/websocket.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAgE5E,cAAc;AACd,KAAK,YAAY,GAAG,CAAC,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEtE,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,oBAAoB,CAAM;IAClC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,QAAQ;IAK5B,cAAc;IACd,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAI5C,WAAW;IACL,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,YAAY;YACE,OAAO;IA2CrB,sBAAsB;YACR,aAAa;IAgC3B,kBAAkB;YACJ,WAAW;IAezB,aAAa;YACC,QAAQ;IAsBtB,WAAW;YACG,MAAM;IAgBpB,sBAAsB;IACtB,OAAO,CAAC,UAAU;IAelB,aAAa;YACC,cAAc;IA4C5B,aAAa;YACC,oBAAoB;IAqBlC,aAAa;YACC,mBAAmB;IAkBjC,gBAAgB;YACF,kBAAkB;IAkBhC,iBAAiB;YACH,gBAAgB;IAkB9B,wBAAwB;IACxB,OAAO,CAAC,YAAY;IAKpB,WAAW;IACX,OAAO,CAAC,cAAc;IAQtB,WAAW;IACX,OAAO,CAAC,aAAa;IAOrB,WAAW;IACX,OAAO,CAAC,aAAa;IAUrB,WAAW;IACX,OAAO,CAAC,IAAI;IAMZ,aAAa;YACC,gBAAgB;IAe9B,WAAW;YACG,SAAS;IAcvB,WAAW;IACL,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,cAAc;IACd,cAAc,IAAI,OAAO;CAG1B"}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQ 机器人 WebSocket 客户端
|
|
3
|
+
* 实现与 QQ 机器人网关的长连接
|
|
4
|
+
*/
|
|
5
|
+
import WebSocket from "ws";
|
|
6
|
+
import { getChildLogger } from "../../utils/logger.js";
|
|
7
|
+
import { QQApiClient } from "./api.js";
|
|
8
|
+
const logger = getChildLogger("qq-ws");
|
|
9
|
+
/** WebSocket OpCode */
|
|
10
|
+
var OpCode;
|
|
11
|
+
(function (OpCode) {
|
|
12
|
+
/** 服务端进行消息推送 */
|
|
13
|
+
OpCode[OpCode["Dispatch"] = 0] = "Dispatch";
|
|
14
|
+
/** 客户端发送心跳 */
|
|
15
|
+
OpCode[OpCode["Heartbeat"] = 1] = "Heartbeat";
|
|
16
|
+
/** 客户端发送鉴权 */
|
|
17
|
+
OpCode[OpCode["Identify"] = 2] = "Identify";
|
|
18
|
+
/** 客户端恢复连接 */
|
|
19
|
+
OpCode[OpCode["Resume"] = 6] = "Resume";
|
|
20
|
+
/** 服务端通知客户端重新连接 */
|
|
21
|
+
OpCode[OpCode["Reconnect"] = 7] = "Reconnect";
|
|
22
|
+
/** 当鉴权或重连失败时 */
|
|
23
|
+
OpCode[OpCode["InvalidSession"] = 9] = "InvalidSession";
|
|
24
|
+
/** 服务端返回的 Hello */
|
|
25
|
+
OpCode[OpCode["Hello"] = 10] = "Hello";
|
|
26
|
+
/** 服务端返回的心跳响应 */
|
|
27
|
+
OpCode[OpCode["HeartbeatAck"] = 11] = "HeartbeatAck";
|
|
28
|
+
})(OpCode || (OpCode = {}));
|
|
29
|
+
export class QQWebSocketClient {
|
|
30
|
+
config;
|
|
31
|
+
apiClient;
|
|
32
|
+
ws = null;
|
|
33
|
+
sessionId = null;
|
|
34
|
+
sequence = null;
|
|
35
|
+
heartbeatInterval = null;
|
|
36
|
+
heartbeatTimer = null;
|
|
37
|
+
reconnectAttempts = 0;
|
|
38
|
+
maxReconnectAttempts = 10;
|
|
39
|
+
eventHandler = null;
|
|
40
|
+
isConnected = false;
|
|
41
|
+
constructor(config) {
|
|
42
|
+
this.config = config;
|
|
43
|
+
this.apiClient = new QQApiClient(config);
|
|
44
|
+
}
|
|
45
|
+
/** 设置事件处理器 */
|
|
46
|
+
setEventHandler(handler) {
|
|
47
|
+
this.eventHandler = handler;
|
|
48
|
+
}
|
|
49
|
+
/** 启动连接 */
|
|
50
|
+
async start() {
|
|
51
|
+
try {
|
|
52
|
+
const gatewayUrl = await this.apiClient.getGatewayUrl();
|
|
53
|
+
logger.info({ gatewayUrl }, "Got gateway URL");
|
|
54
|
+
await this.connect(gatewayUrl);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
logger.error({ error }, "Failed to start WebSocket client");
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** 连接到网关 */
|
|
62
|
+
async connect(gatewayUrl) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
this.ws = new WebSocket(gatewayUrl);
|
|
65
|
+
this.ws.on("open", () => {
|
|
66
|
+
logger.info("WebSocket connected");
|
|
67
|
+
});
|
|
68
|
+
this.ws.on("message", async (data) => {
|
|
69
|
+
try {
|
|
70
|
+
const payload = JSON.parse(data.toString());
|
|
71
|
+
await this.handlePayload(payload);
|
|
72
|
+
// 在收到 Ready 事件后 resolve
|
|
73
|
+
if (payload.t === "READY") {
|
|
74
|
+
resolve();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logger.error({ error, data: data.toString() }, "Failed to handle message");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
this.ws.on("close", (code, reason) => {
|
|
82
|
+
logger.warn({ code, reason: reason.toString() }, "WebSocket closed");
|
|
83
|
+
this.isConnected = false;
|
|
84
|
+
this.stopHeartbeat();
|
|
85
|
+
this.handleDisconnect();
|
|
86
|
+
});
|
|
87
|
+
this.ws.on("error", (error) => {
|
|
88
|
+
logger.error({ error }, "WebSocket error");
|
|
89
|
+
reject(error);
|
|
90
|
+
});
|
|
91
|
+
// 设置连接超时
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
if (!this.isConnected) {
|
|
94
|
+
reject(new Error("Connection timeout"));
|
|
95
|
+
}
|
|
96
|
+
}, 30000);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** 处理 WebSocket 消息 */
|
|
100
|
+
async handlePayload(payload) {
|
|
101
|
+
// 更新序列号
|
|
102
|
+
if (payload.s !== undefined) {
|
|
103
|
+
this.sequence = payload.s;
|
|
104
|
+
}
|
|
105
|
+
switch (payload.op) {
|
|
106
|
+
case OpCode.Hello:
|
|
107
|
+
await this.handleHello(payload.d);
|
|
108
|
+
break;
|
|
109
|
+
case OpCode.Dispatch:
|
|
110
|
+
await this.handleDispatch(payload.t, payload.d);
|
|
111
|
+
break;
|
|
112
|
+
case OpCode.HeartbeatAck:
|
|
113
|
+
logger.debug("Heartbeat acknowledged");
|
|
114
|
+
break;
|
|
115
|
+
case OpCode.Reconnect:
|
|
116
|
+
logger.info("Received reconnect request");
|
|
117
|
+
await this.reconnect();
|
|
118
|
+
break;
|
|
119
|
+
case OpCode.InvalidSession:
|
|
120
|
+
logger.warn("Invalid session, re-identifying");
|
|
121
|
+
this.sessionId = null;
|
|
122
|
+
await this.identify();
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** 处理 Hello 消息 */
|
|
127
|
+
async handleHello(data) {
|
|
128
|
+
this.heartbeatInterval = data.heartbeat_interval;
|
|
129
|
+
logger.info({ heartbeatInterval: this.heartbeatInterval }, "Received Hello");
|
|
130
|
+
// 开始心跳
|
|
131
|
+
this.startHeartbeat();
|
|
132
|
+
// 发送鉴权
|
|
133
|
+
if (this.sessionId) {
|
|
134
|
+
await this.resume();
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
await this.identify();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** 发送鉴权请求 */
|
|
141
|
+
async identify() {
|
|
142
|
+
const token = await this.apiClient.getAccessToken();
|
|
143
|
+
const intents = this.getIntents();
|
|
144
|
+
const payload = {
|
|
145
|
+
op: OpCode.Identify,
|
|
146
|
+
d: {
|
|
147
|
+
token: `QQBot ${token}`,
|
|
148
|
+
intents,
|
|
149
|
+
shard: [0, 1],
|
|
150
|
+
properties: {
|
|
151
|
+
$os: "linux",
|
|
152
|
+
$browser: "mozi",
|
|
153
|
+
$device: "mozi",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
this.send(payload);
|
|
158
|
+
logger.info({ intents }, "Identify sent");
|
|
159
|
+
}
|
|
160
|
+
/** 恢复连接 */
|
|
161
|
+
async resume() {
|
|
162
|
+
const token = await this.apiClient.getAccessToken();
|
|
163
|
+
const payload = {
|
|
164
|
+
op: OpCode.Resume,
|
|
165
|
+
d: {
|
|
166
|
+
token: `QQBot ${token}`,
|
|
167
|
+
session_id: this.sessionId,
|
|
168
|
+
seq: this.sequence,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
this.send(payload);
|
|
172
|
+
logger.info("Resume sent");
|
|
173
|
+
}
|
|
174
|
+
/** 获取订阅的事件 intents */
|
|
175
|
+
getIntents() {
|
|
176
|
+
let intents = 0;
|
|
177
|
+
// 基础事件
|
|
178
|
+
intents |= 1 << 0; // GUILDS
|
|
179
|
+
intents |= 1 << 1; // GUILD_MEMBERS
|
|
180
|
+
intents |= 1 << 9; // GUILD_MESSAGE_REACTIONS
|
|
181
|
+
intents |= 1 << 10; // DIRECT_MESSAGE
|
|
182
|
+
intents |= 1 << 12; // INTERACTION
|
|
183
|
+
intents |= 1 << 25; // GROUP_AND_C2C_EVENT (群聊和私聊)
|
|
184
|
+
intents |= 1 << 30; // PUBLIC_GUILD_MESSAGES (公域消息)
|
|
185
|
+
return intents;
|
|
186
|
+
}
|
|
187
|
+
/** 处理事件分发 */
|
|
188
|
+
async handleDispatch(eventType, data) {
|
|
189
|
+
logger.debug({ eventType }, "Received event");
|
|
190
|
+
switch (eventType) {
|
|
191
|
+
case "READY":
|
|
192
|
+
const readyData = data;
|
|
193
|
+
this.sessionId = readyData.session_id;
|
|
194
|
+
this.isConnected = true;
|
|
195
|
+
this.reconnectAttempts = 0;
|
|
196
|
+
logger.info({ sessionId: this.sessionId, user: readyData.user }, "Ready");
|
|
197
|
+
break;
|
|
198
|
+
case "RESUMED":
|
|
199
|
+
this.isConnected = true;
|
|
200
|
+
this.reconnectAttempts = 0;
|
|
201
|
+
logger.info("Resumed");
|
|
202
|
+
break;
|
|
203
|
+
// 频道消息
|
|
204
|
+
case "AT_MESSAGE_CREATE":
|
|
205
|
+
case "MESSAGE_CREATE":
|
|
206
|
+
await this.handleChannelMessage(data);
|
|
207
|
+
break;
|
|
208
|
+
// 私信消息
|
|
209
|
+
case "DIRECT_MESSAGE_CREATE":
|
|
210
|
+
await this.handleDirectMessage(data);
|
|
211
|
+
break;
|
|
212
|
+
// 群聊消息 (QQ 群)
|
|
213
|
+
case "GROUP_AT_MESSAGE_CREATE":
|
|
214
|
+
await this.handleGroupMessage(data);
|
|
215
|
+
break;
|
|
216
|
+
// 私聊消息 (QQ 单聊)
|
|
217
|
+
case "C2C_MESSAGE_CREATE":
|
|
218
|
+
await this.handleC2CMessage(data);
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
logger.debug({ eventType, data }, "Unhandled event");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/** 处理频道消息 */
|
|
225
|
+
async handleChannelMessage(data) {
|
|
226
|
+
if (!this.eventHandler)
|
|
227
|
+
return;
|
|
228
|
+
// 过滤机器人自己的消息
|
|
229
|
+
if (data.author.id === this.sessionId)
|
|
230
|
+
return;
|
|
231
|
+
const context = {
|
|
232
|
+
channelId: "qq",
|
|
233
|
+
messageId: data.id,
|
|
234
|
+
chatId: data.channel_id,
|
|
235
|
+
chatType: "group",
|
|
236
|
+
senderId: data.author.id,
|
|
237
|
+
senderName: data.author.username,
|
|
238
|
+
content: this.cleanContent(data.content),
|
|
239
|
+
timestamp: new Date(data.timestamp).getTime(),
|
|
240
|
+
raw: data,
|
|
241
|
+
};
|
|
242
|
+
await this.eventHandler(context);
|
|
243
|
+
}
|
|
244
|
+
/** 处理频道私信 */
|
|
245
|
+
async handleDirectMessage(data) {
|
|
246
|
+
if (!this.eventHandler)
|
|
247
|
+
return;
|
|
248
|
+
const context = {
|
|
249
|
+
channelId: "qq",
|
|
250
|
+
messageId: data.id,
|
|
251
|
+
chatId: data.guild_id,
|
|
252
|
+
chatType: "direct",
|
|
253
|
+
senderId: data.author.id,
|
|
254
|
+
senderName: data.author.username,
|
|
255
|
+
content: this.cleanContent(data.content),
|
|
256
|
+
timestamp: new Date(data.timestamp).getTime(),
|
|
257
|
+
raw: data,
|
|
258
|
+
};
|
|
259
|
+
await this.eventHandler(context);
|
|
260
|
+
}
|
|
261
|
+
/** 处理 QQ 群消息 */
|
|
262
|
+
async handleGroupMessage(data) {
|
|
263
|
+
if (!this.eventHandler)
|
|
264
|
+
return;
|
|
265
|
+
const context = {
|
|
266
|
+
channelId: "qq",
|
|
267
|
+
messageId: data.id,
|
|
268
|
+
chatId: `group:${data.group_openid}`,
|
|
269
|
+
chatType: "group",
|
|
270
|
+
senderId: data.author.member_openid || data.author.id,
|
|
271
|
+
senderName: data.author.username,
|
|
272
|
+
content: this.cleanContent(data.content),
|
|
273
|
+
timestamp: new Date(data.timestamp).getTime(),
|
|
274
|
+
raw: data,
|
|
275
|
+
};
|
|
276
|
+
await this.eventHandler(context);
|
|
277
|
+
}
|
|
278
|
+
/** 处理 QQ 私聊消息 */
|
|
279
|
+
async handleC2CMessage(data) {
|
|
280
|
+
if (!this.eventHandler)
|
|
281
|
+
return;
|
|
282
|
+
const context = {
|
|
283
|
+
channelId: "qq",
|
|
284
|
+
messageId: data.id,
|
|
285
|
+
chatId: `c2c:${data.author.union_openid || data.author.id}`,
|
|
286
|
+
chatType: "direct",
|
|
287
|
+
senderId: data.author.union_openid || data.author.id,
|
|
288
|
+
senderName: data.author.username,
|
|
289
|
+
content: this.cleanContent(data.content),
|
|
290
|
+
timestamp: new Date(data.timestamp).getTime(),
|
|
291
|
+
raw: data,
|
|
292
|
+
};
|
|
293
|
+
await this.eventHandler(context);
|
|
294
|
+
}
|
|
295
|
+
/** 清理消息内容 (去除 @ 标记等) */
|
|
296
|
+
cleanContent(content) {
|
|
297
|
+
// 去除 @ 机器人的内容
|
|
298
|
+
return content.replace(/<@!\d+>/g, "").trim();
|
|
299
|
+
}
|
|
300
|
+
/** 开始心跳 */
|
|
301
|
+
startHeartbeat() {
|
|
302
|
+
if (!this.heartbeatInterval)
|
|
303
|
+
return;
|
|
304
|
+
this.heartbeatTimer = setInterval(() => {
|
|
305
|
+
this.sendHeartbeat();
|
|
306
|
+
}, this.heartbeatInterval);
|
|
307
|
+
}
|
|
308
|
+
/** 停止心跳 */
|
|
309
|
+
stopHeartbeat() {
|
|
310
|
+
if (this.heartbeatTimer) {
|
|
311
|
+
clearInterval(this.heartbeatTimer);
|
|
312
|
+
this.heartbeatTimer = null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** 发送心跳 */
|
|
316
|
+
sendHeartbeat() {
|
|
317
|
+
const payload = {
|
|
318
|
+
op: OpCode.Heartbeat,
|
|
319
|
+
d: this.sequence,
|
|
320
|
+
};
|
|
321
|
+
this.send(payload);
|
|
322
|
+
logger.debug({ sequence: this.sequence }, "Heartbeat sent");
|
|
323
|
+
}
|
|
324
|
+
/** 发送消息 */
|
|
325
|
+
send(payload) {
|
|
326
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
327
|
+
this.ws.send(JSON.stringify(payload));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/** 处理断开连接 */
|
|
331
|
+
async handleDisconnect() {
|
|
332
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
333
|
+
logger.error("Max reconnect attempts reached");
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
this.reconnectAttempts++;
|
|
337
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 60000);
|
|
338
|
+
logger.info({ attempt: this.reconnectAttempts, delay }, "Reconnecting...");
|
|
339
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
340
|
+
await this.reconnect();
|
|
341
|
+
}
|
|
342
|
+
/** 重新连接 */
|
|
343
|
+
async reconnect() {
|
|
344
|
+
try {
|
|
345
|
+
this.stopHeartbeat();
|
|
346
|
+
if (this.ws) {
|
|
347
|
+
this.ws.close();
|
|
348
|
+
this.ws = null;
|
|
349
|
+
}
|
|
350
|
+
await this.start();
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
logger.error({ error }, "Failed to reconnect");
|
|
354
|
+
await this.handleDisconnect();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/** 停止连接 */
|
|
358
|
+
async stop() {
|
|
359
|
+
this.stopHeartbeat();
|
|
360
|
+
if (this.ws) {
|
|
361
|
+
this.ws.close();
|
|
362
|
+
this.ws = null;
|
|
363
|
+
}
|
|
364
|
+
this.isConnected = false;
|
|
365
|
+
logger.info("WebSocket client stopped");
|
|
366
|
+
}
|
|
367
|
+
/** 检查是否已连接 */
|
|
368
|
+
checkConnected() {
|
|
369
|
+
return this.isConnected;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=websocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../../src/channels/qq/websocket.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;AAEvC,uBAAuB;AACvB,IAAK,MAiBJ;AAjBD,WAAK,MAAM;IACT,gBAAgB;IAChB,2CAAY,CAAA;IACZ,cAAc;IACd,6CAAa,CAAA;IACb,cAAc;IACd,2CAAY,CAAA;IACZ,cAAc;IACd,uCAAU,CAAA;IACV,mBAAmB;IACnB,6CAAa,CAAA;IACb,gBAAgB;IAChB,uDAAkB,CAAA;IAClB,mBAAmB;IACnB,sCAAU,CAAA;IACV,iBAAiB;IACjB,oDAAiB,CAAA;AACnB,CAAC,EAjBI,MAAM,KAAN,MAAM,QAiBV;AA4CD,MAAM,OAAO,iBAAiB;IACpB,MAAM,CAAW;IACjB,SAAS,CAAc;IACvB,EAAE,GAAqB,IAAI,CAAC;IAC5B,SAAS,GAAkB,IAAI,CAAC;IAChC,QAAQ,GAAkB,IAAI,CAAC;IAC/B,iBAAiB,GAAkB,IAAI,CAAC;IACxC,cAAc,GAA0B,IAAI,CAAC;IAC7C,iBAAiB,GAAG,CAAC,CAAC;IACtB,oBAAoB,GAAG,EAAE,CAAC;IAC1B,YAAY,GAAwB,IAAI,CAAC;IACzC,WAAW,GAAG,KAAK,CAAC;IAE5B,YAAY,MAAgB;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,cAAc;IACd,eAAe,CAAC,OAAqB;QACnC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;IAC9B,CAAC;IAED,WAAW;IACX,KAAK,CAAC,KAAK;QACT,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC;YACxD,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,EAAE,iBAAiB,CAAC,CAAC;YAC/C,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,kCAAkC,CAAC,CAAC;YAC5D,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,YAAY;IACJ,KAAK,CAAC,OAAO,CAAC,UAAkB;QACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,EAAE,GAAG,IAAI,SAAS,CAAC,UAAU,CAAC,CAAC;YAEpC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBACtB,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACrC,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,IAAoB,EAAE,EAAE;gBACnD,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAc,CAAC;oBACzD,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;oBAElC,wBAAwB;oBACxB,IAAI,OAAO,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;wBAC1B,OAAO,EAAE,CAAC;oBACZ,CAAC;gBACH,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,0BAA0B,CAAC,CAAC;gBAC7E,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;gBACnC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,EAAE,kBAAkB,CAAC,CAAC;gBACrE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;gBACzB,IAAI,CAAC,aAAa,EAAE,CAAC;gBACrB,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC5B,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;gBAC3C,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;YAEH,SAAS;YACT,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;oBACtB,MAAM,CAAC,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC;gBAC1C,CAAC;YACH,CAAC,EAAE,KAAK,CAAC,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,sBAAsB;IACd,KAAK,CAAC,aAAa,CAAC,OAAkB;QAC5C,QAAQ;QACR,IAAI,OAAO,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC;QAC5B,CAAC;QAED,QAAQ,OAAO,CAAC,EAAE,EAAE,CAAC;YACnB,KAAK,MAAM,CAAC,KAAK;gBACf,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAc,CAAC,CAAC;gBAC/C,MAAM;YAER,KAAK,MAAM,CAAC,QAAQ;gBAClB,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;gBACjD,MAAM;YAER,KAAK,MAAM,CAAC,YAAY;gBACtB,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;gBACvC,MAAM;YAER,KAAK,MAAM,CAAC,SAAS;gBACnB,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;gBAC1C,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;gBACvB,MAAM;YAER,KAAK,MAAM,CAAC,cAAc;gBACxB,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;gBAC/C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;gBACtB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACtB,MAAM;QACV,CAAC;IACH,CAAC;IAED,kBAAkB;IACV,KAAK,CAAC,WAAW,CAAC,IAAe;QACvC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,kBAAkB,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAE7E,OAAO;QACP,IAAI,CAAC,cAAc,EAAE,CAAC;QAEtB,OAAO;QACP,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAED,aAAa;IACL,KAAK,CAAC,QAAQ;QACpB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC;QACpD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAElC,MAAM,OAAO,GAAc;YACzB,EAAE,EAAE,MAAM,CAAC,QAAQ;YACnB,CAAC,EAAE;gBACD,KAAK,EAAE,SAAS,KAAK,EAAE;gBACvB,OAAO;gBACP,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;gBACb,UAAU,EAAE;oBACV,GAAG,EAAE,OAAO;oBACZ,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,MAAM;iBAChB;aACF;SACF,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,eAAe,CAAC,CAAC;IAC5C,CAAC;IAED,WAAW;IACH,KAAK,CAAC,MAAM;QAClB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC;QAEpD,MAAM,OAAO,GAAc;YACzB,EAAE,EAAE,MAAM,CAAC,MAAM;YACjB,CAAC,EAAE;gBACD,KAAK,EAAE,SAAS,KAAK,EAAE;gBACvB,UAAU,EAAE,IAAI,CAAC,SAAS;gBAC1B,GAAG,EAAE,IAAI,CAAC,QAAQ;aACnB;SACF,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7B,CAAC;IAED,sBAAsB;IACd,UAAU;QAChB,IAAI,OAAO,GAAG,CAAC,CAAC;QAEhB,OAAO;QACP,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,0BAA0B;QAC7C,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,iBAAiB;QACrC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,cAAc;QAClC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,8BAA8B;QAClD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,+BAA+B;QAEnD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,aAAa;IACL,KAAK,CAAC,cAAc,CAAC,SAAiB,EAAE,IAAa;QAC3D,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAE9C,QAAQ,SAAS,EAAE,CAAC;YAClB,KAAK,OAAO;gBACV,MAAM,SAAS,GAAG,IAAiB,CAAC;gBACpC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,UAAU,CAAC;gBACtC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;gBACxB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;gBAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;gBAC1E,MAAM;YAER,KAAK,SAAS;gBACZ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;gBACxB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;gBAC3B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACvB,MAAM;YAER,OAAO;YACP,KAAK,mBAAmB,CAAC;YACzB,KAAK,gBAAgB;gBACnB,MAAM,IAAI,CAAC,oBAAoB,CAAC,IAAwB,CAAC,CAAC;gBAC1D,MAAM;YAER,OAAO;YACP,KAAK,uBAAuB;gBAC1B,MAAM,IAAI,CAAC,mBAAmB,CAAC,IAAwB,CAAC,CAAC;gBACzD,MAAM;YAER,cAAc;YACd,KAAK,yBAAyB;gBAC5B,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAwB,CAAC,CAAC;gBACxD,MAAM;YAER,eAAe;YACf,KAAK,oBAAoB;gBACvB,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAwB,CAAC,CAAC;gBACtD,MAAM;YAER;gBACE,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,iBAAiB,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,aAAa;IACL,KAAK,CAAC,oBAAoB,CAAC,IAAsB;QACvD,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAE/B,aAAa;QACb,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,SAAS;YAAE,OAAO;QAE9C,MAAM,OAAO,GAA0B;YACrC,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,EAAE;YAClB,MAAM,EAAE,IAAI,CAAC,UAAW;YACxB,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE;YACxB,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAChC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;YACxC,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;YAC7C,GAAG,EAAE,IAAI;SACV,CAAC;QAEF,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,aAAa;IACL,KAAK,CAAC,mBAAmB,CAAC,IAAsB;QACtD,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAE/B,MAAM,OAAO,GAA0B;YACrC,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,EAAE;YAClB,MAAM,EAAE,IAAI,CAAC,QAAS;YACtB,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE;YACxB,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAChC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;YACxC,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;YAC7C,GAAG,EAAE,IAAI;SACV,CAAC;QAEF,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,gBAAgB;IACR,KAAK,CAAC,kBAAkB,CAAC,IAAsB;QACrD,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAE/B,MAAM,OAAO,GAA0B;YACrC,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,EAAE;YAClB,MAAM,EAAE,SAAS,IAAI,CAAC,YAAY,EAAE;YACpC,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE;YACrD,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAChC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;YACxC,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;YAC7C,GAAG,EAAE,IAAI;SACV,CAAC;QAEF,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,iBAAiB;IACT,KAAK,CAAC,gBAAgB,CAAC,IAAsB;QACnD,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAE/B,MAAM,OAAO,GAA0B;YACrC,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,EAAE;YAClB,MAAM,EAAE,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE;YAC3D,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE;YACpD,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAChC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;YACxC,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;YAC7C,GAAG,EAAE,IAAI;SACV,CAAC;QAEF,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,wBAAwB;IAChB,YAAY,CAAC,OAAe;QAClC,cAAc;QACd,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,CAAC;IAED,WAAW;IACH,cAAc;QACpB,IAAI,CAAC,IAAI,CAAC,iBAAiB;YAAE,OAAO;QAEpC,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC7B,CAAC;IAED,WAAW;IACH,aAAa;QACnB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,WAAW;IACH,aAAa;QACnB,MAAM,OAAO,GAAc;YACzB,EAAE,EAAE,MAAM,CAAC,SAAS;YACpB,CAAC,EAAE,IAAI,CAAC,QAAQ;SACjB,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,MAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAC9D,CAAC;IAED,WAAW;IACH,IAAI,CAAC,OAAkB;QAC7B,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YACrD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,aAAa;IACL,KAAK,CAAC,gBAAgB;QAC5B,IAAI,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACxD,MAAM,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,EAAE,KAAK,CAAC,CAAC;QAE1E,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;QAE3E,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QAC3D,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;IACzB,CAAC;IAED,WAAW;IACH,KAAK,CAAC,SAAS;QACrB,IAAI,CAAC;YACH,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;gBACZ,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;gBAChB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,qBAAqB,CAAC,CAAC;YAC/C,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAChC,CAAC;IACH,CAAC;IAED,WAAW;IACX,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC1C,CAAC;IAED,cAAc;IACd,cAAc;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;CACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 企业微信 API 客户端
|
|
3
|
+
* 基于企业微信服务端 API
|
|
4
|
+
*/
|
|
5
|
+
import type { WeComConfig } from "../../types/index.js";
|
|
6
|
+
/** 发送消息响应 */
|
|
7
|
+
interface SendMessageResponse {
|
|
8
|
+
errcode: number;
|
|
9
|
+
errmsg: string;
|
|
10
|
+
msgid?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class WeComApiClient {
|
|
13
|
+
private config;
|
|
14
|
+
private client;
|
|
15
|
+
private tokenCache;
|
|
16
|
+
constructor(config: WeComConfig);
|
|
17
|
+
/** 获取 Access Token */
|
|
18
|
+
getAccessToken(): Promise<string>;
|
|
19
|
+
/** 发送应用消息 (文本) */
|
|
20
|
+
sendTextMessage(userId: string, content: string): Promise<SendMessageResponse>;
|
|
21
|
+
/** 发送群聊消息 */
|
|
22
|
+
sendGroupTextMessage(chatId: string, content: string): Promise<SendMessageResponse>;
|
|
23
|
+
/** 获取用户信息 */
|
|
24
|
+
getUserInfo(userId: string): Promise<{
|
|
25
|
+
name: string;
|
|
26
|
+
userid: string;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/channels/wecom/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAaxD,aAAa;AACb,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAA2B;gBAEjC,MAAM,EAAE,WAAW;IAW/B,sBAAsB;IAChB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAiCvC,kBAAkB;IACZ,eAAe,CACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC;IAsB/B,aAAa;IACP,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC;IAqB/B,aAAa;IACP,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAW7E"}
|
|
@@ -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"}
|