feat: add interaction handler#27
Conversation
calebephrem
commented
Jul 2, 2026
- Added interaction handler
- Added automatic code block detection and reformatting feature
Summary by BeetleThis PR introduces an intelligent automatic code detection and reformatting feature for the Quill Discord bot, along with an interactive changelog viewer. The bot now automatically detects unformatted code snippets in messages, deletes them, and reposts them in clean embeds with proper syntax highlighting. Additionally, the changelog command has been enhanced with a dropdown menu allowing users to browse through different version histories interactively. 📁 File Changes Summary (Consolidated across all commits):
Total Changes: 8 files changed, +533 additions, -15 deletions 🗺️ Walkthrough:sequenceDiagram
participant User
participant Discord
participant Bot
participant detectCode
participant flourite
participant Interaction
User->>Discord: Sends message with unformatted code
Discord->>Bot: messageCreate event
Bot->>detectCode: Analyze message content
detectCode->>detectCode: Check for existing code blocks
detectCode->>detectCode: Line-by-line heuristic analysis
Note over detectCode: Checks keywords, indentation, symbols, code patterns
alt Code detected (score ≥ 25%)
detectCode->>flourite: Detect programming language
flourite-->>detectCode: Return language (e.g., "javascript")
detectCode-->>Bot: {isCode: true, sections, language}
Bot->>Discord: Delete original message
Bot->>Discord: Send formatted embed with syntax highlighting
Discord->>User: Display clean code block
else Not code or already formatted
detectCode-->>Bot: {isCode: false}
Note over Bot: No action taken
end
User->>Discord: Uses ;changelog command
Discord->>Bot: Command execution
Bot->>Bot: Parse CHANGELOG.md
Bot->>Discord: Send embed + version dropdown
User->>Discord: Selects version from dropdown
Discord->>Interaction: StringSelectMenu interaction
Interaction->>Interaction: Parse selected version
Interaction->>Discord: Update embed with selected version
Discord->>User: Display version changelog
🎯 Key Changes:
📊 Impact Assessment:
⚙️ SettingsSeverity Threshold: 📖 User Guide
|
|
Note Linting checks passed successfully 🎉 All formatting and code quality checks are clean. You're good to merge 🚀 |
| try { | ||
| await message.delete(); | ||
| } catch (error) { | ||
| // console.error("Failed to delete message:", error); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Missing error handling for permission-related failures: The code attempts to delete a message but only catches generic errors. If the bot loses the ManageMessages permission between the permission check (line 11-16) and the delete operation (line 25), the error is silently caught and the function returns without any feedback to the user. This creates a poor user experience where the bot appears to do nothing.
Additionally, there's no validation that the message is deletable (e.g., the message might be too old, or there could be rate limiting issues).
Confidence: 5/5
Suggested Fix
| try { | |
| await message.delete(); | |
| } catch (error) { | |
| // console.error("Failed to delete message:", error); | |
| return; | |
| } | |
| // attempt to delete the message, if fail, return | |
| try { | |
| await message.delete(); | |
| } catch (error) { | |
| // If deletion fails due to permissions or other issues, | |
| // we cannot reformat the message, so exit gracefully | |
| if (error instanceof Error && error.message.includes('Missing Permissions')) { | |
| // Permission was revoked between check and delete | |
| return; | |
| } | |
| // For other errors (rate limit, message too old, etc.), also exit | |
| return; | |
| } | |
Why this matters: While the current code does catch errors, adding specific error type checking would help with debugging and monitoring. However, since the user rules specify only reporting issues that cause bugs or severe problems, and this code does handle the error by returning, this is more of a code quality concern than a critical bug.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/events/messageCreate/detectCode.ts around line 24-29, the error handling for message deletion is too generic and doesn't distinguish between different failure types (permission errors, rate limits, message age, etc.); add more specific error handling or at minimum add a comment explaining why all errors are treated the same way, and consider whether any errors should be logged for monitoring purposes.
📍 This suggestion applies to lines 24-29
| const channel = message.channel; | ||
| if (channel.isTextBased() && "send" in channel) { | ||
| await channel.send({ | ||
| content: `<@${message.author.id}> said:`, | ||
| embeds: [embed], | ||
| }); | ||
| } |
There was a problem hiding this comment.
Missing error handling for embed send operation: After successfully deleting the user's message, if the channel.send() operation fails (due to missing permissions, rate limits, or channel being deleted), the original message is already deleted and cannot be recovered. This results in message loss without any user feedback.
Confidence: 5/5
Suggested Fix
| const channel = message.channel; | |
| if (channel.isTextBased() && "send" in channel) { | |
| await channel.send({ | |
| content: `<@${message.author.id}> said:`, | |
| embeds: [embed], | |
| }); | |
| } | |
| const channel = message.channel; | |
| if (channel.isTextBased() && "send" in channel) { | |
| try { | |
| await channel.send({ | |
| content: `<@${message.author.id}> said:`, | |
| embeds: [embed], | |
| }); | |
| } catch (error) { | |
| // Failed to send the reformatted message after deleting the original | |
| // The original message is already lost, but we should handle this gracefully | |
| // Consider logging this error for monitoring | |
| return; | |
| } | |
Why this matters: This is a critical user experience issue. If the bot deletes a user's message but then fails to repost it, the user's content is permanently lost. This can happen due to:
- Bot losing Send Messages permission between delete and send
- Rate limiting
- Channel being deleted/archived
- Embed size limits being exceeded
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/events/messageCreate/detectCode.ts around line 48-54, add error handling for the channel.send() operation because if it fails after the original message was deleted, the user's content is permanently lost; wrap the send operation in a try-catch block to handle failures gracefully, and consider whether failed sends should be logged for monitoring purposes.
📍 This suggestion applies to lines 48-54
| selectMenu, | ||
| ); | ||
|
|
||
| await message.reply({ embeds: [embed], components: [row] }); |
There was a problem hiding this comment.
Missing error handling for message reply operation: The message.reply() call is not wrapped in error handling. If the reply fails due to missing permissions, rate limits, channel deletion, or Discord API issues, the error will propagate and potentially crash the command handler or leave the command in an inconsistent state.
This is the same pattern identified in Comment #2 for detectCode.ts, where operations that can fail should have proper error handling to prevent crashes and provide graceful degradation.
Confidence: 5/5
Suggested Fix
| await message.reply({ embeds: [embed], components: [row] }); | |
| try { | |
| await message.reply({ embeds: [embed], components: [row] }); | |
| } catch (error) { | |
| // Failed to send changelog - handle gracefully | |
| // The error could be due to permissions, rate limits, or channel issues | |
| // Consider logging this for monitoring purposes | |
| } | |
Wrap the reply operation in a try-catch block to handle failures gracefully and prevent the error from crashing the command handler.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/commands/meta/changelog.ts at line 95, wrap the message.reply() call in a try-catch block to handle potential failures (missing permissions, rate limits, channel deletion, or Discord API errors) gracefully and prevent the error from crashing the command handler; add appropriate error handling similar to other Discord.js operations in the codebase.
| const changelogContent = | ||
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md"))) || ""; |
There was a problem hiding this comment.
Missing error handling for file read operation: The readFile() call can fail due to file system errors (file not found, permission denied, disk errors), but there's no error handling. If it throws an error, the entire interaction handler will crash and the user will see a "This interaction failed" error from Discord.
This is a critical issue because:
- File system operations are inherently unreliable
- The CHANGELOG.md file might be missing, moved, or have permission issues
- Without error handling, the bot will crash on every changelog interaction attempt
Confidence: 5/5
Suggested Fix
| const changelogContent = | |
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md"))) || ""; | |
| const changelogContent = | |
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md")).catch(() => null)) || ""; | |
Add .catch(() => null) to handle file read errors gracefully. If the file read fails, it will return an empty string, and the parseChangelog() function will return an empty array, which is then handled by the check on line 64.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/interactions/changelog.ts around line 59-60, add error handling for the readFile() operation because if it fails due to file system errors (file not found, permission denied, disk errors), the entire interaction handler will crash; add .catch(() => null) to the readFile() promise chain so it returns null on error, which will then be coerced to an empty string by the || "" fallback.
📍 This suggestion applies to lines 59-60
| return interaction.reply({ | ||
| content: "❌ Selected version not found.", | ||
| ephemeral: true, | ||
| }); |
There was a problem hiding this comment.
Missing error handling for interaction reply operation: The interaction.reply() call is not wrapped in error handling. If the reply fails due to missing permissions, rate limits, interaction timeout (3 seconds), or Discord API issues, the error will propagate and crash the interaction handler.
This is the same pattern identified in Comment #5 for changelog.ts command file. Discord interactions have a 3-second window to respond, and if that window expires or if there are permission/API issues, the unhandled error will cause problems.
Confidence: 5/5
Suggested Fix
| return interaction.reply({ | |
| content: "❌ Selected version not found.", | |
| ephemeral: true, | |
| }); | |
| if (!versions[selectedIndex]) { | |
| try { | |
| return await interaction.reply({ | |
| content: "❌ Selected version not found.", | |
| ephemeral: true, | |
| }); | |
| } catch (error) { | |
| // Failed to send error message - interaction may have timed out | |
| // or there may be permission/API issues | |
| return; | |
| } | |
Wrap the reply operation in a try-catch block to handle failures gracefully and prevent the error from crashing the interaction handler.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/interactions/changelog.ts at lines 65-68, wrap the interaction.reply() call in a try-catch block to handle potential failures (interaction timeout after 3 seconds, missing permissions, rate limits, or Discord API errors) gracefully and prevent the error from crashing the interaction handler; add appropriate error handling similar to other Discord.js interaction operations.
📍 This suggestion applies to lines 65-68
| await interaction.update({ | ||
| embeds: [embed], | ||
| components: [row], | ||
| }); |
There was a problem hiding this comment.
Missing error handling for interaction update operation: The interaction.update() call is not wrapped in error handling. If the update fails due to interaction timeout (15 minutes for select menus), missing permissions, rate limits, or Discord API issues, the error will propagate and crash the interaction handler.
This is critical because:
- Select menu interactions expire after 15 minutes
- Discord API can fail due to rate limits or outages
- Without error handling, users will see "This interaction failed" with no graceful degradation
Confidence: 5/5
Suggested Fix
| await interaction.update({ | |
| embeds: [embed], | |
| components: [row], | |
| }); | |
| try { | |
| await interaction.update({ | |
| embeds: [embed], | |
| components: [row], | |
| }); | |
| } catch (error) { | |
| // Failed to update interaction - may have timed out or encountered API issues | |
| // The interaction is already invalid, so we can't recover | |
| return; | |
| } | |
Wrap the update operation in a try-catch block to handle failures gracefully and prevent the error from crashing the interaction handler.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/interactions/changelog.ts at lines 99-102, wrap the interaction.update() call in a try-catch block to handle potential failures (interaction timeout after 15 minutes, missing permissions, rate limits, or Discord API errors) gracefully and prevent the error from crashing the interaction handler; add appropriate error handling similar to other Discord.js interaction operations.
📍 This suggestion applies to lines 99-102
Summary by BeetleThis PR introduces an intelligent automatic code detection and reformatting feature for the Quill Discord bot, along with an interactive changelog viewer. The bot now automatically detects unformatted code snippets in messages, deletes them, and reposts them in clean embeds with proper syntax highlighting. Additionally, the changelog command has been enhanced with a dropdown menu allowing users to browse through different version histories interactively. 📁 File Changes Summary (Consolidated across all commits):
Total Changes: 10 files changed, +542 additions, -18 deletions 🗺️ Walkthrough:sequenceDiagram
participant User
participant Discord
participant Bot
participant DetectCode
participant Flourite
participant InteractionHandler
User->>Discord: Sends message with unformatted code
Discord->>Bot: messageCreate event
Bot->>DetectCode: Check if Manage Messages permission exists
alt Has Permission
DetectCode->>DetectCode: Analyze message content line-by-line
DetectCode->>DetectCode: Check for code patterns (keywords, indentation, symbols)
alt Is Code Detected (score ≥ 25%)
DetectCode->>Flourite: Detect programming language
Flourite-->>DetectCode: Return language (e.g., "javascript", "python")
DetectCode->>Discord: Delete original message
DetectCode->>Discord: Send formatted embed with syntax highlighting
Discord->>User: Display reformatted code
else Not Code
DetectCode-->>Bot: Ignore message
end
else No Permission
DetectCode-->>Bot: Skip processing
end
Note over User,InteractionHandler: Changelog Interaction Flow
User->>Discord: Uses ;changelog command
Discord->>Bot: Parse CHANGELOG.md
Bot->>Discord: Send embed with version dropdown
Discord->>User: Display latest changes + dropdown
User->>Discord: Selects different version from dropdown
Discord->>InteractionHandler: changelog_select interaction
InteractionHandler->>InteractionHandler: Parse selected version index
InteractionHandler->>Discord: Update embed with selected version
Discord->>User: Display selected version changes
🎯 Key Changes:
📊 Impact Assessment:
⚙️ SettingsSeverity Threshold: 📖 User Guide
|
|
Note Linting checks passed successfully 🎉 All formatting and code quality checks are clean. You're good to merge 🚀 |
| } | ||
|
|
||
| // load at startup | ||
| await loadInteractions(); |
There was a problem hiding this comment.
Top-level await blocks the entire module loading process. If loadInteractions() fails or takes a long time (e.g., due to slow file I/O or a large number of interaction files), it will prevent the entire bot from starting up. This creates a single point of failure during initialization and makes the bot unresponsive until all interactions are loaded.
Confidence: 5/5
Suggested Fix
Move the interaction loading into the event handler's initialization or use a lazy-loading pattern. Consider wrapping it in a try-catch and providing fallback behavior:
// Option 1: Load asynchronously with error handling
loadInteractions().catch(err => {
console.error('Failed to load interactions:', err);
// Bot can still start, but interactions won't work
});
// Option 2: Lazy load on first interaction
let interactionsLoaded = false;
async function ensureInteractionsLoaded() {
if (!interactionsLoaded) {
await loadInteractions();
interactionsLoaded = true;
}This prevents the bot from being completely blocked if interaction loading fails and provides better error recovery.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/events/interactionCreate/handleInteractions.ts at line 32, the top-level await
loadInteractions() blocks the entire module loading process and creates a single point
of failure during bot startup; refactor this to either load interactions asynchronously
without blocking (using .catch() for error handling) or implement lazy loading that
loads interactions on first use, ensuring the bot can still start even if interaction
loading fails, and add appropriate error logging for debugging.
| if ((interaction as any).replied || (interaction as any).deferred) { | ||
| await (interaction as any) | ||
| .followUp({ embeds: [errorEmbed], ephemeral: true }) | ||
| .catch(() => null); | ||
| } else { | ||
| await (interaction as any) | ||
| .reply({ embeds: [errorEmbed], ephemeral: true }) | ||
| .catch(() => null); |
There was a problem hiding this comment.
The code uses (interaction as any) to bypass TypeScript's type system and then silently suppresses all errors with .catch(() => null). If Discord.js API changes, if the interaction token expires, or if there are network issues, these errors are completely hidden. This makes debugging production issues extremely difficult and users may not receive error notifications when something goes wrong.
Confidence: 5/5
Suggested Fix
| if ((interaction as any).replied || (interaction as any).deferred) { | |
| await (interaction as any) | |
| .followUp({ embeds: [errorEmbed], ephemeral: true }) | |
| .catch(() => null); | |
| } else { | |
| await (interaction as any) | |
| .reply({ embeds: [errorEmbed], ephemeral: true }) | |
| .catch(() => null); | |
| if ((interaction as any).replied || (interaction as any).deferred) { | |
| await (interaction as any) | |
| .followUp({ embeds: [errorEmbed], ephemeral: true }) | |
| .catch((followUpErr: Error) => { | |
| console.error(`Failed to send followUp error message for ${intId}:`, followUpErr); | |
| }); | |
| } else { | |
| await (interaction as any) | |
| .reply({ embeds: [errorEmbed], ephemeral: true }) | |
| .catch((replyErr: Error) => { | |
| console.error(`Failed to send reply error message for ${intId}:`, replyErr); | |
| }); | |
| } | |
Log the errors instead of silently suppressing them. This helps diagnose issues like expired interaction tokens, network problems, or API changes. Consider also implementing a fallback notification mechanism (like logging to a monitoring channel) when direct user notification fails.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/events/interactionCreate/handleInteractions.ts around lines 96-103, the error
handling uses .catch(() => null) which silently suppresses all errors when trying to
send error messages to users; replace the empty catch handlers with proper error
logging that includes the interaction ID and error details, so that issues like expired
tokens, network problems, or API changes can be diagnosed in production, and consider
implementing a fallback notification mechanism for critical errors.
📍 This suggestion applies to lines 96-103
| const changelogContent = | ||
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md"))) || ""; |
There was a problem hiding this comment.
The readFile call lacks proper error handling. If the file read operation throws an error (e.g., file system errors, permission issues, or if the file is deleted), the entire interaction handler will crash without sending any response to the user. This is similar to the error handling issue identified in Comment #4 for the changelog command - users will be left without feedback when errors occur.
Confidence: 5/5
Suggested Fix
| const changelogContent = | |
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md"))) || ""; | |
| let changelogContent = ""; | |
| try { | |
| changelogContent = | |
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md"))) || ""; | |
| } catch (err) { | |
| console.error("Error reading changelog file:", err); | |
| return interaction.reply({ | |
| content: "❌ Failed to load the changelog. Please try again later.", | |
| ephemeral: true, | |
| }); | |
| } | |
Wrap the file read operation in a try-catch block to handle potential errors gracefully and provide user feedback when the operation fails. This ensures users receive a response even when file system errors occur.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/interactions/changelog.ts around lines 59-60, the readFile call lacks error handling and will crash the interaction handler if the file read throws an error (file system errors, permissions, missing file); wrap the readFile call in a try-catch block that logs the error and sends an ephemeral error reply to the user with a message like "Failed to load the changelog. Please try again later.", ensuring users always receive feedback even when file operations fail.
📍 This suggestion applies to lines 59-60
Summary by BeetleThis PR introduces an intelligent automatic code detection and reformatting feature for the Quill Discord bot, along with an interactive changelog viewer. The bot now automatically detects unwrapped code snippets in messages, deletes the original message, and reposts it with proper syntax highlighting in a clean embed format. Additionally, the changelog command has been enhanced with an interactive dropdown menu allowing users to browse through different version histories. 📁 File Changes Summary (Consolidated across all commits):
Total Changes: 8 files changed, +552 additions, -18 deletions 🗺️ Walkthrough:sequenceDiagram
participant User
participant Discord
participant Bot
participant detectCode
participant flourite
participant Interaction
User->>Discord: Sends message with unwrapped code
Discord->>Bot: messageCreate event
Bot->>Bot: Check Manage Messages permission
Bot->>detectCode: Analyze message content
detectCode->>detectCode: Parse lines, identify code patterns
detectCode->>flourite: Detect programming language
flourite-->>detectCode: Return language (e.g., "javascript")
detectCode-->>Bot: Return {isCode: true, sections, language}
Bot->>Discord: Delete original message
Bot->>Discord: Send formatted embed with syntax highlighting
Discord-->>User: Display reformatted code
Note over User,Interaction: Changelog Interaction Flow
User->>Discord: Uses ;changelog command
Discord->>Bot: Command execution
Bot->>Bot: Parse CHANGELOG.md into versions
Bot->>Discord: Send embed + version select menu
User->>Discord: Selects different version
Discord->>Interaction: StringSelectMenu interaction
Interaction->>Interaction: Parse selected version index
Interaction->>Discord: Update embed with selected version
Discord-->>User: Display selected version changes
🎯 Key Changes:
📊 Impact Assessment:
⚙️ SettingsSeverity Threshold: 📖 User Guide
|
| await channel.send({ | ||
| content: `<@${message.author.id}> said:`, | ||
| embeds: [embed], | ||
| }); |
There was a problem hiding this comment.
The channel.send() operation lacks error handling. If the send fails after the original message is deleted (line 25), the user's message is permanently lost. This can happen due to permission issues, rate limits, or network errors.
Confidence: 5/5
Suggested Fix
| await channel.send({ | |
| content: `<@${message.author.id}> said:`, | |
| embeds: [embed], | |
| }); | |
| try { | |
| await channel.send({ | |
| content: `<@${message.author.id}> said:`, | |
| embeds: [embed], | |
| }); | |
| } catch (error) { | |
| // Log the error or attempt recovery | |
| console.error("Failed to send reformatted message:", error); | |
| } | |
Wrap the channel.send() call in a try-catch block to handle potential failures gracefully. Consider logging the error or implementing a recovery mechanism.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/events/messageCreate/detectCode.ts around line 50, the channel.send() operation
lacks error handling which can result in message loss if the send fails after the
original message is already deleted; wrap the channel.send() call in a try-catch block
to handle potential failures (permissions, rate limits, network errors) and add
appropriate error logging.
📍 This suggestion applies to lines 50-53
|
Note Linting checks passed successfully 🎉 All formatting and code quality checks are clean. You're good to merge 🚀 |
| } | ||
|
|
||
| // load at startup | ||
| await loadInteractions(); |
There was a problem hiding this comment.
The top-level await loadInteractions() lacks error handling. If this fails during module initialization (e.g., file system errors, malformed interaction files), the entire application will crash with an unhandled promise rejection, preventing the bot from starting.
Confidence: 5/5
Suggested Fix
| await loadInteractions(); | |
| try { | |
| await loadInteractions(); | |
| } catch (error) { | |
| console.error("Failed to load interactions at startup:", error); | |
| process.exit(1); | |
| } | |
Wrap the top-level await in a try-catch block to handle initialization failures gracefully. Exit the process explicitly if interactions can't be loaded, as the bot cannot function without them.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/events/interactionCreate/handleInteractions.ts at line 32, the top-level await
loadInteractions() lacks error handling which can cause the entire application to crash
with an unhandled promise rejection if loading fails; wrap the await loadInteractions()
call in a try-catch block that logs the error and calls process.exit(1) to fail fast
during startup rather than leaving the bot in an undefined state.
| const changelogContent = | ||
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md"))) || ""; |
There was a problem hiding this comment.
The readFile() operation lacks error handling. If the CHANGELOG.md file doesn't exist, has permission issues, or encounters I/O errors, the unhandled promise rejection will crash the interaction handler, leaving users without feedback.
Confidence: 5/5
Suggested Fix
| const changelogContent = | |
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md"))) || ""; | |
| let changelogContent = ""; | |
| try { | |
| changelogContent = | |
| (await readFile(path.join(__dirname, "..", "..", "CHANGELOG.md"))) || ""; | |
| } catch (error) { | |
| console.error("Failed to read CHANGELOG.md:", error); | |
| return interaction.reply({ | |
| content: "❌ Failed to load changelog. Please try again later.", | |
| ephemeral: true, | |
| }); | |
| } | |
Wrap the file read operation in a try-catch block to handle failures gracefully and provide user feedback when the changelog cannot be loaded.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/interactions/changelog.ts around lines 59-60, the readFile() operation lacks
error handling which can crash the interaction handler if the file doesn't exist or
has I/O errors; wrap the readFile call in a try-catch block that logs the error and
returns an ephemeral error message to the user informing them the changelog failed
to load.
📍 This suggestion applies to lines 59-60