@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/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
- #unregisterAgent;
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.addReaction(channelId, message.id, emoji);
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.#options.sideEvents, this.#options.config.id, 'member_increase', member, this.#logger);
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.#options.sideEvents, this.#options.config.id, 'member_decrease', member, this.#logger);
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.#options.gateway.receive({
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.#options.gateway.receive({
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
- // ── Agent tool surface ──────────────────────────────────────────────
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 sendEmbed(channelId, embedData) {
226
- const body = { embeds: [embedData] };
227
- const id = await this.#sendBody(channelId, body);
228
- return { id };
229
- }
230
- async createForumPost(channelId, name, content, tags) {
231
- const channel = await this.#requireClient().channels.fetch(channelId);
232
- if (!channel || channel.type !== ChannelType.GuildForum || !channel.threads) {
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
- export class DiscordInteractionsEndpoint {
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 channelId = conversation.id;
366
- const response = await this.#fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
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
- const response = await this.#fetch(`${DISCORD_API}/channels/${message.conversation.id}/messages/${message.id}`, {
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.#options.gateway.receive({
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(endpoint) {
411
+ export function createDiscordEndpointManagement(requireClient) {
489
412
  return Object.freeze({
490
413
  async listGroups() {
491
414
  const groups = [];
492
- for (const guild of endpoint.getClient().guilds.cache.values()) {
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 endpoint.getClient().guilds.cache.values()) {
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
- return endpoint.getMembers(groupId);
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 { getDiscordAgentDeps, registerDiscordAgentEndpoint, setDiscordAgentDeps, type DiscordAgentDeps, type DiscordAgentEndpoint, } from './discord-agent-deps.js';
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 { getDiscordAgentDeps, registerDiscordAgentEndpoint, setDiscordAgentDeps, } from './discord-agent-deps.js';
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 MessageGateway.receive (gateway owns reply routing). */
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 MessageGateway.receive (gateway owns reply routing). */
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 { SideEventGateway } from '@zhin.js/core/runtime';
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(sideEvents: SideEventGateway | undefined, configId: string, kind: 'member_increase' | 'member_decrease', event: DiscordGuildMemberSideEvent, logger: ReturnType<typeof getAdapterLogger>): void;
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(sideEvents, configId, kind, event, logger) {
4
- if (!sideEvents)
3
+ export function receiveDiscordGuildMemberSideEvent(emit, configId, kind, event, logger) {
4
+ if (!emit)
5
5
  return;
6
- void sideEvents.receiveNotice(buildNotice(event, {
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.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.0",
37
- "@zhin.js/core": "1.5.13",
38
- "@zhin.js/host-http": "1.0.12",
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.76",
41
- "@zhin.js/permission": "1.0.3"
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.0",
46
- "@zhin.js/agent": "1.1.15",
47
- "@zhin.js/command": "1.0.15",
48
- "@zhin.js/core": "1.5.13",
49
- "zhin.js": "6.0.13"
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.15",
71
- "@zhin.js/host-http": "1.0.12",
72
- "zhin.js": "6.0.13"
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');