From 412c0e28975815dab5b60bc8e392da808e57d713 Mon Sep 17 00:00:00 2001 From: twonirwana Date: Sat, 14 Oct 2023 12:54:20 +0200 Subject: [PATCH] Warning if there are no dice in expression (#342) --- .../discord/bot/command/AbstractCommand.java | 12 +++++++++- .../janno/discord/bot/command/RollAnswer.java | 7 +++--- .../command/customDice/CustomDiceCommand.java | 18 ++++++++++++++- .../command/directRoll/DirectRollCommand.java | 15 ++++++++++-- .../command/directRoll/ValidationCommand.java | 3 +-- .../bot/dice/DiceEvaluatorAdapter.java | 18 +++++++++++++++ .../customDice/CustomDiceCommandMockTest.java | 23 +++++++++++++++++-- .../directRoll/DirectRollCommandMockTest.java | 18 +++++++++++++++ .../dice/image/ImageResultCreatorTest.java | 9 ++++++++ .../api/message/EmbedOrMessageDefinition.java | 2 +- 10 files changed, 113 insertions(+), 12 deletions(-) diff --git a/bot/src/main/java/de/janno/discord/bot/command/AbstractCommand.java b/bot/src/main/java/de/janno/discord/bot/command/AbstractCommand.java index 4e1e7811..41dab5d2 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/AbstractCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/AbstractCommand.java @@ -2,6 +2,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import de.janno.discord.bot.BotMetrics; import de.janno.discord.bot.persistance.Mapper; @@ -29,6 +30,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import static de.janno.discord.bot.command.BaseCommandOptions.*; import static de.janno.discord.connector.api.BottomCustomIdUtils.CUSTOM_ID_DELIMITER; @@ -376,7 +378,11 @@ Mono deleteOldAndConcurrentMessageAndData( log.info("{}: '{}'", event.getRequester().toLogString(), commandString.replace("`", "")); - return event.reply(commandString, false) + String replayMessage = Stream.of(commandString, getConfigWarnMessage(config).orElse(null)) + .filter(s -> !Strings.isNullOrEmpty(s)) + .collect(Collectors.joining(" ")); + + return event.reply(replayMessage, false) .then(Mono.defer(() -> { final Optional newMessageConfig = createMessageConfig(configUUID, guildId, channelId, config); newMessageConfig.ifPresent(persistenceManager::saveMessageConfig); @@ -392,6 +398,10 @@ Mono deleteOldAndConcurrentMessageAndData( return Mono.empty(); } + protected @NonNull Optional getConfigWarnMessage(C config) { + return Optional.empty(); + } + protected @NonNull List getStartOptions() { return ImmutableList.of(); } diff --git a/bot/src/main/java/de/janno/discord/bot/command/RollAnswer.java b/bot/src/main/java/de/janno/discord/bot/command/RollAnswer.java index 6e3aed46..1673e67d 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/RollAnswer.java +++ b/bot/src/main/java/de/janno/discord/bot/command/RollAnswer.java @@ -14,7 +14,6 @@ @Value @Builder public class RollAnswer { - private static final String MINUS = "\u2212"; @NonNull AnswerFormatType answerFormatType; @NonNull @@ -31,6 +30,8 @@ public class RollAnswer { List multiRollResults; @Nullable Supplier image; + @Nullable + String warning; public String toShortString() { String fieldStringList = Optional.ofNullable(multiRollResults) @@ -39,10 +40,10 @@ public String toShortString() { .toList().toString()) .orElse(null); return String.format("%s=%s", expression, - Joiner.on(",").skipNulls().join(result, rollDetails, errorMessage, fieldStringList)) + Joiner.on(",").skipNulls().join(result, rollDetails, errorMessage, warning, fieldStringList)) .replace("▢", "0") .replace("+", "+") - .replace(MINUS, "-") + .replace("−", "-") //minus is not hyphen-minus .replace("*", ""); } diff --git a/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceCommand.java b/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceCommand.java index 265e1747..d676f5f9 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/customDice/CustomDiceCommand.java @@ -2,6 +2,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import de.janno.discord.bot.command.*; @@ -208,6 +209,22 @@ private List createButtonLayout(UUID configUUID, CustomD return COMMAND_NAME; } + @Override + protected @NonNull Optional getConfigWarnMessage(CustomDiceConfig config) { + return Optional.ofNullable(Strings.emptyToNull(config.getButtonIdLabelAndDiceExpressions().stream() + .map(b -> { + String warning = diceSystemAdapter.answerRollWithGivenLabel(b.getDiceExpression(), null, false, DiceParserSystem.DICE_EVALUATOR, config.getAnswerFormatType(), + config.getDiceStyleAndColor()).getWarning(); + if (!Strings.isNullOrEmpty(warning)) { + return "`%s`: %s".formatted(b.getDiceExpression(), warning); + } + return null; + }) + .filter(s -> !Strings.isNullOrEmpty(s)) + .distinct() + .collect(Collectors.joining(", ")))); + } + @Value static class ButtonIdAndExpression { @NonNull @@ -215,5 +232,4 @@ static class ButtonIdAndExpression { @NonNull String expression; } - } diff --git a/bot/src/main/java/de/janno/discord/bot/command/directRoll/DirectRollCommand.java b/bot/src/main/java/de/janno/discord/bot/command/directRoll/DirectRollCommand.java index 07a5ae18..e69797dc 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/directRoll/DirectRollCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/directRoll/DirectRollCommand.java @@ -3,6 +3,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; +import com.google.common.base.Strings; import de.janno.discord.bot.BotMetrics; import de.janno.discord.bot.command.AnswerFormatType; import de.janno.discord.bot.command.RollAnswer; @@ -32,6 +33,8 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static de.janno.discord.bot.command.channelConfig.ChannelConfigCommand.DIRECT_ROLL_CONFIG_TYPE_ID; @@ -41,13 +44,18 @@ public class DirectRollCommand implements SlashCommand { public static final String ROLL_COMMAND_ID = "r"; protected static final String ACTION_EXPRESSION = "expression"; private static final String HELP = "help"; + protected final boolean removeSlash; private final DiceEvaluatorAdapter diceEvaluatorAdapter; private final PersistenceManager persistenceManager; - protected boolean removeSlash = true; public DirectRollCommand(PersistenceManager persistenceManager, CachingDiceEvaluator cachingDiceEvaluator) { + this(persistenceManager, cachingDiceEvaluator, true); + } + + public DirectRollCommand(PersistenceManager persistenceManager, CachingDiceEvaluator cachingDiceEvaluator, boolean removeSlash) { this.diceEvaluatorAdapter = new DiceEvaluatorAdapter(cachingDiceEvaluator); this.persistenceManager = persistenceManager; + this.removeSlash = removeSlash; } @Override @@ -124,7 +132,10 @@ DirectRollConfig deserializeConfig(ChannelConfigDTO channelConfigDTO) { RollAnswer answer = diceEvaluatorAdapter.answerRollWithOptionalLabelInExpression(expressionWithOptionalLabelsAndAppliedAliases, DiceSystemAdapter.LABEL_DELIMITER, config.isAlwaysSumResult(), config.getAnswerFormatType(), config.getDiceStyleAndColor()); - return Flux.merge(removeSlash ? Mono.defer(event::acknowledgeAndRemoveSlash) : event.reply(commandString, true), + String replayMessage = Stream.of(commandString, answer.getWarning()) + .filter(s -> !Strings.isNullOrEmpty(s)) + .collect(Collectors.joining(" ")); + return Flux.merge(removeSlash && Strings.isNullOrEmpty(answer.getWarning()) ? Mono.defer(event::acknowledgeAndRemoveSlash) : event.reply(replayMessage, true), Mono.defer(() -> event.createResultMessageWithEventReference(RollAnswerConverter.toEmbedOrMessageDefinition(answer)) .doOnSuccess(v -> log.info("{}: '{}'={} -> {} in {}ms", diff --git a/bot/src/main/java/de/janno/discord/bot/command/directRoll/ValidationCommand.java b/bot/src/main/java/de/janno/discord/bot/command/directRoll/ValidationCommand.java index 95994f5a..6e77863b 100644 --- a/bot/src/main/java/de/janno/discord/bot/command/directRoll/ValidationCommand.java +++ b/bot/src/main/java/de/janno/discord/bot/command/directRoll/ValidationCommand.java @@ -22,9 +22,8 @@ public class ValidationCommand extends DirectRollCommand { private final DiceEvaluatorAdapter diceEvaluatorAdapter; public ValidationCommand(PersistenceManager persistenceManager, CachingDiceEvaluator cachingDiceEvaluator) { - super(persistenceManager, cachingDiceEvaluator); + super(persistenceManager, cachingDiceEvaluator, false); this.diceEvaluatorAdapter = new DiceEvaluatorAdapter(cachingDiceEvaluator); - removeSlash = false; } @Override diff --git a/bot/src/main/java/de/janno/discord/bot/dice/DiceEvaluatorAdapter.java b/bot/src/main/java/de/janno/discord/bot/dice/DiceEvaluatorAdapter.java index c3e0d558..89153017 100644 --- a/bot/src/main/java/de/janno/discord/bot/dice/DiceEvaluatorAdapter.java +++ b/bot/src/main/java/de/janno/discord/bot/dice/DiceEvaluatorAdapter.java @@ -1,5 +1,6 @@ package de.janno.discord.bot.dice; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import de.janno.discord.bot.BotMetrics; import de.janno.discord.bot.command.AnswerFormatType; @@ -16,8 +17,10 @@ import java.io.InputStream; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; import static de.janno.discord.bot.dice.DiceSystemAdapter.LABEL_DELIMITER; @@ -141,6 +144,7 @@ public RollAnswer answerRollWithGivenLabel(String expression, .expression(expression) .expressionLabel(label) .image(diceImage) + .warning(getWarningFromRoll(rolls)) .result(getResult(rolls.get(0), sumUp)) .rollDetails(rolls.get(0).getRandomElementsString()) .build(); @@ -152,6 +156,7 @@ public RollAnswer answerRollWithGivenLabel(String expression, .answerFormatType(answerFormatType) .expression(expression) .expressionLabel(label) + .warning(getWarningFromRoll(rolls)) .multiRollResults(multiRollResults) .build(); } @@ -168,4 +173,17 @@ public boolean validExpression(String expression) { return cachingDiceEvaluator.get(expression).isValid(); } + private String getWarningFromRoll(List rolls) { + return Strings.emptyToNull(rolls.stream() + .map(r -> { + if (r.getRandomElementsInRoll().getRandomElements().isEmpty()) { + return "did not contain any random element, try `d20` to roll a 20 sided die"; + } + return null; + }) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.joining(", "))); + } + } diff --git a/bot/src/test/java/de/janno/discord/bot/command/customDice/CustomDiceCommandMockTest.java b/bot/src/test/java/de/janno/discord/bot/command/customDice/CustomDiceCommandMockTest.java index 44d5bf0f..b3b21b7e 100644 --- a/bot/src/test/java/de/janno/discord/bot/command/customDice/CustomDiceCommandMockTest.java +++ b/bot/src/test/java/de/janno/discord/bot/command/customDice/CustomDiceCommandMockTest.java @@ -46,7 +46,7 @@ public class CustomDiceCommandMockTest { @BeforeEach void setup() throws IOException { File cacheDirectory = new File("imageCache/"); - if(cacheDirectory.exists()){ + if (cacheDirectory.exists()) { FileUtils.cleanDirectory(cacheDirectory); } messageIdCounter = new AtomicLong(0); @@ -56,7 +56,7 @@ void setup() throws IOException { @AfterEach void cleanUp() throws IOException { File cacheDirectory = new File("imageCache/"); - if(cacheDirectory.exists()){ + if (cacheDirectory.exists()) { FileUtils.cleanDirectory(cacheDirectory); } } @@ -298,6 +298,25 @@ void slash_start() { "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d6, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Attack, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3d10,3d10,3d10, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])"); } + @Test + void slash_start_warn() { + CustomDiceCommand underTest = new CustomDiceCommand(persistenceManager, new DiceParser(), new CachingDiceEvaluator(new RandomNumberSupplier(0), 1000, 0)); + + SlashEventAdaptorMock slashEvent = new SlashEventAdaptorMock(List.of(CommandInteractionOption.builder() + .name("start") + .option(CommandInteractionOption.builder() + .name("buttons") + .stringValue("1d6;1d20@Attack;3d10,3d10,3d10;3;'a'") + .build()) + .build())); + underTest.handleSlashCommandEvent(slashEvent, () -> UUID.fromString("00000000-0000-0000-0000-000000000000")).block(); + + assertThat(slashEvent.getActions()).containsExactlyInAnyOrder( + "reply: commandString `3`: did not contain any random element, try `d20` to roll a 20 sided die, `'a'`: did not contain any random element, try `d20` to roll a 20 sided die", + "createButtonMessage: MessageDefinition(content=Click on a button to roll the dice, componentRowDefinitions=[ComponentRowDefinition(buttonDefinitions=[ButtonDefinition(label=1d6, id=custom_dice1_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=Attack, id=custom_dice2_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3d10,3d10,3d10, id=custom_dice3_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label=3, id=custom_dice4_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false), ButtonDefinition(label='a', id=custom_dice5_button00000000-0000-0000-0000-000000000000, style=PRIMARY, disabled=false)])])" + ); + } + @Test void slash_targetChannelTheSame_error() { CustomDiceCommand underTest = new CustomDiceCommand(persistenceManager, new DiceParser(), new CachingDiceEvaluator(new RandomNumberSupplier(0), 1000, 0)); diff --git a/bot/src/test/java/de/janno/discord/bot/command/directRoll/DirectRollCommandMockTest.java b/bot/src/test/java/de/janno/discord/bot/command/directRoll/DirectRollCommandMockTest.java index 5a5d0d47..b6bd643c 100644 --- a/bot/src/test/java/de/janno/discord/bot/command/directRoll/DirectRollCommandMockTest.java +++ b/bot/src/test/java/de/janno/discord/bot/command/directRoll/DirectRollCommandMockTest.java @@ -47,6 +47,24 @@ void roll_default() { "createResultMessageWithEventReference: EmbedOrMessageDefinition(title=1d6 ⇒ 1, descriptionOrContent=, fields=[], hasImage=true, type=EMBED)"); } + @Test + void roll_warn() { + DirectRollCommand directRollCommand = new DirectRollCommand(persistenceManager, new CachingDiceEvaluator(new RandomNumberSupplier(0), 1000, 0)); + + SlashEventAdaptorMock slashEvent = new SlashEventAdaptorMock(List.of(CommandInteractionOption.builder() + .name("expression") + .stringValue("20") + .build())); + directRollCommand.handleSlashCommandEvent(slashEvent, () -> UUID.fromString("00000000-0000-0000-0000-000000000000")).block(); + + + assertThat(slashEvent.getActions()).containsExactlyInAnyOrder( + "reply: commandString did not contain any random element, try `d20` to roll a 20 sided die", + "createResultMessageWithEventReference: EmbedOrMessageDefinition(title=20 ⇒ 20, descriptionOrContent=, fields=[], hasImage=false, type=EMBED)" + ); + } + + @Test void help() { DirectRollCommand directRollCommand = new DirectRollCommand(persistenceManager, new CachingDiceEvaluator(new RandomNumberSupplier(0), 1000, 0)); diff --git a/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java b/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java index 90c3bf2d..f605c4ae 100644 --- a/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java +++ b/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java @@ -470,4 +470,13 @@ void getImageForRoll_polyhedral_RdD() throws ExpressionException, IOException { assertThat(res).isNotNull(); assertThat(getDataHash(res)).isEqualTo("de01222cf4ea85e2fe2eea6539ebdfe9809bfe88f6dfb8ece5736df9eeb6603d"); } + + @Test + void getImageForRoll_polyhedral_RdD_specialColor() throws ExpressionException { + List rolls = new DiceEvaluator(new GivenNumberSupplier( 99), 1000).evaluate("1d100"); + + Supplier res = underTest.getImageForRoll(rolls, new DiceStyleAndColor(DiceImageStyle.polyhedral_RdD, "special")); + + assertThat(res).isNull(); + } } \ No newline at end of file diff --git a/discord-connector/api/src/main/java/de/janno/discord/connector/api/message/EmbedOrMessageDefinition.java b/discord-connector/api/src/main/java/de/janno/discord/connector/api/message/EmbedOrMessageDefinition.java index 61b651bb..28cf65a3 100644 --- a/discord-connector/api/src/main/java/de/janno/discord/connector/api/message/EmbedOrMessageDefinition.java +++ b/discord-connector/api/src/main/java/de/janno/discord/connector/api/message/EmbedOrMessageDefinition.java @@ -11,11 +11,11 @@ @AllArgsConstructor public class EmbedOrMessageDefinition { - //todo ephemeral field String title; String descriptionOrContent; @Singular + @NonNull List fields; Supplier image;