@helyx/bot 0.1.2 → 0.1.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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3 - 2026-07-23
4
+
5
+ - Acknowledge slow interactions safely, edit deferred responses, support
6
+ module-owned public message updates and log bounded interaction identifiers
7
+ when handling fails.
8
+
3
9
  ## 0.1.2 - 2026-07-23
4
10
 
5
11
  - Identify the included Helyx Source Available Licence as `HSAL 1.0` and align public core dependencies.
@@ -19,6 +19,7 @@ export declare function sendToChannel(client: Client, channelId: string, respons
19
19
  messageId: string;
20
20
  }>;
21
21
  export declare function deleteChannelMessage(client: Client, channelId: string, messageId: string): Promise<void>;
22
+ export declare function editChannelMessage(client: Client, channelId: string, messageId: string, response: MessageResponse): Promise<void>;
22
23
  export declare function normalizeDiscordPermission(permission: string): string;
23
24
  export declare function toReplyOptions(response: MessageResponse): InteractionReplyOptions;
24
25
  export declare function toUpdateOptions(response: MessageResponse): InteractionUpdateOptions;
@@ -102,9 +102,17 @@ export function installInteractions(client, modules, registration, logger, servi
102
102
  }
103
103
  else if (interaction.isButton()) {
104
104
  const button = buttons.get(interaction.customId);
105
+ const context = button
106
+ ? createContext(interaction, services)
107
+ : undefined;
108
+ if (context &&
109
+ button?.contribution.acknowledgement === "deferred-ephemeral") {
110
+ await context.deferReply?.(true);
111
+ }
105
112
  if (button &&
113
+ context &&
106
114
  (await authorizeComponent(interaction, button.moduleId, services))) {
107
- await button.contribution.execute(createContext(interaction, services));
115
+ await button.contribution.execute(context);
108
116
  }
109
117
  }
110
118
  else if (interaction.isModalSubmit()) {
@@ -125,7 +133,11 @@ export function installInteractions(client, modules, registration, logger, servi
125
133
  }
126
134
  }
127
135
  catch (error) {
128
- logger.error({ event: "interaction.failed", error: safeError(error) }, "Module interaction failed");
136
+ logger.error({
137
+ event: "interaction.failed",
138
+ ...interactionLogContext(interaction),
139
+ error: safeError(error),
140
+ }, "Module interaction failed");
129
141
  await sendFailure(interaction);
130
142
  }
131
143
  }
@@ -241,7 +253,14 @@ function createContext(interaction, services) {
241
253
  ...(interaction.channelId ? { channelId: interaction.channelId } : {}),
242
254
  ...(interaction.isButton() ? { messageId: interaction.message.id } : {}),
243
255
  services,
244
- reply: async (response) => interaction.reply(toReplyOptions(response)).then(() => undefined),
256
+ deferReply: async (ephemeral = false) => {
257
+ if (interaction.deferred || interaction.replied)
258
+ return;
259
+ await interaction.deferReply({
260
+ ...(ephemeral ? { flags: MessageFlags.Ephemeral } : {}),
261
+ });
262
+ },
263
+ reply: (response) => replyToInteraction(interaction, response),
245
264
  ...(interaction.isMessageComponent()
246
265
  ? {
247
266
  updateResponse: async (response) => interaction.update(toUpdateOptions(response)).then(() => undefined),
@@ -250,6 +269,7 @@ function createContext(interaction, services) {
250
269
  showModal: async (modal) => interaction.showModal(toModal(modal)).then(() => undefined),
251
270
  sendToChannel: (channelId, response) => sendToChannel(interaction.client, channelId, response),
252
271
  deleteChannelMessage: (channelId, messageId) => deleteChannelMessage(interaction.client, channelId, messageId),
272
+ editChannelMessage: (channelId, messageId, response) => editChannelMessage(interaction.client, channelId, messageId, response),
253
273
  addChannelMessageReaction: (channelId, messageId, emojiIdentifier) => addChannelMessageReaction(interaction.client, channelId, messageId, emojiIdentifier),
254
274
  };
255
275
  }
@@ -292,7 +312,14 @@ function createModalContext(interaction, services) {
292
312
  ...(interaction.guildId ? { serverId: interaction.guildId } : {}),
293
313
  ...(interaction.channelId ? { channelId: interaction.channelId } : {}),
294
314
  services,
295
- reply: async (response) => interaction.reply(toReplyOptions(response)).then(() => undefined),
315
+ deferReply: async (ephemeral = false) => {
316
+ if (interaction.deferred || interaction.replied)
317
+ return;
318
+ await interaction.deferReply({
319
+ ...(ephemeral ? { flags: MessageFlags.Ephemeral } : {}),
320
+ });
321
+ },
322
+ reply: (response) => replyToInteraction(interaction, response),
296
323
  ...(updateResponse ? { updateResponse } : {}),
297
324
  getField: (customId) => interaction.fields.getTextInputValue(customId),
298
325
  getStringSelectValues: (customId) => interaction.fields.getStringSelectValues(customId),
@@ -315,6 +342,7 @@ function createModalContext(interaction, services) {
315
342
  ],
316
343
  sendToChannel: (channelId, response) => sendToChannel(interaction.client, channelId, response),
317
344
  deleteChannelMessage: (channelId, messageId) => deleteChannelMessage(interaction.client, channelId, messageId),
345
+ editChannelMessage: (channelId, messageId, response) => editChannelMessage(interaction.client, channelId, messageId, response),
318
346
  addChannelMessageReaction: (channelId, messageId, emojiIdentifier) => addChannelMessageReaction(interaction.client, channelId, messageId, emojiIdentifier),
319
347
  };
320
348
  }
@@ -339,6 +367,21 @@ export async function deleteChannelMessage(client, channelId, messageId) {
339
367
  }
340
368
  await channel.messages.delete(messageId);
341
369
  }
