@onlyne/im-qq 3.1.0 → 3.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/dist/gateway.d.ts +66 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +484 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +303 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize.d.ts +11 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +211 -0
- package/dist/normalize.js.map +1 -0
- package/dist/port.d.ts +86 -0
- package/dist/port.d.ts.map +1 -0
- package/dist/port.js +161 -0
- package/dist/port.js.map +1 -0
- package/dist/send.d.ts +24 -0
- package/dist/send.d.ts.map +1 -0
- package/dist/send.js +106 -0
- package/dist/send.js.map +1 -0
- package/dist/webhook.d.ts +24 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +88 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +14 -9
- package/src/gateway.ts +0 -546
- package/src/index.ts +0 -395
- package/src/normalize.ts +0 -207
- package/src/port.ts +0 -242
- package/src/send.ts +0 -121
- package/src/webhook.ts +0 -90
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The QQ Bot gateway: QQ's own receive path, one WebSocket per shard. Events arrive as `op 0`
|
|
3
|
+
* dispatches; the access token, `/gateway/bot` and every outbound call stay on the HTTP port.
|
|
4
|
+
*
|
|
5
|
+
* The sequence number advances only after the listener accepted an event, so a delivery that
|
|
6
|
+
* failed is replayed by the platform on the next resume instead of being skipped.
|
|
7
|
+
*/
|
|
8
|
+
import type { QqPort } from './port.js';
|
|
9
|
+
/** `idle` before `start`, `connecting` while a socket is being established. */
|
|
10
|
+
export type QqGatewayState = 'idle' | 'connecting' | 'online' | 'resuming' | 'paused' | 'stopped';
|
|
11
|
+
/** Group and c2c messages (bit 25) plus interaction callbacks (bit 26): what this connector translates. */
|
|
12
|
+
export declare const QQ_INTENTS: number;
|
|
13
|
+
/** QQ's `session_start_limit`: how many sessions may be opened, and how fast. */
|
|
14
|
+
export interface QqSessionStartLimit {
|
|
15
|
+
/** Sessions that may be started at the same time. */
|
|
16
|
+
max_concurrency: number;
|
|
17
|
+
/** Sessions left in the current window. */
|
|
18
|
+
remaining: number;
|
|
19
|
+
/** Milliseconds until `remaining` resets. */
|
|
20
|
+
reset_after: number;
|
|
21
|
+
/** Sessions a full window allows. */
|
|
22
|
+
total: number;
|
|
23
|
+
}
|
|
24
|
+
export interface QqGatewayStatus {
|
|
25
|
+
state: QqGatewayState;
|
|
26
|
+
/** Connections opened after the first one. */
|
|
27
|
+
reconnects: number;
|
|
28
|
+
/** Heartbeats that went unanswered; each one closed its socket. */
|
|
29
|
+
heartbeat_timeouts: number;
|
|
30
|
+
/** Sessions continued by `op 6` after a drop. */
|
|
31
|
+
resumes: number;
|
|
32
|
+
/** Resume attempts the platform rejected, each followed by a fresh Identify. */
|
|
33
|
+
resume_failures: number;
|
|
34
|
+
shards: number;
|
|
35
|
+
}
|
|
36
|
+
export interface QqGatewayHandlers {
|
|
37
|
+
/**
|
|
38
|
+
* One `op 0` dispatch. The sequence number advances only after this resolves, so a rejection
|
|
39
|
+
* is redelivered after the next resume.
|
|
40
|
+
*/
|
|
41
|
+
dispatch(payload: Record<string, unknown>): void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export interface QqGatewayOptions {
|
|
44
|
+
/** The HTTP boundary: the access token and `/gateway/bot`. */
|
|
45
|
+
port: QqPort;
|
|
46
|
+
/** The intents bitmask; defaults to `QQ_INTENTS`. */
|
|
47
|
+
intents?: number;
|
|
48
|
+
log?: (message: string, fields?: Record<string, unknown>) => void;
|
|
49
|
+
/**
|
|
50
|
+
* One call per state transition, with the shard, session and attempt the transition knew.
|
|
51
|
+
* A gateway whose host owns the log reports here instead of writing its own line.
|
|
52
|
+
*/
|
|
53
|
+
onState?: (to: QqGatewayState, fields: Record<string, unknown>) => void;
|
|
54
|
+
now?: () => number;
|
|
55
|
+
/** First reconnect waits this long before the exponential starts; 0 reconnects at once. */
|
|
56
|
+
reconnectBaseMs?: number;
|
|
57
|
+
/** The backoff ceiling. */
|
|
58
|
+
reconnectCapMs?: number;
|
|
59
|
+
}
|
|
60
|
+
export interface QqGateway {
|
|
61
|
+
start(handlers: QqGatewayHandlers): Promise<void>;
|
|
62
|
+
stop(): Promise<void>;
|
|
63
|
+
status(): QqGatewayStatus;
|
|
64
|
+
}
|
|
65
|
+
export declare function createQqGateway(options: QqGatewayOptions): QqGateway;
|
|
66
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElG,2GAA2G;AAC3G,eAAO,MAAM,UAAU,QAAwB,CAAC;AAEhD,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,cAAc,CAAC;IACtB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAClE;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACxE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2BAA2B;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,MAAM,IAAI,eAAe,CAAC;CAC3B;AAyCD,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAibpE"}
|
package/dist/gateway.js
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The QQ Bot gateway: QQ's own receive path, one WebSocket per shard. Events arrive as `op 0`
|
|
3
|
+
* dispatches; the access token, `/gateway/bot` and every outbound call stay on the HTTP port.
|
|
4
|
+
*
|
|
5
|
+
* The sequence number advances only after the listener accepted an event, so a delivery that
|
|
6
|
+
* failed is replayed by the platform on the next resume instead of being skipped.
|
|
7
|
+
*/
|
|
8
|
+
/** Group and c2c messages (bit 25) plus interaction callbacks (bit 26): what this connector translates. */
|
|
9
|
+
export const QQ_INTENTS = (1 << 25) | (1 << 26);
|
|
10
|
+
const DEFAULT_RECONNECT_BASE_MS = 1_000;
|
|
11
|
+
const DEFAULT_RECONNECT_CAP_MS = 30_000;
|
|
12
|
+
/** A socket that has not opened by then is dead; QQ's own clients give up at a similar point. */
|
|
13
|
+
const CONNECT_TIMEOUT_MS = 15_000;
|
|
14
|
+
/** How often a paused session start re-checks its budget. */
|
|
15
|
+
const PAUSED_POLL_MS = 250;
|
|
16
|
+
/** The Hello interval when the platform omits it; QQ documents 41 250 ms. */
|
|
17
|
+
const DEFAULT_HEARTBEAT_MS = 41_250;
|
|
18
|
+
/** `session_start_limit.reset_after` of 0 would pause forever, so a missing window is a second. */
|
|
19
|
+
const DEFAULT_LIMIT_WINDOW_MS = 1_000;
|
|
20
|
+
function recordOf(value) {
|
|
21
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
22
|
+
? value
|
|
23
|
+
: undefined;
|
|
24
|
+
}
|
|
25
|
+
function numberOr(value, fallback) {
|
|
26
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
27
|
+
}
|
|
28
|
+
function detailOf(error) {
|
|
29
|
+
return error instanceof Error ? error.message : String(error);
|
|
30
|
+
}
|
|
31
|
+
export function createQqGateway(options) {
|
|
32
|
+
const port = options.port;
|
|
33
|
+
const log = options.log ?? (() => { });
|
|
34
|
+
const onState = options.onState;
|
|
35
|
+
const now = options.now ?? Date.now;
|
|
36
|
+
const intents = options.intents ?? QQ_INTENTS;
|
|
37
|
+
const baseMs = options.reconnectBaseMs ?? DEFAULT_RECONNECT_BASE_MS;
|
|
38
|
+
const capMs = options.reconnectCapMs ?? DEFAULT_RECONNECT_CAP_MS;
|
|
39
|
+
let state = 'idle';
|
|
40
|
+
let stopping = false;
|
|
41
|
+
let handlers;
|
|
42
|
+
let shardCount = 1;
|
|
43
|
+
let reconnects = 0;
|
|
44
|
+
let heartbeatTimeouts = 0;
|
|
45
|
+
let resumes = 0;
|
|
46
|
+
let resumeFailures = 0;
|
|
47
|
+
const loops = [];
|
|
48
|
+
const sockets = new Set();
|
|
49
|
+
const sleepers = new Set();
|
|
50
|
+
// `/gateway/bot` is fetched once and reused: QQ rate-limits it, and a reconnect storm must not
|
|
51
|
+
// turn into a discovery storm.
|
|
52
|
+
let discovered;
|
|
53
|
+
let discovering;
|
|
54
|
+
let limit = { max_concurrency: 1, remaining: 1, reset_after: 0, total: 1 };
|
|
55
|
+
let windowEndsAt = 0;
|
|
56
|
+
let starting = 0;
|
|
57
|
+
function transition(next, fields = {}) {
|
|
58
|
+
if (state === next)
|
|
59
|
+
return;
|
|
60
|
+
const from = state;
|
|
61
|
+
state = next;
|
|
62
|
+
if (onState !== undefined)
|
|
63
|
+
onState(next, fields);
|
|
64
|
+
else
|
|
65
|
+
log('connector.state', { platform: 'qq', from, to: next, ...fields });
|
|
66
|
+
}
|
|
67
|
+
/** Sleeps, but a stop wakes it: `stop` must not wait out a 30 s backoff. */
|
|
68
|
+
function pause(ms) {
|
|
69
|
+
if (stopping || ms <= 0)
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
72
|
+
const wake = () => {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
sleepers.delete(wake);
|
|
75
|
+
resolve();
|
|
76
|
+
};
|
|
77
|
+
const timer = setTimeout(wake, ms);
|
|
78
|
+
sleepers.add(wake);
|
|
79
|
+
return promise;
|
|
80
|
+
}
|
|
81
|
+
/** The first reconnect is immediate, then doubling with ±20% jitter so shards do not sync up. */
|
|
82
|
+
function backoff(attempt) {
|
|
83
|
+
if (attempt <= 0)
|
|
84
|
+
return 0;
|
|
85
|
+
const exponential = Math.min(capMs, baseMs * 2 ** (attempt - 1));
|
|
86
|
+
return Math.round(exponential * (0.8 + Math.random() * 0.4));
|
|
87
|
+
}
|
|
88
|
+
async function fetchInfo() {
|
|
89
|
+
const body = recordOf(await port.request('GET', '/gateway/bot'));
|
|
90
|
+
const url = typeof body?.url === 'string' && body.url.length > 0 ? body.url : undefined;
|
|
91
|
+
if (url === undefined)
|
|
92
|
+
throw new Error('The QQ /gateway/bot answer carries no url.');
|
|
93
|
+
const raw = recordOf(body?.session_start_limit) ?? {};
|
|
94
|
+
return {
|
|
95
|
+
url,
|
|
96
|
+
shards: Math.max(1, Math.floor(numberOr(body?.shards, 1))),
|
|
97
|
+
limit: {
|
|
98
|
+
max_concurrency: Math.max(1, Math.floor(numberOr(raw.max_concurrency, 1))),
|
|
99
|
+
remaining: Math.max(0, Math.floor(numberOr(raw.remaining, 1))),
|
|
100
|
+
reset_after: Math.max(0, numberOr(raw.reset_after, 0)),
|
|
101
|
+
total: Math.max(1, Math.floor(numberOr(raw.total, 1))),
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function info(refresh) {
|
|
106
|
+
if (discovered !== undefined && !refresh)
|
|
107
|
+
return discovered;
|
|
108
|
+
if (discovering !== undefined)
|
|
109
|
+
return discovering;
|
|
110
|
+
const pending = fetchInfo();
|
|
111
|
+
discovering = pending;
|
|
112
|
+
try {
|
|
113
|
+
const next = await pending;
|
|
114
|
+
discovered = next;
|
|
115
|
+
observeLimit(next.limit);
|
|
116
|
+
return next;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
if (discovering === pending)
|
|
120
|
+
discovering = undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function observeLimit(next) {
|
|
124
|
+
limit = next;
|
|
125
|
+
windowEndsAt = next.remaining > 0 ? 0 : now() + Math.max(DEFAULT_LIMIT_WINDOW_MS, next.reset_after);
|
|
126
|
+
}
|
|
127
|
+
/** One session start, or `undefined` when a stop interrupted the wait. */
|
|
128
|
+
async function acquire(shard) {
|
|
129
|
+
let paused = false;
|
|
130
|
+
while (!stopping) {
|
|
131
|
+
if (windowEndsAt !== 0 && now() >= windowEndsAt) {
|
|
132
|
+
limit = { ...limit, remaining: limit.total };
|
|
133
|
+
windowEndsAt = 0;
|
|
134
|
+
}
|
|
135
|
+
if (limit.remaining > 0 && starting < limit.max_concurrency) {
|
|
136
|
+
limit = { ...limit, remaining: limit.remaining - 1 };
|
|
137
|
+
// The last session of the window starts the wait, not the next call: without this the
|
|
138
|
+
// pause would have no end to look forward to.
|
|
139
|
+
if (limit.remaining === 0)
|
|
140
|
+
windowEndsAt = now() + Math.max(DEFAULT_LIMIT_WINDOW_MS, limit.reset_after);
|
|
141
|
+
starting += 1;
|
|
142
|
+
let released = false;
|
|
143
|
+
return () => {
|
|
144
|
+
if (released)
|
|
145
|
+
return;
|
|
146
|
+
released = true;
|
|
147
|
+
starting = Math.max(0, starting - 1);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (!paused) {
|
|
151
|
+
paused = true;
|
|
152
|
+
log('qq gateway session start deferred', {
|
|
153
|
+
shard, remaining: limit.remaining, reset_after: limit.reset_after, concurrency: starting,
|
|
154
|
+
});
|
|
155
|
+
transition('paused', { shard, remaining: limit.remaining });
|
|
156
|
+
}
|
|
157
|
+
await pause(PAUSED_POLL_MS);
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* One socket, from Hello to close. Resolves `true` when the session reached READY or RESUMED,
|
|
163
|
+
* so the caller knows whether the next attempt is a resume or a fresh handshake.
|
|
164
|
+
*/
|
|
165
|
+
function connect(shard, url, total, release) {
|
|
166
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
167
|
+
const run = () => {
|
|
168
|
+
let socket;
|
|
169
|
+
let heartbeat;
|
|
170
|
+
let ack;
|
|
171
|
+
let connectTimer;
|
|
172
|
+
let interval = DEFAULT_HEARTBEAT_MS;
|
|
173
|
+
let online = false;
|
|
174
|
+
let started = false;
|
|
175
|
+
let failed = false;
|
|
176
|
+
let settled = false;
|
|
177
|
+
/** Serialized per connection: one event at a time, in sequence order. */
|
|
178
|
+
let chain = Promise.resolve();
|
|
179
|
+
const send = (payload) => {
|
|
180
|
+
if (socket?.readyState !== 1)
|
|
181
|
+
return;
|
|
182
|
+
try {
|
|
183
|
+
socket.send(JSON.stringify(payload));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
log('qq gateway send failed', { shard: shard.index, op: payload.op, error: detailOf(error) });
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const finish = () => {
|
|
190
|
+
if (settled)
|
|
191
|
+
return;
|
|
192
|
+
settled = true;
|
|
193
|
+
clearTimeout(heartbeat);
|
|
194
|
+
clearTimeout(ack);
|
|
195
|
+
clearTimeout(connectTimer);
|
|
196
|
+
release();
|
|
197
|
+
if (socket !== undefined) {
|
|
198
|
+
sockets.delete(socket);
|
|
199
|
+
try {
|
|
200
|
+
socket.close();
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Already closing: the close event may have been what brought us here.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
resolve(online);
|
|
207
|
+
};
|
|
208
|
+
const beat = () => {
|
|
209
|
+
if (settled)
|
|
210
|
+
return;
|
|
211
|
+
send({ op: 1, d: shard.seq });
|
|
212
|
+
// A missed ACK is a dead connection: QQ answers every op 1 with op 11.
|
|
213
|
+
ack = setTimeout(() => {
|
|
214
|
+
heartbeatTimeouts += 1;
|
|
215
|
+
log('qq gateway heartbeat unanswered', { shard: shard.index, seq: shard.seq, interval });
|
|
216
|
+
transition('connecting', { shard: shard.index, reason: 'heartbeat_timeout' });
|
|
217
|
+
finish();
|
|
218
|
+
}, interval);
|
|
219
|
+
heartbeat = setTimeout(beat, interval);
|
|
220
|
+
};
|
|
221
|
+
const begin = async () => {
|
|
222
|
+
if (started)
|
|
223
|
+
return;
|
|
224
|
+
started = true;
|
|
225
|
+
let token;
|
|
226
|
+
try {
|
|
227
|
+
token = await port.token();
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
log('qq gateway token unavailable', { shard: shard.index, error: detailOf(error) });
|
|
231
|
+
finish();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (settled)
|
|
235
|
+
return;
|
|
236
|
+
if (shard.sessionId !== undefined) {
|
|
237
|
+
transition('resuming', { shard: shard.index, session_id: shard.sessionId, seq: shard.seq });
|
|
238
|
+
send({ op: 6, d: { token: `QQBot ${token}`, session_id: shard.sessionId, seq: shard.seq } });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
send({
|
|
242
|
+
op: 2,
|
|
243
|
+
d: {
|
|
244
|
+
token: `QQBot ${token}`,
|
|
245
|
+
intents,
|
|
246
|
+
shard: [shard.index, total],
|
|
247
|
+
properties: { $os: 'linux', $browser: 'onlyne', $device: 'onlyne' },
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
const dispatch = (payload) => {
|
|
252
|
+
const type = typeof payload.t === 'string' ? payload.t : undefined;
|
|
253
|
+
const seq = typeof payload.s === 'number' ? payload.s : undefined;
|
|
254
|
+
if (type === 'READY') {
|
|
255
|
+
const sessionId = recordOf(payload.d)?.session_id;
|
|
256
|
+
if (typeof sessionId === 'string' && sessionId.length > 0)
|
|
257
|
+
shard.sessionId = sessionId;
|
|
258
|
+
if (seq !== undefined && seq > shard.seq)
|
|
259
|
+
shard.seq = seq;
|
|
260
|
+
online = true;
|
|
261
|
+
release();
|
|
262
|
+
transition('online', { shard: shard.index, session_id: shard.sessionId });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (type === 'RESUMED') {
|
|
266
|
+
resumes += 1;
|
|
267
|
+
if (seq !== undefined && seq > shard.seq)
|
|
268
|
+
shard.seq = seq;
|
|
269
|
+
online = true;
|
|
270
|
+
release();
|
|
271
|
+
transition('online', { shard: shard.index, seq: shard.seq });
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (seq === undefined)
|
|
275
|
+
return;
|
|
276
|
+
chain = chain.then(async () => {
|
|
277
|
+
if (failed || settled)
|
|
278
|
+
return;
|
|
279
|
+
const listener = handlers;
|
|
280
|
+
try {
|
|
281
|
+
if (listener !== undefined)
|
|
282
|
+
await listener.dispatch(payload);
|
|
283
|
+
if (seq > shard.seq)
|
|
284
|
+
shard.seq = seq;
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
// Stop advancing: the resume replays from `shard.seq`, so this event comes back.
|
|
288
|
+
failed = true;
|
|
289
|
+
log('qq gateway dispatch failed', {
|
|
290
|
+
shard: shard.index, seq, type,
|
|
291
|
+
source: recordOf(payload.d)?.id ?? payload.id, error: detailOf(error),
|
|
292
|
+
});
|
|
293
|
+
finish();
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
};
|
|
297
|
+
const onMessage = (event) => {
|
|
298
|
+
let payload;
|
|
299
|
+
try {
|
|
300
|
+
payload = recordOf(JSON.parse(String(event.data)));
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
payload = undefined;
|
|
304
|
+
}
|
|
305
|
+
if (payload === undefined)
|
|
306
|
+
return;
|
|
307
|
+
const op = payload.op;
|
|
308
|
+
if (op === 10) {
|
|
309
|
+
const heartbeatInterval = numberOr(recordOf(payload.d)?.heartbeat_interval, 0);
|
|
310
|
+
if (heartbeatInterval > 0)
|
|
311
|
+
interval = heartbeatInterval;
|
|
312
|
+
heartbeat = setTimeout(beat, interval);
|
|
313
|
+
void begin();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (op === 11) {
|
|
317
|
+
if (ack !== undefined) {
|
|
318
|
+
clearTimeout(ack);
|
|
319
|
+
ack = undefined;
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (op === 1) {
|
|
324
|
+
send({ op: 1, d: shard.seq });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (op === 7) {
|
|
328
|
+
log('qq gateway asked for a reconnect', { shard: shard.index, seq: shard.seq });
|
|
329
|
+
finish();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (op === 9) {
|
|
333
|
+
const resumable = payload.d === true;
|
|
334
|
+
if (shard.sessionId !== undefined)
|
|
335
|
+
resumeFailures += 1;
|
|
336
|
+
log('qq gateway rejected the session', {
|
|
337
|
+
shard: shard.index, resumable, session_id: shard.sessionId, seq: shard.seq,
|
|
338
|
+
});
|
|
339
|
+
if (!resumable) {
|
|
340
|
+
shard.sessionId = undefined;
|
|
341
|
+
shard.seq = 0;
|
|
342
|
+
}
|
|
343
|
+
transition('connecting', { shard: shard.index, reason: 'invalid_session' });
|
|
344
|
+
finish();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (op === 0)
|
|
348
|
+
dispatch(payload);
|
|
349
|
+
};
|
|
350
|
+
try {
|
|
351
|
+
socket = new WebSocket(url);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
log('qq gateway socket could not open', { shard: shard.index, url, error: detailOf(error) });
|
|
355
|
+
finish();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
sockets.add(socket);
|
|
359
|
+
if (stopping) {
|
|
360
|
+
// A stop landed between the session start and the dial: close what was just opened.
|
|
361
|
+
finish();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
connectTimer = setTimeout(() => {
|
|
365
|
+
log('qq gateway socket did not open in time', { shard: shard.index, url, timeout_ms: CONNECT_TIMEOUT_MS });
|
|
366
|
+
finish();
|
|
367
|
+
}, CONNECT_TIMEOUT_MS);
|
|
368
|
+
socket.addEventListener('open', () => {
|
|
369
|
+
if (connectTimer !== undefined) {
|
|
370
|
+
clearTimeout(connectTimer);
|
|
371
|
+
connectTimer = undefined;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
socket.addEventListener('message', onMessage);
|
|
375
|
+
socket.addEventListener('error', error => {
|
|
376
|
+
log('qq gateway socket error', { shard: shard.index, error: detailOf(error) });
|
|
377
|
+
finish();
|
|
378
|
+
});
|
|
379
|
+
socket.addEventListener('close', event => {
|
|
380
|
+
if (!settled)
|
|
381
|
+
log('qq gateway socket closed', { shard: shard.index, code: event.code, seq: shard.seq });
|
|
382
|
+
finish();
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
run();
|
|
386
|
+
return promise;
|
|
387
|
+
}
|
|
388
|
+
/** One shard's whole life: discover, start a session, and reconnect until a stop. */
|
|
389
|
+
async function session(index, total) {
|
|
390
|
+
const shard = { index, seq: 0 };
|
|
391
|
+
let attempt = 0;
|
|
392
|
+
let refresh = false;
|
|
393
|
+
while (!stopping) {
|
|
394
|
+
let target;
|
|
395
|
+
try {
|
|
396
|
+
target = await info(refresh);
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
log('qq gateway discovery failed', { shard: index, error: detailOf(error), attempt });
|
|
400
|
+
transition('connecting', { shard: index, attempt, error: detailOf(error) });
|
|
401
|
+
await pause(backoff(attempt));
|
|
402
|
+
attempt += 1;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const release = await acquire(index);
|
|
406
|
+
if (release === undefined)
|
|
407
|
+
return;
|
|
408
|
+
transition(shard.sessionId === undefined ? 'connecting' : 'resuming', {
|
|
409
|
+
shard: index, seq: shard.seq, attempt, ...(shard.sessionId === undefined ? {} : { session_id: shard.sessionId }),
|
|
410
|
+
});
|
|
411
|
+
const online = await connect(shard, target.url, Math.max(1, target.shards), release);
|
|
412
|
+
if (stopping)
|
|
413
|
+
return;
|
|
414
|
+
reconnects += 1;
|
|
415
|
+
// A session that never came online means the url or the budget is stale: rediscover.
|
|
416
|
+
refresh = !online;
|
|
417
|
+
attempt = online ? 0 : attempt + 1;
|
|
418
|
+
log('qq gateway connection ended', {
|
|
419
|
+
shard: index, reconnects, online, session_id: shard.sessionId, seq: shard.seq, attempt,
|
|
420
|
+
});
|
|
421
|
+
await pause(backoff(online ? 0 : attempt));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function control() {
|
|
425
|
+
let attempt = 0;
|
|
426
|
+
while (!stopping) {
|
|
427
|
+
try {
|
|
428
|
+
const first = await info(true);
|
|
429
|
+
const count = Math.max(1, first.shards);
|
|
430
|
+
shardCount = count;
|
|
431
|
+
log('qq gateway discovered', {
|
|
432
|
+
url: first.url, shards: count, remaining: first.limit.remaining, reset_after: first.limit.reset_after,
|
|
433
|
+
});
|
|
434
|
+
await Promise.all(Array.from({ length: count }, (_, index) => session(index, count)));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
log('qq gateway discovery failed', { error: detailOf(error), attempt });
|
|
439
|
+
transition('connecting', { attempt, error: detailOf(error) });
|
|
440
|
+
await pause(backoff(attempt));
|
|
441
|
+
attempt += 1;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
async start(next) {
|
|
447
|
+
if (loops.length > 0)
|
|
448
|
+
return;
|
|
449
|
+
handlers = next;
|
|
450
|
+
stopping = false;
|
|
451
|
+
transition('connecting');
|
|
452
|
+
// Not awaited: a network outage is a state the operator watches, not a failed start.
|
|
453
|
+
loops.push(control());
|
|
454
|
+
},
|
|
455
|
+
async stop() {
|
|
456
|
+
stopping = true;
|
|
457
|
+
for (const wake of [...sleepers])
|
|
458
|
+
wake();
|
|
459
|
+
for (const socket of [...sockets]) {
|
|
460
|
+
try {
|
|
461
|
+
socket.close();
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
// Already closing.
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
await Promise.all(loops);
|
|
468
|
+
loops.length = 0;
|
|
469
|
+
discovered = undefined;
|
|
470
|
+
transition('stopped');
|
|
471
|
+
},
|
|
472
|
+
status() {
|
|
473
|
+
return {
|
|
474
|
+
state,
|
|
475
|
+
reconnects,
|
|
476
|
+
heartbeat_timeouts: heartbeatTimeouts,
|
|
477
|
+
resumes,
|
|
478
|
+
resume_failures: resumeFailures,
|
|
479
|
+
shards: shardCount,
|
|
480
|
+
};
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The QQ official Bot OpenAPI v2 connector. Inbound events arrive either on the platform's
|
|
3
|
+
* WebSocket gateway (the default, owned by `gateway.ts`) or on the signed HTTP callback the host
|
|
4
|
+
* hands to `handleWebhook`; every outbound platform call goes through the injected port either way.
|
|
5
|
+
*/
|
|
6
|
+
import type { Json } from '@onlyne/lib';
|
|
7
|
+
import { type Connector, type SendOutcome } from '@onlyne/im-core';
|
|
8
|
+
import { type QqPort } from './port.js';
|
|
9
|
+
import { type QqGatewayOptions, type QqGatewayState } from './gateway.js';
|
|
10
|
+
export { createQqPort, messagePath, QqPortError, type QqFetch, type QqHttpRequest, type QqHttpResponse, type QqMessageBody, type QqPort, type QqPortOptions, type QqPortStatus, type QqSendResult, type QqSendTarget, } from './port.js';
|
|
11
|
+
export { normalizeEvent } from './normalize.js';
|
|
12
|
+
export { challenge, deriveEd25519, signHex, verifyHex, type QqEd25519Keys } from './webhook.js';
|
|
13
|
+
export { createSendState, parseConversation, sendMessage, type QqMessagePort, type SendState } from './send.js';
|
|
14
|
+
export { createQqGateway, QQ_INTENTS, type QqGateway, type QqGatewayHandlers, type QqGatewayOptions, type QqGatewayState, type QqGatewayStatus, type QqSessionStartLimit, } from './gateway.js';
|
|
15
|
+
export declare const QQ_CREDENTIAL_VARIABLES: readonly ["QQ_BOT_APP_ID", "QQ_BOT_APP_SECRET"];
|
|
16
|
+
/** Platform operations the connector implements beyond `send`; a resource lists them. */
|
|
17
|
+
export declare const QQ_OPERATIONS: readonly ["recall", "typing"];
|
|
18
|
+
/** Where inbound events come from: the platform's gateway socket, or its signed HTTP callback. */
|
|
19
|
+
export type QqTransport = 'websocket' | 'webhook';
|
|
20
|
+
export type QqConnectionState = 'onboarding'
|
|
21
|
+
/** The webhook transport is authenticated and ready; the platform calls in. */
|
|
22
|
+
| 'connected' | QqGatewayState | 'error';
|
|
23
|
+
/** The optional platform-status surface the gateway duck-types; not part of `Connector`. */
|
|
24
|
+
export interface QqStatus {
|
|
25
|
+
state: string;
|
|
26
|
+
connected: boolean;
|
|
27
|
+
reconnects: number;
|
|
28
|
+
rate_limits: number;
|
|
29
|
+
last_event_at?: string;
|
|
30
|
+
onboarding?: string[];
|
|
31
|
+
error?: string;
|
|
32
|
+
/** The inbound transport in force. */
|
|
33
|
+
transport: QqTransport;
|
|
34
|
+
/** WebSocket gateway counters; present once the gateway transport has started. */
|
|
35
|
+
heartbeat_timeouts?: number;
|
|
36
|
+
resumes?: number;
|
|
37
|
+
resume_failures?: number;
|
|
38
|
+
shards?: number;
|
|
39
|
+
}
|
|
40
|
+
/** The gateway transport's knobs; the connector supplies the port, the log and the clock. */
|
|
41
|
+
export type QqGatewayTuning = Omit<QqGatewayOptions, 'port' | 'log' | 'onState' | 'now'>;
|
|
42
|
+
export interface QqConnectorOptions {
|
|
43
|
+
/** Explicit `appId:appSecret`; wins over the file and the environment pair. */
|
|
44
|
+
credential?: string;
|
|
45
|
+
/** Path to a credential file; `$VAR` references resolve from the environment. */
|
|
46
|
+
credentialFile?: string;
|
|
47
|
+
env?: Record<string, string | undefined>;
|
|
48
|
+
readFile?: (path: string) => Promise<string>;
|
|
49
|
+
/** The HTTP boundary; production leaves it unset and one is built from the credential. */
|
|
50
|
+
port?: QqPort;
|
|
51
|
+
log?: (message: string, fields?: Record<string, unknown>) => void;
|
|
52
|
+
/**
|
|
53
|
+
* One call per connection state transition, with the shard and session the transition knew;
|
|
54
|
+
* the host that owns the log renders it as `connector.state`.
|
|
55
|
+
*/
|
|
56
|
+
onState?: (to: string, fields?: Record<string, unknown>) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Inbound transport. The gateway socket is QQ's own receive path and needs no public endpoint;
|
|
59
|
+
* `webhook` serves the signed HTTP callback instead. Default `websocket`.
|
|
60
|
+
*/
|
|
61
|
+
transport?: QqTransport;
|
|
62
|
+
/** Gateway transport tuning: the reconnect backoff, and the intents bitmask. */
|
|
63
|
+
gateway?: QqGatewayTuning;
|
|
64
|
+
/** The inbound path the operator registers with QQ; reported once at start. */
|
|
65
|
+
webhookPath?: string;
|
|
66
|
+
now?: () => number;
|
|
67
|
+
}
|
|
68
|
+
export interface QqConnector extends Connector {
|
|
69
|
+
readonly operations: readonly string[];
|
|
70
|
+
validate(operation: string, data: Json): void;
|
|
71
|
+
operate(operation: string, data: Json): Promise<SendOutcome>;
|
|
72
|
+
status(): QqStatus;
|
|
73
|
+
/** The platform's callback: op 13 validation and signed op 0 dispatches. */
|
|
74
|
+
handleWebhook(request: Request): Promise<Response>;
|
|
75
|
+
}
|
|
76
|
+
export declare function createQqConnector(options?: QqConnectorOptions): QqConnector;
|
|
77
|
+
//# sourceMappingURL=index.d.ts.map
|