@izhimu/qq 0.3.0 → 0.3.2
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/dist/src/channel.js +32 -33
- package/dist/src/core/connection.js +14 -2
- package/dist/src/core/runtime.d.ts +1 -1
- package/package.json +1 -1
package/dist/src/channel.js
CHANGED
|
@@ -105,7 +105,6 @@ export const qqPlugin = {
|
|
|
105
105
|
probeAccount: async () => {
|
|
106
106
|
const status = await getStatus();
|
|
107
107
|
setContextStatus({
|
|
108
|
-
running: true,
|
|
109
108
|
lastProbeAt: Date.now(),
|
|
110
109
|
});
|
|
111
110
|
return {
|
|
@@ -139,11 +138,12 @@ export const qqPlugin = {
|
|
|
139
138
|
setContext(ctx);
|
|
140
139
|
const { account } = ctx;
|
|
141
140
|
log.info('gateway', `Starting gateway`);
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
// 检查是否已存在连接
|
|
142
|
+
const existingConnection = getConnection();
|
|
143
|
+
if (existingConnection) {
|
|
144
|
+
log.warn('gateway', `A connection is already running`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
147
|
// Create new connection manager
|
|
148
148
|
const connection = new ConnectionManager(account);
|
|
149
149
|
connection.on("event", (event) => eventListener(event));
|
|
@@ -151,12 +151,14 @@ export const qqPlugin = {
|
|
|
151
151
|
log.info('gateway', `State: ${status.state}`);
|
|
152
152
|
if (status.state === "connected") {
|
|
153
153
|
setContextStatus({
|
|
154
|
+
linked: true,
|
|
154
155
|
connected: true,
|
|
155
156
|
lastConnectedAt: Date.now(),
|
|
156
157
|
});
|
|
157
158
|
}
|
|
158
159
|
else if (status.state === "disconnected" || status.state === "failed") {
|
|
159
160
|
setContextStatus({
|
|
161
|
+
linked: false,
|
|
160
162
|
connected: false,
|
|
161
163
|
lastError: status.error,
|
|
162
164
|
});
|
|
@@ -165,14 +167,34 @@ export const qqPlugin = {
|
|
|
165
167
|
connection.on("reconnecting", (info) => {
|
|
166
168
|
log.info('gateway', `Reconnecting: ${info.reason}, attempt ${info.totalAttempts}`);
|
|
167
169
|
setContextStatus({
|
|
170
|
+
linked: false,
|
|
168
171
|
connected: false,
|
|
169
172
|
lastError: `Reconnecting (${info.reason})`,
|
|
170
173
|
reconnectAttempts: info.totalAttempts,
|
|
171
174
|
});
|
|
172
175
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
try {
|
|
177
|
+
await connection.start();
|
|
178
|
+
setConnection(connection);
|
|
179
|
+
// Update start time
|
|
180
|
+
setContextStatus({
|
|
181
|
+
running: true,
|
|
182
|
+
linked: true,
|
|
183
|
+
connected: true,
|
|
184
|
+
lastStartAt: Date.now(),
|
|
185
|
+
});
|
|
186
|
+
log.info('gateway', `Started gateway`);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
log.error('gateway', `Failed to start gateway:`, error);
|
|
190
|
+
setContextStatus({
|
|
191
|
+
running: false,
|
|
192
|
+
linked: false,
|
|
193
|
+
connected: false,
|
|
194
|
+
lastError: error instanceof Error ? error.message : 'Failed to start gateway',
|
|
195
|
+
});
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
176
198
|
},
|
|
177
199
|
stopAccount: async (_ctx) => {
|
|
178
200
|
const connection = getConnection();
|
|
@@ -182,35 +204,12 @@ export const qqPlugin = {
|
|
|
182
204
|
}
|
|
183
205
|
setContextStatus({
|
|
184
206
|
running: false,
|
|
207
|
+
linked: false,
|
|
185
208
|
connected: false,
|
|
186
209
|
lastStopAt: Date.now(),
|
|
187
210
|
});
|
|
188
211
|
clearContext();
|
|
189
212
|
},
|
|
190
|
-
},
|
|
191
|
-
heartbeat: {
|
|
192
|
-
checkReady: async () => {
|
|
193
|
-
const status = await getStatus();
|
|
194
|
-
if (status.status === "ok" && status.data?.online && status.data?.good) {
|
|
195
|
-
setContextStatus({
|
|
196
|
-
linked: true,
|
|
197
|
-
});
|
|
198
|
-
return {
|
|
199
|
-
ok: true,
|
|
200
|
-
reason: 'ok'
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
log.warn('heartbeat', `Heartbeat failed, status: ${status.status}, data: ${status.data}`);
|
|
205
|
-
setContextStatus({
|
|
206
|
-
linked: false,
|
|
207
|
-
});
|
|
208
|
-
return {
|
|
209
|
-
ok: false,
|
|
210
|
-
reason: status.msg
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
213
|
}
|
|
215
214
|
};
|
|
216
215
|
async function outboundSend(ctx) {
|
|
@@ -7,8 +7,8 @@ import EventEmitter from 'events';
|
|
|
7
7
|
import { Logger as log, generateEchoId, calculateBackoff, getCloseCodeMessage, } from '../utils/index.js';
|
|
8
8
|
const MAX_RECONNECT_ATTEMPTS = -1;
|
|
9
9
|
const REQUEST_TIMEOUT = 30000; // 30 seconds
|
|
10
|
-
const HEARTBEAT_TIMEOUT =
|
|
11
|
-
const HEARTBEAT_CHECK_INTERVAL =
|
|
10
|
+
const HEARTBEAT_TIMEOUT = 120000; // 120 seconds - time without heartbeat before reconnecting (increased for NapCat compatibility)
|
|
11
|
+
const HEARTBEAT_CHECK_INTERVAL = 60000; // 60 seconds - how often to check for heartbeat timeout
|
|
12
12
|
/**
|
|
13
13
|
* Connection Manager for a single NapCat account
|
|
14
14
|
*/
|
|
@@ -67,6 +67,11 @@ export class ConnectionManager extends EventEmitter {
|
|
|
67
67
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
|
+
// 防御性清理:确保旧连接和监听器被清理,避免潜在的内存泄漏
|
|
71
|
+
if (this.ws) {
|
|
72
|
+
this.ws.removeAllListeners();
|
|
73
|
+
this.ws = null;
|
|
74
|
+
}
|
|
70
75
|
this.setState('connecting');
|
|
71
76
|
try {
|
|
72
77
|
// Build WebSocket URL with access_token query parameter (NapCat OneBot 11 standard)
|
|
@@ -148,6 +153,7 @@ export class ConnectionManager extends EventEmitter {
|
|
|
148
153
|
};
|
|
149
154
|
this.emit('heartbeat', this.healthStatus);
|
|
150
155
|
// Close connection and trigger immediate reconnect
|
|
156
|
+
this.setState('disconnected');
|
|
151
157
|
this.close('Heartbeat timeout').then(() => {
|
|
152
158
|
if (this.shouldReconnect) {
|
|
153
159
|
// Increment total reconnect attempts
|
|
@@ -254,6 +260,12 @@ export class ConnectionManager extends EventEmitter {
|
|
|
254
260
|
handleClose(code, reason) {
|
|
255
261
|
const reasonStr = reason.toString() || getCloseCodeMessage(code);
|
|
256
262
|
log.warn('connection', `Connection closed: ${code} - ${reasonStr}`);
|
|
263
|
+
// 停止心跳检测
|
|
264
|
+
this.stopHeartbeatCheck();
|
|
265
|
+
// 如果 ws 已经为 null,说明是主动关闭(如心跳超时),不需要再处理
|
|
266
|
+
if (this.ws === null) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
257
269
|
if (this.shouldReconnect && !this.isNormalClosure(code)) {
|
|
258
270
|
this.scheduleReconnect();
|
|
259
271
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Stores the PluginRuntime for access in gateway handlers
|
|
4
4
|
*/
|
|
5
5
|
import type { ChannelAccountSnapshot, ChannelGatewayContext, PluginRuntime } from "openclaw/plugin-sdk";
|
|
6
|
-
import type { QQConfig } from "../types
|
|
6
|
+
import type { QQConfig } from "../types";
|
|
7
7
|
import { ConnectionManager } from "./connection.js";
|
|
8
8
|
export declare function setRuntime(next: PluginRuntime): void;
|
|
9
9
|
export declare function getRuntime(): PluginRuntime | null;
|