@zhin.js/adapter-discord 8.0.0 → 8.0.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/CHANGELOG.md +22 -0
- package/README.md +10 -1
- package/adapters/discord.js +0 -7
- package/adapters/discord.ts +0 -7
- package/agent/tools/add_role.ts +10 -8
- package/agent/tools/create_thread.ts +12 -9
- package/agent/tools/forum_post.ts +18 -9
- package/agent/tools/list_roles.ts +21 -9
- package/agent/tools/react.ts +11 -9
- package/agent/tools/remove_role.ts +10 -8
- package/agent/tools/send_embed.ts +10 -9
- package/lib/client.d.ts +16 -0
- package/lib/client.js +8 -0
- package/lib/discord-endpoint-commands.d.ts +1 -1
- package/lib/endpoint.d.ts +20 -32
- package/lib/endpoint.js +88 -157
- package/lib/gateway.d.ts +1 -0
- package/lib/gateway.js +5 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/protocol.d.ts +1 -1
- package/lib/protocol.js +1 -1
- package/lib/side-event-dispatch.d.ts +2 -2
- package/lib/side-event-dispatch.js +3 -3
- package/lib/webhook.d.ts +1 -0
- package/lib/webhook.js +2 -0
- package/package.json +15 -14
- package/src/client.ts +24 -0
- package/src/endpoint.ts +105 -212
- package/src/gateway.ts +6 -0
- package/src/index.ts +4 -6
- package/src/protocol.ts +1 -1
- package/src/side-event-dispatch.ts +4 -4
- package/src/webhook.ts +2 -0
- package/lib/discord-agent-deps.d.ts +0 -35
- package/lib/discord-agent-deps.js +0 -32
- package/src/discord-agent-deps.ts +0 -79
package/lib/endpoint.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
1
2
|
/**
|
|
2
3
|
* DiscordEndpoint — lifecycle, outbound, admit, gateway / interactions modes, agent tool surface.
|
|
3
4
|
*/
|
|
4
5
|
import { ChannelType } from 'discord.js';
|
|
5
6
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
6
|
-
import { registerDiscordAgentEndpoint } from './discord-agent-deps.js';
|
|
7
7
|
import { connectDiscordGatewayClient, defaultCreateClient, DEFAULT_INTENTS, resolveSenderRole, toMessageCreateOptions, } from './gateway.js';
|
|
8
8
|
import { discordInboundConversation, formatButtonContent, formatButtonSegments, formatInboundContent, formatInboundSegments, formatOutboundBody, senderDisplayName, } from './protocol.js';
|
|
9
9
|
import { registerDiscordInteractionRoutes } from './webhook.js';
|
|
@@ -11,7 +11,7 @@ import { receiveDiscordGuildMemberSideEvent } from './side-event-dispatch.js';
|
|
|
11
11
|
const DISCORD_API = 'https://discord.com/api/v10';
|
|
12
12
|
/** 出站 HTTP 调用统一 30s 超时。 */
|
|
13
13
|
const OUTBOUND_TIMEOUT_MS = 30_000;
|
|
14
|
-
export class DiscordGatewayEndpoint {
|
|
14
|
+
export class DiscordGatewayEndpoint extends Endpoint {
|
|
15
15
|
#logger;
|
|
16
16
|
#options;
|
|
17
17
|
#createClient;
|
|
@@ -19,18 +19,14 @@ export class DiscordGatewayEndpoint {
|
|
|
19
19
|
#client = null;
|
|
20
20
|
#open = false;
|
|
21
21
|
#started = false;
|
|
22
|
-
|
|
23
|
-
management = createDiscordEndpointManagement({
|
|
24
|
-
getClient: () => this.#requireClient(),
|
|
25
|
-
getMembers: (guildId) => this.getMembers(guildId),
|
|
26
|
-
});
|
|
22
|
+
management = createDiscordEndpointManagement(() => this.#requireClient());
|
|
27
23
|
control = Object.freeze({
|
|
28
24
|
recall: (message) => this.recallMessage(message),
|
|
29
25
|
addReaction: async (message, emoji, hint) => {
|
|
30
26
|
const channelId = hint?.channelId ?? message.conversation.id;
|
|
31
27
|
if (!channelId || !message.id)
|
|
32
28
|
return null;
|
|
33
|
-
await this
|
|
29
|
+
await this.#addReaction(channelId, message.id, emoji);
|
|
34
30
|
return emoji;
|
|
35
31
|
},
|
|
36
32
|
});
|
|
@@ -38,29 +34,36 @@ export class DiscordGatewayEndpoint {
|
|
|
38
34
|
resolve: (reference, context) => resolveDiscordContent(this.#fetch, this.#options.config.token, reference, context),
|
|
39
35
|
});
|
|
40
36
|
constructor(options) {
|
|
37
|
+
super();
|
|
41
38
|
this.#logger = getAdapterLogger('discord', options.config.id);
|
|
42
39
|
this.#options = options;
|
|
43
40
|
this.#createClient = options.createClient ?? defaultCreateClient;
|
|
44
41
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
45
42
|
}
|
|
43
|
+
/** The actual discord.js-compatible client used by this connection. */
|
|
44
|
+
get client() {
|
|
45
|
+
return this.#requireClient();
|
|
46
|
+
}
|
|
46
47
|
async start() {
|
|
47
48
|
if (this.#started)
|
|
48
49
|
return;
|
|
49
50
|
this.#started = true;
|
|
50
51
|
try {
|
|
51
|
-
this.#unregisterAgent = registerDiscordAgentEndpoint(this.#options.config.id, this);
|
|
52
52
|
const intents = this.#options.config.intents?.length
|
|
53
53
|
? [...this.#options.config.intents]
|
|
54
54
|
: DEFAULT_INTENTS;
|
|
55
55
|
this.#client = this.#createClient(intents);
|
|
56
56
|
await connectDiscordGatewayClient(this.#client, this.#options.config, {
|
|
57
|
+
onPlatformEvent: (name, event) => {
|
|
58
|
+
void this.#emitPlatformEvent(name, event);
|
|
59
|
+
},
|
|
57
60
|
onMessage: (msg) => this.admit(msg),
|
|
58
61
|
onButton: (interaction) => this.admitButton(interaction),
|
|
59
62
|
onGuildMemberAdd: (member) => {
|
|
60
|
-
receiveDiscordGuildMemberSideEvent(this
|
|
63
|
+
receiveDiscordGuildMemberSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, 'member_increase', member, this.#logger);
|
|
61
64
|
},
|
|
62
65
|
onGuildMemberRemove: (member) => {
|
|
63
|
-
receiveDiscordGuildMemberSideEvent(this
|
|
66
|
+
receiveDiscordGuildMemberSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, 'member_decrease', member, this.#logger);
|
|
64
67
|
},
|
|
65
68
|
});
|
|
66
69
|
this.#logger.info(formatCompact({
|
|
@@ -84,8 +87,6 @@ export class DiscordGatewayEndpoint {
|
|
|
84
87
|
}
|
|
85
88
|
async stop() {
|
|
86
89
|
this.#open = false;
|
|
87
|
-
this.#unregisterAgent?.();
|
|
88
|
-
this.#unregisterAgent = undefined;
|
|
89
90
|
if (this.#client) {
|
|
90
91
|
try {
|
|
91
92
|
this.#client.removeAllListeners();
|
|
@@ -126,7 +127,7 @@ export class DiscordGatewayEndpoint {
|
|
|
126
127
|
if (msg.authorBot)
|
|
127
128
|
return;
|
|
128
129
|
const conversation = discordInboundConversation(String(this.#options.id), msg);
|
|
129
|
-
void this
|
|
130
|
+
void this.emit('message.receive', {
|
|
130
131
|
conversation,
|
|
131
132
|
message: { conversation, id: msg.id },
|
|
132
133
|
content: formatInboundContent(msg),
|
|
@@ -158,7 +159,7 @@ export class DiscordGatewayEndpoint {
|
|
|
158
159
|
if (!this.#open)
|
|
159
160
|
return;
|
|
160
161
|
const conversation = discordInboundConversation(String(this.#options.id), interaction);
|
|
161
|
-
void this
|
|
162
|
+
void this.emit('message.receive', {
|
|
162
163
|
conversation,
|
|
163
164
|
message: { conversation, id: interaction.id },
|
|
164
165
|
content: formatButtonContent(interaction),
|
|
@@ -178,43 +179,7 @@ export class DiscordGatewayEndpoint {
|
|
|
178
179
|
}));
|
|
179
180
|
});
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
-
async addRole(guildId, userId, roleId) {
|
|
183
|
-
const member = await this.#fetchMember(guildId, userId);
|
|
184
|
-
await member.roles.add(roleId);
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
async removeRole(guildId, userId, roleId) {
|
|
188
|
-
const member = await this.#fetchMember(guildId, userId);
|
|
189
|
-
await member.roles.remove(roleId);
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
async getRoles(guildId) {
|
|
193
|
-
const guild = await this.#requireClient().guilds.fetch(guildId);
|
|
194
|
-
await guild.roles.fetch();
|
|
195
|
-
const cache = guild.roles.cache;
|
|
196
|
-
return [...cache.values()].map((role) => ({
|
|
197
|
-
id: role.id,
|
|
198
|
-
name: role.name,
|
|
199
|
-
color: role.hexColor,
|
|
200
|
-
position: role.position,
|
|
201
|
-
permissions: role.permissions.bitfield.toString(),
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
async createThread(channelId, name, messageId, autoArchiveDuration) {
|
|
205
|
-
const channel = await this.#requireClient().channels.fetch(channelId);
|
|
206
|
-
if (!channel || !('threads' in channel) || !channel.threads) {
|
|
207
|
-
throw new Error(`Channel ${channelId} 不支持创建帖子`);
|
|
208
|
-
}
|
|
209
|
-
const options = {
|
|
210
|
-
name,
|
|
211
|
-
autoArchiveDuration: autoArchiveDuration || 1440,
|
|
212
|
-
};
|
|
213
|
-
if (messageId)
|
|
214
|
-
options.startMessage = messageId;
|
|
215
|
-
return channel.threads.create(options);
|
|
216
|
-
}
|
|
217
|
-
async addReaction(channelId, messageId, emoji) {
|
|
182
|
+
async #addReaction(channelId, messageId, emoji) {
|
|
218
183
|
const channel = await this.#requireClient().channels.fetch(channelId);
|
|
219
184
|
if (!channel?.isTextBased() || !channel.messages) {
|
|
220
185
|
throw new Error(`Channel ${channelId} 不是文本频道`);
|
|
@@ -222,75 +187,14 @@ export class DiscordGatewayEndpoint {
|
|
|
222
187
|
const message = await channel.messages.fetch(messageId);
|
|
223
188
|
await message.react(emoji);
|
|
224
189
|
}
|
|
225
|
-
async
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
throw new Error(`Channel ${channelId} 不是论坛频道`);
|
|
234
|
-
}
|
|
235
|
-
const options = {
|
|
236
|
-
name,
|
|
237
|
-
message: { content },
|
|
238
|
-
};
|
|
239
|
-
if (tags?.length && channel.availableTags?.length) {
|
|
240
|
-
const tagIds = channel.availableTags
|
|
241
|
-
.filter((t) => tags.includes(t.name))
|
|
242
|
-
.map((t) => t.id);
|
|
243
|
-
if (tagIds.length)
|
|
244
|
-
options.appliedTags = tagIds;
|
|
245
|
-
}
|
|
246
|
-
return channel.threads.create(options);
|
|
247
|
-
}
|
|
248
|
-
async kickMember(guildId, userId, reason) {
|
|
249
|
-
const member = await this.#fetchMember(guildId, userId);
|
|
250
|
-
await member.kick(reason);
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
async banMember(guildId, userId, reason) {
|
|
254
|
-
const guild = await this.#requireClient().guilds.fetch(guildId);
|
|
255
|
-
await guild.members.ban(userId, { reason });
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
async unbanMember(guildId, userId, reason) {
|
|
259
|
-
const guild = await this.#requireClient().guilds.fetch(guildId);
|
|
260
|
-
await guild.members.unban(userId, reason);
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
|
-
async timeoutMember(guildId, userId, duration = 600, reason) {
|
|
264
|
-
const member = await this.#fetchMember(guildId, userId);
|
|
265
|
-
await member.timeout(duration === 0 ? null : duration * 1000, reason);
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
async setNickname(guildId, userId, nickname) {
|
|
269
|
-
const member = await this.#fetchMember(guildId, userId);
|
|
270
|
-
await member.setNickname(nickname);
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
async getMembers(guildId, limit = 100) {
|
|
274
|
-
const guild = await this.#requireClient().guilds.fetch(guildId);
|
|
275
|
-
const members = await guild.members.fetch({ limit });
|
|
276
|
-
return [...members.values()].map((member) => ({
|
|
277
|
-
id: member.id,
|
|
278
|
-
username: member.user.username,
|
|
279
|
-
nickname: member.nickname,
|
|
280
|
-
roles: member.roles.cache.map((r) => r.id),
|
|
281
|
-
joined_at: member.joinedAt?.toISOString(),
|
|
282
|
-
}));
|
|
283
|
-
}
|
|
284
|
-
async getGuildInfo(guildId) {
|
|
285
|
-
const guild = await this.#requireClient().guilds.fetch(guildId);
|
|
286
|
-
return {
|
|
287
|
-
id: guild.id,
|
|
288
|
-
name: guild.name,
|
|
289
|
-
icon: guild.iconURL?.(),
|
|
290
|
-
owner_id: guild.ownerId,
|
|
291
|
-
member_count: guild.memberCount,
|
|
292
|
-
created_at: guild.createdAt?.toISOString(),
|
|
293
|
-
};
|
|
190
|
+
async #emitPlatformEvent(name, event) {
|
|
191
|
+
await this.emitPlatform(name, event).catch((error) => {
|
|
192
|
+
this.#logger.warn(formatCompact({
|
|
193
|
+
op: 'discord_platform_event_failed',
|
|
194
|
+
event: name,
|
|
195
|
+
error: error instanceof Error ? error.message : String(error),
|
|
196
|
+
}));
|
|
197
|
+
});
|
|
294
198
|
}
|
|
295
199
|
async #sendBody(channelId, body) {
|
|
296
200
|
const channel = await this.#requireClient().channels.fetch(channelId);
|
|
@@ -301,17 +205,45 @@ export class DiscordGatewayEndpoint {
|
|
|
301
205
|
const result = await channel.send(options);
|
|
302
206
|
return result.id;
|
|
303
207
|
}
|
|
304
|
-
async #fetchMember(guildId, userId) {
|
|
305
|
-
const guild = await this.#requireClient().guilds.fetch(guildId);
|
|
306
|
-
return guild.members.fetch(userId);
|
|
307
|
-
}
|
|
308
208
|
#requireClient() {
|
|
309
209
|
if (!this.#client)
|
|
310
210
|
throw new Error('Discord client not connected');
|
|
311
211
|
return this.#client;
|
|
312
212
|
}
|
|
313
213
|
}
|
|
314
|
-
|
|
214
|
+
/** Minimal Discord REST client used when Gateway is intentionally disabled. */
|
|
215
|
+
export class DiscordRestClient {
|
|
216
|
+
token;
|
|
217
|
+
fetch;
|
|
218
|
+
constructor(token, fetch = globalThis.fetch) {
|
|
219
|
+
this.token = token;
|
|
220
|
+
this.fetch = fetch;
|
|
221
|
+
}
|
|
222
|
+
async request(method, path, body) {
|
|
223
|
+
const response = await this.fetch(`${DISCORD_API}${path}`, {
|
|
224
|
+
method,
|
|
225
|
+
headers: {
|
|
226
|
+
Authorization: `Bot ${this.token}`,
|
|
227
|
+
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
|
|
228
|
+
},
|
|
229
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
230
|
+
signal: AbortSignal.timeout(OUTBOUND_TIMEOUT_MS),
|
|
231
|
+
});
|
|
232
|
+
const text = await response.text();
|
|
233
|
+
if (!response.ok) {
|
|
234
|
+
throw new Error(`Discord API ${method} ${path} failed (${response.status}): ${text.slice(0, 200)}`);
|
|
235
|
+
}
|
|
236
|
+
return (text ? JSON.parse(text) : undefined);
|
|
237
|
+
}
|
|
238
|
+
createMessage(channelId, body) {
|
|
239
|
+
return this.request('POST', `/channels/${channelId}/messages`, body);
|
|
240
|
+
}
|
|
241
|
+
deleteMessage(channelId, messageId) {
|
|
242
|
+
return this.request('DELETE', `/channels/${channelId}/messages/${messageId}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
export class DiscordInteractionsEndpoint extends Endpoint {
|
|
246
|
+
client;
|
|
315
247
|
#logger;
|
|
316
248
|
#options;
|
|
317
249
|
#fetch;
|
|
@@ -325,9 +257,11 @@ export class DiscordInteractionsEndpoint {
|
|
|
325
257
|
resolve: (reference, context) => resolveDiscordContent(this.#fetch, this.#options.config.token, reference, context),
|
|
326
258
|
});
|
|
327
259
|
constructor(options) {
|
|
260
|
+
super();
|
|
328
261
|
this.#logger = getAdapterLogger('discord', options.config.id);
|
|
329
262
|
this.#options = options;
|
|
330
263
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
264
|
+
this.client = new DiscordRestClient(options.config.token, this.#fetch);
|
|
331
265
|
}
|
|
332
266
|
get isOpen() {
|
|
333
267
|
return this.#open;
|
|
@@ -362,42 +296,19 @@ export class DiscordInteractionsEndpoint {
|
|
|
362
296
|
}
|
|
363
297
|
async send({ conversation, payload }) {
|
|
364
298
|
const body = formatOutboundBody(payload);
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
method: 'POST',
|
|
368
|
-
headers: {
|
|
369
|
-
Authorization: `Bot ${this.#options.config.token}`,
|
|
370
|
-
'Content-Type': 'application/json',
|
|
371
|
-
},
|
|
372
|
-
body: JSON.stringify(body),
|
|
373
|
-
signal: AbortSignal.timeout(OUTBOUND_TIMEOUT_MS),
|
|
374
|
-
});
|
|
375
|
-
const text = await response.text();
|
|
376
|
-
if (!response.ok) {
|
|
377
|
-
throw new Error(`Discord send failed (${response.status}): ${text.slice(0, 200)}`);
|
|
378
|
-
}
|
|
379
|
-
const data = JSON.parse(text);
|
|
380
|
-
const snowflake = data.id ?? '';
|
|
381
|
-
return snowflake;
|
|
299
|
+
const data = await this.client.createMessage(conversation.id, body);
|
|
300
|
+
return data.id ?? '';
|
|
382
301
|
}
|
|
383
302
|
async recallMessage(message) {
|
|
384
303
|
if (!message.id)
|
|
385
304
|
return;
|
|
386
|
-
|
|
387
|
-
method: 'DELETE',
|
|
388
|
-
headers: { Authorization: `Bot ${this.#options.config.token}` },
|
|
389
|
-
signal: AbortSignal.timeout(OUTBOUND_TIMEOUT_MS),
|
|
390
|
-
});
|
|
391
|
-
if (!response.ok && response.status !== 404) {
|
|
392
|
-
const text = await response.text();
|
|
393
|
-
throw new Error(`Discord recall failed (${response.status}): ${text.slice(0, 200)}`);
|
|
394
|
-
}
|
|
305
|
+
await this.client.deleteMessage(message.conversation.id, message.id);
|
|
395
306
|
}
|
|
396
307
|
admit(msg) {
|
|
397
308
|
if (!this.#open)
|
|
398
309
|
return;
|
|
399
310
|
const conversation = discordInboundConversation(String(this.#options.id), msg);
|
|
400
|
-
void this
|
|
311
|
+
void this.emit('message.receive', {
|
|
401
312
|
conversation,
|
|
402
313
|
message: { conversation, id: msg.id },
|
|
403
314
|
content: formatInboundContent(msg),
|
|
@@ -422,6 +333,18 @@ export class DiscordInteractionsEndpoint {
|
|
|
422
333
|
}));
|
|
423
334
|
});
|
|
424
335
|
}
|
|
336
|
+
admitPlatform(event) {
|
|
337
|
+
if (!this.#open)
|
|
338
|
+
return;
|
|
339
|
+
const type = typeof event.type === 'number' ? `interaction.${event.type}` : 'interaction';
|
|
340
|
+
void this.emitPlatform(type, event).catch((error) => {
|
|
341
|
+
this.#logger.warn(formatCompact({
|
|
342
|
+
op: 'discord_platform_event_failed',
|
|
343
|
+
event: type,
|
|
344
|
+
error: error instanceof Error ? error.message : String(error),
|
|
345
|
+
}));
|
|
346
|
+
});
|
|
347
|
+
}
|
|
425
348
|
}
|
|
426
349
|
async function resolveDiscordContent(fetch, token, reference, context) {
|
|
427
350
|
if (reference.kind === 'forward') {
|
|
@@ -485,11 +408,11 @@ function toGroupId(id) {
|
|
|
485
408
|
* DiscordGatewayEndpoint 的 EndpointManagement 语义端口(参照 qq 的工厂模式)。
|
|
486
409
|
* 数据源为 discord.js SDK 缓存:guilds.cache / guild.channels.cache / guild.members。
|
|
487
410
|
*/
|
|
488
|
-
export function createDiscordEndpointManagement(
|
|
411
|
+
export function createDiscordEndpointManagement(requireClient) {
|
|
489
412
|
return Object.freeze({
|
|
490
413
|
async listGroups() {
|
|
491
414
|
const groups = [];
|
|
492
|
-
for (const guild of
|
|
415
|
+
for (const guild of requireClient().guilds.cache.values()) {
|
|
493
416
|
if (!guild?.id)
|
|
494
417
|
continue;
|
|
495
418
|
groups.push({
|
|
@@ -501,7 +424,7 @@ export function createDiscordEndpointManagement(endpoint) {
|
|
|
501
424
|
},
|
|
502
425
|
async listChannels() {
|
|
503
426
|
const channels = [];
|
|
504
|
-
for (const guild of
|
|
427
|
+
for (const guild of requireClient().guilds.cache.values()) {
|
|
505
428
|
if (!guild?.id)
|
|
506
429
|
continue;
|
|
507
430
|
const guildId = String(guild.id);
|
|
@@ -521,7 +444,15 @@ export function createDiscordEndpointManagement(endpoint) {
|
|
|
521
444
|
return channels;
|
|
522
445
|
},
|
|
523
446
|
async listGroupMembers(groupId) {
|
|
524
|
-
|
|
447
|
+
const guild = await requireClient().guilds.fetch(groupId);
|
|
448
|
+
const members = await guild.members.fetch({ limit: 100 });
|
|
449
|
+
return [...members.values()].map((member) => ({
|
|
450
|
+
id: member.id,
|
|
451
|
+
username: member.user.username,
|
|
452
|
+
nickname: member.nickname,
|
|
453
|
+
roles: member.roles.cache.map((role) => role.id),
|
|
454
|
+
joined_at: member.joinedAt?.toISOString(),
|
|
455
|
+
}));
|
|
525
456
|
},
|
|
526
457
|
});
|
|
527
458
|
}
|
package/lib/gateway.d.ts
CHANGED
|
@@ -127,6 +127,7 @@ export declare function resolveSenderRole(msg: DiscordInboundMessage): string |
|
|
|
127
127
|
export declare function normalizeDiscordMessage(raw: unknown): DiscordInboundMessage | null;
|
|
128
128
|
export declare function toMessageCreateOptions(body: DiscordOutboundBody): Promise<MessageCreateOptions>;
|
|
129
129
|
export interface DiscordGatewayConnectHandlers {
|
|
130
|
+
onPlatformEvent(name: string, event: unknown): void;
|
|
130
131
|
onMessage(msg: DiscordInboundMessage): void;
|
|
131
132
|
onButton(interaction: DiscordButtonInbound): void;
|
|
132
133
|
onGuildMemberAdd?(member: {
|
package/lib/gateway.js
CHANGED
|
@@ -198,6 +198,7 @@ export async function connectDiscordGatewayClient(client, config, handlers) {
|
|
|
198
198
|
return new Promise((resolve, reject) => {
|
|
199
199
|
let settled = false;
|
|
200
200
|
client.on('messageCreate', (raw) => {
|
|
201
|
+
handlers.onPlatformEvent('messageCreate', raw);
|
|
201
202
|
const msg = normalizeDiscordMessage(raw);
|
|
202
203
|
if (!msg)
|
|
203
204
|
return;
|
|
@@ -208,6 +209,7 @@ export async function connectDiscordGatewayClient(client, config, handlers) {
|
|
|
208
209
|
handlers.onMessage(mentionedBot ? { ...msg, mentionedBot: true } : msg);
|
|
209
210
|
});
|
|
210
211
|
client.on('interactionCreate', (raw) => {
|
|
212
|
+
handlers.onPlatformEvent('interactionCreate', raw);
|
|
211
213
|
const interaction = raw;
|
|
212
214
|
if (!interaction.isButton?.())
|
|
213
215
|
return;
|
|
@@ -225,6 +227,7 @@ export async function connectDiscordGatewayClient(client, config, handlers) {
|
|
|
225
227
|
});
|
|
226
228
|
});
|
|
227
229
|
client.on('guildMemberAdd', (raw) => {
|
|
230
|
+
handlers.onPlatformEvent('guildMemberAdd', raw);
|
|
228
231
|
const member = raw;
|
|
229
232
|
const guildId = member.guild?.id;
|
|
230
233
|
const userId = member.user?.id;
|
|
@@ -237,6 +240,7 @@ export async function connectDiscordGatewayClient(client, config, handlers) {
|
|
|
237
240
|
});
|
|
238
241
|
});
|
|
239
242
|
client.on('guildMemberRemove', (raw) => {
|
|
243
|
+
handlers.onPlatformEvent('guildMemberRemove', raw);
|
|
240
244
|
const member = raw;
|
|
241
245
|
const guildId = member.guild?.id;
|
|
242
246
|
const userId = member.user?.id;
|
|
@@ -249,6 +253,7 @@ export async function connectDiscordGatewayClient(client, config, handlers) {
|
|
|
249
253
|
});
|
|
250
254
|
});
|
|
251
255
|
client.once('clientReady', () => {
|
|
256
|
+
handlers.onPlatformEvent('clientReady', client.user);
|
|
252
257
|
void (async () => {
|
|
253
258
|
try {
|
|
254
259
|
if (config.defaultActivity && client.user?.setActivity) {
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { activityTypeCode, formatButtonContent, formatInboundContent, formatOutboundBody, resolveChannelKind, resolveDiscordConfig, senderDisplayName, type DiscordAdapterConfig, type DiscordButtonInbound, type DiscordInboundAttachment, type DiscordInboundMessage, type DiscordOutboundBody, type DiscordWireSegment, type ResolvedDiscordConfig, type ResolvedDiscordGatewayConfig, type ResolvedDiscordInteractionsConfig, } from './protocol.js';
|
|
2
|
-
export {
|
|
2
|
+
export { discordClient, type DiscordClient, type DiscordClientEventMap, } from './client.js';
|
|
3
3
|
export { checkDiscordPlatformPermit, discordGroupPermitResolver, normalizeDiscordSenderForPermit, platformPermit, } from './platform-permit.js';
|
|
4
4
|
export { DiscordGatewayEndpoint, DiscordInteractionsEndpoint, type CreateDiscordClient, type DiscordClientTransport, type DiscordEndpointOptions, type DiscordInteractionsEndpointOptions, } from './endpoint.js';
|
|
5
5
|
export { connectDiscordGatewayClient, defaultCreateClient, normalizeDiscordMessage, resolveSenderRole, toMessageCreateOptions, type DiscordGatewayConnectHandlers, } from './gateway.js';
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { activityTypeCode, formatButtonContent, formatInboundContent, formatOutboundBody, resolveChannelKind, resolveDiscordConfig, senderDisplayName, } from './protocol.js';
|
|
2
|
-
export {
|
|
2
|
+
export { discordClient, } from './client.js';
|
|
3
3
|
export { checkDiscordPlatformPermit, discordGroupPermitResolver, normalizeDiscordSenderForPermit, platformPermit, } from './platform-permit.js';
|
|
4
4
|
export { DiscordGatewayEndpoint, DiscordInteractionsEndpoint, } from './endpoint.js';
|
|
5
5
|
export { connectDiscordGatewayClient, defaultCreateClient, normalizeDiscordMessage, resolveSenderRole, toMessageCreateOptions, } from './gateway.js';
|
package/lib/protocol.d.ts
CHANGED
|
@@ -134,7 +134,7 @@ export declare function discordInboundConversation(endpointKey: string, msg: {
|
|
|
134
134
|
readonly guildId?: string;
|
|
135
135
|
}): ConversationRef;
|
|
136
136
|
export declare function senderDisplayName(msg: DiscordInboundMessage): string;
|
|
137
|
-
/** Build inbound text for
|
|
137
|
+
/** Build inbound text for OutboundMessageService.receive (gateway owns reply routing). */
|
|
138
138
|
export declare function formatInboundContent(msg: DiscordInboundMessage): string;
|
|
139
139
|
export declare function formatButtonContent(interaction: DiscordButtonInbound): string;
|
|
140
140
|
/**
|
package/lib/protocol.js
CHANGED
|
@@ -77,7 +77,7 @@ export function discordInboundConversation(endpointKey, msg) {
|
|
|
77
77
|
export function senderDisplayName(msg) {
|
|
78
78
|
return msg.authorName || msg.authorId;
|
|
79
79
|
}
|
|
80
|
-
/** Build inbound text for
|
|
80
|
+
/** Build inbound text for OutboundMessageService.receive (gateway owns reply routing). */
|
|
81
81
|
export function formatInboundContent(msg) {
|
|
82
82
|
const parts = [];
|
|
83
83
|
if (msg.content?.trim())
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EndpointEventEmitter } from 'zhin.js/adapter';
|
|
2
2
|
import { type getAdapterLogger } from '@zhin.js/logger';
|
|
3
3
|
export interface DiscordGuildMemberSideEvent {
|
|
4
4
|
readonly guildId: string;
|
|
5
5
|
readonly userId: string;
|
|
6
6
|
readonly userName?: string;
|
|
7
7
|
}
|
|
8
|
-
export declare function receiveDiscordGuildMemberSideEvent(
|
|
8
|
+
export declare function receiveDiscordGuildMemberSideEvent(emit: EndpointEventEmitter, configId: string, kind: 'member_increase' | 'member_decrease', event: DiscordGuildMemberSideEvent, logger: ReturnType<typeof getAdapterLogger>): void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { buildNotice, senderFromId } from '@zhin.js/core';
|
|
2
2
|
import { formatCompact } from '@zhin.js/logger';
|
|
3
|
-
export function receiveDiscordGuildMemberSideEvent(
|
|
4
|
-
if (!
|
|
3
|
+
export function receiveDiscordGuildMemberSideEvent(emit, configId, kind, event, logger) {
|
|
4
|
+
if (!emit)
|
|
5
5
|
return;
|
|
6
|
-
void
|
|
6
|
+
void emit('notice.receive', buildNotice(event, {
|
|
7
7
|
$id: `discord:guild_member:${kind}:${event.guildId}:${event.userId}:${Date.now()}`,
|
|
8
8
|
$adapter: 'discord',
|
|
9
9
|
$endpoint: configId,
|
package/lib/webhook.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface DiscordInteractionsHandler {
|
|
|
8
8
|
readonly config: ResolvedDiscordInteractionsConfig;
|
|
9
9
|
readonly isOpen: boolean;
|
|
10
10
|
admit(msg: DiscordInboundMessage): void;
|
|
11
|
+
admitPlatform(event: Record<string, unknown>): void;
|
|
11
12
|
}
|
|
12
13
|
export declare function registerDiscordInteractionRoutes(http: HttpHost, handler: DiscordInteractionsHandler): HttpRouteRegistration[];
|
|
13
14
|
export declare function handleDiscordInteractionRequest(request: IncomingMessage, response: ServerResponse, handler: DiscordInteractionsHandler): Promise<void>;
|
package/lib/webhook.js
CHANGED
|
@@ -31,6 +31,8 @@ export async function handleDiscordInteractionRequest(request, response, handler
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
const interaction = JSON.parse(rawBody);
|
|
34
|
+
if (handler.isOpen)
|
|
35
|
+
handler.admitPlatform(interaction);
|
|
34
36
|
if (interaction.type === INTERACTION_TYPE_PING) {
|
|
35
37
|
writeJson(response, 200, { type: INTERACTION_RESPONSE_PONG });
|
|
36
38
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-discord",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.1",
|
|
4
4
|
"description": "Zhin.js Discord adapter for Plugin Runtime (Gateway WebSocket)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -33,20 +33,21 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"discord.js": "^14.27.0",
|
|
36
|
-
"@zhin.js/adapter": "1.2.
|
|
37
|
-
"@zhin.js/core": "1.5.
|
|
38
|
-
"@zhin.js/
|
|
36
|
+
"@zhin.js/adapter": "1.2.1",
|
|
37
|
+
"@zhin.js/core": "1.5.14",
|
|
38
|
+
"@zhin.js/feature-kit": "1.0.13",
|
|
39
|
+
"@zhin.js/host-http": "1.0.13",
|
|
39
40
|
"@zhin.js/im-contract": "1.0.4",
|
|
40
|
-
"@zhin.js/logger": "1.0.
|
|
41
|
-
"@zhin.js/permission": "1.0.
|
|
41
|
+
"@zhin.js/logger": "1.0.77",
|
|
42
|
+
"@zhin.js/permission": "1.0.4"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"zod": "^4.0.0",
|
|
45
|
-
"@zhin.js/adapter": "1.2.
|
|
46
|
-
"@zhin.js/agent": "1.1.
|
|
47
|
-
"@zhin.js/command": "1.0.
|
|
48
|
-
"@zhin.js/core": "1.5.
|
|
49
|
-
"zhin.js": "6.0.
|
|
46
|
+
"@zhin.js/adapter": "1.2.1",
|
|
47
|
+
"@zhin.js/agent": "1.1.16",
|
|
48
|
+
"@zhin.js/command": "1.0.16",
|
|
49
|
+
"@zhin.js/core": "1.5.14",
|
|
50
|
+
"zhin.js": "6.0.14"
|
|
50
51
|
},
|
|
51
52
|
"peerDependenciesMeta": {
|
|
52
53
|
"@zhin.js/agent": {
|
|
@@ -67,9 +68,9 @@
|
|
|
67
68
|
"typescript": "^6.0.3",
|
|
68
69
|
"vitest": "^4.1.10",
|
|
69
70
|
"zod": "^4.4.3",
|
|
70
|
-
"@zhin.js/agent": "1.1.
|
|
71
|
-
"@zhin.js/host-http": "1.0.
|
|
72
|
-
"zhin.js": "6.0.
|
|
71
|
+
"@zhin.js/agent": "1.1.16",
|
|
72
|
+
"@zhin.js/host-http": "1.0.13",
|
|
73
|
+
"zhin.js": "6.0.14"
|
|
73
74
|
},
|
|
74
75
|
"files": [
|
|
75
76
|
"adapters",
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineEndpointClient } from 'zhin.js/adapter';
|
|
2
|
+
import type { DiscordRestClient } from './endpoint.js';
|
|
3
|
+
import type { DiscordClientTransport } from './gateway.js';
|
|
4
|
+
|
|
5
|
+
/** Exact Client variants produced by the Gateway and Interactions Endpoints. */
|
|
6
|
+
export type DiscordClient = DiscordClientTransport | DiscordRestClient;
|
|
7
|
+
export type DiscordClientEventMap = Record<string, unknown>;
|
|
8
|
+
|
|
9
|
+
/** Narrow an adapter Client for Gateway-only SDK operations. */
|
|
10
|
+
export function requireDiscordGatewayClient(client: DiscordClient): DiscordClientTransport {
|
|
11
|
+
if ('guilds' in client && 'channels' in client) return client;
|
|
12
|
+
throw new Error('This Discord tool requires a Gateway Endpoint Client');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module '@zhin.js/feature-kit' {
|
|
16
|
+
interface AdapterClientRegistry {
|
|
17
|
+
readonly discord: {
|
|
18
|
+
readonly client: DiscordClient;
|
|
19
|
+
readonly events: DiscordClientEventMap;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const discordClient = defineEndpointClient<DiscordClient, DiscordClientEventMap>('discord');
|