From 7679c4bbddd8c7e8f367b3340373b06a3f6d51f7 Mon Sep 17 00:00:00 2001 From: Neil Tomar Date: Sat, 20 Jun 2026 19:25:00 +0530 Subject: [PATCH 1/7] fix formatting of code longer than Discord's message limit --- .../user_commands/format_code/Code.java | 69 +++++++++++++++++++ .../FormatAndIndentCodeMessageContext.java | 53 ++++++++++++-- .../format_code/FormatCodeCommand.java | 43 +++++++++--- .../format_code/FormatCodeMessageContext.java | 63 +++++++++++++++-- .../user_commands/format_code/Language.java | 49 +++++++++++++ 5 files changed, 258 insertions(+), 19 deletions(-) create mode 100644 src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java create mode 100644 src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java new file mode 100644 index 000000000..9982b1de9 --- /dev/null +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java @@ -0,0 +1,69 @@ +package net.discordjug.javabot.systems.user_commands.format_code; + +import java.util.ArrayList; +import java.util.List; + +/** + * Holds a piece of code and its {@link Language}, and turns it into + * Discord-friendly representations that respect Discord's 2000-character limit. + */ +public class Code { + + /** + * Maximum characters per chunk. Discord's hard limit per message is 2000; + * the remaining headroom covers the surrounding ```language fences. + */ + private static final int MAX_SIZE = 1980; + + private Language language; + private final String content; + + public Code(Language language, String content) { + this.language = language; + this.content = content; + } + + public String getContent() { + return content; + } + + public Language getLanguage() { + return language; + } + + public void setLanguage(Language language) { + this.language = language; + } + + /** + * Splits {@link #content} into pieces that each fit within {@link #MAX_SIZE}, + * breaking on newlines where possible so lines are not cut in half. + */ + public List toDiscordChunks() { + List chunks = new ArrayList<>(); + String remaining = content; + + while (remaining.length() > MAX_SIZE) { + int split = remaining.lastIndexOf('\n', MAX_SIZE); + if (split <= 0) { + // No newline in range (or only at the very start) -> hard cut, + // guaranteeing progress so this can never infinite-loop. + chunks.add(remaining.substring(0, MAX_SIZE)); + remaining = remaining.substring(MAX_SIZE); + } else { + chunks.add(remaining.substring(0, split)); + remaining = remaining.substring(split + 1); // +1 consumes the '\n' + } + } + chunks.add(remaining); + return chunks; + } + + /** Wraps each chunk in a language-tagged Discord code block. */ + public List toDiscordMessages() { + return toDiscordChunks() + .stream() + .map(chunk -> String.format("```%s\n%s\n```", language.getDiscordName(), chunk)) + .toList(); + } +} diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java index d544c7d63..9cfd906a2 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java @@ -1,15 +1,19 @@ package net.discordjug.javabot.systems.user_commands.format_code; - +import net.discordjug.javabot.util.ExceptionLogger; import net.discordjug.javabot.util.IndentationHelper; +import net.discordjug.javabot.util.Responses; import net.discordjug.javabot.util.StringUtils; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionContextType; import net.dv8tion.jda.api.interactions.commands.build.Commands; - +import net.dv8tion.jda.api.utils.FileUpload; import org.jetbrains.annotations.NotNull; import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; import java.util.List; /** @@ -27,9 +31,48 @@ public FormatAndIndentCodeMessageContext() { @Override public void execute(@NotNull MessageContextInteractionEvent event) { - event.replyFormat("```java\n%s\n```", IndentationHelper.formatIndentation(StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()), IndentationHelper.IndentationType.TABS)) + String indented = IndentationHelper.formatIndentation( + StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()), + IndentationHelper.IndentationType.TABS); + + if (indented.isBlank()) { + event.reply("There is no code to format in that message.").setEphemeral(true).queue(); + return; + } + + Code code = new Code(Language.JAVA, indented); + List messages = code.toDiscordMessages(); + + // Reply with the full code as a file (acknowledges the interaction), then post + // the readable code-block chunks in order. + FileUpload file = FileUpload.fromData(indented.getBytes(StandardCharsets.UTF_8), + "code." + code.getLanguage().getDiscordName()); + MessageChannel channel = event.getChannel(); + event.replyFiles(file) + .setAllowedMentions(List.of()) + .queue( + success -> sendChunksInOrder(channel, messages, 0, event), + error -> { + ExceptionLogger.capture(error, getClass().getSimpleName()); + Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") + .queue(); + } + ); + } + + private void sendChunksInOrder(MessageChannel channel, List messages, int index, @Nonnull MessageContextInteractionEvent event) { + if (index >= messages.size()) { + return; + } + channel.sendMessage(messages.get(index)) .setAllowedMentions(List.of()) - .setComponents(FormatCodeCommand.buildActionRow(event.getTarget(), event.getUser().getIdLong())) - .queue(); + .queue( + success -> sendChunksInOrder(channel, messages, index + 1, event), + error -> { + ExceptionLogger.capture(error, getClass().getSimpleName()); + Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") + .queue(); + } + ); } } diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java index 9f88bc1b6..fdaf6b49a 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java @@ -1,5 +1,6 @@ package net.discordjug.javabot.systems.user_commands.format_code; +import net.dv8tion.jda.api.interactions.InteractionHook; import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; import net.discordjug.javabot.util.*; import net.dv8tion.jda.api.components.actionrow.ActionRow; @@ -77,10 +78,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { .filter(m -> !m.getAuthor().isBot()).findFirst() .orElse(null); if (target != null) { - event.getHook().sendMessageFormat("```%s\n%s\n```", format, IndentationHelper.formatIndentation(StringUtils.standardSanitizer().compute(target.getContentRaw()),IndentationHelper.IndentationType.valueOf(indentation))) - .setAllowedMentions(List.of()) - .setComponents(buildActionRow(target, event.getUser().getIdLong())) - .queue(); + sendFormattedCode(event, target, format, indentation); } else { Responses.error(event.getHook(), "Could not find message; please specify a message id.").queue(); } @@ -92,11 +90,38 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { } long messageId = idOption.getAsLong(); event.getChannel().retrieveMessageById(messageId).queue( - target -> event.getHook().sendMessageFormat("```%s\n%s\n```", format, IndentationHelper.formatIndentation(StringUtils.standardSanitizer().compute(target.getContentRaw()), IndentationHelper.IndentationType.valueOf(indentation))) - .setAllowedMentions(List.of()) - .setComponents(buildActionRow(target, event.getUser().getIdLong())) - .queue(), + target -> sendFormattedCode(event, target, format, indentation), e -> Responses.error(event.getHook(), "Could not retrieve message with id: " + messageId).queue()); } } -} + + private void sendFormattedCode(SlashCommandInteractionEvent event, Message target, String format, String indentation) { + String content = IndentationHelper.formatIndentation( + StringUtils.standardSanitizer().compute(target.getContentRaw()), + IndentationHelper.IndentationType.valueOf(indentation)); + + if (content.isBlank()) { + Responses.error(event.getHook(), "There is no code to format in that message.").queue(); + return; + } + + Code code = new Code(Language.fromString(format), content); + sendChunksInOrder(event.getHook(), code.toDiscordMessages(), 0); + } + + private void sendChunksInOrder(InteractionHook hook, List messages, int index) { + if (index >= messages.size()) { + return; + } + var action = hook.sendMessage(messages.get(index)).setAllowedMentions(List.of()); + + action.queue( + success -> sendChunksInOrder(hook, messages, index + 1), + error -> { + ExceptionLogger.capture(error, getClass().getSimpleName()); + Responses.error(hook, "The message could not be converted into a formatted code block.") + .queue(); + } + ); + } +} \ No newline at end of file diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java index e0777670d..1f1736884 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java @@ -1,13 +1,18 @@ package net.discordjug.javabot.systems.user_commands.format_code; -import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; +import net.discordjug.javabot.util.ExceptionLogger; +import net.discordjug.javabot.util.Responses; import net.discordjug.javabot.util.StringUtils; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.utils.FileUpload; +import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionContextType; import net.dv8tion.jda.api.interactions.commands.build.Commands; - import org.jetbrains.annotations.NotNull; +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; import java.util.List; /** @@ -25,9 +30,57 @@ public FormatCodeMessageContext() { @Override public void execute(@NotNull MessageContextInteractionEvent event) { - event.replyFormat("```java\n%s\n```", StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw())) + String sanitized = StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()); + + if (sanitized.isBlank()) { + event.reply("There is no code to format in that message.") + .setEphemeral(true) + .queue(); + return; + } + + // Currently we always format as Java. A language dropdown will be added in the future. + Code code = new Code(Language.JAVA, sanitized); + List messages = code.toDiscordMessages(); + + // The reply both acknowledges the interaction and hands users the full, + // un-split code as a downloadable file (so chunking never loses anything). + FileUpload file = FileUpload.fromData( + sanitized.getBytes(StandardCharsets.UTF_8), + "code." + code.getLanguage().getDiscordName() + ); + + MessageChannel channel = event.getChannel(); + + event.replyFiles(file) .setAllowedMentions(List.of()) - .setComponents(FormatCodeCommand.buildActionRow(event.getTarget(), event.getUser().getIdLong())) - .queue(); + .queue( + success -> sendChunksInOrder(channel, messages, 0, event), + error -> { + ExceptionLogger.capture(error, getClass().getSimpleName()); + Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") + .queue(); + } + ); + } + + /** + * Sends the code-block chunks one at a time — each in the success callback of + * the previous — so Discord keeps them in order. + */ + private void sendChunksInOrder(MessageChannel channel, List messages, int index,@Nonnull MessageContextInteractionEvent event) { + if (index >= messages.size()) { + return; + } + channel.sendMessage(messages.get(index)) + .setAllowedMentions(List.of()) // never ping people from pasted code + .queue( + success -> sendChunksInOrder(channel, messages, index + 1, event), + error -> { + ExceptionLogger.capture(error, getClass().getSimpleName()); + Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") + .queue(); + } + ); } } diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java new file mode 100644 index 000000000..ea2265fc9 --- /dev/null +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java @@ -0,0 +1,49 @@ +package net.discordjug.javabot.systems.user_commands.format_code; + +public enum Language { + C("c"), + CPP("cpp"), + CSHARP("csharp"), + CSS("css"), + D("d"), + GO("go"), + HTML("html"), + JAVA("java"), + JAVASCRIPT("js"), + KOTLIN("kotlin"), + PHP("php"), + PYTHON("python"), + RUBY("ruby"), + RUST("rust"), + SQL("sql"), + SWIFT("swift"), + TYPESCRIPT("typescript"), + XML("xml"), + UNKNOWN("txt"); + + private final String discordName; + + Language(String discordName) { + this.discordName = discordName; + } + + public String getDiscordName() { + return discordName; + } + + /** + * Resolves a language from a string (e.g. the value of the /format-code "format" + * option) by matching its Discord code-fence name, falling back to {@link #UNKNOWN}. + * + * @param name the code-fence name to look up (case-insensitive) + * @return the matching language, or {@link #UNKNOWN} if none matches + */ + public static Language fromString(String name) { + for (Language language : values()) { + if (language.discordName.equalsIgnoreCase(name)) { + return language; + } + } + return UNKNOWN; + } +} \ No newline at end of file From d2f25e6cee1c47e8f1a24ce082ea7661a7211fdc Mon Sep 17 00:00:00 2001 From: Neil Tomar Date: Tue, 23 Jun 2026 20:59:36 +0530 Subject: [PATCH 2/7] add javadocs, and fixed requested changes. --- .../user_commands/format_code/Code.java | 23 +++-- .../FormatAndIndentCodeMessageContext.java | 45 +--------- .../format_code/FormatCodeCommand.java | 81 +++++++----------- .../format_code/FormatCodeDispatcher.java | 83 +++++++++++++++++++ .../format_code/FormatCodeMessageContext.java | 60 +------------- .../user_commands/format_code/Language.java | 68 +++++++++------ 6 files changed, 179 insertions(+), 181 deletions(-) create mode 100644 src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java index 9982b1de9..65ffa41c4 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java @@ -18,28 +18,34 @@ public class Code { private Language language; private final String content; + /** + * @param language the language the code is written in, used for syntax highlighting + * @param content the raw, already-sanitized code to format + */ public Code(Language language, String content) { this.language = language; this.content = content; } + /** + * @return the raw code content + */ public String getContent() { return content; } + /** + * @return the {@link Language} this code is formatted as + */ public Language getLanguage() { return language; } - public void setLanguage(Language language) { - this.language = language; - } - /** * Splits {@link #content} into pieces that each fit within {@link #MAX_SIZE}, * breaking on newlines where possible so lines are not cut in half. */ - public List toDiscordChunks() { + private List toDiscordChunks() { List chunks = new ArrayList<>(); String remaining = content; @@ -59,7 +65,12 @@ public List toDiscordChunks() { return chunks; } - /** Wraps each chunk in a language-tagged Discord code block. */ + /** + * Splits the content into chunks that each fit within Discord's character limit and wraps + * every chunk in a language-tagged code block. + * + * @return the formatted code-block messages, one per Discord message + */ public List toDiscordMessages() { return toDiscordChunks() .stream() diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java index 9cfd906a2..ca92222fc 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java @@ -1,20 +1,13 @@ package net.discordjug.javabot.systems.user_commands.format_code; -import net.discordjug.javabot.util.ExceptionLogger; import net.discordjug.javabot.util.IndentationHelper; -import net.discordjug.javabot.util.Responses; import net.discordjug.javabot.util.StringUtils; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionContextType; import net.dv8tion.jda.api.interactions.commands.build.Commands; -import net.dv8tion.jda.api.utils.FileUpload; import org.jetbrains.annotations.NotNull; import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; -import javax.annotation.Nonnull; -import java.nio.charset.StandardCharsets; -import java.util.List; /** *

This class represents the "Format and Indent Code" Message Context command.

@@ -35,44 +28,8 @@ public void execute(@NotNull MessageContextInteractionEvent event) { StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()), IndentationHelper.IndentationType.TABS); - if (indented.isBlank()) { - event.reply("There is no code to format in that message.").setEphemeral(true).queue(); - return; - } - Code code = new Code(Language.JAVA, indented); - List messages = code.toDiscordMessages(); - - // Reply with the full code as a file (acknowledges the interaction), then post - // the readable code-block chunks in order. - FileUpload file = FileUpload.fromData(indented.getBytes(StandardCharsets.UTF_8), - "code." + code.getLanguage().getDiscordName()); - MessageChannel channel = event.getChannel(); - event.replyFiles(file) - .setAllowedMentions(List.of()) - .queue( - success -> sendChunksInOrder(channel, messages, 0, event), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") - .queue(); - } - ); - } - private void sendChunksInOrder(MessageChannel channel, List messages, int index, @Nonnull MessageContextInteractionEvent event) { - if (index >= messages.size()) { - return; - } - channel.sendMessage(messages.get(index)) - .setAllowedMentions(List.of()) - .queue( - success -> sendChunksInOrder(channel, messages, index + 1, event), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") - .queue(); - } - ); + FormatCodeDispatcher.sendCode(code, event, event.getTarget()); } } diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java index fdaf6b49a..893266b78 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java @@ -1,6 +1,5 @@ package net.discordjug.javabot.systems.user_commands.format_code; -import net.dv8tion.jda.api.interactions.InteractionHook; import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; import net.discordjug.javabot.util.*; import net.dv8tion.jda.api.components.actionrow.ActionRow; @@ -16,7 +15,6 @@ import org.jetbrains.annotations.NotNull; import java.util.Collections; -import java.util.List; /** *

This class represents the /format-code command.

@@ -30,25 +28,7 @@ public FormatCodeCommand() { .setContexts(InteractionContextType.GUILD) .addOptions( new OptionData(OptionType.STRING, "message-id", "Message to be formatted, last message used if left blank.", false), - new OptionData(OptionType.STRING, "format", "The language used to format the code, defaults to Java if left blank.", false) - .addChoice("C", "c") - .addChoice("C#", "csharp") - .addChoice("C++", "cpp") - .addChoice("CSS", "css") - .addChoice("D", "d") - .addChoice("Go", "go") - .addChoice("HTML", "html") - .addChoice("Java", "java") - .addChoice("JavaScript", "js") - .addChoice("Kotlin", "kotlin") - .addChoice("PHP", "php") - .addChoice("Python", "python") - .addChoice("Ruby", "ruby") - .addChoice("Rust", "rust") - .addChoice("SQL", "sql") - .addChoice("Swift", "swift") - .addChoice("TypeScript", "typescript") - .addChoice("XML", "xml"), + formatOption(), new OptionData(OptionType.STRING,"auto-indent","The type of indentation applied to the message, does not automatically indent if left blank.",false) .addChoice("Four Spaces","FOUR_SPACES") .addChoice("Two Spaces","TWO_SPACES") @@ -57,6 +37,29 @@ public FormatCodeCommand() { ); } + /** + * Builds the {@code format} option, generating one choice per {@link Language} (excluding + * {@link Language#UNKNOWN}) so the enum stays the single source of truth for the language list. + * + * @return the configured {@code format} option + */ + private static OptionData formatOption() { + OptionData option = new OptionData(OptionType.STRING, "format", "The language used to format the code, defaults to Java if left blank.", false); + for (Language language : Language.values()) { + if (language != Language.UNKNOWN) { // UNKNOWN is the fallback, not a real choice + option.addChoice(language.getDisplayName(), language.name()); // value = enum name so valueOf() reverses it + } + } + return option; + } + + /** + * Builds the action row placed on the file-upload message: a delete button and a "View Original" link. + * + * @param target the original message linked by the "View Original" button + * @param requesterId the id of the user permitted to delete the message + * @return an action row containing the delete and "View Original" buttons + */ @Contract("_ -> new") static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) { return ActionRow.of(InteractionUtils.createDeleteButton(requesterId), @@ -66,9 +69,9 @@ public FormatCodeCommand() { @Override public void execute(@NotNull SlashCommandInteractionEvent event) { OptionMapping idOption = event.getOption("message-id"); - String format = event.getOption("format", "java", OptionMapping::getAsString); + Language language = event.getOption("format", Language.JAVA, o -> Language.fromString(o.getAsString())); String indentation = event.getOption("auto-indent","NULL",OptionMapping::getAsString); - event.deferReply().queue(); + if (idOption == null) { event.getChannel().getHistory() .retrievePast(10) @@ -78,7 +81,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { .filter(m -> !m.getAuthor().isBot()).findFirst() .orElse(null); if (target != null) { - sendFormattedCode(event, target, format, indentation); + sendFormattedCode(event, target, language, indentation); } else { Responses.error(event.getHook(), "Could not find message; please specify a message id.").queue(); } @@ -90,38 +93,18 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { } long messageId = idOption.getAsLong(); event.getChannel().retrieveMessageById(messageId).queue( - target -> sendFormattedCode(event, target, format, indentation), + target -> sendFormattedCode(event, target, language, indentation), e -> Responses.error(event.getHook(), "Could not retrieve message with id: " + messageId).queue()); } } - private void sendFormattedCode(SlashCommandInteractionEvent event, Message target, String format, String indentation) { + private void sendFormattedCode(SlashCommandInteractionEvent event, Message target, Language language, String indentation) { String content = IndentationHelper.formatIndentation( StringUtils.standardSanitizer().compute(target.getContentRaw()), IndentationHelper.IndentationType.valueOf(indentation)); - if (content.isBlank()) { - Responses.error(event.getHook(), "There is no code to format in that message.").queue(); - return; - } - - Code code = new Code(Language.fromString(format), content); - sendChunksInOrder(event.getHook(), code.toDiscordMessages(), 0); - } + Code code = new Code(language,content); - private void sendChunksInOrder(InteractionHook hook, List messages, int index) { - if (index >= messages.size()) { - return; - } - var action = hook.sendMessage(messages.get(index)).setAllowedMentions(List.of()); - - action.queue( - success -> sendChunksInOrder(hook, messages, index + 1), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(hook, "The message could not be converted into a formatted code block.") - .queue(); - } - ); + FormatCodeDispatcher.sendCode(code, event, target); } -} \ No newline at end of file +} diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java new file mode 100644 index 000000000..6a65d9c93 --- /dev/null +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java @@ -0,0 +1,83 @@ +package net.discordjug.javabot.systems.user_commands.format_code; + +import net.discordjug.javabot.util.*; +import net.dv8tion.jda.api.components.actionrow.ActionRow; +import net.dv8tion.jda.api.components.buttons.Button; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.interactions.commands.CommandInteraction; +import net.dv8tion.jda.api.utils.FileUpload; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Shared sending logic for the code-formatting commands. Replies with the full code as a + * downloadable file, then posts it as one or more ordered code-block messages that each respect + * Discord's 2000-character limit. + */ +public class FormatCodeDispatcher { + + /** + * Acknowledges the interaction by replying with the full code as a file, then posts the code as + * ordered code-block messages. Replies with an error instead if there is nothing to format. + * + * @param code the code to send + * @param event the interaction to reply to + * @param target the original message the code came from, used for the channel and the + * "View Original" / delete buttons + */ + public static void sendCode(Code code, @Nonnull CommandInteraction event, Message target){ + if (code.getContent().isBlank()) { + Responses.error(event.getHook(), "There is no code to format in that message.").queue(); + return; + } + // Currently we always format as Java. A language dropdown will be added in the future. + List messages = code.toDiscordMessages(); + + // The reply both acknowledges the interaction and hands users the full, + // un-split code as a downloadable file (so chunking never loses anything). + FileUpload file = FileUpload.fromData( + code.getContent().getBytes(StandardCharsets.UTF_8), + "code." + code.getLanguage().getDiscordName() + ); + + MessageChannel channel = target.getChannel(); + + event.replyFiles(file) + .setAllowedMentions(List.of()) + .setComponents(FormatCodeCommand.buildActionRow(target, event.getUser().getIdLong())) + .queue(success -> sendChunksInOrder(channel, messages, 0, target,event)); + } + + + private static void sendChunksInOrder(MessageChannel channel, List messages, int index, Message target, @Nonnull CommandInteraction event) { + if (index >= messages.size()) { + return; + } + var action = channel.sendMessage(messages.get(index)) + .setAllowedMentions(List.of()); + + if (index == messages.size() - 1) { + action.setComponents(buildActionRow(target, event.getUser().getIdLong())); + } + + action.queue(success -> + sendChunksInOrder(channel, messages, index + 1, target, event)); + } + + /** + * Builds the action row placed on the last code-block message. + * + * @param target the original message linked by the "View Original" button + * @param requesterId the id of the requesting user + * @return an action row containing the "View Original" link button + */ + @Contract("_ -> new") + static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) { + return ActionRow.of(Button.link(target.getJumpUrl(), "View Original")); + } +} diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java index 1f1736884..07afbe0d9 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java @@ -1,19 +1,12 @@ package net.discordjug.javabot.systems.user_commands.format_code; -import net.discordjug.javabot.util.ExceptionLogger; -import net.discordjug.javabot.util.Responses; import net.discordjug.javabot.util.StringUtils; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.utils.FileUpload; import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionContextType; import net.dv8tion.jda.api.interactions.commands.build.Commands; import org.jetbrains.annotations.NotNull; -import javax.annotation.Nonnull; -import java.nio.charset.StandardCharsets; -import java.util.List; /** *

This class represents the "Format Code" Message Context command.

@@ -30,57 +23,10 @@ public FormatCodeMessageContext() { @Override public void execute(@NotNull MessageContextInteractionEvent event) { - String sanitized = StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()); + String content = StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()); - if (sanitized.isBlank()) { - event.reply("There is no code to format in that message.") - .setEphemeral(true) - .queue(); - return; - } + Code code = new Code(Language.JAVA, content); - // Currently we always format as Java. A language dropdown will be added in the future. - Code code = new Code(Language.JAVA, sanitized); - List messages = code.toDiscordMessages(); - - // The reply both acknowledges the interaction and hands users the full, - // un-split code as a downloadable file (so chunking never loses anything). - FileUpload file = FileUpload.fromData( - sanitized.getBytes(StandardCharsets.UTF_8), - "code." + code.getLanguage().getDiscordName() - ); - - MessageChannel channel = event.getChannel(); - - event.replyFiles(file) - .setAllowedMentions(List.of()) - .queue( - success -> sendChunksInOrder(channel, messages, 0, event), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") - .queue(); - } - ); - } - - /** - * Sends the code-block chunks one at a time — each in the success callback of - * the previous — so Discord keeps them in order. - */ - private void sendChunksInOrder(MessageChannel channel, List messages, int index,@Nonnull MessageContextInteractionEvent event) { - if (index >= messages.size()) { - return; - } - channel.sendMessage(messages.get(index)) - .setAllowedMentions(List.of()) // never ping people from pasted code - .queue( - success -> sendChunksInOrder(channel, messages, index + 1, event), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") - .queue(); - } - ); + FormatCodeDispatcher.sendCode(code, event, event.getTarget()); } } diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java index ea2265fc9..f68d1edd8 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java @@ -1,36 +1,55 @@ package net.discordjug.javabot.systems.user_commands.format_code; +/** + * The programming languages supported by the code-formatting commands. Each constant maps a + * human-readable {@link #displayName} to the {@link #discordName} tag Discord uses for + * syntax highlighting inside code blocks. + */ public enum Language { - C("c"), - CPP("cpp"), - CSHARP("csharp"), - CSS("css"), - D("d"), - GO("go"), - HTML("html"), - JAVA("java"), - JAVASCRIPT("js"), - KOTLIN("kotlin"), - PHP("php"), - PYTHON("python"), - RUBY("ruby"), - RUST("rust"), - SQL("sql"), - SWIFT("swift"), - TYPESCRIPT("typescript"), - XML("xml"), - UNKNOWN("txt"); + C("C", "c"), + CPP("C++", "cpp"), + CSHARP("C#", "csharp"), + CSS("CSS", "css"), + D("D", "d"), + GO("Go", "go"), + HTML("HTML", "html"), + JAVA("Java", "java"), + JAVASCRIPT("JavaScript", "javascript"), + KOTLIN("Kotlin", "kotlin"), + PHP("PHP", "php"), + PYTHON("Python", "python"), + RUBY("Ruby", "ruby"), + RUST("Rust", "rust"), + SQL("SQL", "sql"), + SWIFT("Swift", "swift"), + TYPESCRIPT("TypeScript", "typescript"), + XML("XML", "xml"), + UNKNOWN("Unknown", "txt"); + /** + * @return the human-readable language name shown to users (e.g. {@code "C#"}) + */ + private final String displayName; + + /** + * @return the tag Discord uses to syntax-highlight this language in a code block (e.g. {@code "csharp"}) + */ private final String discordName; - Language(String discordName) { + Language(String displayName, String discordName) { + this.displayName = displayName; this.discordName = discordName; } + public String getDisplayName() { + return displayName; + } + public String getDiscordName() { return discordName; } + /** * Resolves a language from a string (e.g. the value of the /format-code "format" * option) by matching its Discord code-fence name, falling back to {@link #UNKNOWN}. @@ -39,11 +58,10 @@ public String getDiscordName() { * @return the matching language, or {@link #UNKNOWN} if none matches */ public static Language fromString(String name) { - for (Language language : values()) { - if (language.discordName.equalsIgnoreCase(name)) { - return language; - } + try { + return Language.valueOf(name.toUpperCase()); + } catch (IllegalArgumentException e) { + return UNKNOWN; } - return UNKNOWN; } } \ No newline at end of file From 1cdeef715c7fb55b62543797c4b945035e448e38 Mon Sep 17 00:00:00 2001 From: Neil Tomar Date: Tue, 23 Jun 2026 20:59:36 +0530 Subject: [PATCH 3/7] added javadocs, and fixed requested changes, fixed build issue. --- .../user_commands/format_code/Code.java | 21 ++- .../FormatAndIndentCodeMessageContext.java | 45 +----- .../format_code/FormatCodeCommand.java | 81 ++++------ .../format_code/FormatCodeDispatcher.java | 83 ++++++++++ .../format_code/FormatCodeMessageContext.java | 60 +------ .../user_commands/format_code/Language.java | 150 +++++++++++++----- 6 files changed, 243 insertions(+), 197 deletions(-) create mode 100644 src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java index 9982b1de9..10e2bb20e 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java @@ -18,6 +18,12 @@ public class Code { private Language language; private final String content; + /** + * Creates a code block for the given language and content. + * + * @param language the language the code is written in, used for syntax highlighting + * @param content the raw, already-sanitized code to format + */ public Code(Language language, String content) { this.language = language; this.content = content; @@ -31,15 +37,13 @@ public Language getLanguage() { return language; } - public void setLanguage(Language language) { - this.language = language; - } - /** * Splits {@link #content} into pieces that each fit within {@link #MAX_SIZE}, * breaking on newlines where possible so lines are not cut in half. + * + * @return the content split into chunks that each fit within the limit */ - public List toDiscordChunks() { + private List toDiscordChunks() { List chunks = new ArrayList<>(); String remaining = content; @@ -59,7 +63,12 @@ public List toDiscordChunks() { return chunks; } - /** Wraps each chunk in a language-tagged Discord code block. */ + /** + * Splits the content into chunks that each fit within Discord's character limit and wraps + * every chunk in a language-tagged code block. + * + * @return the formatted code-block messages, one per Discord message + */ public List toDiscordMessages() { return toDiscordChunks() .stream() diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java index 9cfd906a2..ca92222fc 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatAndIndentCodeMessageContext.java @@ -1,20 +1,13 @@ package net.discordjug.javabot.systems.user_commands.format_code; -import net.discordjug.javabot.util.ExceptionLogger; import net.discordjug.javabot.util.IndentationHelper; -import net.discordjug.javabot.util.Responses; import net.discordjug.javabot.util.StringUtils; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionContextType; import net.dv8tion.jda.api.interactions.commands.build.Commands; -import net.dv8tion.jda.api.utils.FileUpload; import org.jetbrains.annotations.NotNull; import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; -import javax.annotation.Nonnull; -import java.nio.charset.StandardCharsets; -import java.util.List; /** *

This class represents the "Format and Indent Code" Message Context command.

@@ -35,44 +28,8 @@ public void execute(@NotNull MessageContextInteractionEvent event) { StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()), IndentationHelper.IndentationType.TABS); - if (indented.isBlank()) { - event.reply("There is no code to format in that message.").setEphemeral(true).queue(); - return; - } - Code code = new Code(Language.JAVA, indented); - List messages = code.toDiscordMessages(); - - // Reply with the full code as a file (acknowledges the interaction), then post - // the readable code-block chunks in order. - FileUpload file = FileUpload.fromData(indented.getBytes(StandardCharsets.UTF_8), - "code." + code.getLanguage().getDiscordName()); - MessageChannel channel = event.getChannel(); - event.replyFiles(file) - .setAllowedMentions(List.of()) - .queue( - success -> sendChunksInOrder(channel, messages, 0, event), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") - .queue(); - } - ); - } - private void sendChunksInOrder(MessageChannel channel, List messages, int index, @Nonnull MessageContextInteractionEvent event) { - if (index >= messages.size()) { - return; - } - channel.sendMessage(messages.get(index)) - .setAllowedMentions(List.of()) - .queue( - success -> sendChunksInOrder(channel, messages, index + 1, event), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") - .queue(); - } - ); + FormatCodeDispatcher.sendCode(code, event, event.getTarget()); } } diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java index fdaf6b49a..893266b78 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java @@ -1,6 +1,5 @@ package net.discordjug.javabot.systems.user_commands.format_code; -import net.dv8tion.jda.api.interactions.InteractionHook; import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; import net.discordjug.javabot.util.*; import net.dv8tion.jda.api.components.actionrow.ActionRow; @@ -16,7 +15,6 @@ import org.jetbrains.annotations.NotNull; import java.util.Collections; -import java.util.List; /** *

This class represents the /format-code command.

@@ -30,25 +28,7 @@ public FormatCodeCommand() { .setContexts(InteractionContextType.GUILD) .addOptions( new OptionData(OptionType.STRING, "message-id", "Message to be formatted, last message used if left blank.", false), - new OptionData(OptionType.STRING, "format", "The language used to format the code, defaults to Java if left blank.", false) - .addChoice("C", "c") - .addChoice("C#", "csharp") - .addChoice("C++", "cpp") - .addChoice("CSS", "css") - .addChoice("D", "d") - .addChoice("Go", "go") - .addChoice("HTML", "html") - .addChoice("Java", "java") - .addChoice("JavaScript", "js") - .addChoice("Kotlin", "kotlin") - .addChoice("PHP", "php") - .addChoice("Python", "python") - .addChoice("Ruby", "ruby") - .addChoice("Rust", "rust") - .addChoice("SQL", "sql") - .addChoice("Swift", "swift") - .addChoice("TypeScript", "typescript") - .addChoice("XML", "xml"), + formatOption(), new OptionData(OptionType.STRING,"auto-indent","The type of indentation applied to the message, does not automatically indent if left blank.",false) .addChoice("Four Spaces","FOUR_SPACES") .addChoice("Two Spaces","TWO_SPACES") @@ -57,6 +37,29 @@ public FormatCodeCommand() { ); } + /** + * Builds the {@code format} option, generating one choice per {@link Language} (excluding + * {@link Language#UNKNOWN}) so the enum stays the single source of truth for the language list. + * + * @return the configured {@code format} option + */ + private static OptionData formatOption() { + OptionData option = new OptionData(OptionType.STRING, "format", "The language used to format the code, defaults to Java if left blank.", false); + for (Language language : Language.values()) { + if (language != Language.UNKNOWN) { // UNKNOWN is the fallback, not a real choice + option.addChoice(language.getDisplayName(), language.name()); // value = enum name so valueOf() reverses it + } + } + return option; + } + + /** + * Builds the action row placed on the file-upload message: a delete button and a "View Original" link. + * + * @param target the original message linked by the "View Original" button + * @param requesterId the id of the user permitted to delete the message + * @return an action row containing the delete and "View Original" buttons + */ @Contract("_ -> new") static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) { return ActionRow.of(InteractionUtils.createDeleteButton(requesterId), @@ -66,9 +69,9 @@ public FormatCodeCommand() { @Override public void execute(@NotNull SlashCommandInteractionEvent event) { OptionMapping idOption = event.getOption("message-id"); - String format = event.getOption("format", "java", OptionMapping::getAsString); + Language language = event.getOption("format", Language.JAVA, o -> Language.fromString(o.getAsString())); String indentation = event.getOption("auto-indent","NULL",OptionMapping::getAsString); - event.deferReply().queue(); + if (idOption == null) { event.getChannel().getHistory() .retrievePast(10) @@ -78,7 +81,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { .filter(m -> !m.getAuthor().isBot()).findFirst() .orElse(null); if (target != null) { - sendFormattedCode(event, target, format, indentation); + sendFormattedCode(event, target, language, indentation); } else { Responses.error(event.getHook(), "Could not find message; please specify a message id.").queue(); } @@ -90,38 +93,18 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { } long messageId = idOption.getAsLong(); event.getChannel().retrieveMessageById(messageId).queue( - target -> sendFormattedCode(event, target, format, indentation), + target -> sendFormattedCode(event, target, language, indentation), e -> Responses.error(event.getHook(), "Could not retrieve message with id: " + messageId).queue()); } } - private void sendFormattedCode(SlashCommandInteractionEvent event, Message target, String format, String indentation) { + private void sendFormattedCode(SlashCommandInteractionEvent event, Message target, Language language, String indentation) { String content = IndentationHelper.formatIndentation( StringUtils.standardSanitizer().compute(target.getContentRaw()), IndentationHelper.IndentationType.valueOf(indentation)); - if (content.isBlank()) { - Responses.error(event.getHook(), "There is no code to format in that message.").queue(); - return; - } - - Code code = new Code(Language.fromString(format), content); - sendChunksInOrder(event.getHook(), code.toDiscordMessages(), 0); - } + Code code = new Code(language,content); - private void sendChunksInOrder(InteractionHook hook, List messages, int index) { - if (index >= messages.size()) { - return; - } - var action = hook.sendMessage(messages.get(index)).setAllowedMentions(List.of()); - - action.queue( - success -> sendChunksInOrder(hook, messages, index + 1), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(hook, "The message could not be converted into a formatted code block.") - .queue(); - } - ); + FormatCodeDispatcher.sendCode(code, event, target); } -} \ No newline at end of file +} diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java new file mode 100644 index 000000000..8149ab628 --- /dev/null +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java @@ -0,0 +1,83 @@ +package net.discordjug.javabot.systems.user_commands.format_code; + +import net.discordjug.javabot.util.*; +import net.dv8tion.jda.api.components.actionrow.ActionRow; +import net.dv8tion.jda.api.components.buttons.Button; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.interactions.commands.CommandInteraction; +import net.dv8tion.jda.api.utils.FileUpload; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Shared sending logic for the code-formatting commands. Replies with the full code as a + * downloadable file, then posts it as one or more ordered code-block messages that each respect + * Discord's 2000-character limit. + */ +public class FormatCodeDispatcher { + + /** + * Acknowledges the interaction by replying with the full code as a file, then posts the code as + * ordered code-block messages. Replies with an error instead if there is nothing to format. + * + * @param code the code to send + * @param event the interaction to reply to + * @param target the original message the code came from, used for the channel and the + * "View Original" / delete buttons + */ + public static void sendCode(Code code, @Nonnull CommandInteraction event, Message target){ + if (code.getContent().isBlank()) { + Responses.error(event.getHook(), "There is no code to format in that message.").queue(); + return; + } + // Currently we always format as Java. A language dropdown will be added in the future. + List messages = code.toDiscordMessages(); + + // The reply both acknowledges the interaction and hands users the full, + // un-split code as a downloadable file (so chunking never loses anything). + FileUpload file = FileUpload.fromData( + code.getContent().getBytes(StandardCharsets.UTF_8), + "code." + code.getLanguage().getDiscordName() + ); + + MessageChannel channel = target.getChannel(); + + event.replyFiles(file) + .setAllowedMentions(List.of()) + .setComponents(FormatCodeCommand.buildActionRow(target, event.getUser().getIdLong())) + .queue(success -> sendChunksInOrder(channel, messages, 0, target,event)); + } + + + private static void sendChunksInOrder(MessageChannel channel, List messages, int index, Message target, @Nonnull CommandInteraction event) { + if (index >= messages.size()) { + return; + } + var action = channel.sendMessage(messages.get(index)) + .setAllowedMentions(List.of()); + + if (index == messages.size() - 1) { + action.setComponents(buildActionRow(target, event.getUser().getIdLong())); + } + + action.queue(success -> + sendChunksInOrder(channel, messages, index + 1, target, event)); + } + + /** + * Builds the action row placed on the last code-block message. + * + * @param target the original message linked by the "View Original" button + * @param requesterId the id of the requesting user + * @return an action row containing the "View Original" link button + */ + @Contract("_ -> new") + static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) { + return ActionRow.of(Button.link(target.getJumpUrl(), "View Original")); + } +} diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java index 1f1736884..07afbe0d9 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeMessageContext.java @@ -1,19 +1,12 @@ package net.discordjug.javabot.systems.user_commands.format_code; -import net.discordjug.javabot.util.ExceptionLogger; -import net.discordjug.javabot.util.Responses; import net.discordjug.javabot.util.StringUtils; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.utils.FileUpload; import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionContextType; import net.dv8tion.jda.api.interactions.commands.build.Commands; import org.jetbrains.annotations.NotNull; -import javax.annotation.Nonnull; -import java.nio.charset.StandardCharsets; -import java.util.List; /** *

This class represents the "Format Code" Message Context command.

@@ -30,57 +23,10 @@ public FormatCodeMessageContext() { @Override public void execute(@NotNull MessageContextInteractionEvent event) { - String sanitized = StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()); + String content = StringUtils.standardSanitizer().compute(event.getTarget().getContentRaw()); - if (sanitized.isBlank()) { - event.reply("There is no code to format in that message.") - .setEphemeral(true) - .queue(); - return; - } + Code code = new Code(Language.JAVA, content); - // Currently we always format as Java. A language dropdown will be added in the future. - Code code = new Code(Language.JAVA, sanitized); - List messages = code.toDiscordMessages(); - - // The reply both acknowledges the interaction and hands users the full, - // un-split code as a downloadable file (so chunking never loses anything). - FileUpload file = FileUpload.fromData( - sanitized.getBytes(StandardCharsets.UTF_8), - "code." + code.getLanguage().getDiscordName() - ); - - MessageChannel channel = event.getChannel(); - - event.replyFiles(file) - .setAllowedMentions(List.of()) - .queue( - success -> sendChunksInOrder(channel, messages, 0, event), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") - .queue(); - } - ); - } - - /** - * Sends the code-block chunks one at a time — each in the success callback of - * the previous — so Discord keeps them in order. - */ - private void sendChunksInOrder(MessageChannel channel, List messages, int index,@Nonnull MessageContextInteractionEvent event) { - if (index >= messages.size()) { - return; - } - channel.sendMessage(messages.get(index)) - .setAllowedMentions(List.of()) // never ping people from pasted code - .queue( - success -> sendChunksInOrder(channel, messages, index + 1, event), - error -> { - ExceptionLogger.capture(error, getClass().getSimpleName()); - Responses.error(event.getHook(), "The message could not be converted into a formatted code block.") - .queue(); - } - ); + FormatCodeDispatcher.sendCode(code, event, event.getTarget()); } } diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java index ea2265fc9..7905cf089 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java @@ -1,49 +1,117 @@ package net.discordjug.javabot.systems.user_commands.format_code; +/** + * The programming languages supported by the code-formatting commands. Each constant maps a + * human-readable {@link #displayName} to the {@link #discordName} tag Discord uses for + * syntax highlighting inside code blocks. + */ public enum Language { - C("c"), - CPP("cpp"), - CSHARP("csharp"), - CSS("css"), - D("d"), - GO("go"), - HTML("html"), - JAVA("java"), - JAVASCRIPT("js"), - KOTLIN("kotlin"), - PHP("php"), - PYTHON("python"), - RUBY("ruby"), - RUST("rust"), - SQL("sql"), - SWIFT("swift"), - TYPESCRIPT("typescript"), - XML("xml"), - UNKNOWN("txt"); + /** + * The C programming language. + */ + C("C", "c"), + /** + * The C++ programming language. + */ + CPP("C++", "cpp"), + /** + * The C# programming language. + */ + CSHARP("C#", "csharp"), + /** + * Cascading Style Sheets. + */ + CSS("CSS", "css"), + /** + * The D programming language. + */ + D("D", "d"), + /** + * The Go programming language. + */ + GO("Go", "go"), + /** + * HyperText Markup Language. + */ + HTML("HTML", "html"), + /** + * The Java programming language. + */ + JAVA("Java", "java"), + /** + * The JavaScript programming language. + */ + JAVASCRIPT("JavaScript", "javascript"), + /** + * The Kotlin programming language. + */ + KOTLIN("Kotlin", "kotlin"), + /** + * The PHP programming language. + */ + PHP("PHP", "php"), + /** + * The Python programming language. + */ + PYTHON("Python", "python"), + /** + * The Ruby programming language. + */ + RUBY("Ruby", "ruby"), + /** + * The Rust programming language. + */ + RUST("Rust", "rust"), + /** + * Structured Query Language. + */ + SQL("SQL", "sql"), + /** + * The Swift programming language. + */ + SWIFT("Swift", "swift"), + /** + * The TypeScript programming language. + */ + TYPESCRIPT("TypeScript", "typescript"), + /** + * Extensible Markup Language. + */ + XML("XML", "xml"), + /** + * Fallback used when the language is unrecognised; renders as plain text. + */ + UNKNOWN("Unknown", "txt"); - private final String discordName; + private final String displayName; + private final String discordName; - Language(String discordName) { - this.discordName = discordName; - } + Language(String displayName, String discordName) { + this.displayName = displayName; + this.discordName = discordName; + } - public String getDiscordName() { - return discordName; - } + public String getDisplayName() { + return displayName; + } - /** - * Resolves a language from a string (e.g. the value of the /format-code "format" - * option) by matching its Discord code-fence name, falling back to {@link #UNKNOWN}. - * - * @param name the code-fence name to look up (case-insensitive) - * @return the matching language, or {@link #UNKNOWN} if none matches - */ - public static Language fromString(String name) { - for (Language language : values()) { - if (language.discordName.equalsIgnoreCase(name)) { - return language; - } - } - return UNKNOWN; - } + public String getDiscordName() { + return discordName; + } + + + /** + * Resolves a language from a string (e.g. the value of the /format-code "format" + * option) by matching its Discord code-fence name, falling back to {@link #UNKNOWN}. + * + * @param name the code-fence name to look up (case-insensitive) + * @return the matching language, or {@link #UNKNOWN} if none matches + */ + public static Language fromString(String name) { + try { + return Language.valueOf(name.toUpperCase()); + } catch (IllegalArgumentException e) { + return UNKNOWN; + } + } } \ No newline at end of file From f5c30379d151d534b6e6ad04e9a11d0139e3367b Mon Sep 17 00:00:00 2001 From: Neil Tomar Date: Wed, 24 Jun 2026 13:27:56 +0530 Subject: [PATCH 4/7] refactor: address format-code review feedback --- .../user_commands/format_code/Code.java | 2 +- .../format_code/FormatCodeCommand.java | 23 +++----------- .../format_code/FormatCodeDispatcher.java | 30 ++++++++++++++----- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java index 10e2bb20e..a894649e9 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java @@ -15,7 +15,7 @@ public class Code { */ private static final int MAX_SIZE = 1980; - private Language language; + private final Language language; private final String content; /** diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java index 893266b78..a32ccd01a 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java @@ -2,8 +2,6 @@ import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; import net.discordjug.javabot.util.*; -import net.dv8tion.jda.api.components.actionrow.ActionRow; -import net.dv8tion.jda.api.components.buttons.Button; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionContextType; @@ -11,7 +9,7 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import org.jetbrains.annotations.Contract; + import org.jetbrains.annotations.NotNull; import java.util.Collections; @@ -53,19 +51,6 @@ private static OptionData formatOption() { return option; } - /** - * Builds the action row placed on the file-upload message: a delete button and a "View Original" link. - * - * @param target the original message linked by the "View Original" button - * @param requesterId the id of the user permitted to delete the message - * @return an action row containing the delete and "View Original" buttons - */ - @Contract("_ -> new") - static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) { - return ActionRow.of(InteractionUtils.createDeleteButton(requesterId), - Button.link(target.getJumpUrl(), "View Original")); - } - @Override public void execute(@NotNull SlashCommandInteractionEvent event) { OptionMapping idOption = event.getOption("message-id"); @@ -83,18 +68,18 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { if (target != null) { sendFormattedCode(event, target, language, indentation); } else { - Responses.error(event.getHook(), "Could not find message; please specify a message id.").queue(); + Responses.error(event, "Could not find message; please specify a message id.").queue(); } }); } else { if (Checks.isInvalidLongInput(idOption)) { - Responses.error(event.getHook(), "Please provide a valid message id!").queue(); + Responses.error(event, "Please provide a valid message id!").queue(); return; } long messageId = idOption.getAsLong(); event.getChannel().retrieveMessageById(messageId).queue( target -> sendFormattedCode(event, target, language, indentation), - e -> Responses.error(event.getHook(), "Could not retrieve message with id: " + messageId).queue()); + e -> Responses.error(event, "Could not retrieve message with id: " + messageId).queue()); } } diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java index 8149ab628..38b9b96f5 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java @@ -19,7 +19,7 @@ * downloadable file, then posts it as one or more ordered code-block messages that each respect * Discord's 2000-character limit. */ -public class FormatCodeDispatcher { +class FormatCodeDispatcher { /** * Acknowledges the interaction by replying with the full code as a file, then posts the code as @@ -32,10 +32,10 @@ public class FormatCodeDispatcher { */ public static void sendCode(Code code, @Nonnull CommandInteraction event, Message target){ if (code.getContent().isBlank()) { - Responses.error(event.getHook(), "There is no code to format in that message.").queue(); + Responses.error(event, "There is no code to format in that message.").queue(); return; } - // Currently we always format as Java. A language dropdown will be added in the future. + List messages = code.toDiscordMessages(); // The reply both acknowledges the interaction and hands users the full, @@ -49,7 +49,7 @@ public static void sendCode(Code code, @Nonnull CommandInteraction event, Messag event.replyFiles(file) .setAllowedMentions(List.of()) - .setComponents(FormatCodeCommand.buildActionRow(target, event.getUser().getIdLong())) + .setComponents(buildActionRow(target, event.getUser().getIdLong())) .queue(success -> sendChunksInOrder(channel, messages, 0, target,event)); } @@ -62,7 +62,11 @@ private static void sendChunksInOrder(MessageChannel channel, List messa .setAllowedMentions(List.of()); if (index == messages.size() - 1) { - action.setComponents(buildActionRow(target, event.getUser().getIdLong())); + if(index == 0){ + action.setComponents(buildActionRow(target, event.getUser().getIdLong())); + } else { + action.setComponents(buildActionRow(target)); + } } action.queue(success -> @@ -73,11 +77,23 @@ private static void sendChunksInOrder(MessageChannel channel, List messa * Builds the action row placed on the last code-block message. * * @param target the original message linked by the "View Original" button - * @param requesterId the id of the requesting user * @return an action row containing the "View Original" link button */ @Contract("_ -> new") - static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) { + static @NotNull ActionRow buildActionRow(@NotNull Message target) { return ActionRow.of(Button.link(target.getJumpUrl(), "View Original")); } + + /** + * Builds the action row placed on the file-upload message: a delete button and a "View Original" link. + * + * @param target the original message linked by the "View Original" button + * @param requesterId the id of the user permitted to delete the message + * @return an action row containing the delete and "View Original" buttons + */ + @Contract("_ -> new") + static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) { + return ActionRow.of(InteractionUtils.createDeleteButton(requesterId), + Button.link(target.getJumpUrl(), "View Original")); + } } From 6e4731467521c288474dd2a2f589c0245e799dd4 Mon Sep 17 00:00:00 2001 From: Neil Tomar Date: Wed, 24 Jun 2026 13:28:24 +0530 Subject: [PATCH 5/7] fix: normalize line endings in IndentationHelperTest --- .../javabot/util/IndentationHelperTest.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/test/java/net/discordjug/javabot/util/IndentationHelperTest.java b/src/test/java/net/discordjug/javabot/util/IndentationHelperTest.java index bfbbd351f..30a993f4d 100644 --- a/src/test/java/net/discordjug/javabot/util/IndentationHelperTest.java +++ b/src/test/java/net/discordjug/javabot/util/IndentationHelperTest.java @@ -22,10 +22,15 @@ public void testFormatIndentation() throws IOException { formatted = StringResourceCache.load("/Formatted Strings.txt").split("----"); for (int i = 0, k = 0; i < unformatted.length; i++) { - assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.FOUR_SPACES), "Method failed to format a text with four spaces correctly"); - assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.TWO_SPACES), "Method failed to format a text with two spaces correctly"); - assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.TABS), "Method failed to format a text with tabs correctly."); - assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.NULL), "Method returned a String not matching the input"); + assertEquals(normalizeLineEndings(formatted[k++]), normalizeLineEndings(IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.FOUR_SPACES)), "Method failed to format a text with four spaces correctly"); + assertEquals(normalizeLineEndings(formatted[k++]), normalizeLineEndings(IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.TWO_SPACES)), "Method failed to format a text with two spaces correctly"); + assertEquals(normalizeLineEndings(formatted[k++]), normalizeLineEndings(IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.TABS)), "Method failed to format a text with tabs correctly."); + assertEquals(normalizeLineEndings(formatted[k++]), normalizeLineEndings(IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.NULL)), "Method returned a String not matching the input"); } } + + private static String normalizeLineEndings(String text) { + return text.replace("\r\n", "\n") + .replace("\r", "\n"); + } } From 6556600c597801c980d90d9d2c5ab1805ca55eee Mon Sep 17 00:00:00 2001 From: Neil Tomar Date: Wed, 24 Jun 2026 22:10:36 +0530 Subject: [PATCH 6/7] fix: minor fixes --- .../user_commands/format_code/FormatCodeCommand.java | 8 +++++--- .../format_code/FormatCodeDispatcher.java | 12 +++++++++++- .../systems/user_commands/format_code/Language.java | 4 ++++ .../java/net/discordjug/javabot/util/Responses.java | 5 +++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java index a32ccd01a..805348759 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java @@ -68,18 +68,20 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { if (target != null) { sendFormattedCode(event, target, language, indentation); } else { - Responses.error(event, "Could not find message; please specify a message id.").queue(); + Responses.errorWithTitle(event, "Message Not Found", "No recent user message could be found. Please specify a message ID.") + .queue(); } }); } else { if (Checks.isInvalidLongInput(idOption)) { - Responses.error(event, "Please provide a valid message id!").queue(); + Responses.errorWithTitle(event, "Invalid Message ID", "Please provide a valid Discord message ID.") + .queue(); return; } long messageId = idOption.getAsLong(); event.getChannel().retrieveMessageById(messageId).queue( target -> sendFormattedCode(event, target, language, indentation), - e -> Responses.error(event, "Could not retrieve message with id: " + messageId).queue()); + error -> Responses.errorWithTitle(event, "Message Not Found", "Could not retrieve the message with ID `" + messageId + "`. Make sure the message exists and is accessible.").queue()); } } diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java index 38b9b96f5..bc1789dab 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java @@ -21,6 +21,11 @@ */ class FormatCodeDispatcher { + /** + * The maximum number of code-block messages to post inline; longer code is sent only as a file. + */ + private static final int MAX_MESSAGES = 5; + /** * Acknowledges the interaction by replying with the full code as a file, then posts the code as * ordered code-block messages. Replies with an error instead if there is nothing to format. @@ -32,7 +37,7 @@ class FormatCodeDispatcher { */ public static void sendCode(Code code, @Nonnull CommandInteraction event, Message target){ if (code.getContent().isBlank()) { - Responses.error(event, "There is no code to format in that message.").queue(); + Responses.errorWithTitle(event, "404 Code not found","There is no code to format in that message.").queue(); return; } @@ -58,6 +63,11 @@ private static void sendChunksInOrder(MessageChannel channel, List messa if (index >= messages.size()) { return; } + if (messages.size() > MAX_MESSAGES) { + Responses.errorWithTitle(event.getHook(), "Output Too Large", "The formatted result is too large to send. Please provide a smaller code snippet or use a paste service instead." + ).queue(); + return; + } var action = channel.sendMessage(messages.get(index)) .setAllowedMentions(List.of()); diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java index 7905cf089..eac7ad1e6 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java @@ -78,6 +78,10 @@ public enum Language { * Extensible Markup Language. */ XML("XML", "xml"), + /** + * A structured data format. + */ + JSON("JSON", "json"), /** * Fallback used when the language is unrecognised; renders as plain text. */ diff --git a/src/main/java/net/discordjug/javabot/util/Responses.java b/src/main/java/net/discordjug/javabot/util/Responses.java index b48de0085..93f1a579f 100644 --- a/src/main/java/net/discordjug/javabot/util/Responses.java +++ b/src/main/java/net/discordjug/javabot/util/Responses.java @@ -62,6 +62,11 @@ private Responses() { return reply(event, title, String.format(message, args), Type.ERROR.getColor(), true); } + @CheckReturnValue + public static @NotNull WebhookMessageCreateAction errorWithTitle(InteractionHook hook, String title, String message, Object... args) { + return reply(hook, title, String.format(message, args), Type.ERROR.getColor(), true); + } + @CheckReturnValue public static @NotNull WebhookMessageCreateAction error(InteractionHook hook, String message, Object... args) { return reply(hook, "An Error Occurred", String.format(message, args), Type.ERROR.getColor(), true); From fd2be73dfc637ec81b907d283761d1044c20a875 Mon Sep 17 00:00:00 2001 From: Neil Tomar Date: Thu, 25 Jun 2026 06:27:19 +0530 Subject: [PATCH 7/7] fix: more minor fixes --- .../format_code/FormatCodeCommand.java | 3 -- .../format_code/FormatCodeDispatcher.java | 28 ++++++------------- .../user_commands/format_code/Language.java | 8 ++++-- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java index 805348759..45fb674e2 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeCommand.java @@ -12,8 +12,6 @@ import org.jetbrains.annotations.NotNull; -import java.util.Collections; - /** *

This class represents the /format-code command.

*/ @@ -61,7 +59,6 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { event.getChannel().getHistory() .retrievePast(10) .queue(messages -> { - Collections.reverse(messages); Message target = messages.stream() .filter(m -> !m.getAuthor().isBot()).findFirst() .orElse(null); diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java index bc1789dab..38fbcb6a6 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java @@ -6,12 +6,10 @@ import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.interactions.commands.CommandInteraction; -import net.dv8tion.jda.api.utils.FileUpload; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; -import java.nio.charset.StandardCharsets; import java.util.List; /** @@ -22,7 +20,7 @@ class FormatCodeDispatcher { /** - * The maximum number of code-block messages to post inline; longer code is sent only as a file. + * The maximum number of code-block messages to post inline; longer code results as an ERROR. */ private static final int MAX_MESSAGES = 5; @@ -43,18 +41,15 @@ public static void sendCode(Code code, @Nonnull CommandInteraction event, Messag List messages = code.toDiscordMessages(); - // The reply both acknowledges the interaction and hands users the full, - // un-split code as a downloadable file (so chunking never loses anything). - FileUpload file = FileUpload.fromData( - code.getContent().getBytes(StandardCharsets.UTF_8), - "code." + code.getLanguage().getDiscordName() - ); - MessageChannel channel = target.getChannel(); - event.replyFiles(file) - .setAllowedMentions(List.of()) - .setComponents(buildActionRow(target, event.getUser().getIdLong())) + if (messages.size() > MAX_MESSAGES) { + Responses.errorWithTitle(event.getHook(), "Output Too Large", "The formatted result is too large to send. Please provide a smaller code snippet or use a paste service instead." + ).queue(); + return; + } + + Responses.success(event, "Success", "The formatted message is being sent to this channel.") .queue(success -> sendChunksInOrder(channel, messages, 0, target,event)); } @@ -63,11 +58,6 @@ private static void sendChunksInOrder(MessageChannel channel, List messa if (index >= messages.size()) { return; } - if (messages.size() > MAX_MESSAGES) { - Responses.errorWithTitle(event.getHook(), "Output Too Large", "The formatted result is too large to send. Please provide a smaller code snippet or use a paste service instead." - ).queue(); - return; - } var action = channel.sendMessage(messages.get(index)) .setAllowedMentions(List.of()); @@ -101,7 +91,7 @@ private static void sendChunksInOrder(MessageChannel channel, List messa * @param requesterId the id of the user permitted to delete the message * @return an action row containing the delete and "View Original" buttons */ - @Contract("_ -> new") + @Contract("_,_ -> new") static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) { return ActionRow.of(InteractionUtils.createDeleteButton(requesterId), Button.link(target.getJumpUrl(), "View Original")); diff --git a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java index eac7ad1e6..5a5d71067 100644 --- a/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java +++ b/src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java @@ -83,9 +83,13 @@ public enum Language { */ JSON("JSON", "json"), /** - * Fallback used when the language is unrecognised; renders as plain text. + * Simple plain text. */ - UNKNOWN("Unknown", "txt"); + TXT("TXT", "txt"), + /** + * Fallback used when the language is unrecognised. + */ + UNKNOWN("Unknown", ""); private final String displayName; private final String discordName;