@alemonjs/qq-bot 2.1.31 → 2.1.33
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 +1950 -25
- package/dist/assets/index.css +1 -1
- package/dist/assets/index.js +3 -3
- package/dist/index.html +1 -1
- package/lib/config.d.ts +8 -0
- package/lib/config.js +23 -1
- package/lib/desktop.js +8 -1
- package/lib/hook.d.ts +2 -1
- package/lib/index.js +5 -8
- package/lib/index.webhook.js +3 -0
- package/lib/index.websoket.d.ts +2 -0
- package/lib/index.websoket.js +20 -18
- package/lib/message/group/C2C_MESSAGE_CREATE.d.ts +7 -0
- package/lib/message/group/GROUP_AT_MESSAGE_CREATE.d.ts +7 -0
- package/lib/message/group/GROUP_JOIN_REQUEST.d.ts +23 -0
- package/lib/message/group/GROUP_JOIN_REQUEST.js +1 -0
- package/lib/message/group/GROUP_MESSAGE_CREATE.d.ts +7 -0
- package/lib/register.d.ts +10 -1
- package/lib/register.js +508 -53
- package/lib/sdk/api.d.ts +116 -3
- package/lib/sdk/api.js +390 -17
- package/lib/sdk/client.webhook.js +14 -14
- package/lib/sdk/client.websoket.d.ts +2 -1
- package/lib/sdk/client.websoket.js +321 -110
- package/lib/sdk/config.d.ts +3 -1
- package/lib/sdk/config.js +6 -2
- package/lib/sdk/instance.js +2 -0
- package/lib/sdk/intents.d.ts +1 -1
- package/lib/sdk/intents.js +2 -0
- package/lib/sdk/message.group.d.ts +2 -0
- package/lib/sdk/registry.d.ts +25 -0
- package/lib/sdk/registry.js +116 -0
- package/lib/sdk/session.d.ts +18 -0
- package/lib/sdk/session.js +56 -0
- package/lib/sdk/typing.d.ts +125 -1
- package/lib/sends.d.ts +6 -2
- package/lib/sends.js +136 -57
- package/lib/upload.d.ts +21 -0
- package/lib/upload.js +60 -0
- package/package.json +6 -5
|
@@ -3,7 +3,6 @@ import bodyParser from 'koa-bodyparser';
|
|
|
3
3
|
import Router from 'koa-router';
|
|
4
4
|
import { WebhookAPI } from './webhook.secret.js';
|
|
5
5
|
import Koa from 'koa';
|
|
6
|
-
import { config } from './config.js';
|
|
7
6
|
import { v4 } from 'uuid';
|
|
8
7
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
9
8
|
|
|
@@ -23,10 +22,7 @@ class QQBotClient extends QQBotAPI {
|
|
|
23
22
|
#client = [];
|
|
24
23
|
#ws = null;
|
|
25
24
|
constructor(opstion) {
|
|
26
|
-
super();
|
|
27
|
-
for (const key in opstion) {
|
|
28
|
-
config.set(key, opstion[key]);
|
|
29
|
-
}
|
|
25
|
+
super(opstion);
|
|
30
26
|
}
|
|
31
27
|
on(key, val) {
|
|
32
28
|
if (!this.#events[key]) {
|
|
@@ -37,13 +33,13 @@ class QQBotClient extends QQBotAPI {
|
|
|
37
33
|
}
|
|
38
34
|
async #setTimeoutBotConfig() {
|
|
39
35
|
const callBack = async () => {
|
|
40
|
-
const app_id = config.get('app_id');
|
|
41
|
-
const secret = config.get('secret');
|
|
36
|
+
const app_id = this.config.get('app_id');
|
|
37
|
+
const secret = this.config.get('secret');
|
|
42
38
|
if (!app_id || !secret) {
|
|
43
39
|
return;
|
|
44
40
|
}
|
|
45
41
|
const data = await this.getAuthentication();
|
|
46
|
-
config.set('access_token', data.access_token);
|
|
42
|
+
this.config.set('access_token', data.access_token);
|
|
47
43
|
console.info('refresh', data.expires_in, 's');
|
|
48
44
|
setTimeout(() => void callBack(), data.expires_in * 1000);
|
|
49
45
|
};
|
|
@@ -51,15 +47,16 @@ class QQBotClient extends QQBotAPI {
|
|
|
51
47
|
}
|
|
52
48
|
connect() {
|
|
53
49
|
try {
|
|
54
|
-
const ws = config.get('ws');
|
|
50
|
+
const ws = this.config.get('ws');
|
|
51
|
+
this.updateConnectionStatus({ state: 'connecting', transport: ws ? 'proxy' : 'webhook' });
|
|
55
52
|
if (!ws) {
|
|
56
53
|
void this.#setTimeoutBotConfig();
|
|
57
54
|
this.#app = new Koa();
|
|
58
55
|
this.#app.use(bodyParser());
|
|
59
56
|
const router = new Router();
|
|
60
|
-
const port = config.get('port');
|
|
61
|
-
const secret = config.get('secret');
|
|
62
|
-
const route = config.get('route') ?? '/webhook';
|
|
57
|
+
const port = this.config.get('port');
|
|
58
|
+
const secret = this.config.get('secret');
|
|
59
|
+
const route = this.config.get('route') ?? '/webhook';
|
|
63
60
|
const cfg = {
|
|
64
61
|
secret: secret ?? '',
|
|
65
62
|
port: port ? Number(port) : 17157
|
|
@@ -96,7 +93,7 @@ class QQBotClient extends QQBotAPI {
|
|
|
96
93
|
for (const event of this.#events[body.t] || []) {
|
|
97
94
|
event(body.d);
|
|
98
95
|
}
|
|
99
|
-
const accessToken = config.get('access_token');
|
|
96
|
+
const accessToken = this.config.get('access_token');
|
|
100
97
|
for (const client of this.#client) {
|
|
101
98
|
try {
|
|
102
99
|
if (accessToken) {
|
|
@@ -114,6 +111,7 @@ class QQBotClient extends QQBotAPI {
|
|
|
114
111
|
this.#app.use(router.allowedMethods());
|
|
115
112
|
const server = this.#app.listen(cfg.port, () => {
|
|
116
113
|
console.log('Server running at http://localhost:' + cfg.port + route);
|
|
114
|
+
this.updateConnectionStatus({ state: 'ready' });
|
|
117
115
|
});
|
|
118
116
|
const wss = new WebSocketServer({ server: server });
|
|
119
117
|
console.log('Server running at ws://localhost:' + cfg.port + '/');
|
|
@@ -144,6 +142,7 @@ class QQBotClient extends QQBotAPI {
|
|
|
144
142
|
this.#ws = new WebSocket(ws);
|
|
145
143
|
this.#ws.on('open', () => {
|
|
146
144
|
this.#count = 0;
|
|
145
|
+
this.updateConnectionStatus({ state: 'ready' });
|
|
147
146
|
console.log('ws connected');
|
|
148
147
|
});
|
|
149
148
|
this.#ws.on('message', data => {
|
|
@@ -151,7 +150,7 @@ class QQBotClient extends QQBotAPI {
|
|
|
151
150
|
const body = normalizeGatewayMessage(JSON.parse(data.toString()));
|
|
152
151
|
const accessToken = body['access_token'];
|
|
153
152
|
if (accessToken) {
|
|
154
|
-
config.set('access_token', accessToken);
|
|
153
|
+
this.config.set('access_token', accessToken);
|
|
155
154
|
}
|
|
156
155
|
for (const event of this.#events[body.t] || []) {
|
|
157
156
|
event(body.d);
|
|
@@ -162,6 +161,7 @@ class QQBotClient extends QQBotAPI {
|
|
|
162
161
|
}
|
|
163
162
|
});
|
|
164
163
|
this.#ws.on('close', () => {
|
|
164
|
+
this.updateConnectionStatus({ state: 'offline' });
|
|
165
165
|
console.log('ws closed');
|
|
166
166
|
if (this.#count > 5) {
|
|
167
167
|
return;
|
|
@@ -3,7 +3,8 @@ import { QQBotEventMap } from './message.js';
|
|
|
3
3
|
import { Options } from './typing.js';
|
|
4
4
|
export declare class QQBotClients extends QQBotAPI {
|
|
5
5
|
#private;
|
|
6
|
-
constructor(
|
|
6
|
+
constructor(option: Options);
|
|
7
|
+
disconnect(): void;
|
|
7
8
|
on<T extends keyof QQBotEventMap>(key: T, val: (event: QQBotEventMap[T]) => any): this;
|
|
8
9
|
connect(gatewayURL?: string): Promise<void>;
|
|
9
10
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
2
|
import { QQBotAPI } from './api.js';
|
|
3
|
-
import { config } from './config.js';
|
|
4
3
|
import { getIntentsMask } from './intents.js';
|
|
5
|
-
import {
|
|
4
|
+
import { FileSessionStore } from './session.js';
|
|
6
5
|
|
|
7
6
|
const normalizeGatewayMessage = (message) => {
|
|
8
7
|
if (!message?.id || !message?.d || typeof message.d !== 'object' || Array.isArray(message.d)) {
|
|
@@ -14,41 +13,192 @@ const normalizeGatewayMessage = (message) => {
|
|
|
14
13
|
return message;
|
|
15
14
|
};
|
|
16
15
|
class QQBotClients extends QQBotAPI {
|
|
17
|
-
#counter = new Counter(1);
|
|
18
16
|
#isConnected = false;
|
|
19
|
-
#
|
|
20
|
-
#
|
|
17
|
+
#stopped = false;
|
|
18
|
+
#sessionId = null;
|
|
19
|
+
#sessionStore;
|
|
20
|
+
#sessionLoaded = false;
|
|
21
|
+
#sequence = null;
|
|
22
|
+
#heartbeatAcknowledged = true;
|
|
23
|
+
#heartbeatInterval = 30000;
|
|
24
|
+
#heartbeatTimer = null;
|
|
25
|
+
#accessTokenTimer = null;
|
|
26
|
+
#accessTokenTask = null;
|
|
27
|
+
#reconnectTimer = null;
|
|
28
|
+
#reconnectAttempts = 0;
|
|
21
29
|
#gatewayUrl = null;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
#ws = null;
|
|
31
|
+
#events = {};
|
|
32
|
+
constructor(option) {
|
|
33
|
+
super(option);
|
|
34
|
+
this.#sessionStore = option.sessionStore || new FileSessionStore();
|
|
35
|
+
}
|
|
36
|
+
async #loadSession() {
|
|
37
|
+
if (this.#sessionLoaded)
|
|
38
|
+
return;
|
|
39
|
+
this.#sessionLoaded = true;
|
|
40
|
+
const botId = String(this.config.get('app_id') || '');
|
|
41
|
+
const session = botId ? await this.#sessionStore.load(botId) : null;
|
|
42
|
+
if (session) {
|
|
43
|
+
this.#sessionId = session.sessionId;
|
|
44
|
+
this.#sequence = session.sequence;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
#persistSession() {
|
|
48
|
+
const botId = String(this.config.get('app_id') || '');
|
|
49
|
+
if (!botId || !this.#sessionId || this.#sequence === null)
|
|
50
|
+
return;
|
|
51
|
+
void this.#sessionStore.save({ botId, sessionId: this.#sessionId, sequence: this.#sequence }).catch(err => {
|
|
52
|
+
console.warn('[ws-qqbot] session persistence failed', err);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
#clearSession() {
|
|
56
|
+
const botId = String(this.config.get('app_id') || '');
|
|
57
|
+
this.#sessionId = null;
|
|
58
|
+
this.#sequence = null;
|
|
59
|
+
if (botId)
|
|
60
|
+
void this.#sessionStore.clear(botId).catch(() => undefined);
|
|
61
|
+
}
|
|
62
|
+
#clearHeartbeat() {
|
|
63
|
+
if (this.#heartbeatTimer) {
|
|
64
|
+
clearInterval(this.#heartbeatTimer);
|
|
65
|
+
this.#heartbeatTimer = null;
|
|
26
66
|
}
|
|
27
67
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
68
|
+
#startHeartbeat(ws) {
|
|
69
|
+
this.#clearHeartbeat();
|
|
70
|
+
this.#heartbeatTimer = setInterval(() => {
|
|
71
|
+
if (this.#ws !== ws || !this.#isConnected || ws.readyState !== WebSocket.OPEN) {
|
|
72
|
+
this.#clearHeartbeat();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!this.#heartbeatAcknowledged) {
|
|
76
|
+
this.#requestReconnect('heartbeat acknowledgement timeout');
|
|
33
77
|
return;
|
|
34
78
|
}
|
|
79
|
+
try {
|
|
80
|
+
this.#heartbeatAcknowledged = false;
|
|
81
|
+
this.updateConnectionStatus({ heartbeatAcknowledged: false });
|
|
82
|
+
ws.send(JSON.stringify({
|
|
83
|
+
op: 1,
|
|
84
|
+
d: null
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.error('[ws-qqbot] heartbeat failed', err);
|
|
89
|
+
this.#requestReconnect('heartbeat failed');
|
|
90
|
+
}
|
|
91
|
+
}, this.#heartbeatInterval);
|
|
92
|
+
}
|
|
93
|
+
#getReconnectDelay() {
|
|
94
|
+
const delay = Math.min(1000 * 2 ** Math.min(this.#reconnectAttempts, 5), 30000);
|
|
95
|
+
const jitter = Math.floor(Math.random() * Math.min(1000, Math.floor(delay / 4)));
|
|
96
|
+
return delay + jitter;
|
|
97
|
+
}
|
|
98
|
+
#scheduleReconnect(reason) {
|
|
99
|
+
if (this.#stopped || this.#reconnectTimer) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const delay = this.#getReconnectDelay();
|
|
103
|
+
this.#reconnectAttempts++;
|
|
104
|
+
this.updateConnectionStatus({
|
|
105
|
+
state: 'reconnecting',
|
|
106
|
+
reconnectAttempts: this.#reconnectAttempts,
|
|
107
|
+
heartbeatAcknowledged: this.#heartbeatAcknowledged,
|
|
108
|
+
lastError: reason
|
|
109
|
+
});
|
|
110
|
+
console.info(`[ws-qqbot] ${reason}; reconnecting in ${delay}ms (attempt ${this.#reconnectAttempts})`);
|
|
111
|
+
this.#reconnectTimer = setTimeout(() => {
|
|
112
|
+
this.#reconnectTimer = null;
|
|
113
|
+
void this.connect();
|
|
114
|
+
}, delay);
|
|
115
|
+
}
|
|
116
|
+
#requestReconnect(reason) {
|
|
117
|
+
this.#isConnected = false;
|
|
118
|
+
this.#clearHeartbeat();
|
|
119
|
+
this.cancelStreams();
|
|
120
|
+
this.#scheduleReconnect(reason);
|
|
121
|
+
const ws = this.#ws;
|
|
122
|
+
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {
|
|
123
|
+
ws.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
disconnect() {
|
|
127
|
+
this.#stopped = true;
|
|
128
|
+
this.#isConnected = false;
|
|
129
|
+
this.#clearHeartbeat();
|
|
130
|
+
if (this.#reconnectTimer) {
|
|
131
|
+
clearTimeout(this.#reconnectTimer);
|
|
132
|
+
}
|
|
133
|
+
if (this.#accessTokenTimer) {
|
|
134
|
+
clearTimeout(this.#accessTokenTimer);
|
|
135
|
+
}
|
|
136
|
+
this.#reconnectTimer = null;
|
|
137
|
+
this.#accessTokenTimer = null;
|
|
138
|
+
const ws = this.#ws;
|
|
139
|
+
this.#ws = null;
|
|
140
|
+
this.cancelStreams();
|
|
141
|
+
if (ws && ws.readyState !== WebSocket.CLOSED) {
|
|
142
|
+
ws.close(1000, 'client stopped');
|
|
143
|
+
}
|
|
144
|
+
this.updateConnectionStatus({ state: 'stopped', heartbeatAcknowledged: false });
|
|
145
|
+
}
|
|
146
|
+
#scheduleAccessTokenRefresh(delay) {
|
|
147
|
+
if (this.#accessTokenTimer) {
|
|
148
|
+
clearTimeout(this.#accessTokenTimer);
|
|
149
|
+
}
|
|
150
|
+
this.#accessTokenTimer = setTimeout(() => {
|
|
151
|
+
this.#accessTokenTimer = null;
|
|
152
|
+
void this.#startAccessTokenRefresh().catch(() => undefined);
|
|
153
|
+
}, delay);
|
|
154
|
+
}
|
|
155
|
+
async #refreshAccessToken() {
|
|
156
|
+
const appId = this.config.get('app_id');
|
|
157
|
+
const secret = this.config.get('secret');
|
|
158
|
+
if (!appId || !secret) {
|
|
159
|
+
throw new Error('QQ Bot app_id or secret is missing');
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
35
162
|
const data = await this.getAuthentication();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
163
|
+
if (!data?.access_token) {
|
|
164
|
+
throw new Error('QQ Bot access token is empty');
|
|
165
|
+
}
|
|
166
|
+
this.config.set('access_token', data.access_token);
|
|
167
|
+
const expiresIn = Number(data.expires_in);
|
|
168
|
+
const refreshIn = Math.max(60000, (Number.isFinite(expiresIn) ? expiresIn * 1000 : 3600000) - 60000);
|
|
169
|
+
this.#scheduleAccessTokenRefresh(refreshIn);
|
|
170
|
+
console.info(`[ws-qqbot] access token refreshed; next refresh in ${refreshIn}ms`);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
this.#scheduleAccessTokenRefresh(30000);
|
|
174
|
+
console.error('[ws-qqbot] access token refresh failed; retrying in 30000ms', err);
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
#startAccessTokenRefresh() {
|
|
179
|
+
if (this.#accessTokenTask === null) {
|
|
180
|
+
this.#accessTokenTask = this.#refreshAccessToken().finally(() => {
|
|
181
|
+
this.#accessTokenTask = null;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return this.#accessTokenTask;
|
|
185
|
+
}
|
|
186
|
+
async #ensureAccessToken() {
|
|
187
|
+
if (this.#accessTokenTimer) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
await this.#startAccessTokenRefresh();
|
|
41
191
|
}
|
|
42
192
|
#aut() {
|
|
43
|
-
const token = config.get('access_token');
|
|
44
|
-
const intents = config.get('intents');
|
|
45
|
-
const shard = config.get('shard');
|
|
193
|
+
const token = this.config.get('access_token');
|
|
194
|
+
const intents = this.config.get('intents');
|
|
195
|
+
const shard = this.config.get('shard');
|
|
46
196
|
return {
|
|
47
197
|
op: 2,
|
|
48
198
|
d: {
|
|
49
199
|
token: `QQBot ${token}`,
|
|
50
200
|
intents: getIntentsMask(intents),
|
|
51
|
-
shard
|
|
201
|
+
shard,
|
|
52
202
|
properties: {
|
|
53
203
|
$os: process.platform,
|
|
54
204
|
$browser: 'alemonjs',
|
|
@@ -57,8 +207,16 @@ class QQBotClients extends QQBotAPI {
|
|
|
57
207
|
}
|
|
58
208
|
};
|
|
59
209
|
}
|
|
60
|
-
#
|
|
61
|
-
|
|
210
|
+
#resume() {
|
|
211
|
+
return {
|
|
212
|
+
op: 6,
|
|
213
|
+
d: {
|
|
214
|
+
token: `QQBot ${this.config.get('access_token')}`,
|
|
215
|
+
session_id: this.#sessionId,
|
|
216
|
+
seq: this.#sequence
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
62
220
|
on(key, val) {
|
|
63
221
|
if (!this.#events[key]) {
|
|
64
222
|
this.#events[key] = [];
|
|
@@ -66,119 +224,172 @@ class QQBotClients extends QQBotAPI {
|
|
|
66
224
|
this.#events[key].push(val);
|
|
67
225
|
return this;
|
|
68
226
|
}
|
|
227
|
+
#emitError(err) {
|
|
228
|
+
for (const event of this.#events.ERROR || []) {
|
|
229
|
+
try {
|
|
230
|
+
event(err);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
console.error('[ws-qqbot] error handler failed', error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
#handleDispatch(t, d) {
|
|
238
|
+
for (const event of this.#events[t] || []) {
|
|
239
|
+
try {
|
|
240
|
+
event(d);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
this.#emitError(err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
69
247
|
async connect(gatewayURL) {
|
|
70
|
-
|
|
248
|
+
this.#stopped = false;
|
|
249
|
+
if (gatewayURL) {
|
|
250
|
+
this.#gatewayUrl = gatewayURL;
|
|
251
|
+
}
|
|
252
|
+
if (this.#ws && this.#ws.readyState !== WebSocket.CLOSED) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
await this.#loadSession();
|
|
256
|
+
try {
|
|
257
|
+
await this.#ensureAccessToken();
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
this.#scheduleReconnect('access token unavailable');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
71
263
|
if (!this.#gatewayUrl) {
|
|
72
|
-
|
|
264
|
+
try {
|
|
265
|
+
this.#gatewayUrl = await this.gateway().then(res => res?.url);
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
this.#emitError(err);
|
|
269
|
+
this.#scheduleReconnect('gateway request failed');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
73
272
|
}
|
|
74
273
|
if (!this.#gatewayUrl) {
|
|
274
|
+
this.#scheduleReconnect('gateway URL is empty');
|
|
75
275
|
return;
|
|
76
276
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
277
|
+
let ws;
|
|
278
|
+
try {
|
|
279
|
+
ws = new WebSocket(this.#gatewayUrl);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
this.#emitError(err);
|
|
283
|
+
this.#scheduleReconnect('gateway connection creation failed');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
this.#ws = ws;
|
|
287
|
+
this.updateConnectionStatus({ state: 'connecting', transport: 'websocket', lastError: undefined });
|
|
288
|
+
ws.on('open', () => {
|
|
289
|
+
if (this.#ws === ws) {
|
|
290
|
+
console.info('[ws-qqbot] open');
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
ws.on('message', msg => {
|
|
294
|
+
if (this.#ws !== ws) {
|
|
80
295
|
return;
|
|
81
296
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
const start = () => {
|
|
89
|
-
if (this.#gatewayUrl) {
|
|
297
|
+
try {
|
|
298
|
+
const message = normalizeGatewayMessage(JSON.parse(msg.toString('utf8')));
|
|
299
|
+
if (process.env.NTQQ_WS === 'dev') {
|
|
300
|
+
console.info('message', message);
|
|
301
|
+
}
|
|
90
302
|
const map = {
|
|
91
|
-
0: ({ t, d }) => {
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
for (const event of this.#events[t]) {
|
|
95
|
-
if (typeof event !== 'function') {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
event(d);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch (err) {
|
|
102
|
-
if (this.#events['ERROR']) {
|
|
103
|
-
for (const event of this.#events['ERROR']) {
|
|
104
|
-
if (typeof event !== 'function') {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
event(err);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
303
|
+
0: ({ t, d, s }) => {
|
|
304
|
+
if (typeof s === 'number') {
|
|
305
|
+
this.#sequence = s;
|
|
111
306
|
}
|
|
112
|
-
if (t === 'READY') {
|
|
113
|
-
|
|
114
|
-
clearInterval(this.#IntervalId);
|
|
115
|
-
}
|
|
116
|
-
this.#IntervalId = setInterval(() => {
|
|
117
|
-
if (this.#isConnected && this.#ws) {
|
|
118
|
-
this.#ws.send(JSON.stringify({
|
|
119
|
-
op: 1,
|
|
120
|
-
d: null
|
|
121
|
-
}));
|
|
122
|
-
}
|
|
123
|
-
}, this.#heartbeat_interval);
|
|
307
|
+
if (t === 'READY' && d?.session_id) {
|
|
308
|
+
this.#sessionId = String(d.session_id);
|
|
124
309
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
310
|
+
this.#persistSession();
|
|
311
|
+
this.#handleDispatch(t, d);
|
|
312
|
+
if (t === 'READY' || t === 'RESUMED') {
|
|
313
|
+
this.#reconnectAttempts = 0;
|
|
314
|
+
this.updateConnectionStatus({
|
|
315
|
+
state: 'ready',
|
|
316
|
+
reconnectAttempts: 0,
|
|
317
|
+
heartbeatAcknowledged: this.#heartbeatAcknowledged,
|
|
318
|
+
sessionId: this.#sessionId ?? undefined,
|
|
319
|
+
sequence: this.#sequence ?? undefined,
|
|
320
|
+
resumed: t === 'RESUMED'
|
|
321
|
+
});
|
|
322
|
+
console.info(t === 'READY' ? '[ws-qqbot] ready' : '[ws-qqbot] restored connection');
|
|
128
323
|
}
|
|
129
324
|
},
|
|
130
|
-
6: ({ d }) => {
|
|
131
|
-
console.info('[ws-qqbot] connection attempt', d);
|
|
132
|
-
},
|
|
133
325
|
7: ({ d }) => {
|
|
134
|
-
console.info('[ws-qqbot] reconnect', d);
|
|
135
|
-
|
|
136
|
-
clearInterval(this.#IntervalId);
|
|
137
|
-
}
|
|
326
|
+
console.info('[ws-qqbot] gateway requested reconnect', d);
|
|
327
|
+
this.#requestReconnect('gateway requested reconnect');
|
|
138
328
|
},
|
|
139
329
|
9: ({ d }) => {
|
|
140
|
-
console.
|
|
330
|
+
console.error('[ws-qqbot] invalid session', d);
|
|
331
|
+
this.#clearSession();
|
|
332
|
+
this.#requestReconnect('invalid session');
|
|
141
333
|
},
|
|
142
334
|
10: ({ d }) => {
|
|
143
335
|
this.#isConnected = true;
|
|
144
|
-
this.#
|
|
145
|
-
|
|
146
|
-
|
|
336
|
+
this.#heartbeatInterval = Number(d?.heartbeat_interval) || 30000;
|
|
337
|
+
this.#startHeartbeat(ws);
|
|
338
|
+
try {
|
|
339
|
+
const resumable = this.#sessionId && this.#sequence !== null;
|
|
340
|
+
ws.send(JSON.stringify(resumable ? this.#resume() : this.#aut()));
|
|
341
|
+
this.updateConnectionStatus({
|
|
342
|
+
state: 'connecting',
|
|
343
|
+
heartbeatAcknowledged: true,
|
|
344
|
+
sessionId: this.#sessionId ?? undefined,
|
|
345
|
+
sequence: this.#sequence ?? undefined,
|
|
346
|
+
resumed: Boolean(resumable)
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
console.error('[ws-qqbot] identify failed', err);
|
|
351
|
+
this.#emitError(err);
|
|
352
|
+
this.#requestReconnect('identify failed');
|
|
147
353
|
}
|
|
148
354
|
},
|
|
149
355
|
11: () => {
|
|
150
|
-
|
|
151
|
-
this
|
|
356
|
+
this.#heartbeatAcknowledged = true;
|
|
357
|
+
this.updateConnectionStatus({ heartbeatAcknowledged: true });
|
|
358
|
+
console.debug('[ws-qqbot] heartbeat acknowledged');
|
|
152
359
|
},
|
|
153
360
|
12: ({ d }) => {
|
|
154
361
|
console.debug('[ws-qqbot] platform data', d);
|
|
155
362
|
}
|
|
156
363
|
};
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
});
|
|
161
|
-
this.#ws.on('message', msg => {
|
|
162
|
-
const message = normalizeGatewayMessage(JSON.parse(msg.toString('utf8')));
|
|
163
|
-
if (process.env.NTQQ_WS === 'dev') {
|
|
164
|
-
console.info('message', message);
|
|
165
|
-
}
|
|
166
|
-
if (map[message.op]) {
|
|
167
|
-
map[message.op](message);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
this.#ws.on('close', err => {
|
|
171
|
-
this.#isConnected = false;
|
|
172
|
-
if (this.#IntervalId) {
|
|
173
|
-
clearInterval(this.#IntervalId);
|
|
174
|
-
this.#IntervalId = null;
|
|
175
|
-
}
|
|
176
|
-
void reconnect();
|
|
177
|
-
console.info('[ws-qqbot] close', err);
|
|
178
|
-
});
|
|
364
|
+
if (map[message.op]) {
|
|
365
|
+
map[message.op](message);
|
|
366
|
+
}
|
|
179
367
|
}
|
|
180
|
-
|
|
181
|
-
|
|
368
|
+
catch (err) {
|
|
369
|
+
console.error('[ws-qqbot] invalid gateway message', err);
|
|
370
|
+
this.#emitError(err);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
ws.on('close', (code, reason) => {
|
|
374
|
+
if (this.#ws !== ws) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
this.#ws = null;
|
|
378
|
+
this.#isConnected = false;
|
|
379
|
+
this.#clearHeartbeat();
|
|
380
|
+
this.updateConnectionStatus({ state: 'offline', heartbeatAcknowledged: false });
|
|
381
|
+
console.info(`[ws-qqbot] close ${code}${reason.length ? `: ${reason.toString('utf8')}` : ''}`);
|
|
382
|
+
this.#scheduleReconnect('connection closed');
|
|
383
|
+
});
|
|
384
|
+
ws.on('error', err => {
|
|
385
|
+
if (this.#ws !== ws) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
console.error('[ws-qqbot] error', err);
|
|
389
|
+
this.updateConnectionStatus({ state: 'offline', lastError: err.message });
|
|
390
|
+
this.#emitError(err);
|
|
391
|
+
this.#requestReconnect('connection error');
|
|
392
|
+
});
|
|
182
393
|
}
|
|
183
394
|
}
|
|
184
395
|
|
package/lib/sdk/config.d.ts
CHANGED
package/lib/sdk/config.js
CHANGED
package/lib/sdk/instance.js
CHANGED
package/lib/sdk/intents.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const AvailableIntentsEventsEnum: readonly ["GUILDS", "GUILD_MEMBERS", "GUILD_MESSAGES", "GUILD_MESSAGE_REACTIONS", "DIRECT_MESSAGE", "INTERACTION", "MESSAGE_AUDIT", "FORUMS_EVENT", "AUDIO_ACTION", "PUBLIC_GUILD_MESSAGES", "GROUP_AND_C2C_EVENT"];
|
|
1
|
+
export declare const AvailableIntentsEventsEnum: readonly ["GUILDS", "GUILD_MEMBERS", "GUILD_MESSAGES", "GUILD_MESSAGE_REACTIONS", "DIRECT_MESSAGE", "GROUP_MEMBER_EVENT", "INTERACTION", "MESSAGE_AUDIT", "FORUMS_EVENT", "AUDIO_ACTION", "PUBLIC_GUILD_MESSAGES", "GROUP_AND_C2C_EVENT"];
|
|
2
2
|
export type IntentsEnum = (typeof AvailableIntentsEventsEnum)[number];
|
|
3
3
|
export declare function getIntentsMask(intents: IntentsEnum[]): number;
|
package/lib/sdk/intents.js
CHANGED
|
@@ -4,6 +4,7 @@ const AvailableIntentsEventsEnum = [
|
|
|
4
4
|
'GUILD_MESSAGES',
|
|
5
5
|
'GUILD_MESSAGE_REACTIONS',
|
|
6
6
|
'DIRECT_MESSAGE',
|
|
7
|
+
'GROUP_MEMBER_EVENT',
|
|
7
8
|
'INTERACTION',
|
|
8
9
|
'MESSAGE_AUDIT',
|
|
9
10
|
'FORUMS_EVENT',
|
|
@@ -17,6 +18,7 @@ const intentsMap = {
|
|
|
17
18
|
GUILD_MESSAGES: 1 << 9,
|
|
18
19
|
GUILD_MESSAGE_REACTIONS: 1 << 10,
|
|
19
20
|
DIRECT_MESSAGE: 1 << 12,
|
|
21
|
+
GROUP_MEMBER_EVENT: 1 << 24,
|
|
20
22
|
INTERACTION: 1 << 26,
|
|
21
23
|
MESSAGE_AUDIT: 1 << 27,
|
|
22
24
|
FORUMS_EVENT: 1 << 28,
|
|
@@ -14,6 +14,7 @@ import { FRIEND_DEL_TYPE } from '../message/group/FRIEND_DEL';
|
|
|
14
14
|
import { ERROR_TYPE } from '../message/ERROR';
|
|
15
15
|
import { GROUP_MEMBER_REMOVE } from '../message/group/GROUP_MEMBER_REMOVE';
|
|
16
16
|
import { GROUP_MEMBER_ADD } from '../message/group/GROUP_MEMBER_ADD';
|
|
17
|
+
import { GROUP_JOIN_REQUEST_TYPE } from '../message/group/GROUP_JOIN_REQUEST';
|
|
17
18
|
export type QQBotGroupEventMap = {
|
|
18
19
|
C2C_MESSAGE_CREATE: C2C_MESSAGE_CREATE_TYPE;
|
|
19
20
|
C2C_MSG_REJECT: C2C_MSG_REJECT_TYPE;
|
|
@@ -31,4 +32,5 @@ export type QQBotGroupEventMap = {
|
|
|
31
32
|
ERROR: ERROR_TYPE;
|
|
32
33
|
GROUP_MEMBER_ADD: GROUP_MEMBER_ADD;
|
|
33
34
|
GROUP_MEMBER_REMOVE: GROUP_MEMBER_REMOVE;
|
|
35
|
+
GROUP_JOIN_REQUEST: GROUP_JOIN_REQUEST_TYPE;
|
|
34
36
|
};
|