Skip to content

Conversation

@firasrg
Copy link
Member

@firasrg firasrg commented Jan 13, 2026

New Feature: Rewrite Command

The /rewrite command allows users to have their messages rewritten in a clearer, more professional, or better structured form using ChatGPT AI (GPT 5 mini). The rewritten message is displayed as an ephemeral message (visible only to the user), making it perfect for getting quick writing improvements without cluttering the channel where they are.

Usage

/rewrite message:<text> [tone:<style>]

Parameters

  • message (required): The message to rewrite (3-500 characters)
  • tone (optional): The tone/style enum value for the rewrite. Defaults to CLEAR if not provided

Available Tones

  • CLEAR (default): Make the message clear and easy to understand
  • PRO: Use a professional and polished tone
  • DETAILED: Expand with more detail and explanation
  • TECHNICAL: Use technical and specialized language where appropriate

How It Works

  1. You invoke the command with your message and optional tone
  2. ChatGPT analyzes and rewrites the message according to the selected tone
  3. A formatted embed displays both the original and rewritten versions
  4. The response is ephemeral—only visible to you for 15 minutes
  5. You can copy the rewritten text and use it.

Example

image

UPDATE

  • The command name has been changed into /rewrite-msg for consistency with the class in source code.
  • The following is a demo that shows all possible states :

tjbot-rewrite-demo-3

@firasrg firasrg requested a review from a team as a code owner January 13, 2026 21:56
* <p>
* Users can optionally specify a tone/style for the rewrite. If not provided, defaults to CLEAR.
*/
public final class RewriteMsgCommand extends SlashCommandAdapter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont abbreviate, RewriteMsgCommand. but to avoid confusion i would try to stick to the actual slash-command name, so just RewriteCommand would be better imo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about doing that in the beginning, but I found that it may be confusing, since it's possible to be interpreted "rewrite something else, other than a message", so I found it better to write "Message" as abbreviation to avoid verbosity

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that confusion then also applies to the slashcommand. if u think its confusing then the slash command has to be renamed as well, to /rewrite-msg for example. then the class name is okay again

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/rewrite is perfectly fine

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tj-wazei is this a requirement or an optional suggestion?

Comment on lines 34 to 35


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double new line



