@zhin.js/adapter-discord 7.0.1 → 7.0.3

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 CHANGED
@@ -1,5 +1,29 @@
1
1
  # @zhin.js/adapter-discord
2
2
 
3
+ ## 7.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [afc0e66]
8
+ - Updated dependencies [2e41ad5]
9
+ - Updated dependencies [9f57124]
10
+ - @zhin.js/core@1.5.2
11
+ - @zhin.js/adapter@1.1.5
12
+ - @zhin.js/im-contract@1.0.1
13
+ - @zhin.js/plugin-runtime@1.1.3
14
+ - @zhin.js/command@1.0.7
15
+ - @zhin.js/host-http@1.0.6
16
+ - @zhin.js/agent@1.1.3
17
+ - zhin.js@6.0.2
18
+
19
+ ## 7.0.2
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [696ab1b]
24
+ - @zhin.js/agent@1.1.2
25
+ - zhin.js@6.0.1
26
+
3
27
  ## 7.0.1
4
28
 
5
29
  ### Patch Changes
package/README.md CHANGED
@@ -7,7 +7,7 @@ Zhin.js Discord 适配器(Plugin Runtime),默认通过 **Gateway WebSocket
7
7
  - Gateway WebSocket 入站(默认;无需公网 HTTPS / host)
8
8
  - 解析 text / mention / attachment / embed / sticker / button
9
9
  - 支持私聊、群组与服务器频道
10
- - 出站 `send({ target, payload })` → Discord channel message(text / media / embed / keyboard)
10
+ - 出站 `send({ conversation, payload })` → Discord channel message(text / media / embed / keyboard)
11
11
  - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
12
12
  - Interactions HTTP webhook 延期(需 `httpHostToken`);配置 `connection: interactions` 会明确报错
13
13
 
@@ -25,8 +25,8 @@ pnpm add @zhin.js/adapter-discord discord.js
25
25
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
26
  - **无需** `@zhin.js/host-http` / `@zhin.js/host-router`(Gateway 路径)
27
27
 
28
- 入站:`gateway.receive({ adapter, target: channelId, content: text, sender, metadata })`
29
- 出站:`send({ target, payload })` → discord.js channel.send
28
+ 入站:`gateway.receive({ conversation, message, content: text, sender, metadata })`(`conversation` 为 `ConversationRef`,guild 频道带 `parent` guild 容器)
29
+ 出站:`send({ conversation, payload })` → discord.js channel.send
30
30
 
31
31
  ### 平台权限(platform permit)
32
32
 
