@zhin.js/adapter-satori 3.0.2 → 4.0.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/CHANGELOG.md +460 -0
- package/README.md +38 -40
- package/adapters/satori.ts +41 -0
- package/lib/endpoint.d.ts +61 -0
- package/lib/endpoint.js +379 -0
- package/lib/index.d.ts +4 -18
- package/lib/index.js +4 -25
- package/lib/protocol.d.ts +150 -0
- package/lib/protocol.js +196 -0
- package/lib/webhook.d.ts +17 -0
- package/lib/webhook.js +79 -0
- package/lib/ws.d.ts +13 -0
- package/lib/ws.js +5 -0
- package/package.json +41 -15
- package/plugin.ts +8 -0
- package/schema.json +59 -0
- package/src/endpoint.ts +462 -0
- package/src/index.ts +47 -35
- package/src/protocol.ts +315 -0
- package/src/webhook.ts +104 -0
- package/src/ws.ts +22 -0
- package/lib/adapter.d.ts +0 -19
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -37
- package/lib/adapter.js.map +0 -1
- package/lib/api.d.ts +0 -15
- package/lib/api.d.ts.map +0 -1
- package/lib/api.js +0 -37
- package/lib/api.js.map +0 -1
- package/lib/endpoint-webhook.d.ts +0 -27
- package/lib/endpoint-webhook.d.ts.map +0 -1
- package/lib/endpoint-webhook.js +0 -99
- package/lib/endpoint-webhook.js.map +0 -1
- package/lib/endpoint-ws.d.ts +0 -30
- package/lib/endpoint-ws.d.ts.map +0 -1
- package/lib/endpoint-ws.js +0 -195
- package/lib/endpoint-ws.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/segment-mapper.d.ts +0 -2
- package/lib/segment-mapper.d.ts.map +0 -1
- package/lib/segment-mapper.js +0 -2
- package/lib/segment-mapper.js.map +0 -1
- package/lib/types.d.ts +0 -91
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -13
- package/lib/types.js.map +0 -1
- package/lib/utils.d.ts +0 -40
- package/lib/utils.d.ts.map +0 -1
- package/lib/utils.js +0 -34
- package/lib/utils.js.map +0 -1
- package/src/adapter.ts +0 -45
- package/src/api.ts +0 -48
- package/src/endpoint-webhook.ts +0 -117
- package/src/endpoint-ws.ts +0 -214
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -91
- package/src/utils.ts +0 -65
package/lib/endpoint.js
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
2
|
+
import { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatMessageId, formatSatoriOutbound, isMessageEvent, isSelfMentioned, parseMessageRef, resolveInboundSender, resolveInboundTarget, } from './protocol.js';
|
|
3
|
+
import { registerSatoriWebhookRoutes } from './webhook.js';
|
|
4
|
+
import { WS_OPEN, defaultCreateWebSocket, } from './ws.js';
|
|
5
|
+
const logger = getLogger('satori');
|
|
6
|
+
export class SatoriWsEndpoint {
|
|
7
|
+
#options;
|
|
8
|
+
#ws = null;
|
|
9
|
+
#login;
|
|
10
|
+
#lastSn;
|
|
11
|
+
#reconnectTimer = null;
|
|
12
|
+
#heartbeatTimer = null;
|
|
13
|
+
#open = false;
|
|
14
|
+
#started = false;
|
|
15
|
+
#stopping = false;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.#options = options;
|
|
18
|
+
}
|
|
19
|
+
async start() {
|
|
20
|
+
if (this.#started)
|
|
21
|
+
return;
|
|
22
|
+
this.#started = true;
|
|
23
|
+
this.#stopping = false;
|
|
24
|
+
await this.#connect();
|
|
25
|
+
}
|
|
26
|
+
open() {
|
|
27
|
+
this.#open = true;
|
|
28
|
+
}
|
|
29
|
+
close() {
|
|
30
|
+
this.#open = false;
|
|
31
|
+
}
|
|
32
|
+
async stop() {
|
|
33
|
+
this.#open = false;
|
|
34
|
+
this.#stopping = true;
|
|
35
|
+
this.#clearReconnect();
|
|
36
|
+
this.#clearHeartbeat();
|
|
37
|
+
if (this.#ws) {
|
|
38
|
+
try {
|
|
39
|
+
this.#ws.close();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* ignore */
|
|
43
|
+
}
|
|
44
|
+
this.#ws = null;
|
|
45
|
+
}
|
|
46
|
+
this.#started = false;
|
|
47
|
+
logger.debug(formatCompact({
|
|
48
|
+
op: 'disconnect',
|
|
49
|
+
endpoint: this.#options.config.name,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
async send({ target, payload }) {
|
|
53
|
+
const content = formatSatoriOutbound(payload);
|
|
54
|
+
const result = await this.#api('message', 'create', {
|
|
55
|
+
channel_id: target,
|
|
56
|
+
content,
|
|
57
|
+
});
|
|
58
|
+
const msgId = extractCreatedMessageId(result);
|
|
59
|
+
logger.debug(formatCompact({
|
|
60
|
+
op: 'satori_send',
|
|
61
|
+
endpoint: this.#options.config.name,
|
|
62
|
+
target,
|
|
63
|
+
messageId: msgId || undefined,
|
|
64
|
+
}));
|
|
65
|
+
return msgId ? formatMessageId(target, msgId) : '';
|
|
66
|
+
}
|
|
67
|
+
/** Test / internal: admit a gateway event when the endpoint is open. */
|
|
68
|
+
admit(body) {
|
|
69
|
+
if (!this.#open)
|
|
70
|
+
return;
|
|
71
|
+
if (body.login && !this.#login)
|
|
72
|
+
this.#login = body.login;
|
|
73
|
+
if (!isMessageEvent(body))
|
|
74
|
+
return;
|
|
75
|
+
const target = resolveInboundTarget(body);
|
|
76
|
+
const content = formatInboundContent(body);
|
|
77
|
+
const sender = resolveInboundSender(body);
|
|
78
|
+
const messageId = formatMessageId(target, body.message.id);
|
|
79
|
+
const selfId = this.#login?.user?.id ?? body.login?.user?.id;
|
|
80
|
+
const mentioned = isSelfMentioned(body, selfId);
|
|
81
|
+
void this.#options.gateway.receive({
|
|
82
|
+
adapter: this.#options.id,
|
|
83
|
+
target,
|
|
84
|
+
content,
|
|
85
|
+
sender,
|
|
86
|
+
id: messageId,
|
|
87
|
+
metadata: Object.freeze({
|
|
88
|
+
type: body.type,
|
|
89
|
+
channelType: isPrivateChannelType(body) ? 'private' : 'group',
|
|
90
|
+
sn: body.sn,
|
|
91
|
+
platform: this.#login?.platform,
|
|
92
|
+
endpoint: this.#options.config.name,
|
|
93
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
94
|
+
}),
|
|
95
|
+
}).catch((err) => {
|
|
96
|
+
logger.warn(formatCompact({
|
|
97
|
+
op: 'satori_gateway_receive_failed',
|
|
98
|
+
target,
|
|
99
|
+
error: err instanceof Error ? err.message : String(err),
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async recall(id) {
|
|
104
|
+
const { channelId, messageId } = parseMessageRef(id);
|
|
105
|
+
await this.#api('message', 'delete', {
|
|
106
|
+
channel_id: channelId,
|
|
107
|
+
message_id: messageId,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** Test helper: inject a READY login without a live socket. */
|
|
111
|
+
setLogin(login) {
|
|
112
|
+
this.#login = login;
|
|
113
|
+
}
|
|
114
|
+
async #connect() {
|
|
115
|
+
const { config } = this.#options;
|
|
116
|
+
const createWs = this.#options.createWebSocket ?? defaultCreateWebSocket;
|
|
117
|
+
const headers = {};
|
|
118
|
+
if (config.token)
|
|
119
|
+
headers.Authorization = `Bearer ${config.token}`;
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
let settled = false;
|
|
122
|
+
const ws = createWs(buildWsUrl(config.baseUrl, config.token), { headers });
|
|
123
|
+
this.#ws = ws;
|
|
124
|
+
ws.on('open', () => {
|
|
125
|
+
logger.debug(formatCompact({ endpoint: config.name, mode: 'ws' }));
|
|
126
|
+
this.#sendSignal(SatoriOpcode.IDENTIFY, {
|
|
127
|
+
token: config.token,
|
|
128
|
+
sn: this.#lastSn,
|
|
129
|
+
});
|
|
130
|
+
this.#startHeartbeat();
|
|
131
|
+
if (!settled) {
|
|
132
|
+
settled = true;
|
|
133
|
+
resolve();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
ws.on('message', (data) => {
|
|
137
|
+
try {
|
|
138
|
+
const raw = typeof data === 'string'
|
|
139
|
+
? data
|
|
140
|
+
: Buffer.isBuffer(data)
|
|
141
|
+
? data.toString('utf8')
|
|
142
|
+
: String(data ?? '');
|
|
143
|
+
const signal = JSON.parse(raw);
|
|
144
|
+
this.#handleSignal(signal);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
logger.warn(formatCompact({
|
|
148
|
+
op: 'ws_parse_error',
|
|
149
|
+
endpoint: config.name,
|
|
150
|
+
error: error instanceof Error ? error.message : String(error),
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
ws.on('close', (code, reason) => {
|
|
155
|
+
this.#clearHeartbeat();
|
|
156
|
+
const reasonStr = typeof reason === 'string'
|
|
157
|
+
? reason
|
|
158
|
+
: Buffer.isBuffer(reason)
|
|
159
|
+
? reason.toString('utf8')
|
|
160
|
+
: String(reason ?? '');
|
|
161
|
+
const numericCode = typeof code === 'number' ? code : 0;
|
|
162
|
+
logger.warn(formatCompact({
|
|
163
|
+
op: 'disconnect',
|
|
164
|
+
endpoint: config.name,
|
|
165
|
+
code: numericCode,
|
|
166
|
+
error: reasonStr || 'closed',
|
|
167
|
+
reconnect_ms: this.#stopping ? undefined : 5000,
|
|
168
|
+
}));
|
|
169
|
+
if (!settled) {
|
|
170
|
+
settled = true;
|
|
171
|
+
reject(new Error(`Satori WS closed: ${numericCode} ${reasonStr}`));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!this.#stopping)
|
|
175
|
+
this.#scheduleReconnect();
|
|
176
|
+
});
|
|
177
|
+
ws.on('error', (error) => {
|
|
178
|
+
logger.warn(formatCompact({
|
|
179
|
+
op: 'ws_error',
|
|
180
|
+
endpoint: config.name,
|
|
181
|
+
ok: false,
|
|
182
|
+
error: error instanceof Error ? error.message : String(error),
|
|
183
|
+
}));
|
|
184
|
+
if (!settled) {
|
|
185
|
+
settled = true;
|
|
186
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
#handleSignal(signal) {
|
|
192
|
+
if (signal.op === SatoriOpcode.READY && signal.body?.logins) {
|
|
193
|
+
const logins = signal.body.logins;
|
|
194
|
+
this.#login = logins[0];
|
|
195
|
+
if (!this.#login?.platform || !this.#login?.user?.id) {
|
|
196
|
+
logger.warn(formatCompact({ op: 'ready', ok: false, error: 'missing platform/user' }));
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (signal.op === SatoriOpcode.EVENT && signal.body) {
|
|
201
|
+
if (typeof signal.body.sn === 'number')
|
|
202
|
+
this.#lastSn = signal.body.sn;
|
|
203
|
+
this.admit(signal.body);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
#sendSignal(op, body) {
|
|
207
|
+
if (!this.#ws || this.#ws.readyState !== WS_OPEN)
|
|
208
|
+
return;
|
|
209
|
+
this.#ws.send(JSON.stringify({ op, body: body ?? {} }));
|
|
210
|
+
}
|
|
211
|
+
#startHeartbeat() {
|
|
212
|
+
this.#clearHeartbeat();
|
|
213
|
+
const interval = this.#options.config.heartbeat_interval;
|
|
214
|
+
this.#heartbeatTimer = setInterval(() => {
|
|
215
|
+
this.#sendSignal(SatoriOpcode.PING);
|
|
216
|
+
}, interval);
|
|
217
|
+
}
|
|
218
|
+
#scheduleReconnect() {
|
|
219
|
+
if (this.#reconnectTimer || this.#stopping)
|
|
220
|
+
return;
|
|
221
|
+
this.#reconnectTimer = setTimeout(() => {
|
|
222
|
+
this.#reconnectTimer = null;
|
|
223
|
+
void this.#connect().catch((err) => {
|
|
224
|
+
logger.warn(formatCompact({
|
|
225
|
+
op: 'reconnect',
|
|
226
|
+
endpoint: this.#options.config.name,
|
|
227
|
+
ok: false,
|
|
228
|
+
error: err instanceof Error ? err.message : String(err),
|
|
229
|
+
}));
|
|
230
|
+
});
|
|
231
|
+
}, 5000);
|
|
232
|
+
}
|
|
233
|
+
#clearReconnect() {
|
|
234
|
+
if (this.#reconnectTimer) {
|
|
235
|
+
clearTimeout(this.#reconnectTimer);
|
|
236
|
+
this.#reconnectTimer = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
#clearHeartbeat() {
|
|
240
|
+
if (this.#heartbeatTimer) {
|
|
241
|
+
clearInterval(this.#heartbeatTimer);
|
|
242
|
+
this.#heartbeatTimer = null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
#apiOptions() {
|
|
246
|
+
return {
|
|
247
|
+
baseUrl: this.#options.config.baseUrl,
|
|
248
|
+
platform: this.#login?.platform ?? '',
|
|
249
|
+
userId: this.#login?.user?.id ?? '',
|
|
250
|
+
token: this.#options.config.token,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
#api(resource, method, params) {
|
|
254
|
+
const call = this.#options.callApi ?? callSatoriApi;
|
|
255
|
+
return call(this.#apiOptions(), resource, method, params);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export class SatoriWebhookEndpoint {
|
|
259
|
+
#options;
|
|
260
|
+
#login;
|
|
261
|
+
#routeReleases = [];
|
|
262
|
+
#open = false;
|
|
263
|
+
#started = false;
|
|
264
|
+
constructor(options) {
|
|
265
|
+
this.#options = options;
|
|
266
|
+
}
|
|
267
|
+
/** Used by webhook handler. */
|
|
268
|
+
get isOpen() {
|
|
269
|
+
return this.#open;
|
|
270
|
+
}
|
|
271
|
+
get config() {
|
|
272
|
+
return this.#options.config;
|
|
273
|
+
}
|
|
274
|
+
async start() {
|
|
275
|
+
if (this.#started)
|
|
276
|
+
return;
|
|
277
|
+
this.#started = true;
|
|
278
|
+
this.#routeReleases.push(...registerSatoriWebhookRoutes(this.#options.http, this));
|
|
279
|
+
logger.info(formatCompact({
|
|
280
|
+
op: 'listen',
|
|
281
|
+
endpoint: this.#options.config.name,
|
|
282
|
+
mode: 'webhook',
|
|
283
|
+
path: this.#options.config.path,
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
open() {
|
|
287
|
+
this.#open = true;
|
|
288
|
+
}
|
|
289
|
+
close() {
|
|
290
|
+
this.#open = false;
|
|
291
|
+
}
|
|
292
|
+
async stop() {
|
|
293
|
+
this.#open = false;
|
|
294
|
+
for (const release of this.#routeReleases.splice(0))
|
|
295
|
+
release();
|
|
296
|
+
this.#started = false;
|
|
297
|
+
logger.debug(formatCompact({
|
|
298
|
+
op: 'disconnect',
|
|
299
|
+
endpoint: this.#options.config.name,
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
async send({ target, payload }) {
|
|
303
|
+
const content = formatSatoriOutbound(payload);
|
|
304
|
+
const result = await this.#api('message', 'create', {
|
|
305
|
+
channel_id: target,
|
|
306
|
+
content,
|
|
307
|
+
});
|
|
308
|
+
const msgId = extractCreatedMessageId(result);
|
|
309
|
+
logger.debug(formatCompact({
|
|
310
|
+
op: 'satori_send',
|
|
311
|
+
endpoint: this.#options.config.name,
|
|
312
|
+
target,
|
|
313
|
+
messageId: msgId || undefined,
|
|
314
|
+
}));
|
|
315
|
+
return msgId ? formatMessageId(target, msgId) : '';
|
|
316
|
+
}
|
|
317
|
+
admit(body) {
|
|
318
|
+
if (!this.#open)
|
|
319
|
+
return;
|
|
320
|
+
if (body.login && !this.#login)
|
|
321
|
+
this.#login = body.login;
|
|
322
|
+
if (!isMessageEvent(body))
|
|
323
|
+
return;
|
|
324
|
+
const target = resolveInboundTarget(body);
|
|
325
|
+
const content = formatInboundContent(body);
|
|
326
|
+
const sender = resolveInboundSender(body);
|
|
327
|
+
const messageId = formatMessageId(target, body.message.id);
|
|
328
|
+
const selfId = this.#login?.user?.id ?? body.login?.user?.id;
|
|
329
|
+
const mentioned = isSelfMentioned(body, selfId);
|
|
330
|
+
void this.#options.gateway.receive({
|
|
331
|
+
adapter: this.#options.id,
|
|
332
|
+
target,
|
|
333
|
+
content,
|
|
334
|
+
sender,
|
|
335
|
+
id: messageId,
|
|
336
|
+
metadata: Object.freeze({
|
|
337
|
+
type: body.type,
|
|
338
|
+
channelType: isPrivateChannelType(body) ? 'private' : 'group',
|
|
339
|
+
sn: body.sn,
|
|
340
|
+
platform: this.#login?.platform,
|
|
341
|
+
endpoint: this.#options.config.name,
|
|
342
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
343
|
+
}),
|
|
344
|
+
}).catch((err) => {
|
|
345
|
+
logger.warn(formatCompact({
|
|
346
|
+
op: 'satori_gateway_receive_failed',
|
|
347
|
+
target,
|
|
348
|
+
error: err instanceof Error ? err.message : String(err),
|
|
349
|
+
}));
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async recall(id) {
|
|
353
|
+
const { channelId, messageId } = parseMessageRef(id);
|
|
354
|
+
await this.#api('message', 'delete', {
|
|
355
|
+
channel_id: channelId,
|
|
356
|
+
message_id: messageId,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
/** Test helper: inject login without a live webhook push. */
|
|
360
|
+
setLogin(login) {
|
|
361
|
+
this.#login = login;
|
|
362
|
+
}
|
|
363
|
+
#apiOptions() {
|
|
364
|
+
return {
|
|
365
|
+
baseUrl: this.#options.config.baseUrl,
|
|
366
|
+
platform: this.#login?.platform ?? '',
|
|
367
|
+
userId: this.#login?.user?.id ?? '',
|
|
368
|
+
token: this.#options.config.token,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
#api(resource, method, params) {
|
|
372
|
+
const call = this.#options.callApi ?? callSatoriApi;
|
|
373
|
+
return call(this.#apiOptions(), resource, method, params);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function isPrivateChannelType(body) {
|
|
377
|
+
const channel = body.channel ?? body.message?.channel;
|
|
378
|
+
return channel?.type === 1;
|
|
379
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
5
|
-
export { SatoriWsClient } from './endpoint-ws.js';
|
|
6
|
-
export { SatoriWebhookEndpoint } from './endpoint-webhook.js';
|
|
7
|
-
export { SatoriAdapter, type SatoriBot } from './adapter.js';
|
|
8
|
-
declare module 'zhin.js' {
|
|
9
|
-
namespace Plugin {
|
|
10
|
-
interface Contexts {
|
|
11
|
-
router: import('@zhin.js/host-router').Router;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
interface Adapters {
|
|
15
|
-
satori: SatoriAdapter;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
export { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatMessageId, formatSatoriOutbound, isMessageEvent, isPrivateChannel, parseMessageRef, resolveInboundSender, resolveInboundTarget, resolveSatoriConfig, type ResolvedSatoriWebhookConfig, type ResolvedSatoriWsConfig, type SatoriAdapterConfig, type SatoriApiOptions, type SatoriChannel, type SatoriEventBody, type SatoriLogin, type SatoriMessage, type SatoriSignal, type SatoriUser, type SatoriWireSegment, } from './protocol.js';
|
|
2
|
+
export { SatoriWebhookEndpoint, SatoriWsEndpoint, type CreateSatoriWebSocket, type SatoriApiCaller, type SatoriWebhookEndpointOptions, type SatoriWsEndpointOptions, type SatoriWsSocket, } from './endpoint.js';
|
|
3
|
+
export { handleSatoriWebhookRequest, readRequestBody, registerSatoriWebhookRoutes, resolveSatoriOpcode, verifySatoriToken, type SatoriWebhookHandler, } from './webhook.js';
|
|
4
|
+
export { WS_OPEN, defaultCreateWebSocket, } from './ws.js';
|
package/lib/index.js
CHANGED
|
@@ -1,25 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { SatoriAdapter } from './adapter.js';
|
|
6
|
-
export * from './types.js';
|
|
7
|
-
export { callSatoriApi } from './api.js';
|
|
8
|
-
export * from './utils.js';
|
|
9
|
-
export { SatoriWsClient } from './endpoint-ws.js';
|
|
10
|
-
export { SatoriWebhookEndpoint } from './endpoint-webhook.js';
|
|
11
|
-
export { SatoriAdapter } from './adapter.js';
|
|
12
|
-
const { provide } = usePlugin();
|
|
13
|
-
provide({
|
|
14
|
-
name: 'satori',
|
|
15
|
-
description: 'Satori 协议适配器(WS 正向 / Webhook)',
|
|
16
|
-
mounted: async (p) => {
|
|
17
|
-
const adapter = new SatoriAdapter(p);
|
|
18
|
-
await adapter.start();
|
|
19
|
-
return adapter;
|
|
20
|
-
},
|
|
21
|
-
dispose: async (adapter) => {
|
|
22
|
-
await adapter.stop();
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatMessageId, formatSatoriOutbound, isMessageEvent, isPrivateChannel, parseMessageRef, resolveInboundSender, resolveInboundTarget, resolveSatoriConfig, } from './protocol.js';
|
|
2
|
+
export { SatoriWebhookEndpoint, SatoriWsEndpoint, } from './endpoint.js';
|
|
3
|
+
export { handleSatoriWebhookRequest, readRequestBody, registerSatoriWebhookRoutes, resolveSatoriOpcode, verifySatoriToken, } from './webhook.js';
|
|
4
|
+
export { WS_OPEN, defaultCreateWebSocket, } from './ws.js';
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Satori protocol helpers (no legacy Adapter/Endpoint / segment-mapper).
|
|
3
|
+
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
|
+
* Spec: https://satori.chat/zh-CN/protocol/overview.html
|
|
5
|
+
*/
|
|
6
|
+
/** Opcode:EVENT=0, PING=1, PONG=2, IDENTIFY=3, READY=4, META=5 */
|
|
7
|
+
export declare const SatoriOpcode: {
|
|
8
|
+
readonly EVENT: 0;
|
|
9
|
+
readonly PING: 1;
|
|
10
|
+
readonly PONG: 2;
|
|
11
|
+
readonly IDENTIFY: 3;
|
|
12
|
+
readonly READY: 4;
|
|
13
|
+
readonly META: 5;
|
|
14
|
+
};
|
|
15
|
+
export interface SatoriSignal {
|
|
16
|
+
readonly op: number;
|
|
17
|
+
readonly body?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export interface SatoriUser {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly name?: string;
|
|
22
|
+
readonly avatar?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SatoriChannel {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
/** 0=TEXT, 1=DIRECT, 2=CATEGORY, 3=VOICE */
|
|
27
|
+
readonly type?: number;
|
|
28
|
+
readonly name?: string;
|
|
29
|
+
readonly parent_id?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SatoriMessage {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly content?: string;
|
|
34
|
+
readonly channel?: SatoriChannel;
|
|
35
|
+
readonly user?: SatoriUser;
|
|
36
|
+
readonly member?: {
|
|
37
|
+
readonly user?: SatoriUser;
|
|
38
|
+
readonly nick?: string;
|
|
39
|
+
};
|
|
40
|
+
readonly created_at?: number;
|
|
41
|
+
readonly updated_at?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface SatoriLogin {
|
|
44
|
+
readonly platform?: string;
|
|
45
|
+
readonly user?: SatoriUser;
|
|
46
|
+
readonly status?: number;
|
|
47
|
+
readonly sn?: number;
|
|
48
|
+
}
|
|
49
|
+
export interface SatoriEventBody {
|
|
50
|
+
readonly type?: string;
|
|
51
|
+
readonly sn?: number;
|
|
52
|
+
readonly timestamp?: number;
|
|
53
|
+
readonly login?: SatoriLogin;
|
|
54
|
+
readonly message?: SatoriMessage;
|
|
55
|
+
readonly channel?: SatoriChannel;
|
|
56
|
+
readonly user?: SatoriUser;
|
|
57
|
+
readonly guild?: {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly name?: string;
|
|
60
|
+
};
|
|
61
|
+
readonly member?: {
|
|
62
|
+
readonly user?: SatoriUser;
|
|
63
|
+
readonly nick?: string;
|
|
64
|
+
readonly roles?: string[];
|
|
65
|
+
};
|
|
66
|
+
readonly [key: string]: unknown;
|
|
67
|
+
}
|
|
68
|
+
export interface SatoriApiOptions {
|
|
69
|
+
readonly baseUrl: string;
|
|
70
|
+
readonly platform: string;
|
|
71
|
+
readonly userId: string;
|
|
72
|
+
readonly token?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface SatoriWireSegment {
|
|
75
|
+
readonly type: string;
|
|
76
|
+
readonly data?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
79
|
+
export interface SatoriAdapterConfig {
|
|
80
|
+
readonly name?: string;
|
|
81
|
+
readonly connection?: 'ws' | 'webhook';
|
|
82
|
+
readonly baseUrl?: string;
|
|
83
|
+
readonly token?: string;
|
|
84
|
+
readonly heartbeat_interval?: number;
|
|
85
|
+
/** Webhook POST path (connection: webhook). */
|
|
86
|
+
readonly path?: string;
|
|
87
|
+
/** Transitional: legacy root `endpoints[]` with `context: satori`. */
|
|
88
|
+
readonly endpoints?: ReadonlyArray<Partial<ResolvedSatoriWsConfig> & {
|
|
89
|
+
readonly context?: string;
|
|
90
|
+
readonly connection?: 'ws' | 'webhook';
|
|
91
|
+
readonly path?: string;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
export interface ResolvedSatoriWsConfig {
|
|
95
|
+
readonly context: 'satori';
|
|
96
|
+
readonly connection: 'ws';
|
|
97
|
+
readonly name: string;
|
|
98
|
+
readonly baseUrl: string;
|
|
99
|
+
readonly token?: string;
|
|
100
|
+
readonly heartbeat_interval: number;
|
|
101
|
+
}
|
|
102
|
+
export interface ResolvedSatoriWebhookConfig {
|
|
103
|
+
readonly context: 'satori';
|
|
104
|
+
readonly connection: 'webhook';
|
|
105
|
+
readonly name: string;
|
|
106
|
+
readonly baseUrl: string;
|
|
107
|
+
readonly token?: string;
|
|
108
|
+
readonly path: string;
|
|
109
|
+
}
|
|
110
|
+
export type ResolvedSatoriConfig = ResolvedSatoriWsConfig | ResolvedSatoriWebhookConfig;
|
|
111
|
+
export declare function resolveSatoriConfig(config?: SatoriAdapterConfig): ResolvedSatoriConfig;
|
|
112
|
+
/** Channel.type 1 = DIRECT (private). */
|
|
113
|
+
export declare function isPrivateChannel(channel?: SatoriChannel): boolean;
|
|
114
|
+
export declare function isMessageEvent(body: SatoriEventBody): body is SatoriEventBody & {
|
|
115
|
+
message: SatoriMessage;
|
|
116
|
+
};
|
|
117
|
+
export declare function buildWsUrl(baseUrl: string, token?: string): string;
|
|
118
|
+
/**
|
|
119
|
+
* Call Satori HTTP API: POST {baseUrl}/v1/{resource}.{method}
|
|
120
|
+
* @see https://satori.chat/en-US/protocol/api.html
|
|
121
|
+
*/
|
|
122
|
+
export declare function callSatoriApi<T = unknown>(options: SatoriApiOptions, resource: string, method: string, params?: Record<string, unknown>): Promise<T>;
|
|
123
|
+
/** Build inbound text for MessageGateway.receive. */
|
|
124
|
+
export declare function formatInboundContent(body: SatoriEventBody & {
|
|
125
|
+
message: SatoriMessage;
|
|
126
|
+
}): string;
|
|
127
|
+
export declare function resolveInboundTarget(body: SatoriEventBody & {
|
|
128
|
+
message: SatoriMessage;
|
|
129
|
+
}): string;
|
|
130
|
+
export declare function resolveInboundSender(body: SatoriEventBody & {
|
|
131
|
+
message: SatoriMessage;
|
|
132
|
+
}): string;
|
|
133
|
+
/**
|
|
134
|
+
* Detect `<at id="…"/>` elements in message content targeting the bot selfId.
|
|
135
|
+
* selfId 来源:READY/事件携带的 `login.user.id`。
|
|
136
|
+
*/
|
|
137
|
+
export declare function isSelfMentioned(body: SatoriEventBody & {
|
|
138
|
+
message: SatoriMessage;
|
|
139
|
+
}, selfId?: string): boolean;
|
|
140
|
+
export declare function formatMessageId(channelId: string, messageId: string): string;
|
|
141
|
+
export declare function parseMessageRef(id: string): {
|
|
142
|
+
channelId: string;
|
|
143
|
+
messageId: string;
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Wire-encode an already-rendered outbound payload into Satori message content.
|
|
147
|
+
* Segment canonicalization is intentionally not done here.
|
|
148
|
+
*/
|
|
149
|
+
export declare function formatSatoriOutbound(payload: unknown): string;
|
|
150
|
+
export declare function extractCreatedMessageId(result: unknown): string;
|