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
Expand Up @@ -40,6 +40,8 @@
import org.togetherjava.tjbot.features.mathcommands.wolframalpha.WolframAlphaCommand;
import org.togetherjava.tjbot.features.mediaonly.MediaOnlyChannelListener;
import org.togetherjava.tjbot.features.messages.MessageCommand;
import org.togetherjava.tjbot.features.messages.RewriteMsgCommand;
import org.togetherjava.tjbot.features.messages.RewriteMsgService;
import org.togetherjava.tjbot.features.moderation.BanCommand;
import org.togetherjava.tjbot.features.moderation.KickCommand;
import org.togetherjava.tjbot.features.moderation.ModerationActionsStore;
Expand Down Expand Up @@ -125,6 +127,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
TopHelpersService topHelpersService = new TopHelpersService(database);
TopHelpersAssignmentRoutine topHelpersAssignmentRoutine =
new TopHelpersAssignmentRoutine(config, topHelpersService);
RewriteMsgService rewriteService = new RewriteMsgService(chatGptService, helpSystemHelper);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

// NOTE The system can add special system relevant commands also by itself,
// hence this list may not necessarily represent the full list of all commands actually
Expand Down Expand Up @@ -205,6 +208,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new ChatGptCommand(chatGptService, helpSystemHelper));
features.add(new JShellCommand(jshellEval));
features.add(new MessageCommand());
features.add(new RewriteMsgCommand(rewriteService));

FeatureBlacklist<Class<?>> blacklist = blacklistConfig.normal();
return blacklist.filterStream(features.stream(), Object::getClass).toList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.togetherjava.tjbot.features.messages;

import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.togetherjava.tjbot.features.CommandVisibility;
import org.togetherjava.tjbot.features.SlashCommandAdapter;

import java.util.Arrays;
import java.util.Optional;

