Skip to content

Commit 74a4ad9

Browse files
AlathreonSquidXTV
authored andcommitted
Feature/jshell rework (#998)
* [Feature/JShell] Using the new reworked jshell api * [Feature/JShell] Reworked renderer * [feature/JShell] Running Spotless/Sonar * [Feature/JShell] RenderResult deleted * [Feature/JShell] Added doc on JShellEval * [Feature/JShell] Added braces * Rebase and rename user to member * [Feature/JShell] Fixing checks * [Feature/JShell] Fixing from some feedback * [Feature/JShell] Replaced if elses with a switch thanks to java 21 --------- Co-authored-by: Connor Schweighoefer <[email protected]>
1 parent a207172 commit 74a4ad9

18 files changed

+643
-187
lines changed

application/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ dependencies {
6767
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion"
6868
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jacksonVersion"
6969
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
70+
implementation "com.sigpwned:jackson-modules-java17-sealed-classes:0.0.0"
7071

7172
implementation 'com.github.freva:ascii-table:1.8.0'
7273

application/src/main/java/org/togetherjava/tjbot/features/Features.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private Features() {
7979
*/
8080
public static Collection<Feature> createFeatures(JDA jda, Database database, Config config) {
8181
FeatureBlacklistConfig blacklistConfig = config.getFeatureBlacklistConfig();
82-
JShellEval jshellEval = new JShellEval(config.getJshell());
82+
JShellEval jshellEval = new JShellEval(config.getJshell(), config.getGitHubApiKey());
8383

8484
TagSystem tagSystem = new TagSystem(database);
8585
BookmarksSystem bookmarksSystem = new BookmarksSystem(config, database);

application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellCommand.java

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package org.togetherjava.tjbot.features.jshell;
22

33
import net.dv8tion.jda.api.EmbedBuilder;
4+
import net.dv8tion.jda.api.entities.Member;
45
import net.dv8tion.jda.api.entities.Message;
56
import net.dv8tion.jda.api.entities.MessageEmbed;
6-
import net.dv8tion.jda.api.entities.User;
77
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
88
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
99
import net.dv8tion.jda.api.interactions.InteractionHook;
@@ -52,6 +52,8 @@ public class JShellCommand extends SlashCommandAdapter {
5252
private static final int MIN_MESSAGE_INPUT_LENGTH = 0;
5353
private static final int MAX_MESSAGE_INPUT_LENGTH = TextInput.MAX_VALUE_LENGTH;
5454

55+
private static final String MAX_SNIPPETS_FILE_PREFIX = " // Snippet 1000";
56+
private static final String MAX_SNIPPETS_EMBED_PREFIX = "Snippet 10```java\n```";
5557
private final JShellEval jshellEval;
5658

5759
/**
@@ -103,7 +105,7 @@ public void onModalSubmitted(ModalInteractionEvent event, List<String> args) {
103105
mapping = event.getValue(TEXT_INPUT_PART_ID);
104106
}
105107
if (mapping != null) {
106-
handleEval(event, event.getUser(), true, mapping.getAsString(), startupScript);
108+
handleEval(event, event.getMember(), true, mapping.getAsString(), startupScript);
107109
}
108110
}
109111

@@ -125,7 +127,7 @@ private void handleEvalCommand(SlashCommandInteractionEvent event) {
125127
if (code == null) {
126128
sendEvalModal(event, startupScript);
127129
} else {
128-
handleEval(event, event.getUser(), true, code.getAsString(), startupScript);
130+
handleEval(event, event.getMember(), true, code.getAsString(), startupScript);
129131
}
130132
}
131133

@@ -145,78 +147,80 @@ private void sendEvalModal(SlashCommandInteractionEvent event, boolean startupSc
145147
* Handle evaluation of code.
146148
*
147149
* @param replyCallback the callback to reply to
148-
* @param user the user, if null, will create a single use session
150+
* @param member the member, if null, will create a single use session
149151
* @param showCode if the embed should contain the original code
150152
* @param startupScript if the startup script should be used or not
151153
* @param code the code
152154
*/
153-
private void handleEval(IReplyCallback replyCallback, @Nullable User user, boolean showCode,
155+
private void handleEval(IReplyCallback replyCallback, @Nullable Member member, boolean showCode,
154156
String code, boolean startupScript) {
155157
replyCallback.deferReply().queue(interactionHook -> {
156158
try {
157-
interactionHook
158-
.editOriginalEmbeds(
159-
jshellEval.evaluateAndRespond(user, code, showCode, startupScript))
160-
.queue();
159+
MessageEmbed messageEmbed =
160+
jshellEval.evaluateAndRespond(member, code, showCode, startupScript);
161+
interactionHook.sendMessageEmbeds(messageEmbed).queue();
161162
} catch (RequestFailedException | ConnectionFailedException e) {
162-
interactionHook.editOriginalEmbeds(createUnexpectedErrorEmbed(user, e)).queue();
163+
interactionHook.editOriginalEmbeds(createUnexpectedErrorEmbed(member, e)).queue();
163164
}
164165
});
165166
}
166167

167168
private void handleSnippetsCommand(SlashCommandInteractionEvent event) {
168169
event.deferReply().queue(interactionHook -> {
169170
OptionMapping userOption = event.getOption(USER_PARAMETER);
170-
User user = userOption == null ? event.getUser() : userOption.getAsUser();
171+
Member member = Objects
172+
.requireNonNull(userOption == null ? event.getMember() : userOption.getAsMember());
171173
OptionMapping includeStartupScriptOption =
172174
event.getOption(INCLUDE_STARTUP_SCRIPT_PARAMETER);
173175
boolean includeStartupScript =
174176
includeStartupScriptOption != null && includeStartupScriptOption.getAsBoolean();
175177
List<String> snippets;
176178
try {
177179
snippets = jshellEval.getApi()
178-
.snippetsSession(user.getId(), includeStartupScript)
180+
.snippetsSession(member.getId(), includeStartupScript)
179181
.snippets();
180182
} catch (RequestFailedException e) {
181183
if (e.getStatus() == JShellApi.SESSION_NOT_FOUND) {
182-
interactionHook.editOriginalEmbeds(createSessionNotFoundErrorEmbed(user))
184+
interactionHook.editOriginalEmbeds(createSessionNotFoundErrorEmbed(member))
183185
.queue();
184186
} else {
185-
interactionHook.editOriginalEmbeds(createUnexpectedErrorEmbed(user, e)).queue();
187+
interactionHook.editOriginalEmbeds(createUnexpectedErrorEmbed(member, e))
188+
.queue();
186189
}
187190
return;
188191
} catch (ConnectionFailedException e) {
189-
interactionHook.editOriginalEmbeds(createUnexpectedErrorEmbed(user, e)).queue();
192+
interactionHook.editOriginalEmbeds(createUnexpectedErrorEmbed(member, e)).queue();
190193
return;
191194
}
192195

193-
sendSnippets(interactionHook, user, snippets);
196+
sendSnippets(interactionHook, member, snippets);
194197
});
195198
}
196199

197-
private void sendSnippets(InteractionHook interactionHook, User user, List<String> snippets) {
200+
private void sendSnippets(InteractionHook interactionHook, Member member,
201+
List<String> snippets) {
198202
if (canBeSentAsEmbed(snippets)) {
199-
sendSnippetsAsEmbed(interactionHook, user, snippets);
203+
sendSnippetsAsEmbed(interactionHook, member, snippets);
200204
} else if (canBeSentAsFile(snippets)) {
201-
sendSnippetsAsFile(interactionHook, user, snippets);
205+
sendSnippetsAsFile(interactionHook, member, snippets);
202206
} else {
203-
sendSnippetsTooLong(interactionHook, user);
207+
sendSnippetsTooLong(interactionHook, member);
204208
}
205209
}
206210

207211
private boolean canBeSentAsEmbed(List<String> snippets) {
208212
return snippets.stream().noneMatch(s -> s.length() >= MessageEmbed.VALUE_MAX_LENGTH)
209213
&& snippets.stream()
210-
.mapToInt(s -> (s + "Snippet 10```java\n```").length())
214+
.mapToInt(s -> (s + MAX_SNIPPETS_EMBED_PREFIX).length())
211215
.sum() < MessageEmbed.EMBED_MAX_LENGTH_BOT - 100
212216
&& snippets.size() <= MessageUtils.MAXIMUM_VISIBLE_EMBEDS;
213217
}
214218

215-
private void sendSnippetsAsEmbed(InteractionHook interactionHook, User user,
219+
private void sendSnippetsAsEmbed(InteractionHook interactionHook, Member member,
216220
List<String> snippets) {
217221
EmbedBuilder builder = new EmbedBuilder().setColor(Colors.SUCCESS_COLOR)
218-
.setAuthor(user.getName())
219-
.setTitle(snippetsTitle(user));
222+
.setAuthor(member.getEffectiveName())
223+
.setTitle(snippetsTitle(member));
220224
int i = 1;
221225
for (String snippet : snippets) {
222226
builder.addField("Snippet " + i, "```java\n" + snippet + "```", false);
@@ -227,35 +231,45 @@ private void sendSnippetsAsEmbed(InteractionHook interactionHook, User user,
227231

228232
private boolean canBeSentAsFile(List<String> snippets) {
229233
return snippets.stream()
230-
.mapToInt(s -> (s + "// Snippet 10").getBytes().length)
234+
.mapToInt(s -> (s + MAX_SNIPPETS_FILE_PREFIX).getBytes().length)
231235
.sum() < Message.MAX_FILE_SIZE;
232236
}
233237

234-
private void sendSnippetsAsFile(InteractionHook interactionHook, User user,
238+
private void sendSnippetsAsFile(InteractionHook interactionHook, Member member,
235239
List<String> snippets) {
236240
StringBuilder sb = new StringBuilder();
237241
int i = 1;
238242
for (String snippet : snippets) {
239-
sb.append("// Snippet ").append(i).append("\n").append(snippet);
243+
snippet = snippet.replaceAll("^\n+", "");
244+
if (!snippet.endsWith("\n")) {
245+
snippet += "\n";
246+
}
247+
int idxOf = snippet.indexOf("\n");
248+
int insertIndex = idxOf != -1 ? idxOf : snippet.length();
249+
sb.append(snippet, 0, insertIndex)
250+
.append(" // Snippet ")
251+
.append(i)
252+
.append(snippet.substring(insertIndex));
240253
i++;
241254
}
242255
interactionHook
243256
.editOriginalEmbeds(new EmbedBuilder().setColor(Colors.SUCCESS_COLOR)
244-
.setAuthor(user.getName())
245-
.setTitle(snippetsTitle(user))
257+
.setAuthor(member.getEffectiveName())
258+
.setTitle(snippetsTitle(member))
246259
.build())
247-
.setFiles(FileUpload.fromData(sb.toString().getBytes(), snippetsTitle(user)))
260+
.setFiles(
261+
FileUpload.fromData(sb.toString().getBytes(), snippetsTitle(member) + ".java"))
248262
.queue();
249263
}
250264

251-
private String snippetsTitle(User user) {
252-
return user.getName() + "'s snippets";
265+
private String snippetsTitle(Member member) {
266+
return member.getEffectiveName() + "'s snippets";
253267
}
254268

255-
private void sendSnippetsTooLong(InteractionHook interactionHook, User user) {
269+
private void sendSnippetsTooLong(InteractionHook interactionHook, Member member) {
256270
interactionHook
257271
.editOriginalEmbeds(new EmbedBuilder().setColor(Colors.ERROR_COLOR)
258-
.setAuthor(user.getName())
272+
.setAuthor(member.getEffectiveName())
259273
.setTitle("Too much code to send...")
260274
.build())
261275
.queue();
@@ -266,13 +280,16 @@ private void handleCloseCommand(SlashCommandInteractionEvent event) {
266280
jshellEval.getApi().closeSession(event.getUser().getId());
267281
} catch (RequestFailedException e) {
268282
if (e.getStatus() == JShellApi.SESSION_NOT_FOUND) {
269-
event.replyEmbeds(createSessionNotFoundErrorEmbed(event.getUser())).queue();
283+
event
284+
.replyEmbeds(createSessionNotFoundErrorEmbed(
285+
Objects.requireNonNull(event.getMember())))
286+
.queue();
270287
} else {
271-
event.replyEmbeds(createUnexpectedErrorEmbed(event.getUser(), e)).queue();
288+
event.replyEmbeds(createUnexpectedErrorEmbed(event.getMember(), e)).queue();
272289
}
273290
return;
274291
} catch (ConnectionFailedException e) {
275-
event.replyEmbeds(createUnexpectedErrorEmbed(event.getUser(), e)).queue();
292+
event.replyEmbeds(createUnexpectedErrorEmbed(event.getMember(), e)).queue();
276293
return;
277294
}
278295

@@ -296,23 +313,23 @@ private void handleStartupScriptCommand(SlashCommandInteractionEvent event) {
296313
.build())
297314
.queue();
298315
} catch (RequestFailedException | ConnectionFailedException e) {
299-
event.replyEmbeds(createUnexpectedErrorEmbed(event.getUser(), e)).queue();
316+
event.replyEmbeds(createUnexpectedErrorEmbed(event.getMember(), e)).queue();
300317
}
301318
});
302319
}
303320

304-
private MessageEmbed createSessionNotFoundErrorEmbed(User user) {
305-
return new EmbedBuilder().setAuthor(user.getName() + "'s result")
321+
private MessageEmbed createSessionNotFoundErrorEmbed(Member member) {
322+
return new EmbedBuilder().setAuthor(member.getEffectiveName() + "'s result")
306323
.setColor(Colors.ERROR_COLOR)
307-
.setDescription("Could not find session for user " + user.getName())
324+
.setDescription("Could not find session for member " + member.getEffectiveName())
308325
.build();
309326
}
310327

311-
private MessageEmbed createUnexpectedErrorEmbed(@Nullable User user, Exception e) {
328+
private MessageEmbed createUnexpectedErrorEmbed(@Nullable Member member, Exception e) {
312329
EmbedBuilder embedBuilder = new EmbedBuilder().setColor(Colors.ERROR_COLOR)
313330
.setDescription("Request failed: " + e.getMessage());
314-
if (user != null) {
315-
embedBuilder.setAuthor(user.getName() + "'s result");
331+
if (member != null) {
332+
embedBuilder.setAuthor(member.getEffectiveName() + "'s result");
316333
}
317334
return embedBuilder.build();
318335
}

application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package org.togetherjava.tjbot.features.jshell;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.sigpwned.jackson.modules.jdk17.sealedclasses.Jdk17SealedClassesModule;
45
import net.dv8tion.jda.api.EmbedBuilder;
6+
import net.dv8tion.jda.api.entities.Member;
57
import net.dv8tion.jda.api.entities.MessageEmbed;
6-
import net.dv8tion.jda.api.entities.User;
78
import net.dv8tion.jda.api.utils.TimeFormat;
89

910
import org.togetherjava.tjbot.config.JShellConfig;
1011
import org.togetherjava.tjbot.features.jshell.backend.JShellApi;
1112
import org.togetherjava.tjbot.features.jshell.backend.dto.JShellResult;
13+
import org.togetherjava.tjbot.features.jshell.renderer.ResultRenderer;
1214
import org.togetherjava.tjbot.features.utils.Colors;
1315
import org.togetherjava.tjbot.features.utils.ConnectionFailedException;
1416
import org.togetherjava.tjbot.features.utils.RateLimiter;
@@ -24,6 +26,7 @@
2426
* including JShell commands and JShell code actions.
2527
*/
2628
public class JShellEval {
29+
private final String gistApiToken;
2730
private final JShellApi api;
2831

2932
private final ResultRenderer renderer;
@@ -33,9 +36,12 @@ public class JShellEval {
3336
* Creates a JShell evaluation instance
3437
*
3538
* @param config the JShell configuration to use
39+
* @param gistApiToken token of Gist api in case a JShell result is uploaded here
3640
*/
37-
public JShellEval(JShellConfig config) {
38-
this.api = new JShellApi(new ObjectMapper(), config.baseUrl());
41+
public JShellEval(JShellConfig config, String gistApiToken) {
42+
this.gistApiToken = gistApiToken;
43+
this.api = new JShellApi(new ObjectMapper().registerModule(new Jdk17SealedClassesModule()),
44+
config.baseUrl());
3945
this.renderer = new ResultRenderer();
4046

4147
this.rateLimiter = new RateLimiter(Duration.ofSeconds(config.rateLimitWindowSeconds()),
@@ -49,7 +55,7 @@ public JShellApi getApi() {
4955
/**
5056
* Evaluate code and return a message containing the response.
5157
*
52-
* @param user the user, if null, will create a single use session
58+
* @param member the member, if null, will create a single use session
5359
* @param code the code
5460
* @param showCode if the original code should be displayed
5561
* @param startupScript if the startup script should be used or not
@@ -58,26 +64,24 @@ public JShellApi getApi() {
5864
* @throws ConnectionFailedException if the connection to the API couldn't be made at the first
5965
* place
6066
*/
61-
public MessageEmbed evaluateAndRespond(@Nullable User user, String code, boolean showCode,
67+
public MessageEmbed evaluateAndRespond(@Nullable Member member, String code, boolean showCode,
6268
boolean startupScript) throws RequestFailedException, ConnectionFailedException {
63-
MessageEmbed rateLimitedMessage = wasRateLimited(user, Instant.now());
69+
MessageEmbed rateLimitedMessage = wasRateLimited(member, Instant.now());
6470
if (rateLimitedMessage != null) {
6571
return rateLimitedMessage;
6672
}
6773
JShellResult result;
68-
if (user == null) {
74+
if (member == null) {
6975
result = api.evalOnce(code, startupScript);
7076
} else {
71-
result = api.evalSession(code, user.getId(), startupScript);
77+
result = api.evalSession(code, member.getId(), startupScript);
7278
}
7379

74-
return renderer
75-
.renderToEmbed(user, showCode ? code : null, user != null, result, new EmbedBuilder())
76-
.build();
80+
return renderer.render(gistApiToken, member, showCode, result);
7781
}
7882

7983
@Nullable
80-
private MessageEmbed wasRateLimited(@Nullable User user, Instant checkTime) {
84+
private MessageEmbed wasRateLimited(@Nullable Member member, Instant checkTime) {
8185
if (rateLimiter.allowRequest(checkTime)) {
8286
return null;
8387
}
@@ -88,8 +92,8 @@ private MessageEmbed wasRateLimited(@Nullable User user, Instant checkTime) {
8892
.setDescription(
8993
"You are currently rate-limited. Please try again " + nextAllowedTime + ".")
9094
.setColor(Colors.ERROR_COLOR);
91-
if (user != null) {
92-
embedBuilder.setAuthor(user.getName() + "'s result");
95+
if (member != null) {
96+
embedBuilder.setAuthor(member.getEffectiveName() + "'s result");
9397
}
9498
return embedBuilder.build();
9599
}

0 commit comments

Comments
 (0)