From 549f55fdfa502badb1665693fcacf7c8e45a3661 Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Sat, 24 Jan 2026 13:36:21 +0200 Subject: [PATCH 1/2] quotes-board: allow reactions with any emoji Recently from the server suggestions, many people have been slightly upset that the only reaction emoji considered for adding to the quotes board is the default star emoji. Some members have suggested specific additional emojis to be considered, others suggested that the star emoji should have a weight of 1.0 while the rest of the emojis should have a weight of 0.5. While both solutions can work, all emojis can have a custom weight for the purpose of customizability. Introduce a scoring concept for each emoji, configurable for each particular one, and provide the ability to set a default value if an emoji is not defined in the configuration file. For those who are wondering, _any_ kind of emoji that Discord can handle is able to be added in the configuration, including custom emojis in the server. JDA can keep track of those. For adding a unicode emoji, the actual unicode value has to be provided, like it has been done in "config.json.template". For adding a guild emoji, a "code" for the emoji has to be provided, for instance: youtube:1464573182206804010 Which stands for the friendly name of the emoji, a colon right after, and finally the ID of the custom emoji. The "config.json.template" is _NOT_ exhaustive, more emojis have to be added and some others removed according to preference. Signed-off-by: Chris Sdogkos --- application/config.json.template | 29 +++++++++- .../tjbot/config/QuoteBoardConfig.java | 23 +++++--- .../features/basic/QuoteBoardForwarder.java | 57 +++++++++++-------- 3 files changed, 73 insertions(+), 36 deletions(-) diff --git a/application/config.json.template b/application/config.json.template index e2e1963c80..7c10c30880 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -195,9 +195,34 @@ "pollIntervalInMinutes": 10 }, "quoteBoardConfig": { - "minimumReactionsToTrigger": 5, + "minimumScoreToTrigger": 5.0, "channel": "quotes", - "reactionEmoji": "โญ" + "botEmoji": "โญ", + "defaultEmojiScore": 0.5, + "emojiScores": { + "๐Ÿ˜ฌ": -0.5, + "๐Ÿ’”": -0.5, + "๐Ÿ˜": -0.5, + "๐Ÿ˜Š": -0.5, + "๐Ÿ–•": -0.5, + "๐Ÿ‘Ž": -0.5, + "๐Ÿ’ฉ": -0.5, + "๐Ÿคข": -0.5, + "๐Ÿคฎ": -0.5, + "๐Ÿคฌ": -0.5, + "๐Ÿ˜ก": -0.5, + "๐Ÿ˜’": -0.5, + "๐Ÿคจ": -0.5, + + "๐Ÿ‡ท๐Ÿ‡บ": 0.0, + "๐Ÿ‡ต๐Ÿ‡ธ": 0.0, + "๐Ÿ‡ฎ๐Ÿ‡ฑ": 0.0, + "๐Ÿณ๏ธโ€๐ŸŒˆ": 0.0, + + "โญ": 1.0, + + "youtube:1464573182206804010": 0.0 + } }, "memberCountCategoryPattern": "Info", "topHelpers": { diff --git a/application/src/main/java/org/togetherjava/tjbot/config/QuoteBoardConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/QuoteBoardConfig.java index faf756b4a8..d7837e13b9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/QuoteBoardConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/QuoteBoardConfig.java @@ -6,6 +6,7 @@ import org.togetherjava.tjbot.features.basic.QuoteBoardForwarder; +import java.util.Map; import java.util.Objects; /** @@ -13,31 +14,35 @@ */ @JsonRootName("quoteBoardConfig") public record QuoteBoardConfig( - @JsonProperty(value = "minimumReactionsToTrigger", required = true) int minimumReactions, + @JsonProperty(value = "minimumScoreToTrigger", required = true) float minimumScoreToTrigger, @JsonProperty(required = true) String channel, - @JsonProperty(value = "reactionEmoji", required = true) String reactionEmoji) { + @JsonProperty(value = "botEmoji", required = true) String botEmoji, + @JsonProperty(value = "defaultEmojiScore", required = true) float defaultEmojiScore, + @JsonProperty(value = "emojiScores", required = true) Map emojiScores) { /** * Creates a QuoteBoardConfig. * - * @param minimumReactions the minimum amount of reactions + * @param minimumScoreToTrigger the minimum amount of reaction score for a message to be quoted * @param channel the pattern for the board channel - * @param reactionEmoji the emoji with which users should react to + * @param defaultEmojiScore the default score of an emoji if it's not in the emojiScores map + * @param botEmoji the emoji with which the bot will mark quoted messages */ public QuoteBoardConfig { - if (minimumReactions <= 0) { - throw new IllegalArgumentException("minimumReactions must be greater than zero"); + if (minimumScoreToTrigger <= 0) { + throw new IllegalArgumentException("minimumScoreToTrigger must be greater than zero"); } Objects.requireNonNull(channel); if (channel.isBlank()) { throw new IllegalArgumentException("channel must not be empty or blank"); } - Objects.requireNonNull(reactionEmoji); - if (reactionEmoji.isBlank()) { + Objects.requireNonNull(botEmoji); + if (botEmoji.isBlank()) { throw new IllegalArgumentException("reactionEmoji must not be empty or blank"); } + Objects.requireNonNull(emojiScores); LogManager.getLogger(QuoteBoardConfig.class) .debug("Quote-Board configs loaded: minimumReactions={}, channel='{}', reactionEmoji='{}'", - minimumReactions, channel, reactionEmoji); + minimumScoreToTrigger, channel, botEmoji); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/basic/QuoteBoardForwarder.java b/application/src/main/java/org/togetherjava/tjbot/features/basic/QuoteBoardForwarder.java index a6d067e16e..8121ccf037 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/basic/QuoteBoardForwarder.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/basic/QuoteBoardForwarder.java @@ -6,6 +6,7 @@ import net.dv8tion.jda.api.entities.MessageReaction; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; import net.dv8tion.jda.api.requests.RestAction; import org.slf4j.Logger; @@ -36,7 +37,7 @@ public final class QuoteBoardForwarder extends MessageReceiverAdapter { private static final Logger logger = LoggerFactory.getLogger(QuoteBoardForwarder.class); - private final Emoji triggerReaction; + private final Emoji botEmoji; private final Predicate isQuoteBoardChannelName; private final QuoteBoardConfig config; @@ -48,7 +49,7 @@ public final class QuoteBoardForwarder extends MessageReceiverAdapter { */ public QuoteBoardForwarder(Config config) { this.config = config.getQuoteBoardConfig(); - this.triggerReaction = Emoji.fromUnicode(this.config.reactionEmoji()); + this.botEmoji = Emoji.fromUnicode(this.config.botEmoji()); this.isQuoteBoardChannelName = Pattern.compile(this.config.channel()).asMatchPredicate(); } @@ -60,24 +61,11 @@ public void onMessageReactionAdd(MessageReactionAddEvent event) { final MessageReaction messageReaction = event.getReaction(); - if (!messageReaction.getEmoji().equals(triggerReaction)) { - logger.debug("Reaction emoji '{}' does not match trigger emoji '{}'. Ignoring.", - messageReaction.getEmoji(), triggerReaction); - return; - } - if (hasAlreadyForwardedMessage(event.getJDA(), messageReaction)) { logger.debug("Message has already been forwarded by the bot. Skipping."); return; } - long reactionCount = messageReaction.retrieveUsers().stream().count(); - if (reactionCount < config.minimumReactions()) { - logger.debug("Reaction count {} is less than minimum required {}. Skipping.", - reactionCount, config.minimumReactions()); - return; - } - final long guildId = event.getGuild().getIdLong(); Optional boardChannelOptional = findQuoteBoardChannel(event.getJDA(), guildId); @@ -96,21 +84,27 @@ public void onMessageReactionAdd(MessageReactionAddEvent event) { return; } - logger.debug("Forwarding message to quote board channel: {}", boardChannel.getName()); + event.retrieveMessage().queue(message -> { + float emojiScore = calculateMessageScore(message.getReactions()); - event.retrieveMessage() - .queue(message -> markAsProcessed(message).flatMap(v -> message.forwardTo(boardChannel)) - .queue(_ -> logger.debug("Message forwarded to quote board channel: {}", - boardChannel.getName())), + if (emojiScore < config.minimumScoreToTrigger()) { + return; + } - e -> logger.warn( - "Unknown error while attempting to retrieve and forward message for quote-board, message is ignored.", - e)); + logger.debug("Attempting to forward message to quote board channel: {}", + boardChannel.getName()); + markAsProcessed(message).flatMap(_ -> message.forwardTo(boardChannel)) + .queue(_ -> logger.debug("Message forwarded to quote board channel: {}", + boardChannel.getName()), + e -> logger.warn( + "Unknown error while attempting to retrieve and forward message for quote-board, message is ignored.", + e)); + }); } private RestAction markAsProcessed(Message message) { - return message.addReaction(triggerReaction); + return message.addReaction(botEmoji); } /** @@ -146,7 +140,7 @@ private Optional findQuoteBoardChannel(JDA jda, long guildId) { * Checks a {@link MessageReaction} to see if the bot has reacted to it. */ private boolean hasAlreadyForwardedMessage(JDA jda, MessageReaction messageReaction) { - if (!triggerReaction.equals(messageReaction.getEmoji())) { + if (!botEmoji.equals(messageReaction.getEmoji())) { return false; } @@ -154,4 +148,17 @@ private boolean hasAlreadyForwardedMessage(JDA jda, MessageReaction messageReact .parallelStream() .anyMatch(user -> jda.getSelfUser().getIdLong() == user.getIdLong()); } + + private float calculateMessageScore(List reactions) { + return (float) reactions.stream() + .mapToDouble(reaction -> reaction.getCount() * getEmojiScore(reaction.getEmoji())) + .sum(); + } + + private float getEmojiScore(EmojiUnion emoji) { + float defaultScore = config.defaultEmojiScore(); + String reactionCode = emoji.getAsReactionCode(); + + return config.emojiScores().getOrDefault(reactionCode, defaultScore); + } } From 6ad39b909bed7bf0541720fd1505e38b17b3e8a1 Mon Sep 17 00:00:00 2001 From: Chris Sdogkos Date: Sat, 24 Jan 2026 14:06:05 +0200 Subject: [PATCH 2/2] doc(QuoteBoardConfig.java): update to reflect new configuration --- .../java/org/togetherjava/tjbot/config/QuoteBoardConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/config/QuoteBoardConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/QuoteBoardConfig.java index d7837e13b9..767af2c16d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/QuoteBoardConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/QuoteBoardConfig.java @@ -15,7 +15,7 @@ @JsonRootName("quoteBoardConfig") public record QuoteBoardConfig( @JsonProperty(value = "minimumScoreToTrigger", required = true) float minimumScoreToTrigger, - @JsonProperty(required = true) String channel, + @JsonProperty(value = "channel", required = true) String channel, @JsonProperty(value = "botEmoji", required = true) String botEmoji, @JsonProperty(value = "defaultEmojiScore", required = true) float defaultEmojiScore, @JsonProperty(value = "emojiScores", required = true) Map emojiScores) { @@ -25,8 +25,9 @@ public record QuoteBoardConfig( * * @param minimumScoreToTrigger the minimum amount of reaction score for a message to be quoted * @param channel the pattern for the board channel - * @param defaultEmojiScore the default score of an emoji if it's not in the emojiScores map * @param botEmoji the emoji with which the bot will mark quoted messages + * @param defaultEmojiScore the default score of an emoji if it's not in the emojiScores map + * @param emojiScores a map of each emoji's custom score */ public QuoteBoardConfig { if (minimumScoreToTrigger <= 0) {