Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Comment thread
danthe1st marked this conversation as resolved.
Outdated
private final String content;

public Code(Language language, String content) {
this.language = language;
this.content = content;
}

public String getContent() {
Comment thread
danthe1st marked this conversation as resolved.
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<String> toDiscordChunks() {
Comment thread
danthe1st marked this conversation as resolved.
Outdated
List<String> 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<String> toDiscordMessages() {
return toDiscordChunks()
.stream()
.map(chunk -> String.format("```%s\n%s\n```", language.getDiscordName(), chunk))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -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();
Comment thread
danthe1st marked this conversation as resolved.
Outdated
return;
}

Code code = new Code(Language.JAVA, indented);
List<String> 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());
Comment thread
danthe1st marked this conversation as resolved.
Outdated
Responses.error(event.getHook(), "The message could not be converted into a formatted code block.")
.queue();
}
);
}

private void sendChunksInOrder(MessageChannel channel, List<String> 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()))
Comment thread
danthe1st marked this conversation as resolved.
.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();
}
);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}
Expand All @@ -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) {
Comment thread
danthe1st marked this conversation as resolved.
Outdated
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<String> 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();
}
);
}
}
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -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<String> 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<String> 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();
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package net.discordjug.javabot.systems.user_commands.format_code;

public enum Language {
Comment thread
danthe1st marked this conversation as resolved.
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()) {
Comment thread
danthe1st marked this conversation as resolved.
Outdated
if (language.discordName.equalsIgnoreCase(name)) {
return language;
}
}
return UNKNOWN;
}
}
Loading