/**
* The implemented command is {@code /rewrite}, which allows users to have their message rewritten
* in a clearer, more professional, or better structured form using ChatGPT AI.
* <p>
* The rewritten message is shown as an ephemeral message visible only to the user who triggered the
* command, making it perfect for getting quick writing improvements without cluttering the channel.
Comment thread
tj-wazei marked this conversation as resolved.
Outdated
* <p>
* Users can optionally specify a tone/style for the rewrite. If not provided, defaults to CLEAR.
Comment thread
tj-wazei marked this conversation as resolved.
Outdated
*/
public final class RewriteMsgCommand extends SlashCommandAdapter {
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
private static final Logger logger = LoggerFactory.getLogger(RewriteMsgCommand.class);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
public static final String COMMAND_NAME = "rewrite";
private static final String MESSAGE_OPTION = "message";
private static final String TONE_OPTION = "tone";
private static final int MAX_MESSAGE_LENGTH = 500;
private static final int MIN_MESSAGE_LENGTH = 3;

private final RewriteMsgService rewriteMsgService;


Comment thread
Zabuzard marked this conversation as resolved.
Outdated
public RewriteMsgCommand(RewriteMsgService rewriteMsgService) {
super(COMMAND_NAME, "Rewrite your message in a clearer, more professional form",
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
CommandVisibility.GUILD);

this.rewriteMsgService = rewriteMsgService;

logger.debug("Initializing RewriteMsgCommand with ChatGptService and HelpSystemHelper");
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

final OptionData toneOption = new OptionData(OptionType.STRING, TONE_OPTION,
"The tone/style for the rewritten message (default: "
+ RewriteMsgTone.CLEAR.getDisplayName() + ")",
false);

logger.debug("Adding tone choices to command options");
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
Arrays.stream(RewriteMsgTone.values()).forEach(tone -> {
toneOption.addChoice(tone.getDisplayName(), tone.name());
logger.debug("Added tone choice: {} ({})", tone.getDisplayName(), tone.name());
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
});

final OptionData messageOption =
new OptionData(OptionType.STRING, MESSAGE_OPTION, "The message you want to rewrite",
true)
.setMinLength(MIN_MESSAGE_LENGTH)
.setMaxLength(MAX_MESSAGE_LENGTH);

logger.debug("Configured message option: min={}, max={}", MIN_MESSAGE_LENGTH,
MAX_MESSAGE_LENGTH);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

super.getData().addOptions(messageOption, toneOption);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
logger.debug("RewriteMsgCommand initialization complete");
}

@Override
public void onSlashCommand(SlashCommandInteractionEvent event) {
logger.debug("onSlashCommand method invoked");
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
event.deferReply(true).queue();
logger.debug("Reply deferred as ephemeral");

final String userId = event.getUser().getId();

logger.info("Rewrite command triggered by user: {}", userId);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

final String userMessage =
this.rewriteMsgService.validateMsg(event.getOption(MESSAGE_OPTION), userId);

final RewriteMsgTone tone =
this.rewriteMsgService.parseTone(event.getOption(TONE_OPTION), userId);

final Optional<String> rewrittenMessage =
this.rewriteMsgService.rewrite(userMessage, tone, userId);

final Optional<MessageEmbed> responseEmbed =
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
this.rewriteMsgService.buildResponse(userMessage, rewrittenMessage.orElse(null),
tone, userId, event.getJDA().getSelfUser());

logger.debug("Sending embed response to user: {}", userId);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

if (responseEmbed.isPresent()) {
event.getHook()
.sendMessageEmbeds(responseEmbed.get())
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
.queue(_ -> logger.info("Rewrite response sent successfully to user: {}", userId),
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
error -> logger.error("Failed to send rewrite response to user: {}", userId,
error));
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
} else {
logger.error("Failed to build response embed for user: {}", userId);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
event.getHook()
.sendMessage(
"An error occurred while processing your request. Please try again later.")
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
.queue();
}
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package org.togetherjava.tjbot.features.messages;

import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.SelfUser;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.togetherjava.tjbot.features.chatgpt.ChatGptModel;
import org.togetherjava.tjbot.features.chatgpt.ChatGptService;
import org.togetherjava.tjbot.features.help.HelpSystemHelper;

import java.util.Optional;

/**
* Service for handling rewrite command business logic and ChatGPT integration.
*/
public class RewriteMsgService {
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
private static final Logger logger = LoggerFactory.getLogger(RewriteMsgService.class);
private static final ChatGptModel CHAT_GPT_MODEL = ChatGptModel.HIGH_QUALITY;

private final ChatGptService chatGptService;
private final HelpSystemHelper helper;
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

/**
* Creates a new RewriteMsgService.
*
* @param chatGptService the ChatGPT service
* @param helper the help system helper for embed formatting
*/
public RewriteMsgService(ChatGptService chatGptService, HelpSystemHelper helper) {
this.chatGptService = chatGptService;
this.helper = helper;
}

public String validateMsg(@Nullable OptionMapping messageOption, String userId) {
logger.debug("Extracting message option for user: {}", userId);
logger.debug("Retrieved message option: {}", messageOption != null ? "present" : "null");
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

final String userMessage = messageOption != null ? messageOption.getAsString() : "";

if (userMessage.isEmpty()) {
logger.warn("User {} provided an empty message", userId);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
} else {
logger.debug("User {} provided message of length: {}", userId, userMessage.length());
logMessagePreview(userMessage);
}

return userMessage;
}

public RewriteMsgTone parseTone(@Nullable OptionMapping toneOption, String userId) {
logger.debug("Extracting tone option for user: {}", userId);
logger.debug("Retrieved tone option: {}", toneOption != null ? "present" : "null");
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

if (toneOption == null) {
logger.debug("Tone option not provided, using default: {}",
RewriteMsgTone.CLEAR.getDisplayName());
return RewriteMsgTone.CLEAR;
}

final String toneValue = toneOption.getAsString();
try {
final RewriteMsgTone tone = RewriteMsgTone.valueOf(toneValue);
logger.debug("Parsed tone value from option: {}", toneValue);
return tone;
} catch (IllegalArgumentException e) {
logger.error("Invalid tone value provided: {}, using default CLEAR", toneValue, e);
return RewriteMsgTone.CLEAR;
}
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
}

public Optional<String> rewrite(String userMessage, RewriteMsgTone tone, String userId) {
logger.debug("Rewriting message for user {} with tone: {}", userId, tone.getDisplayName());

final String rewritePrompt = buildChatGptPrompt(userMessage, tone);
logger.debug("ChatGPT prompt prepared: {} characters", rewritePrompt.length());
Comment thread
Zabuzard marked this conversation as resolved.
Outdated

try {
final Optional<String> rewrittenMessage = chatGptService.ask(rewritePrompt,
"Professional writing improvement", CHAT_GPT_MODEL);

if (rewrittenMessage.isPresent()) {
logger.info("Successfully rewrote message for user: {} with tone: {}", userId,
tone.getDisplayName());
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
logMessagePreview(rewrittenMessage.get());
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
} else {
logger.warn("ChatGPT returned empty response for user: {}", userId);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
}

return rewrittenMessage;
} catch (Exception e) {
logger.error("Failed to rewrite message for user: {}", userId, e);
return Optional.empty();
}
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
}

public Optional<MessageEmbed> buildResponse(String userMessage,
@Nullable String rewrittenMessage, RewriteMsgTone tone, String userId,
SelfUser selfUser) {
logger.debug("Building response embed for user: {}", userId);

final String responseContent = rewrittenMessage != null ? rewrittenMessage
: "Sorry, I couldn't rewrite your message at this time. Please try again later.";
final String embedTitle = "Rewritten message (" + tone.getDisplayName() + ")";
logger.debug("Prepared embed title: {}", embedTitle);

try {
final MessageEmbed responseEmbed = helper.generateGptResponseEmbed(
"**Original:**\n" + userMessage + "\n\n**Rewritten:**\n" + responseContent,
selfUser, embedTitle, CHAT_GPT_MODEL);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
logger.debug("Message embed created successfully for user: {}", userId);
return Optional.of(responseEmbed);
} catch (Exception e) {
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
logger.error("Failed to create message embed for user: {}", userId, e);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
return Optional.empty();
}
}

private String buildChatGptPrompt(String userMessage, RewriteMsgTone tone) {
return """
Please rewrite the following message to make it clearer, more professional, \
and better structured. Maintain the original meaning while improving the quality \
of the writing. Do NOT use em-dashes (—). %s

If the message is already well-written, provide minor improvements.

Original message:
%s""".formatted(tone.getPromptInstruction(), userMessage);
}

private void logMessagePreview(String message) {
final int previewLength = Math.min(50, message.length());
final String preview = message.substring(0, previewLength);

logger.debug("Message content preview: {}", preview);
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.togetherjava.tjbot.features.messages;

/**
* Enum representing the available tone/style options for message rewriting.
* <p>
* Each tone provides a specific instruction to ChatGPT for how to approach the rewrite.
*/
public enum RewriteMsgTone {
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
CLEAR("Clear"),
PRO("Pro"),
DETAILED("Detailed"),
TECHNICAL("Technical");

private final String displayName;

RewriteMsgTone(String displayName) {
this.displayName = displayName;
}

/**
* Gets the display name of this tone.
*
* @return the display name
*/
public String getDisplayName() {
return displayName;
}

/**
* Gets the prompt instruction for this tone.
*
* @return the prompt instruction to include in ChatGPT prompt
*/
public String getPromptInstruction() {
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
return switch (this) {
case CLEAR -> "Make it clear and easy to understand.";
case PRO -> "Use a professional and polished tone.";
case DETAILED -> "Expand with more detail and explanation.";
case TECHNICAL -> "Use technical and specialized language where appropriate.";
};
}
Comment thread
Zabuzard marked this conversation as resolved.
Outdated
}
Loading