370
+ export async function editChannelMessage(client, channelId, messageId, response) {
371
+ const channel = await client.channels.fetch(channelId);
372
+ if (!channel?.isTextBased()) {
373
+ throw new Error("Configured channel cannot contain messages");
374
+ }
375
+ const message = await channel.messages.fetch(messageId);
376
+ await message.edit(toUpdateOptions(response));
377
+ }
378
+ async function replyToInteraction(interaction, response) {
379
+ if (interaction.deferred || interaction.replied) {
380
+ await interaction.editReply(toUpdateOptions(response));
381
+ return;
382
+ }
383
+ await interaction.reply(toReplyOptions(response));
384
+ }
342
385
  async function addChannelMessageReaction(client, channelId, messageId, emojiIdentifier) {
343
386
  const channel = await client.channels.fetch(channelId);
344
387
  if (!channel?.isTextBased()) {
@@ -354,12 +397,16 @@ async function authorizeComponent(interaction, moduleId, services) {
354
397
  if (await installations.isModuleEnabled(interaction.guildId, moduleId)) {
355
398
  return true;
356
399
  }
357
- await interaction.reply({
358
- content: "This module is disabled in this server. Ask a server administrator to enable it with `/helyx` or in the Helyx dashboard.",
359
- flags: MessageFlags.Ephemeral,
360
- });
400
+ await replyEphemeralText(interaction, "This module is disabled in this server. Ask a server administrator to enable it with `/helyx` or in the Helyx dashboard.");
361
401
  return false;
362
402
  }
403
+ async function replyEphemeralText(interaction, content) {
404
+ if (interaction.deferred || interaction.replied) {
405
+ await interaction.editReply({ content });
406
+ return;
407
+ }
408
+ await interaction.reply({ content, flags: MessageFlags.Ephemeral });
409
+ }
363
410
  async function authorizeCommand(interaction, moduleId, commandId, rateLimit, services) {
364
411
  if (!interaction.guildId)
365
412
  return true;
@@ -650,17 +697,41 @@ export function toModal(definition) {
650
697
  return modal;
651
698
  }
652
699
  async function sendFailure(interaction) {
653
- if (!interaction.isRepliable() || interaction.replied || interaction.deferred)
700
+ if (!interaction.isRepliable())
701
+ return;
702
+ const message = "Something went wrong while handling that interaction.";
703
+ if (interaction.replied || interaction.deferred) {
704
+ await interaction.editReply({ content: message }).catch(() => undefined);
654
705
  return;
706
+ }
655
707
  await interaction
656
- .reply({
657
- content: "Something went wrong while handling that interaction.",
658
- flags: MessageFlags.Ephemeral,
659
- })
708
+ .reply({ content: message, flags: MessageFlags.Ephemeral })
660
709
  .catch(() => undefined);
661
710
  }
662
711
  function safeError(error) {
663
712
  const value = error instanceof Error ? error : new Error(String(error));
664
- return { name: value.name, message: value.message };
713
+ return {
714
+ name: value.name,
715
+ message: value.message,
716
+ ...("code" in value &&
717
+ (typeof value.code === "string" || typeof value.code === "number")
718
+ ? { code: value.code }
719
+ : {}),
720
+ };
721
+ }
722
+ function interactionLogContext(interaction) {
723
+ return {
724
+ interactionId: interaction.id,
725
+ interactionType: interaction.type,
726
+ ...(interaction.guildId ? { guildId: interaction.guildId } : {}),
727
+ ...(interaction.isChatInputCommand()
728
+ ? {
729
+ commandName: interaction.commandName,
730
+ subcommandName: interaction.options.getSubcommand(false) ?? undefined,
731
+ }
732
+ : "customId" in interaction && typeof interaction.customId === "string"
733
+ ? { customId: interaction.customId }
734
+ : {}),
735
+ };
665
736
  }
666
737
  //# sourceMappingURL=interactions.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helyx/bot",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Production Discord runtime for the modular Helyx platform.",
5
5
  "keywords": [
6
6
  "helyx",
@@ -49,12 +49,12 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@helyx/config": "0.1.2",
52
- "@helyx/core": "0.1.1",
52
+ "@helyx/core": "0.1.2",
53
53
  "@helyx/database": "0.1.1",
54
- "@helyx/framework": "0.1.1",
54
+ "@helyx/framework": "0.1.2",
55
55
  "@helyx/module-manifest": "0.1.1",
56
- "@helyx/platform": "0.1.1",
57
- "@helyx/sdk": "0.2.1",
56
+ "@helyx/platform": "0.1.2",
57
+ "@helyx/sdk": "0.2.2",
58
58
  "discord.js": "14.27.0",
59
59
  "fastify": "5.10.0",
60
60
  "pino": "10.3.1",