Skip to content

Commit

Permalink
JShell javadoc and many minor code improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Alathreon committed Aug 8, 2023
1 parent 7fa5e1e commit 900780c
Show file tree
Hide file tree
Showing 19 changed files with 277 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,43 @@

import com.linkedin.urls.Url;

import org.togetherjava.tjbot.features.utils.RateLimiter;

import java.net.MalformedURLException;

/**
* JShell config.
*
* @param baseUrl the base url of the JShell REST API
* @param rateLimitWindowSeconds the number of seconds of the {@link RateLimiter rate limiter} for
* jshell commands and code actions
* @param rateLimitRequestsInWindow the number of requests of the {@link RateLimiter rate limiter}
* for jshell commands and code actions
*/
public record JShellConfig(String baseUrl, int rateLimitWindowSeconds,
int rateLimitRequestsInWindow) {
/**
* Creates a JShell config.
*
* @param baseUrl the base url of the JShell REST API, must be valid
* @param rateLimitWindowSeconds the number of seconds of the {@link RateLimiter rate limiter}
* for jshell commands and code actions, must be higher than 0
* @param rateLimitRequestsInWindow the number of requests of the {@link RateLimiter rate
* limiter} for jshell commands and code actions, must be higher than 0
*/
public JShellConfig {
try {
Url.create(baseUrl);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
if (rateLimitWindowSeconds <= 0)
if (rateLimitWindowSeconds < 0) {
throw new IllegalArgumentException(
"Illegal rateLimitWindowSeconds : " + rateLimitWindowSeconds);
if (rateLimitRequestsInWindow <= 0)
}
if (rateLimitRequestsInWindow < 0) {
throw new IllegalArgumentException(
"Illegal rateLimitRequestsInWindow : " + rateLimitRequestsInWindow);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public final class CodeMessageHandler extends MessageReceiverAdapter implements

/**
* Creates a new instance.
*
* @param jshellEval the jshell evaluation instance used in the code actions
*/
public CodeMessageHandler(JShellEval jshellEval) {
componentIdInteractor = new ComponentIdInteractor(getInteractionType(), getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import net.dv8tion.jda.api.entities.MessageEmbed;

import org.togetherjava.tjbot.features.jshell.JShellEval;
import org.togetherjava.tjbot.features.jshell.render.Colors;
import org.togetherjava.tjbot.features.utils.CodeFence;
import org.togetherjava.tjbot.features.utils.Colors;
import org.togetherjava.tjbot.features.utils.RequestFailedException;

/**
* Evaluates the given code.
* Evaluates the given code with jshell.
* <p>
* It will not work of the code isn't valid java or jshell compatible code.
*/
final class EvalCodeCommand implements CodeAction {
private final JShellEval jshellEval;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,30 @@
import org.togetherjava.tjbot.features.CommandVisibility;
import org.togetherjava.tjbot.features.SlashCommandAdapter;
import org.togetherjava.tjbot.features.jshell.backend.JShellApi;
import org.togetherjava.tjbot.features.jshell.render.Colors;
import org.togetherjava.tjbot.features.utils.Colors;
import org.togetherjava.tjbot.features.utils.MessageUtils;
import org.togetherjava.tjbot.features.utils.RequestFailedException;

import javax.annotation.Nullable;

import java.util.List;
import java.util.Objects;

/**
* The JShell command, provide functionalities to create JShell sessions, evaluate code, etc.
*/
public class JShellCommand extends SlashCommandAdapter {
private static final String JSHELL_TEXT_INPUT_ID = "jshell";
private static final String TEXT_INPUT_PART_ID = "jshell";
private static final String JSHELL_COMMAND = "jshell";
private static final String JSHELL_VERSION_SUBCOMMAND = "version";
private static final String JSHELL_EVAL_SUBCOMMAND = "eval";
private static final String JSHELL_SNIPPETS_SUBCOMMAND = "snippets";
private static final String JSHELL_CLOSE_SUBCOMMAND = "shutdown";
private static final String JSHELL_STARTUP_SCRIPT_SUBCOMMAND = "startup-script";
private static final String JSHELL_CODE_PARAMETER = "code";
private static final String JSHELL_STARTUP_SCRIPT_PARAMETER = "startup-script";
private static final String JSHELL_USER_PARAMETER = "user";
private static final String JSHELL_INCLUDE_STARTUP_SCRIPT_PARAMETER = "include-startup-script";
private static final String VERSION_SUBCOMMAND = "version";
private static final String EVAL_SUBCOMMAND = "eval";
private static final String SNIPPETS_SUBCOMMAND = "snippets";
private static final String CLOSE_SUBCOMMAND = "shutdown";
private static final String STARTUP_SCRIPT_SUBCOMMAND = "startup-script";
private static final String CODE_PARAMETER = "code";
private static final String STARTUP_SCRIPT_PARAMETER = "startup-script";
private static final String USER_PARAMETER = "user";
private static final String INCLUDE_STARTUP_SCRIPT_PARAMETER = "include-startup-script";

private static final int MIN_MESSAGE_INPUT_LENGTH = 0;
private static final int MAX_MESSAGE_INPUT_LENGTH = TextInput.MAX_VALUE_LENGTH;
Expand All @@ -48,51 +52,53 @@ public class JShellCommand extends SlashCommandAdapter {

/**
* Creates an instance of the command.
*
* @param jshellEval the jshell evaluation instance used in the jshell command
*/
public JShellCommand(JShellEval jshellEval) {
super(JSHELL_COMMAND, "JShell as a command.", CommandVisibility.GUILD);
super(JSHELL_COMMAND,
"JShell as a command, a Read–eval–print loop which allows to rapidly and simply execute java code.",
CommandVisibility.GUILD);

this.jshellEval = jshellEval;

getData().addSubcommands(
new SubcommandData(JSHELL_VERSION_SUBCOMMAND, "Get the version of JShell"),
new SubcommandData(JSHELL_EVAL_SUBCOMMAND,
"Evaluate java code in JShell, don't fill the optional parameter to access a bigger input box.")
.addOption(OptionType.STRING, JSHELL_CODE_PARAMETER,
"Code to evaluate. If not supplied, open an inout box.")
.addOption(OptionType.BOOLEAN, JSHELL_STARTUP_SCRIPT_PARAMETER,
new SubcommandData(VERSION_SUBCOMMAND, "Get the version of JShell"),
new SubcommandData(EVAL_SUBCOMMAND,
"Evaluate java code in JShell, submit the command without code for inputting longer, multi-line code.")
.addOption(OptionType.STRING, CODE_PARAMETER,
"Code to evaluate. Leave empty to input longer, multi-line code.")
.addOption(OptionType.BOOLEAN, STARTUP_SCRIPT_PARAMETER,
"If the startup script should be loaded, true by default."),
new SubcommandData(JSHELL_SNIPPETS_SUBCOMMAND,
"Get the evaluated snippets of the user who sent the command, or the user specified user if any.")
.addOption(OptionType.USER, JSHELL_USER_PARAMETER,
"User to get the snippets from. If null, get the snippets of the user who sent the command.")
.addOption(OptionType.BOOLEAN, JSHELL_INCLUDE_STARTUP_SCRIPT_PARAMETER,
new SubcommandData(SNIPPETS_SUBCOMMAND,
"Display your snippets, or the snippets of the specified user if any.")
.addOption(OptionType.USER, USER_PARAMETER,
"User to get the snippets from. If null, get your snippets.")
.addOption(OptionType.BOOLEAN, INCLUDE_STARTUP_SCRIPT_PARAMETER,
"if the startup script should be included, false by default."),
new SubcommandData(JSHELL_CLOSE_SUBCOMMAND, "Close your session."),
new SubcommandData(JSHELL_STARTUP_SCRIPT_SUBCOMMAND,
"Display the startup script."));
new SubcommandData(CLOSE_SUBCOMMAND, "Close your session."),
new SubcommandData(STARTUP_SCRIPT_SUBCOMMAND, "Display the startup script."));
}

@Override
public void onSlashCommand(SlashCommandInteractionEvent event) {
switch (Objects.requireNonNull(event.getSubcommandName())) {
case JSHELL_VERSION_SUBCOMMAND -> handleVersionCommand(event);
case JSHELL_EVAL_SUBCOMMAND -> handleEvalCommand(event);
case JSHELL_SNIPPETS_SUBCOMMAND -> handleSnippetsCommand(event);
case JSHELL_CLOSE_SUBCOMMAND -> handleCloseCommand(event);
case JSHELL_STARTUP_SCRIPT_SUBCOMMAND -> handleStartupScriptCommand(event);
case VERSION_SUBCOMMAND -> handleVersionCommand(event);
case EVAL_SUBCOMMAND -> handleEvalCommand(event);
case SNIPPETS_SUBCOMMAND -> handleSnippetsCommand(event);
case CLOSE_SUBCOMMAND -> handleCloseCommand(event);
case STARTUP_SCRIPT_SUBCOMMAND -> handleStartupScriptCommand(event);
default -> throw new AssertionError(
"Unexpected Subcommand: " + event.getSubcommandName());
}
}

@Override
public void onModalSubmitted(ModalInteractionEvent event, List<String> args) {
ModalMapping mapping =
event.getValue(JSHELL_TEXT_INPUT_ID + "|" + JSHELL_STARTUP_SCRIPT_PARAMETER);
ModalMapping mapping = event.getValue(TEXT_INPUT_PART_ID + "|" + STARTUP_SCRIPT_PARAMETER);
boolean startupScript = mapping != null;
if (mapping == null) {
mapping = event.getValue(JSHELL_TEXT_INPUT_ID);
mapping = event.getValue(TEXT_INPUT_PART_ID);
}
if (mapping != null) {
handleEval(event, event.getUser(), true, mapping.getAsString(), startupScript);
Expand All @@ -106,15 +112,14 @@ private void handleVersionCommand(SlashCommandInteractionEvent event) {
System.out.println("Vendor: " + System.getProperty("java.vendor"));
System.out.println("OS: " + System.getProperty("os.name"));
System.out.println("Arch: " + System.getProperty("os.arch"));
System.out.println("```");""";
System.out.println("```");""";
handleEval(event, null, false, code, false);
}

private void handleEvalCommand(SlashCommandInteractionEvent event) {
OptionMapping code = event.getOption(JSHELL_CODE_PARAMETER);
boolean startupScript = event.getOption(JSHELL_STARTUP_SCRIPT_PARAMETER) == null
|| Objects.requireNonNull(event.getOption(JSHELL_STARTUP_SCRIPT_PARAMETER))
.getAsBoolean();
OptionMapping code = event.getOption(CODE_PARAMETER);
boolean startupScript = event.getOption(STARTUP_SCRIPT_PARAMETER) == null
|| Objects.requireNonNull(event.getOption(STARTUP_SCRIPT_PARAMETER)).getAsBoolean();
if (code == null) {
sendEvalModal(event, startupScript);
} else {
Expand All @@ -124,8 +129,7 @@ private void handleEvalCommand(SlashCommandInteractionEvent event) {

private void sendEvalModal(SlashCommandInteractionEvent event, boolean startupScript) {
TextInput body = TextInput
.create(JSHELL_TEXT_INPUT_ID
+ (startupScript ? "|" + JSHELL_STARTUP_SCRIPT_PARAMETER : ""),
.create(TEXT_INPUT_PART_ID + (startupScript ? "|" + STARTUP_SCRIPT_PARAMETER : ""),
"Enter code to evaluate.", TextInputStyle.PARAGRAPH)
.setPlaceholder("Put your code here.")
.setRequiredRange(MIN_MESSAGE_INPUT_LENGTH, MAX_MESSAGE_INPUT_LENGTH)
Expand Down Expand Up @@ -160,10 +164,10 @@ private void handleEval(IReplyCallback replyCallback, @Nullable User user, boole

private void handleSnippetsCommand(SlashCommandInteractionEvent event) {
event.deferReply().queue(interactionHook -> {
OptionMapping userOption = event.getOption(JSHELL_USER_PARAMETER);
OptionMapping userOption = event.getOption(USER_PARAMETER);
User user = userOption == null ? event.getUser() : userOption.getAsUser();
OptionMapping includeStartupScriptOption =
event.getOption(JSHELL_INCLUDE_STARTUP_SCRIPT_PARAMETER);
event.getOption(INCLUDE_STARTUP_SCRIPT_PARAMETER);
boolean includeStartupScript =
includeStartupScriptOption != null && includeStartupScriptOption.getAsBoolean();
List<String> snippets;
Expand All @@ -185,10 +189,7 @@ private void handleSnippetsCommand(SlashCommandInteractionEvent event) {
&& snippets.stream()
.mapToInt(s -> (s + "Snippet 10```java\n```").length())
.sum() < MessageEmbed.EMBED_MAX_LENGTH_BOT - 100
&& snippets.size() <= 25/*
* Max visible embed fields in an embed TODO replace
* with constant
*/) {
&& snippets.size() <= MessageUtils.MAXIMUM_VISIBLE_EMBEDS) {
sendSnippetsAsEmbed(interactionHook, user, snippets);
} else if (snippets.stream()
.mapToInt(s -> (s + "// Snippet 10").getBytes().length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import org.togetherjava.tjbot.config.JShellConfig;
import org.togetherjava.tjbot.features.jshell.backend.JShellApi;
import org.togetherjava.tjbot.features.jshell.backend.dto.JShellResult;
import org.togetherjava.tjbot.features.jshell.render.Colors;
import org.togetherjava.tjbot.features.jshell.render.ResultRenderer;
import org.togetherjava.tjbot.features.utils.Colors;
import org.togetherjava.tjbot.features.utils.RateLimiter;
import org.togetherjava.tjbot.features.utils.RequestFailedException;

Expand All @@ -19,12 +18,21 @@
import java.time.Duration;
import java.time.Instant;

/**
* Provides a mid-ground between JDA and JShell API which can be used from many places in the bot,
* including JShell commands and JShell code actions.
*/
public class JShellEval {
private final JShellApi api;

private final ResultRenderer renderer;
private final RateLimiter rateLimiter;

/**
* Creates a JShell evaluation instance
*
* @param config the JShell configuration to use
*/
public JShellEval(JShellConfig config) {
this.api = new JShellApi(new ObjectMapper(), config.baseUrl());
this.renderer = new ResultRenderer();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.togetherjava.tjbot.features.jshell.render;
package org.togetherjava.tjbot.features.jshell;

import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
Expand All @@ -12,9 +12,9 @@

import java.awt.Color;

import static org.togetherjava.tjbot.features.jshell.render.Colors.*;
import static org.togetherjava.tjbot.features.utils.Colors.*;

public class ResultRenderer {
class ResultRenderer {

public EmbedBuilder renderToEmbed(@Nullable User originator, @Nullable String originalCode,
boolean partOfSession, JShellResult result, EmbedBuilder builder) {
Expand Down
Loading

0 comments on commit 900780c

Please sign in to comment.