package/lib/endpoint.d.ts CHANGED
@@ -20,7 +20,7 @@ export declare class DiscordGatewayEndpoint implements EndpointInstance {
20
20
  open(): void;
21
21
  close(): void;
22
22
  stop(): Promise<void>;
23
- send({ target, conversation, payload }: EndpointSendRequest): Promise<string>;
23
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
24
24
  recallMessage(messageId: string): Promise<void>;
25
25
  /** Test / internal: admit a message when open. */
26
26
  admit(msg: DiscordInboundMessage): void;
@@ -64,7 +64,7 @@ export declare class DiscordInteractionsEndpoint implements EndpointInstance {
64
64
  open(): void;
65
65
  close(): void;
66
66
  stop(): Promise<void>;
67
- send({ target, conversation, payload }: EndpointSendRequest): Promise<string>;
67
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
68
68
  recallMessage(messageId: string): Promise<void>;
69
69
  admit(msg: DiscordInboundMessage): void;
70
70
  }
package/lib/endpoint.js CHANGED
@@ -2,11 +2,11 @@
2
2
  * DiscordEndpoint — lifecycle, outbound, admit, gateway / interactions modes, agent tool surface.
3
3
  */
4
4
  import { ChannelType } from 'discord.js';
5
- import { formatLegacyMessageReference, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
5
+ import { formatLegacyConversationRef, formatLegacyMessageReference, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
6
6
  import { formatCompact, getLogger } from '@zhin.js/logger';
7
7
  import { registerDiscordAgentEndpoint } from './discord-agent-deps.js';
8
8
  import { connectDiscordGatewayClient, defaultCreateClient, DEFAULT_INTENTS, resolveSenderRole, toMessageCreateOptions, } from './gateway.js';
9
- import { formatButtonContent, formatButtonSegments, formatInboundContent, formatInboundSegments, formatOutboundBody, senderDisplayName, } from './protocol.js';
9
+ import { discordInboundConversation, formatButtonContent, formatButtonSegments, formatInboundContent, formatInboundSegments, formatOutboundBody, senderDisplayName, } from './protocol.js';
10
10
  import { registerDiscordInteractionRoutes } from './webhook.js';
11
11
  const DISCORD_API = 'https://discord.com/api/v10';
12
12
  /** 出站 HTTP 调用统一 30s 超时。 */
@@ -90,10 +90,11 @@ export class DiscordGatewayEndpoint {
90
90
  this.#started = false;
91
91
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
92
92
  }
93
- async send({ target, conversation, payload }) {
93
+ async send({ conversation, payload }) {
94
94
  const body = formatOutboundBody(payload);
95
- const channelId = nativeConversationId(target, conversation);
96
- const snowflake = await this.#sendBody(channelId, body);
95
+ // recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生 target
96
+ const target = formatLegacyConversationRef(conversation);
97
+ const snowflake = await this.#sendBody(conversation.id, body);
97
98
  const messageId = formatLegacyMessageReference({ target, messageId: snowflake });
98
99
  logger.debug(formatCompact({
99
100
  op: 'discord_send',
@@ -123,13 +124,13 @@ export class DiscordGatewayEndpoint {
123
124
  return;
124
125
  if (msg.authorBot)
125
126
  return;
127
+ const conversation = discordInboundConversation(String(this.#options.id), msg);
126
128
  void this.#options.gateway.receive({
127
- adapter: this.#options.id,
128
- target: msg.channelId,
129
+ conversation,
130
+ message: { conversation, id: msg.id },
129
131
  content: formatInboundContent(msg),
130
132
  segments: formatInboundSegments(msg),
131
133
  sender: senderDisplayName(msg),
132
- id: msg.id,
133
134
  metadata: Object.freeze({
134
135
  endpoint: this.#options.config.name,
135
136
  channelKind: msg.channelKind,
@@ -142,7 +143,7 @@ export class DiscordGatewayEndpoint {
142
143
  }).catch((err) => {
143
144
  logger.warn(formatCompact({
144
145
  op: 'discord_gateway_receive_failed',
145
- target: msg.channelId,
146
+ target: `${conversation.kind}:${conversation.id}`,
146
147
  error: err instanceof Error ? err.message : String(err),
147
148
  }));
148
149
  });
@@ -151,13 +152,13 @@ export class DiscordGatewayEndpoint {
151
152
  admitButton(interaction) {
152
153
  if (!this.#open)
153
154
  return;
155
+ const conversation = discordInboundConversation(String(this.#options.id), interaction);
154
156
  void this.#options.gateway.receive({
155
- adapter: this.#options.id,
156
- target: interaction.channelId,
157
+ conversation,
158
+ message: { conversation, id: interaction.id },
157
159
  content: formatButtonContent(interaction),
158
160
  segments: formatButtonSegments(interaction),
159
161
  sender: interaction.userName,
160
- id: interaction.id,
161
162
  metadata: Object.freeze({
162
163
  endpoint: this.#options.config.name,
163
164
  eventType: 'button',
@@ -167,7 +168,7 @@ export class DiscordGatewayEndpoint {
167
168
  }).catch((err) => {
168
169
  logger.warn(formatCompact({
169
170
  op: 'discord_gateway_receive_failed',
170
- target: interaction.channelId,
171
+ target: `${conversation.kind}:${conversation.id}`,
171
172
  error: err instanceof Error ? err.message : String(err),
172
173
  }));
173
174
  });
@@ -349,9 +350,9 @@ export class DiscordInteractionsEndpoint {
349
350
  this.#started = false;
350
351
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
351
352
  }
352
- async send({ target, conversation, payload }) {
353
+ async send({ conversation, payload }) {
353
354
  const body = formatOutboundBody(payload);
354
- const channelId = nativeConversationId(target, conversation);
355
+ const channelId = conversation.id;
355
356
  const response = await this.#fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
356
357
  method: 'POST',
357
358
  headers: {
@@ -367,7 +368,13 @@ export class DiscordInteractionsEndpoint {
367
368
  }
368
369
  const data = JSON.parse(text);
369
370
  const snowflake = data.id ?? '';
370
- return snowflake ? formatLegacyMessageReference({ target, messageId: snowflake }) : '';
371
+ // recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生 target
372
+ return snowflake
373
+ ? formatLegacyMessageReference({
374
+ target: formatLegacyConversationRef(conversation),
375
+ messageId: snowflake,
376
+ })
377
+ : '';
371
378
  }
372
379
  async recallMessage(messageId) {
373
380
  if (!messageId)
@@ -390,13 +397,13 @@ export class DiscordInteractionsEndpoint {
390
397
  admit(msg) {
391
398
  if (!this.#open)
392
399
  return;
400
+ const conversation = discordInboundConversation(String(this.#options.id), msg);
393
401
  void this.#options.gateway.receive({
394
- adapter: this.#options.id,
395
- target: msg.channelId,
402
+ conversation,
403
+ message: { conversation, id: msg.id },
396
404
  content: formatInboundContent(msg),
397
405
  segments: formatInboundSegments(msg),
398
406
  sender: senderDisplayName(msg),
399
- id: msg.id,
400
407
  metadata: Object.freeze({
401
408
  endpoint: this.#options.config.name,
402
409
  channelKind: msg.channelKind,
@@ -407,7 +414,7 @@ export class DiscordInteractionsEndpoint {
407
414
  }).catch((err) => {
408
415
  logger.warn(formatCompact({
409
416
  op: 'discord_gateway_receive_failed',
410
- target: msg.channelId,
417
+ target: `${conversation.kind}:${conversation.id}`,
411
418
  error: err instanceof Error ? err.message : String(err),
412
419
  }));
413
420
  });
package/lib/protocol.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Canonicalization is owned by gateway/core before endpoint.send.
4
4
  */
5
5
  import type { Segment } from '@zhin.js/core/runtime';
6
+ import type { ConversationRef } from '@zhin.js/im-contract';
6
7
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
7
8
  export interface DiscordAdapterConfig {
8
9
  readonly name?: string;
@@ -122,6 +123,16 @@ export interface DiscordOutboundBody {
122
123
  }
123
124
  export declare function resolveDiscordConfig(config?: DiscordAdapterConfig): ResolvedDiscordConfig;
124
125
  export declare function resolveChannelKind(channelType: number | string | undefined): 'private' | 'group' | 'channel';
126
+ /**
127
+ * 入站归一化 → ConversationRef:DM → 'private';GroupDM → 'group';
128
+ * guild 频道 → 'channel' + guild 容器进 parent(guild 是频道容器,kind 'channel')。
129
+ * endpoint.adapter = owner PluginId(CapabilityId 以 \0 分隔)。
130
+ */
131
+ export declare function discordInboundConversation(endpointId: string, msg: {
132
+ readonly channelId: string;
133
+ readonly channelKind: 'private' | 'group' | 'channel';
134
+ readonly guildId?: string;
135
+ }): ConversationRef;
125
136
  export declare function senderDisplayName(msg: DiscordInboundMessage): string;
126
137
  /** Build inbound text for MessageGateway.receive (gateway owns reply routing). */
127
138
  export declare function formatInboundContent(msg: DiscordInboundMessage): string;
package/lib/protocol.js CHANGED
@@ -59,6 +59,21 @@ export function resolveChannelKind(channelType) {
59
59
  return 'group';
60
60
  return 'channel';
61
61
  }
62
+ /**
63
+ * 入站归一化 → ConversationRef:DM → 'private';GroupDM → 'group';
64
+ * guild 频道 → 'channel' + guild 容器进 parent(guild 是频道容器,kind 'channel')。
65
+ * endpoint.adapter = owner PluginId(CapabilityId 以 \0 分隔)。
66
+ */
67
+ export function discordInboundConversation(endpointId, msg) {
68
+ return {
69
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
70
+ kind: msg.channelKind,
71
+ id: msg.channelId,
72
+ ...(msg.guildId && msg.channelKind === 'channel'
73
+ ? { parent: { kind: 'channel', id: msg.guildId } }
74
+ : {}),
75
+ };
76
+ }
62
77
  export function senderDisplayName(msg) {
63
78
  return msg.authorName || msg.authorId;
64
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-discord",
3
- "version": "7.0.1",
3
+ "version": "7.0.3",
4
4
  "description": "Zhin.js Discord adapter for Plugin Runtime (Gateway WebSocket)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -33,21 +33,21 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "discord.js": "^14.27.0",
36
- "@zhin.js/adapter": "1.1.4",
37
- "@zhin.js/command": "1.0.6",
38
- "@zhin.js/core": "1.5.1",
39
- "@zhin.js/host-http": "1.0.5",
40
- "@zhin.js/im-contract": "1.0.0",
36
+ "@zhin.js/adapter": "1.1.5",
37
+ "@zhin.js/command": "1.0.7",
38
+ "@zhin.js/core": "1.5.2",
39
+ "@zhin.js/host-http": "1.0.6",
40
+ "@zhin.js/im-contract": "1.0.1",
41
41
  "@zhin.js/logger": "1.0.75",
42
- "@zhin.js/plugin-runtime": "1.1.2"
42
+ "@zhin.js/plugin-runtime": "1.1.3"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "zod": "^4.0.0",
46
- "@zhin.js/adapter": "1.1.4",
47
- "@zhin.js/agent": "1.1.1",
48
- "@zhin.js/core": "1.5.1",
49
- "@zhin.js/plugin-runtime": "1.1.2",
50
- "zhin.js": "6.0.1"
46
+ "@zhin.js/adapter": "1.1.5",
47
+ "@zhin.js/agent": "1.1.3",
48
+ "@zhin.js/core": "1.5.2",
49
+ "@zhin.js/plugin-runtime": "1.1.3",
50
+ "zhin.js": "6.0.2"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "zhin.js": {
@@ -65,9 +65,9 @@
65
65
  "typescript": "^6.0.3",
66
66
  "vitest": "^4.1.10",
67
67
  "zod": "^4.4.3",
68
- "@zhin.js/agent": "1.1.1",
69
- "@zhin.js/host-http": "1.0.5",
70
- "zhin.js": "6.0.1"
68
+ "@zhin.js/agent": "1.1.3",
69
+ "@zhin.js/host-http": "1.0.6",
70
+ "zhin.js": "6.0.2"
71
71
  },
72
72
  "files": [
73
73
  "adapters",
package/src/endpoint.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  import type { MessageGateway } from '@zhin.js/core/runtime';
14
14
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
15
15
  import {
16
+ formatLegacyConversationRef,
16
17
  formatLegacyMessageReference,
17
18
  nativeConversationId,
18
19
  parseLegacyMessageReference,
@@ -30,6 +31,7 @@ import {
30
31
  type DiscordClientTransport,
31
32
  } from './gateway.js';
32
33
  import {
34
+ discordInboundConversation,
33
35
  formatButtonContent,
34
36
  formatButtonSegments,
35
37
  formatInboundContent,
@@ -145,10 +147,11 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
145
147
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
146
148
  }
147
149
 
148
- async send({ target, conversation, payload }: EndpointSendRequest): Promise<string> {
150
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
149
151
  const body = formatOutboundBody(payload);
150
- const channelId = nativeConversationId(target, conversation);
151
- const snowflake = await this.#sendBody(channelId, body);
152
+ // recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生 target
153
+ const target = formatLegacyConversationRef(conversation);
154
+ const snowflake = await this.#sendBody(conversation.id, body);
152
155
  const messageId = formatLegacyMessageReference({ target, messageId: snowflake });
153
156
  logger.debug(formatCompact({
154
157
  op: 'discord_send',
@@ -175,13 +178,13 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
175
178
  admit(msg: DiscordInboundMessage): void {
176
179
  if (!this.#open) return;
177
180
  if (msg.authorBot) return;
181
+ const conversation = discordInboundConversation(String(this.#options.id), msg);
178
182
  void this.#options.gateway.receive({
179
- adapter: this.#options.id,
180
- target: msg.channelId,
183
+ conversation,
184
+ message: { conversation, id: msg.id },
181
185
  content: formatInboundContent(msg),
182
186
  segments: formatInboundSegments(msg),
183
187
  sender: senderDisplayName(msg),
184
- id: msg.id,
185
188
  metadata: Object.freeze({
186
189
  endpoint: this.#options.config.name,
187
190
  channelKind: msg.channelKind,
@@ -194,7 +197,7 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
194
197
  }).catch((err) => {
195
198
  logger.warn(formatCompact({
196
199
  op: 'discord_gateway_receive_failed',
197
- target: msg.channelId,
200
+ target: `${conversation.kind}:${conversation.id}`,
198
201
  error: err instanceof Error ? err.message : String(err),
199
202
  }));
200
203
  });
@@ -203,13 +206,13 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
203
206
  /** Test / internal: admit a button interaction when open. */
204
207
  admitButton(interaction: DiscordButtonInbound): void {
205
208
  if (!this.#open) return;
209
+ const conversation = discordInboundConversation(String(this.#options.id), interaction);
206
210
  void this.#options.gateway.receive({
207
- adapter: this.#options.id,
208
- target: interaction.channelId,
211
+ conversation,
212
+ message: { conversation, id: interaction.id },
209
213
  content: formatButtonContent(interaction),
210
214
  segments: formatButtonSegments(interaction),
211
215
  sender: interaction.userName,
212
- id: interaction.id,
213
216
  metadata: Object.freeze({
214
217
  endpoint: this.#options.config.name,
215
218
  eventType: 'button',
@@ -219,7 +222,7 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
219
222
  }).catch((err) => {
220
223
  logger.warn(formatCompact({
221
224
  op: 'discord_gateway_receive_failed',
222
- target: interaction.channelId,
225
+ target: `${conversation.kind}:${conversation.id}`,
223
226
  error: err instanceof Error ? err.message : String(err),
224
227
  }));
225
228
  });
@@ -465,9 +468,9 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
465
468
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
466
469
  }
467
470
 
468
- async send({ target, conversation, payload }: EndpointSendRequest): Promise<string> {
471
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
469
472
  const body = formatOutboundBody(payload);
470
- const channelId = nativeConversationId(target, conversation);
473
+ const channelId = conversation.id;
471
474
  const response = await this.#fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
472
475
  method: 'POST',
473
476
  headers: {
@@ -483,7 +486,13 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
483
486
  }
484
487
  const data = JSON.parse(text) as { id?: string };
485
488
  const snowflake = data.id ?? '';
486
- return snowflake ? formatLegacyMessageReference({ target, messageId: snowflake }) : '';
489
+ // recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生 target
490
+ return snowflake
491
+ ? formatLegacyMessageReference({
492
+ target: formatLegacyConversationRef(conversation),
493
+ messageId: snowflake,
494
+ })
495
+ : '';
487
496
  }
488
497
 
489
498
  async recallMessage(messageId: string): Promise<void> {
@@ -505,13 +514,13 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
505
514
 
506
515
  admit(msg: DiscordInboundMessage): void {
507
516
  if (!this.#open) return;
517
+ const conversation = discordInboundConversation(String(this.#options.id), msg);
508
518
  void this.#options.gateway.receive({
509
- adapter: this.#options.id,
510
- target: msg.channelId,
519
+ conversation,
520
+ message: { conversation, id: msg.id },
511
521
  content: formatInboundContent(msg),
512
522
  segments: formatInboundSegments(msg),
513
523
  sender: senderDisplayName(msg),
514
- id: msg.id,
515
524
  metadata: Object.freeze({
516
525
  endpoint: this.#options.config.name,
517
526
  channelKind: msg.channelKind,
@@ -522,7 +531,7 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
522
531
  }).catch((err) => {
523
532
  logger.warn(formatCompact({
524
533
  op: 'discord_gateway_receive_failed',
525
- target: msg.channelId,
534
+ target: `${conversation.kind}:${conversation.id}`,
526
535
  error: err instanceof Error ? err.message : String(err),
527
536
  }));
528
537
  });
package/src/protocol.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import { createPublicKey, verify as cryptoVerify } from 'node:crypto';
7
7
  import { isMediaRef } from '@zhin.js/core';
8
8
  import type { Segment } from '@zhin.js/core/runtime';
9
+ import type { ConversationRef } from '@zhin.js/im-contract';
9
10
  import { formatCompact, getLogger } from '@zhin.js/logger';
10
11
 
11
12
  const logger = getLogger('discord');
@@ -199,6 +200,29 @@ export function resolveChannelKind(channelType: number | string | undefined): 'p
199
200
  return 'channel';
200
201
  }
201
202
 
203
+ /**
204
+ * 入站归一化 → ConversationRef:DM → 'private';GroupDM → 'group';
205
+ * guild 频道 → 'channel' + guild 容器进 parent(guild 是频道容器,kind 'channel')。
206
+ * endpoint.adapter = owner PluginId(CapabilityId 以 \0 分隔)。
207
+ */
208
+ export function discordInboundConversation(
209
+ endpointId: string,
210
+ msg: {
211
+ readonly channelId: string;
212
+ readonly channelKind: 'private' | 'group' | 'channel';
213
+ readonly guildId?: string;
214
+ },
215
+ ): ConversationRef {
216
+ return {
217
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
218
+ kind: msg.channelKind,
219
+ id: msg.channelId,
220
+ ...(msg.guildId && msg.channelKind === 'channel'
221
+ ? { parent: { kind: 'channel' as const, id: msg.guildId } }
222
+ : {}),
223
+ };
224
+ }
225
+
202
226
  export function senderDisplayName(msg: DiscordInboundMessage): string {
203
227
  return msg.authorName || msg.authorId;
204
228
  }