-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/slash deploy commands #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughThis PR removes the BaseClient module and introduces a comprehensive Discord bot framework consisting of new Client, Command, Event, and Context classes alongside handler components for managing commands, events, and slash command deployment. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant CH as CommandHandler
participant Cmd as Command
participant Ctx as Context
participant User as Discord User
User->>Client: messageCreate event
Client->>CH: handleMessage(message)
CH->>CH: parse prefix & args
CH->>CH: resolve command by name/alias
CH->>Ctx: construct Context
Ctx->>Ctx: extract author, guild, channel
CH->>Cmd: execute(context)
Cmd->>Ctx: reply/send response
Ctx->>User: message reply or channel send
CH-->>User: on error: error message
sequenceDiagram
participant Client as Client
participant EH as EventHandler
participant Evt as Event
participant Discord as Discord.js
Client->>EH: loadEvents(directory)
EH->>EH: discover .ts/.js files recursively
loop for each event file
EH->>EH: clear & import module
EH->>Evt: instantiate Event subclass
alt once flag set
EH->>Discord: register with once()
else
EH->>Discord: register with on()
end
Discord->>Evt: execute() when event fires
end
sequenceDiagram
participant Client as Client
participant Deployer as Deployer
participant Discord as Discord REST API
Client->>Deployer: deployGlobal() or deployGuild(id)
Deployer->>Deployer: validate token & application
Deployer->>Deployer: filter commands by supportsSlash
Deployer->>Deployer: construct slash command payload
Deployer->>Discord: PUT to /applications/{id}/commands
Discord->>Deployer: registered commands response
Deployer-->>Client: success/error log
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In `@src/handlers/CommandHandler.ts`:
- Around line 70-72: The interaction guard in CommandHandler.handleInteraction
currently uses interaction.isCommand(), which may be deprecated; replace that
check with interaction.isChatInputCommand() to correctly detect slash
(chat-input) commands in discord.js v14+. Locate the handleInteraction method
and update the conditional to use isChatInputCommand(), ensuring any downstream
code that assumes a ChatInputCommandInteraction is still valid (cast or narrow
the type if needed).
- Around line 15-38: The loadCommands method currently uses require(file) which
fails for ESM runtimes and .ts files; replace the synchronous require-based
loading in loadCommands (and the require.cache deletion) with an async dynamic
import using file URLs (use pathToFileURL from 'url') and normalize the imported
module to get the Command class (check both module.default and module exports),
handle both .js and .ts by ensuring the runtime/ts-loader is configured or skip
.ts in pure-CJS environments, and keep the existing guards: validate that the
resolved CommandClass inherits from Command (or validate via typeof/constructor)
before instantiating, then set this.client.commands and this.client.aliases and
log errors via the existing catch block.
In `@src/handlers/Deployer.ts`:
- Around line 21-32: Fix the typo "globaly" to "globally" in all log and error
messages in Deployer.ts where commands are registered: update the strings passed
to this.client.logger.log and this.client.logger.error (the messages around the
rest.put(Routes.applicationCommands(this.client.application.id), { body:
slashCommands }) call that reference "Started refreshing ... application (/)
commands globaly.", "Successfully reloaded application (/) commands globaly.",
and "Failed to register commands globaly:") so they read "globally".
In `@src/handlers/EventHandler.ts`:
- Around line 24-29: Event handlers created from EventClass call
event.execute(...) without awaiting or handling returned promises, which can
lead to unhandled rejections; update the registration in EventHandler (the
this.client.once(...) / this.client.on(...) callbacks) to wrap the call with
Promise.resolve(event.execute(...)).catch(err => /* log error */) or use an
async wrapper (e.g., async (...args) => { try { await event.execute(...args) }
catch (err) { /* log error */ } }) so any rejected Promise from Event.execute is
caught and logged; reference EventClass, Event.execute, event.once, event.name,
and the this.client.once/on registration points when making the change.
In `@src/structures/Context.ts`:
- Around line 23-35: The Context constructor must validate presence of either
interaction or message and fail fast if both are missing: inside the Context
constructor (class Context) check that data.interaction or data.message is
defined and throw a clear Error (e.g., "Context requires an interaction or
message") when both are undefined so the getters/reply methods (which rely on
non-null interaction/message) cannot later throw unexpectedly; update any tests
or callers accordingly to ensure a valid Context is always constructed.
- Around line 60-63: The Context.send method currently accepts ReplyOptions and
casts this.channel to any — change it to accept only message-safe options (e.g.,
string | MessageCreateOptions | MessagePayload or a new alias like
ChannelMessageOptions) instead of ReplyOptions, remove the `as any` cast, and
call the typed channel's send directly; update the method signature in the
Context class (Context.send) to use the narrowed type and rely on the channel's
native send overloads so fields like `ephemeral` from InteractionReplyOptions
are no longer allowed.
- Around line 41-43: The guild getter currently uses this.interaction?.guild ??
this.message! which can throw when interaction.guild is null (DM) and message is
undefined; update the Context.guild getter to safely return
this.interaction?.guild ?? this.message?.guild ?? null (remove the non-null
assertion on message) so both optional sources are checked and null is returned
if neither exists, keeping the return type Guild | null and touching the getter
method named "guild" in the Context class.
- Around line 50-58: The reply method currently uses followUp when
this.interaction.deferred is true which leaves the original deferred reply
unresolved; update public async reply(options: ReplyOptions) to check
this.interaction and, if this.interaction.deferred && !this.interaction.replied,
call this.interaction.editReply(options as InteractionEditReplyOptions) instead
of followUp, otherwise keep the existing logic (use followUp when already
replied, reply when neither deferred nor replied, and message!.reply for
non-interaction flows) and import InteractionEditReplyOptions from discord.js to
type the editReply path.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (12)
src/classes/base/BaseClient.tssrc/handlers/CommandHandler.tssrc/handlers/Deployer.tssrc/handlers/EventHandler.tssrc/index.tssrc/structures/Argument.tssrc/structures/Client.tssrc/structures/Command.tssrc/structures/Context.tssrc/structures/Event.tssrc/types/index.tssrc/utils/Logger.ts
💤 Files with no reviewable changes (1)
- src/classes/base/BaseClient.ts
🧰 Additional context used
🧬 Code graph analysis (7)
src/handlers/Deployer.ts (2)
src/structures/Client.ts (1)
Client(10-43)src/utils/Logger.ts (1)
error(36-39)
src/types/index.ts (2)
src/utils/Logger.ts (1)
LogLevel(2-2)src/structures/Argument.ts (1)
Argument(6-36)
src/handlers/EventHandler.ts (2)
src/structures/Client.ts (1)
Client(10-43)src/utils/Logger.ts (1)
error(36-39)
src/structures/Context.ts (1)
src/structures/Client.ts (1)
Client(10-43)
src/structures/Command.ts (4)
src/structures/Client.ts (1)
Client(10-43)src/types/index.ts (1)
CommandOptions(11-18)src/structures/Argument.ts (1)
Argument(6-36)src/structures/Context.ts (1)
Context(17-64)
src/handlers/CommandHandler.ts (3)
src/structures/Client.ts (1)
Client(10-43)src/utils/Logger.ts (1)
error(36-39)src/structures/Context.ts (1)
Context(17-64)
src/structures/Event.ts (1)
src/structures/Client.ts (1)
Client(10-43)
🔇 Additional comments (7)
src/structures/Event.ts (1)
4-15: LGTM. Clean base abstraction and clear contract for event implementations.src/structures/Argument.ts (1)
6-35: LGTM. Simple, clear value object with a predictable JSON shape.src/index.ts (1)
1-10: LGTM. Clean public surface re-exports for the new framework types and handlers.src/handlers/EventHandler.ts (1)
17-19: The package is explicitly configured for CommonJS ("type": "commonjs"in package.json and"module": "CommonJS"in tsconfig.json), sorequireandrequire.cacheare appropriate and will work correctly. No ESM incompatibility issue exists here.Likely an incorrect or invalid review comment.
src/structures/Command.ts (1)
16-41: Command metadata normalization looks good.Clean handling of
Argumentinstances and sensible defaults for slash/prefix support.src/types/index.ts (1)
5-18: Types are clean and cohesive.Interfaces align well with the new framework surfaces.
src/structures/Client.ts (1)
20-42: Client wiring looks consistent.Handlers, deployer, and listeners are initialized coherently.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| public async loadCommands(dir: string) { | ||
| const files = this.getFiles(dir); | ||
| for (const file of files) { | ||
| try { | ||
| delete require.cache[require.resolve(file)]; | ||
| const { default: CommandClass } = await require(file); | ||
|
|
||
| if (!CommandClass || !(CommandClass.prototype instanceof Command)) { | ||
| continue; | ||
| } | ||
|
|
||
| const command: Command = new CommandClass(this.client); | ||
| this.client.commands.set(command.name, command); | ||
|
|
||
| for (const alias of command.aliases) { | ||
| this.client.aliases.set(alias, command.name); | ||
| } | ||
|
|
||
| this.client.logger.debug(`Loaded command: ${command.name}`); | ||
| } catch (error) { | ||
| this.client.logger.error(`Error loading command ${file}:`, error); | ||
| } | ||
| } | ||
| this.client.logger.log(`Loaded ${this.client.commands.size} commands.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Verify command loading works in your runtime (CJS/ESM + .ts/.js).
require() with .ts files will fail unless a TS loader is registered, and require() is incompatible with ESM. Consider dynamic import() with file URLs (still requires TS loader for .ts).
♻️ Suggested ESM-friendly import
-import fs from "fs";
-import path from "path";
+import fs from "fs";
+import path from "path";
+import { pathToFileURL } from "url";
...
- const { default: CommandClass } = await require(file);
+ const { default: CommandClass } = await import(pathToFileURL(file).href);🤖 Prompt for AI Agents
In `@src/handlers/CommandHandler.ts` around lines 15 - 38, The loadCommands method
currently uses require(file) which fails for ESM runtimes and .ts files; replace
the synchronous require-based loading in loadCommands (and the require.cache
deletion) with an async dynamic import using file URLs (use pathToFileURL from
'url') and normalize the imported module to get the Command class (check both
module.default and module exports), handle both .js and .ts by ensuring the
runtime/ts-loader is configured or skip .ts in pure-CJS environments, and keep
the existing guards: validate that the resolved CommandClass inherits from
Command (or validate via typeof/constructor) before instantiating, then set
this.client.commands and this.client.aliases and log errors via the existing
catch block.
| public async handleInteraction(interaction: Interaction) { | ||
| if (!interaction.isCommand()) return; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Use the chat-input guard for slash commands.
For discord.js v14+, isChatInputCommand() is the typical guard for slash commands; isCommand() may be deprecated or absent.
♻️ Proposed change
- if (!interaction.isCommand()) return;
+ if (!interaction.isChatInputCommand()) return;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public async handleInteraction(interaction: Interaction) { | |
| if (!interaction.isCommand()) return; | |
| public async handleInteraction(interaction: Interaction) { | |
| if (!interaction.isChatInputCommand()) return; | |
🤖 Prompt for AI Agents
In `@src/handlers/CommandHandler.ts` around lines 70 - 72, The interaction guard
in CommandHandler.handleInteraction currently uses interaction.isCommand(),
which may be deprecated; replace that check with
interaction.isChatInputCommand() to correctly detect slash (chat-input) commands
in discord.js v14+. Locate the handleInteraction method and update the
conditional to use isChatInputCommand(), ensuring any downstream code that
assumes a ChatInputCommandInteraction is still valid (cast or narrow the type if
needed).
| this.client.logger.log( | ||
| `Started refreshing ${slashCommands.length} application (/) commands globaly.` | ||
| ); | ||
| await rest.put(Routes.applicationCommands(this.client.application.id), { | ||
| body: slashCommands, | ||
| }); | ||
| this.client.logger.log( | ||
| `Successfully reloaded application (/) commands globaly.` | ||
| ); | ||
| } catch (error) { | ||
| this.client.logger.error("Failed to register commands globaly:", error); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix typo in log messages (“globaly”).
Minor but user-facing.
✏️ Proposed fix
- `Started refreshing ${slashCommands.length} application (/) commands globaly.`
+ `Started refreshing ${slashCommands.length} application (/) commands globally.`
...
- `Successfully reloaded application (/) commands globaly.`
+ `Successfully reloaded application (/) commands globally.`
...
- this.client.logger.error("Failed to register commands globaly:", error);
+ this.client.logger.error("Failed to register commands globally:", error);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| this.client.logger.log( | |
| `Started refreshing ${slashCommands.length} application (/) commands globaly.` | |
| ); | |
| await rest.put(Routes.applicationCommands(this.client.application.id), { | |
| body: slashCommands, | |
| }); | |
| this.client.logger.log( | |
| `Successfully reloaded application (/) commands globaly.` | |
| ); | |
| } catch (error) { | |
| this.client.logger.error("Failed to register commands globaly:", error); | |
| } | |
| this.client.logger.log( | |
| `Started refreshing ${slashCommands.length} application (/) commands globally.` | |
| ); | |
| await rest.put(Routes.applicationCommands(this.client.application.id), { | |
| body: slashCommands, | |
| }); | |
| this.client.logger.log( | |
| `Successfully reloaded application (/) commands globally.` | |
| ); | |
| } catch (error) { | |
| this.client.logger.error("Failed to register commands globally:", error); | |
| } |
🤖 Prompt for AI Agents
In `@src/handlers/Deployer.ts` around lines 21 - 32, Fix the typo "globaly" to
"globally" in all log and error messages in Deployer.ts where commands are
registered: update the strings passed to this.client.logger.log and
this.client.logger.error (the messages around the
rest.put(Routes.applicationCommands(this.client.application.id), { body:
slashCommands }) call that reference "Started refreshing ... application (/)
commands globaly.", "Successfully reloaded application (/) commands globaly.",
and "Failed to register commands globaly:") so they read "globally".
| const event: Event<any> = new EventClass(this.client); | ||
| if (event.once) { | ||
| this.client.once(event.name, (...args) => event.execute(...args)); | ||
| } else { | ||
| this.client.on(event.name, (...args) => event.execute(...args)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find and examine EventHandler.ts
find . -name "EventHandler.ts" -type fRepository: AroxBot/framework
Length of output: 91
🏁 Script executed:
# Also search for Event class/interface definition
find . -name "*.ts" -type f | head -20Repository: AroxBot/framework
Length of output: 347
🏁 Script executed:
# Read EventHandler.ts to check the implementation
cat -n ./src/handlers/EventHandler.tsRepository: AroxBot/framework
Length of output: 1819
🏁 Script executed:
# Read Event.ts to check the execute method signature
cat -n ./src/structures/Event.tsRepository: AroxBot/framework
Length of output: 610
Handle async execute failures to prevent unhandled rejections.
The Event.execute() method signature returns Promise<void> | void, allowing event handlers to be asynchronous. The current implementation doesn't await or catch these promises, risking unhandled rejections that silently fail or crash the application.
Wrap the handler to catch and log promise rejections:
Suggested fix
const event: Event<any> = new EventClass(this.client);
+const handler = (...args: any[]) => {
+ Promise.resolve(event.execute(...args)).catch((error) =>
+ this.client.logger.error(`Error in event ${event.name}:`, error)
+ );
+};
if (event.once) {
- this.client.once(event.name, (...args) => event.execute(...args));
+ this.client.once(event.name, handler);
} else {
- this.client.on(event.name, (...args) => event.execute(...args));
+ this.client.on(event.name, handler);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const event: Event<any> = new EventClass(this.client); | |
| if (event.once) { | |
| this.client.once(event.name, (...args) => event.execute(...args)); | |
| } else { | |
| this.client.on(event.name, (...args) => event.execute(...args)); | |
| } | |
| const event: Event<any> = new EventClass(this.client); | |
| const handler = (...args: any[]) => { | |
| Promise.resolve(event.execute(...args)).catch((error) => | |
| this.client.logger.error(`Error in event ${event.name}:`, error) | |
| ); | |
| }; | |
| if (event.once) { | |
| this.client.once(event.name, handler); | |
| } else { | |
| this.client.on(event.name, handler); | |
| } |
🤖 Prompt for AI Agents
In `@src/handlers/EventHandler.ts` around lines 24 - 29, Event handlers created
from EventClass call event.execute(...) without awaiting or handling returned
promises, which can lead to unhandled rejections; update the registration in
EventHandler (the this.client.once(...) / this.client.on(...) callbacks) to wrap
the call with Promise.resolve(event.execute(...)).catch(err => /* log error */)
or use an async wrapper (e.g., async (...args) => { try { await
event.execute(...args) } catch (err) { /* log error */ } }) so any rejected
Promise from Event.execute is caught and logged; reference EventClass,
Event.execute, event.once, event.name, and the this.client.once/on registration
points when making the change.
| constructor( | ||
| client: Client, | ||
| data: { | ||
| interaction?: CommandInteraction; | ||
| message?: Message; | ||
| args?: string[]; | ||
| } | ||
| ) { | ||
| this.client = client; | ||
| this.interaction = data.interaction; | ||
| this.message = data.message; | ||
| if (data.args) this.args = data.args; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against Context without an interaction or message.
Non-null assertions in getters/reply will throw if both are absent. Fail fast in the constructor to make misuse explicit.
🔒 Suggested guard
constructor(
client: Client,
data: {
interaction?: CommandInteraction;
message?: Message;
args?: string[];
}
) {
+ if (!data.interaction && !data.message) {
+ throw new Error("Context requires either an interaction or a message.");
+ }
this.client = client;
this.interaction = data.interaction;
this.message = data.message;
if (data.args) this.args = data.args;
}🤖 Prompt for AI Agents
In `@src/structures/Context.ts` around lines 23 - 35, The Context constructor must
validate presence of either interaction or message and fail fast if both are
missing: inside the Context constructor (class Context) check that
data.interaction or data.message is defined and throw a clear Error (e.g.,
"Context requires an interaction or message") when both are undefined so the
getters/reply methods (which rely on non-null interaction/message) cannot later
throw unexpectedly; update any tests or callers accordingly to ensure a valid
Context is always constructed.
| public get guild(): Guild | null { | ||
| return this.interaction?.guild ?? this.message!.guild; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the Context.ts file
find . -name "Context.ts" -type f | head -20Repository: AroxBot/framework
Length of output: 88
🏁 Script executed:
# Check the file size and structure
wc -l src/structures/Context.ts
head -60 src/structures/Context.tsRepository: AroxBot/framework
Length of output: 1575
🏁 Script executed:
# Look at the property declarations and the guild getter
sed -n '1,80p' src/structures/Context.tsRepository: AroxBot/framework
Length of output: 1623
Fix nullish-coalescing crash for DM interactions in the guild getter.
interaction.guild can be null (e.g., in DMs). When this occurs, the ?? operator falls back to this.message!.guild, which crashes if message is undefined. The non-null assertion on message is unsafe given that both interaction and message are optional properties.
Suggested fix
public get guild(): Guild | null {
- return this.interaction?.guild ?? this.message!.guild;
+ if (this.interaction) return this.interaction.guild ?? null;
+ return this.message?.guild ?? null;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public get guild(): Guild | null { | |
| return this.interaction?.guild ?? this.message!.guild; | |
| } | |
| public get guild(): Guild | null { | |
| if (this.interaction) return this.interaction.guild ?? null; | |
| return this.message?.guild ?? null; | |
| } |
🤖 Prompt for AI Agents
In `@src/structures/Context.ts` around lines 41 - 43, The guild getter currently
uses this.interaction?.guild ?? this.message! which can throw when
interaction.guild is null (DM) and message is undefined; update the
Context.guild getter to safely return this.interaction?.guild ??
this.message?.guild ?? null (remove the non-null assertion on message) so both
optional sources are checked and null is returned if neither exists, keeping the
return type Guild | null and touching the getter method named "guild" in the
Context class.
| public async reply(options: ReplyOptions): Promise<any> { | ||
| if (this.interaction) { | ||
| if (this.interaction.replied || this.interaction.deferred) { | ||
| return this.interaction.followUp(options as InteractionReplyOptions); | ||
| } | ||
| return this.interaction.reply(options as InteractionReplyOptions); | ||
| } | ||
| return this.message!.reply(options as MessageReplyOptions); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate and examine the Context.ts file
find . -name "Context.ts" -type fRepository: AroxBot/framework
Length of output: 88
🏁 Script executed:
# Check the package.json to understand discord.js version
cat package.json | grep -A 2 -B 2 "discord.js"Repository: AroxBot/framework
Length of output: 192
🏁 Script executed:
# Read the relevant lines from Context.ts
sed -n '40,70p' src/structures/Context.ts | cat -nRepository: AroxBot/framework
Length of output: 991
🏁 Script executed:
# Check the imports in Context.ts
head -n 50 src/structures/Context.ts | cat -nRepository: AroxBot/framework
Length of output: 1513
🌐 Web query:
Discord.js interaction editReply method and InteractionEditReplyOptions type documentation v14
💡 Result:
Short answer:
-
Method signature: interaction.editReply(options: string | MessagePayload | InteractionEditReplyOptions): Promise<Message<...>> — edits an interaction reply. [1]
-
InteractionEditReplyOptions type: extends WebhookMessageEditOptions (plus poll in some versions); includes properties such as message (MessageResolvable | '@original'), attachments, flags, threadId, withComponents (and poll where present). See full field list in the API docs. [2]
References:
- CommandInteraction / editReply (discord.js v14). [1]
- InteractionEditReplyOptions interface (discord.js v14). [2]
Sources:
[1] https://discord.js.org/docs/packages/discord.js/14.20.0/CommandInteraction%3AClass
[2] https://discord.js.org/docs/packages/discord.js/14.19.3/InteractionEditReplyOptions%3AInterface
Use editReply after deferReply to avoid dangling deferred responses.
When deferred but not yet replied, followUp sends a secondary response and leaves the original deferred reply untouched. Prefer editReply for the deferred-only state.
✅ Suggested flow
if (this.interaction) {
- if (this.interaction.replied || this.interaction.deferred) {
- return this.interaction.followUp(options as InteractionReplyOptions);
- }
- return this.interaction.reply(options as InteractionReplyOptions);
+ if (this.interaction.deferred && !this.interaction.replied) {
+ return this.interaction.editReply(options as InteractionEditReplyOptions);
+ }
+ if (this.interaction.replied) {
+ return this.interaction.followUp(options as InteractionReplyOptions);
+ }
+ return this.interaction.reply(options as InteractionReplyOptions);
}
return this.message!.reply(options as MessageReplyOptions);Import InteractionEditReplyOptions from discord.js to support the editReply path.
🤖 Prompt for AI Agents
In `@src/structures/Context.ts` around lines 50 - 58, The reply method currently
uses followUp when this.interaction.deferred is true which leaves the original
deferred reply unresolved; update public async reply(options: ReplyOptions) to
check this.interaction and, if this.interaction.deferred &&
!this.interaction.replied, call this.interaction.editReply(options as
InteractionEditReplyOptions) instead of followUp, otherwise keep the existing
logic (use followUp when already replied, reply when neither deferred nor
replied, and message!.reply for non-interaction flows) and import
InteractionEditReplyOptions from discord.js to type the editReply path.
| public async send(options: ReplyOptions): Promise<Message | undefined> { | ||
| const channel = this.channel as any; | ||
| return channel?.send(options); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Narrow send() options and remove the any cast.
ReplyOptions includes InteractionReplyOptions, which can contain fields (e.g., ephemeral) not accepted by channel.send. Narrow to message-safe options and rely on the typed channel.
♻️ Suggested refactor
export type ReplyOptions =
| string
| MessageReplyOptions
| InteractionReplyOptions;
+
+export type SendOptions = string | MessageReplyOptions;
-public async send(options: ReplyOptions): Promise<Message | undefined> {
- const channel = this.channel as any;
- return channel?.send(options);
-}
+public async send(options: SendOptions): Promise<Message | undefined> {
+ return this.channel?.send(options);
+}🤖 Prompt for AI Agents
In `@src/structures/Context.ts` around lines 60 - 63, The Context.send method
currently accepts ReplyOptions and casts this.channel to any — change it to
accept only message-safe options (e.g., string | MessageCreateOptions |
MessagePayload or a new alias like ChannelMessageOptions) instead of
ReplyOptions, remove the `as any` cast, and call the typed channel's send
directly; update the method signature in the Context class (Context.send) to use
the narrowed type and rely on the channel's native send overloads so fields like
`ephemeral` from InteractionReplyOptions are no longer allowed.
|
Duplicate of #2 |
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.