Skip to content

Commit 0be28de

Browse files
authored
bugfix and UX improvements for /vc-activity
* bugfix causing crash when using high max-age on /vc-activity * UX improvements on max-age, users now enter days instead of seconds
1 parent 126c40e commit 0be28de

File tree

1 file changed

+31
-74
lines changed

1 file changed

+31
-74
lines changed

application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java

Lines changed: 31 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919
import org.slf4j.LoggerFactory;
2020
import org.togetherjava.tjbot.commands.SlashCommandAdapter;
2121
import org.togetherjava.tjbot.commands.SlashCommandVisibility;
22-
2322
import java.util.List;
2423
import java.util.Map;
2524
import java.util.Objects;
2625
import java.util.Optional;
26+
import java.util.concurrent.TimeUnit;
27+
2728

2829
/**
2930
* Implements the {@code vc-activity} command. Creates VC activities.
@@ -45,6 +46,9 @@ public final class VcActivityCommand extends SlashCommandAdapter {
4546
private static final String MAX_USES_OPTION = "max-uses";
4647
private static final String MAX_AGE_OPTION = "max-age";
4748

49+
private static final long MAX_AGE_DAYS_LIMIT = 7;
50+
private static final long MAX_USES_LIMIT = 100;
51+
4852
public static final String YOUTUBE_TOGETHER_NAME = "YouTube Together";
4953
public static final String POKER_NAME = "Poker";
5054
public static final String BETRAYAL_IO_NAME = "Betrayal.io";
@@ -69,7 +73,6 @@ public final class VcActivityCommand extends SlashCommandAdapter {
6973
new Command.Choice(WORDSNACK_NAME, WORDSNACK_NAME),
7074
new Command.Choice(LETTERTILE_NAME, LETTERTILE_NAME));
7175

72-
7376
/**
7477
* List comes from <a href="https://github.com/DV8FromTheWorld/JDA/pull/1628">the "Implement
7578
* invite targets" PR on JDA</a>. There is no official list from Discord themselves, so this is
@@ -82,12 +85,15 @@ public final class VcActivityCommand extends SlashCommandAdapter {
8285
"852509694341283871", DOODLECREW_NAME, "878067389634314250", WORDSNACK_NAME,
8386
"879863976006127627", LETTERTILE_NAME, "879863686565621790");
8487

85-
private static final List<OptionData> inviteOptions = List.of(
86-
new OptionData(OptionType.STRING, MAX_USES_OPTION,
87-
"The amount of times the invite can be used, default is infinity", false),
88+
private static final List<OptionData> inviteOptions = List.of(new OptionData(OptionType.INTEGER,
89+
MAX_USES_OPTION,
90+
"How many times this invite can be used, 0 infinite (default) - %d being the highest."
91+
.formatted(MAX_USES_LIMIT),
92+
false).setRequiredRange(0, MAX_USES_LIMIT),
8893
new OptionData(OptionType.INTEGER, MAX_AGE_OPTION,
89-
"Max age in seconds. Set this to 0 to never expire, default is 1 day", false));
90-
94+
"How long, in days this activity can be used before it expires, 0 (No expiry), Max is %d days."
95+
.formatted(MAX_AGE_DAYS_LIMIT),
96+
false).setRequiredRange(0, MAX_AGE_DAYS_LIMIT));
9197

9298
/**
9399
* Constructs an instance
@@ -148,29 +154,10 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) {
148154
OptionMapping maxUsesOption = event.getOption(MAX_USES_OPTION);
149155
OptionMapping maxAgeOption = event.getOption(MAX_AGE_OPTION);
150156

151-
Integer maxUses;
152-
153-
// the user already received the error in the handleIntegerTypeOption method
154-
// it still throws to tell us to return this method and stop the proceeding code
155-
try {
156-
maxUses = handleIntegerTypeOption(event, maxUsesOption);
157-
} catch (IllegalArgumentException ignore) {
158-
return;
159-
}
160-
161-
Integer maxAge;
162-
163-
// the user already received the error in the handleIntegerTypeOption method
164-
// it still throws to tell us to return this method and stop the proceeding code
165-
try {
166-
maxAge = handleIntegerTypeOption(event, maxAgeOption);
167-
} catch (IllegalArgumentException ignore) {
168-
return;
169-
}
170-
171-
172157
String applicationId;
173158
String applicationName;
159+
Integer maxUses = requireIntOptionIfPresent(maxUsesOption);
160+
Integer maxAgeDays = requireIntOptionIfPresent(maxAgeOption);
174161

175162
if (applicationOption != null) {
176163
applicationName = applicationOption.getAsString();
@@ -182,9 +169,10 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) {
182169
getKeyByValue(VC_APPLICATION_TO_ID, applicationId).orElse("an activity");
183170
}
184171

185-
handleSubcommand(event, voiceChannel, applicationId, maxUses, maxAge, applicationName);
172+
handleSubcommand(event, voiceChannel, applicationId, maxUses, maxAgeDays, applicationName);
186173
}
187174

175+
188176
private static <K, V> @NotNull Optional<K> getKeyByValue(@NotNull Map<K, V> map,
189177
@NotNull V value) {
190178
for (Map.Entry<K, V> entry : map.entrySet()) {
@@ -198,14 +186,18 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) {
198186

199187
private static void handleSubcommand(@NotNull SlashCommandEvent event,
200188
@NotNull VoiceChannel voiceChannel, @NotNull String applicationId,
201-
@Nullable Integer maxUses, @Nullable Integer maxAge, @NotNull String applicationName) {
189+
@Nullable Integer maxUses, @Nullable Integer maxAgeDays,
190+
@NotNull String applicationName) {
191+
202192

203193
voiceChannel.createInvite()
204194
.setTargetApplication(applicationId)
205195
.setMaxUses(maxUses)
206-
.setMaxAge(maxAge)
196+
.setMaxAge(maxAgeDays == null ? null
197+
: Math.toIntExact(TimeUnit.DAYS.toSeconds(maxAgeDays)))
207198
.flatMap(invite -> replyInvite(event, invite, applicationName))
208199
.queue(null, throwable -> handleErrors(event, throwable));
200+
209201
}
210202

211203
private static @NotNull ReplyAction replyInvite(@NotNull SlashCommandEvent event,
@@ -223,51 +215,16 @@ private static void handleErrors(@NotNull SlashCommandEvent event,
223215
logger.warn("Something went wrong in the VcActivityCommand", throwable);
224216
}
225217

226-
227218
/**
228-
* This grabs the OptionMapping, after this it <br />
229-
* - validates whenever it's within an {@link Integer Integer's} range <br />
230-
* - validates whenever it's positive <br />
231-
*
232-
* <p>
233-
* <p/>
234-
*
235-
* @param event the {@link SlashCommandEvent}
236-
* @param optionMapping the {@link OptionMapping}
237-
* @return nullable {@link Integer}
238-
* @throws java.lang.IllegalArgumentException if the option's value is - outside of
239-
* {@link Integer#MAX_VALUE} - negative
240-
*/
241-
@Contract("_, null -> null")
242-
private static @Nullable Integer handleIntegerTypeOption(@NotNull SlashCommandEvent event,
243-
@Nullable OptionMapping optionMapping) {
244-
245-
int optionValue;
246-
247-
if (optionMapping == null) {
248-
return null;
249-
}
250-
251-
try {
252-
optionValue = Math.toIntExact(optionMapping.getAsLong());
253-
} catch (ArithmeticException e) {
254-
event
255-
.reply("The " + optionMapping.getName() + " is above `" + Integer.MAX_VALUE
256-
+ "`, which is too high")
257-
.setEphemeral(true)
258-
.queue();
259-
throw new IllegalArgumentException(
260-
optionMapping.getName() + " can't be above " + Integer.MAX_VALUE);
261-
}
262-
263-
if (optionValue < 0) {
264-
event.reply("The " + optionMapping.getName() + " is negative, which isn't supported")
265-
.setEphemeral(true)
266-
.queue();
267-
throw new IllegalArgumentException(optionMapping.getName() + " can't be negative");
268-
}
219+
* Interprets the given option as integer. Throws if the option is not an integer.
220+
*
221+
* @param option the option that contains the integer to extract, or null if not present
222+
* @return the extracted integer if present, null otherwise
223+
**/
224+
@Contract("null -> null")
225+
private static @Nullable Integer requireIntOptionIfPresent(@Nullable OptionMapping option) {
269226

227+
return option == null ? null : Math.toIntExact(option.getAsLong());
270228

271-
return optionValue;
272229
}
273230
}

0 commit comments

Comments
 (0)