@izhimu/qq 0.3.0 → 0.3.1
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 +38 -39
- 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,11 @@ 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
|
+
await stopAccount();
|
|
145
|
+
}
|
|
147
146
|
// Create new connection manager
|
|
148
147
|
const connection = new ConnectionManager(account);
|
|
149
148
|
connection.on("event", (event) => eventListener(event));
|
|
@@ -151,12 +150,14 @@ export const qqPlugin = {
|
|
|
151
150
|
log.info('gateway', `State: ${status.state}`);
|
|
152
151
|
if (status.state === "connected") {
|
|
153
152
|
setContextStatus({
|
|
153
|
+
linked: true,
|
|
154
154
|
connected: true,
|
|
155
155
|
lastConnectedAt: Date.now(),
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
else if (status.state === "disconnected" || status.state === "failed") {
|
|
159
159
|
setContextStatus({
|
|
160
|
+
linked: false,
|
|
160
161
|
connected: false,
|
|
161
162
|
lastError: status.error,
|
|
162
163
|
});
|
|
@@ -165,54 +166,52 @@ export const qqPlugin = {
|
|
|
165
166
|
connection.on("reconnecting", (info) => {
|
|
166
167
|
log.info('gateway', `Reconnecting: ${info.reason}, attempt ${info.totalAttempts}`);
|
|
167
168
|
setContextStatus({
|
|
169
|
+
linked: false,
|
|
168
170
|
connected: false,
|
|
169
171
|
lastError: `Reconnecting (${info.reason})`,
|
|
170
172
|
reconnectAttempts: info.totalAttempts,
|
|
171
173
|
});
|
|
172
174
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
stopAccount: async (_ctx) => {
|
|
178
|
-
const connection = getConnection();
|
|
179
|
-
if (connection) {
|
|
180
|
-
await connection.stop();
|
|
181
|
-
clearConnection();
|
|
182
|
-
}
|
|
183
|
-
setContextStatus({
|
|
184
|
-
running: false,
|
|
185
|
-
connected: false,
|
|
186
|
-
lastStopAt: Date.now(),
|
|
187
|
-
});
|
|
188
|
-
clearContext();
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
heartbeat: {
|
|
192
|
-
checkReady: async () => {
|
|
193
|
-
const status = await getStatus();
|
|
194
|
-
if (status.status === "ok" && status.data?.online && status.data?.good) {
|
|
175
|
+
try {
|
|
176
|
+
await connection.start();
|
|
177
|
+
setConnection(connection);
|
|
178
|
+
// Update start time
|
|
195
179
|
setContextStatus({
|
|
180
|
+
running: true,
|
|
196
181
|
linked: true,
|
|
182
|
+
connected: true,
|
|
183
|
+
lastStartAt: Date.now(),
|
|
197
184
|
});
|
|
198
|
-
|
|
199
|
-
ok: true,
|
|
200
|
-
reason: 'ok'
|
|
201
|
-
};
|
|
185
|
+
log.info('gateway', `Started gateway`);
|
|
202
186
|
}
|
|
203
|
-
|
|
204
|
-
log.
|
|
187
|
+
catch (error) {
|
|
188
|
+
log.error('gateway', `Failed to start gateway:`, error);
|
|
205
189
|
setContextStatus({
|
|
190
|
+
running: false,
|
|
206
191
|
linked: false,
|
|
192
|
+
connected: false,
|
|
193
|
+
lastError: error instanceof Error ? error.message : 'Failed to start gateway',
|
|
207
194
|
});
|
|
208
|
-
|
|
209
|
-
ok: false,
|
|
210
|
-
reason: status.msg
|
|
211
|
-
};
|
|
195
|
+
throw error;
|
|
212
196
|
}
|
|
213
|
-
}
|
|
197
|
+
},
|
|
198
|
+
stopAccount,
|
|
214
199
|
}
|
|
215
200
|
};
|
|
201
|
+
async function stopAccount() {
|
|
202
|
+
const connection = getConnection();
|
|
203
|
+
if (connection) {
|
|
204
|
+
await connection.stop();
|
|
205
|
+
clearConnection();
|
|
206
|
+
}
|
|
207
|
+
setContextStatus({
|
|
208
|
+
running: false,
|
|
209
|
+
linked: false,
|
|
210
|
+
connected: false,
|
|
211
|
+
lastStopAt: Date.now(),
|
|
212
|
+
});
|
|
213
|
+
clearContext();
|
|
214
|
+
}
|
|
216
215
|
async function outboundSend(ctx) {
|
|
217
216
|
const { to, text, mediaUrl, accountId, replyToId } = ctx;
|
|
218
217
|
log.debug("outbound", `send called - accountId: ${accountId}, to: ${to}, mediaUrl: ${mediaUrl ?? "null"}, replyToId: ${replyToId ?? "none"}`);
|
|
@@ -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;
|