public RewriteMsgCommand(RewriteMsgService rewriteMsgService) {
super(COMMAND_NAME, "Rewrite your message in a clearer, more professional form",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would reword it to "Let AI rephrase and improve your message"

* Users can optionally specify a tone/style for the rewrite. If not provided, defaults to CLEAR.
*/
public final class RewriteMsgCommand extends SlashCommandAdapter {
private static final Logger logger = LoggerFactory.getLogger(RewriteMsgCommand.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your logging in this entire class is mostly all over. almost all your "debug" logs are trace logs instead.
and most of your info logs are debug logs.

this has to be adjusted. ill try to leave comments on a per-log base


this.rewriteMsgService = rewriteMsgService;

logger.debug("Initializing RewriteMsgCommand with ChatGptService and HelpSystemHelper");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

obsolete, BotCore already logs this stuff on trace level. remove

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

logger.debug("Message content preview: {}", preview);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that log is missing context and not that helpful without

* <p>
* Each tone provides a specific instruction to ChatGPT for how to approach the rewrite.
*/
public enum RewriteMsgTone {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put it as private helper inside RewriteCommand, then remove all the javadoc. all javadoc in this class is obsolete and just documenting what the code already says, its not needed and just bloat

Comment on lines 34 to 41
public String getPromptInstruction() {
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.";
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make that a field instead, like your displayName

Comment on lines 25 to 34
public String getDisplayName() {
return displayName;
}

/**
* Gets the prompt instruction for this tone.
*
* @return the prompt instruction to include in ChatGPT prompt
*/
public String getPromptInstruction() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

once you moved that into RewriteCommand as private enum Tone {...}, the getters are obsolete as RewriteCommand can access the fields directly. so go for just:

private enum Tone {
  CLEAR("Clear", "Make it clear and easy to understand."),
  PRO("Pro", "Use a professional and polished tone."),
  ...

  final String displayName;
  final String promptInstructions;

  Tone(String displayName, String promptInstructions) { ... }
}

TopHelpersService topHelpersService = new TopHelpersService(database);
TopHelpersAssignmentRoutine topHelpersAssignmentRoutine =
new TopHelpersAssignmentRoutine(config, topHelpersService);
RewriteMsgService rewriteService = new RewriteMsgService(chatGptService, helpSystemHelper);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(as commented on earlier, this service shouldnt exist)

@sonarqubecloud
Copy link

Copy link
Contributor

@tj-wazei tj-wazei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see comments. There's a lot of final abuse that does nothing but add clutter to the code. It's best to use final when you need it.


/**
* The implemented command is {@code /rewrite-msg}, which allows users to have their message
* rewritten in a clearer, more professional, or better structured form using ChatGPT AI.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove ChatGPT from the docs as you leak too much implementation details. "using AI" is fine.

* 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

making it perfect for getting quick writing improvements without cluttering the channel. is unnecessary for code documentation.

* 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.
* <p>
* Users can optionally specify a tone/style for the rewrite. If not provided, defaults to CLEAR.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaults to CLEAR does not tell a developer what CLEAR is so please can you change this so it makes sense without coupling it to the enum (that isn't even linked).

* <p>
* Users can optionally specify a tone/style for the rewrite. If not provided, defaults to CLEAR.
*/
public final class RewriteMsgCommand extends SlashCommandAdapter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/rewrite is perfectly fine

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

if (rewrittenMessage.isEmpty()) {
logger.debug("Failed to obtain a response for /rewrite-msg, original message: '{}'",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not hard code the name here, instead used the constant you defined COMMAND_NAME

logger.debug("Failed to obtain a response for /rewrite-msg, original message: '{}'",
userMessage);
event.getHook()
.editOriginal(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't good UX. If the user spent a fair bit of time trying to craft their message and an error occurs, they lose it. Instead, return back the original message - an embed might be useful here to add the fact there was an "error" but the user can still copy their original message and send that, should they wish.

return;
}

final String response = buildResponse(userMessage, rewrittenMessage.orElseThrow(), tone);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary use of orElseThrow since it will never throw. You are doing an isEmpty check above.

Ideally, this entire block is a good candidate for rewrittenMessage.ifPresentOrElse() if you want that fluency.

}

private Optional<String> rewrite(String userMessage, MsgTone tone) {
final String rewritePrompt = buildChatGptPrompt(userMessage, tone);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can inline this String

private Optional<String> rewrite(String userMessage, MsgTone tone) {
final String rewritePrompt = buildChatGptPrompt(userMessage, tone);

return chatGptService.ask(rewritePrompt, tone.displayName, CHAT_GPT_MODEL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to update the ask method. The original implementor of the ChatGptService did not make this abstract enough so your request will be mixed with the following prefixed:

For code supplied for review, refer to the old code supplied rather than  rewriting the code. DON'T supply a corrected version of the code.

KEEP IT CONCISE, NOT MORE THAN 280 WORDS

%s

Question: %s

%s
**Rewritten:**
%s""".formatted(toneLabel, userMessage, rewrittenMessage);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont forget to String#stripIndent when using multi-line strings

Comment on lines +46 to +49
**Original:**
%s
**Rewritten:**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to use discord embed fields

If the message is already well-written, provide minor improvements.
Original message:
%s""".formatted(tone.description, userMessage);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont forget to String#stripIndent when using multi-line strings

@Override
public void onSlashCommand(SlashCommandInteractionEvent event) {
final String userMessage =
Objects.requireNonNull(event.getOption(MESSAGE_OPTION)).getAsString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for requireNonNull

final OptionData toneOption = new OptionData(OptionType.STRING, TONE_OPTION,
"The tone/style for the rewritten message (default: " + MsgTone.CLEAR.displayName
+ ")",
false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

y not also make it auto-completeable?

final String toneLabel = tone.displayName;

return """
**Rewritten message (%s)**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use embed title

If the message is already well-written, provide minor improvements.
Original message:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use embed field

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put this in embed description

Comment on lines +120 to +123
if (toneOption == null) {
logger.debug("Tone option not provided for user: {}, using default CLEAR", userId);
return MsgTone.CLEAR;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot be null

event.getHook().editOriginal(response).queue();
}

private MsgTone parseTone(@Nullable OptionMapping toneOption, String userId)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you taking in the userId just for the logs? seems unnecessary

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants