From a1eaf0dfdb4c62c512d65beae7cde1cc20c05053 Mon Sep 17 00:00:00 2001 From: Alathreon Date: Mon, 13 Apr 2026 13:54:54 +0200 Subject: [PATCH 1/2] |feature/numeric-score] WIP base idea --- application/config.json.template | 11 + .../org/togetherjava/tjbot/config/Config.java | 15 +- .../tjbot/config/NumericScoreConfig.java | 34 ++++ .../togetherjava/tjbot/features/Features.java | 2 + .../numericscore/NumericScoreFeature.java | 190 ++++++++++++++++++ 5 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 application/src/main/java/org/togetherjava/tjbot/config/NumericScoreConfig.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/numericscore/NumericScoreFeature.java diff --git a/application/config.json.template b/application/config.json.template index e2e1963c80..1c79990ab2 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -214,5 +214,16 @@ "archiveCategoryPattern": "Voice Channel Archives", "cleanChannelsAmount": 20, "minimumChannelsAmount": 40 + }, + "numericScoreConfig": { + "12345678901234567890": { + "zeroScore": "0️⃣", + "positiveScores": [ + "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟" + ], + "negativeScores": [ + "🟡", "🟠", "🔴" + ] + } } } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/Config.java b/application/src/main/java/org/togetherjava/tjbot/config/Config.java index 33362afcb0..d01757a3b6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -51,6 +51,7 @@ public final class Config { private final QuoteBoardConfig quoteBoardConfig; private final TopHelpersConfig topHelpers; private final DynamicVoiceChatConfig dynamicVoiceChatConfig; + private final List numericScoreConfig; @SuppressWarnings("ConstructorWithTooManyParameters") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) @@ -108,7 +109,9 @@ private Config(@JsonProperty(value = "token", required = true) String token, required = true) QuoteBoardConfig quoteBoardConfig, @JsonProperty(value = "topHelpers", required = true) TopHelpersConfig topHelpers, @JsonProperty(value = "dynamicVoiceChatConfig", - required = true) DynamicVoiceChatConfig dynamicVoiceChatConfig) { + required = true) DynamicVoiceChatConfig dynamicVoiceChatConfig, + @JsonProperty(value = "numericScoreConfig", + required = true) List numericScoreConfig) { this.token = Objects.requireNonNull(token); this.githubApiKey = Objects.requireNonNull(githubApiKey); this.databasePath = Objects.requireNonNull(databasePath); @@ -146,6 +149,7 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.quoteBoardConfig = Objects.requireNonNull(quoteBoardConfig); this.topHelpers = Objects.requireNonNull(topHelpers); this.dynamicVoiceChatConfig = Objects.requireNonNull(dynamicVoiceChatConfig); + this.numericScoreConfig = numericScoreConfig; } /** @@ -486,4 +490,13 @@ public TopHelpersConfig getTopHelpers() { public DynamicVoiceChatConfig getDynamicVoiceChatConfig() { return dynamicVoiceChatConfig; } + + /** + * Gets the numeric score configuration. + * + * @return numeric score configuration + */ + public List getNumericScoreConfig() { + return numericScoreConfig; + } } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/NumericScoreConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/NumericScoreConfig.java new file mode 100644 index 0000000000..73deab08b6 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/config/NumericScoreConfig.java @@ -0,0 +1,34 @@ +package org.togetherjava.tjbot.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.stream.Stream; + +/** + * Configuration of the numeric score feature. + * + * @param forumId the forum where to apply the numeric score feature + * @param upvoteEmoji the upvote emoji + * @param downvoteEmoji the downvote emoji + * @param zeroScoreEmoji the emoji for the score zero + * @param positiveScoresEmojis the emojis for positive scores starting at 1 ascending + * @param negativeScoresEmojis the emojis for negative scores starting at -1 descending + * @param addedEmojiBlackList the blacklisted emojis on top of score emojis + */ +public record NumericScoreConfig(@JsonProperty(value = "forumId", required = true) long forumId, + @JsonProperty(value = "upvoteEmoji", required = true) String upvoteEmoji, + @JsonProperty(value = "downvoteEmoji", required = true) String downvoteEmoji, + @JsonProperty(value = "zeroScoreEmoji", required = true) String zeroScoreEmoji, + @JsonProperty(value = "positiveScoresEmojis", + required = true) List positiveScoresEmojis, + @JsonProperty(value = "negativeScoresEmojis", + required = true) List negativeScoresEmojis, + @JsonProperty(value = "addedEmojiBlackList", + required = true) List addedEmojiBlackList) { + public Stream streamAllBlacklistedEmojis() { + return Stream.concat( + Stream.concat(positiveScoresEmojis.stream(), negativeScoresEmojis.stream()), + Stream.concat(Stream.of(zeroScoreEmoji), addedEmojiBlackList.stream())); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index c360bacdd1..caf266c95a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -66,6 +66,7 @@ import org.togetherjava.tjbot.features.moderation.scam.ScamHistoryPurgeRoutine; import org.togetherjava.tjbot.features.moderation.scam.ScamHistoryStore; import org.togetherjava.tjbot.features.moderation.temp.TemporaryModerationRoutine; +import org.togetherjava.tjbot.features.numericscore.NumericScoreFeature; import org.togetherjava.tjbot.features.projects.ProjectsThreadCreatedListener; import org.togetherjava.tjbot.features.reminder.RemindRoutine; import org.togetherjava.tjbot.features.reminder.ReminderCommand; @@ -219,6 +220,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new JShellCommand(jshellEval)); features.add(new MessageCommand()); features.add(new RewriteCommand(chatGptService)); + features.add(new NumericScoreFeature(config.getNumericScoreConfig())); FeatureBlacklist> blacklist = blacklistConfig.normal(); return blacklist.filterStream(features.stream(), Object::getClass).toList(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/numericscore/NumericScoreFeature.java b/application/src/main/java/org/togetherjava/tjbot/features/numericscore/NumericScoreFeature.java new file mode 100644 index 0000000000..7a69b18786 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/numericscore/NumericScoreFeature.java @@ -0,0 +1,190 @@ +package org.togetherjava.tjbot.features.numericscore; + +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageReaction; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.events.channel.ChannelCreateEvent; +import net.dv8tion.jda.api.events.message.GenericMessageEvent; +import net.dv8tion.jda.api.events.message.react.*; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.internal.requests.CompletedRestAction; +import org.jetbrains.annotations.NotNull; + +import org.togetherjava.tjbot.config.NumericScoreConfig; +import org.togetherjava.tjbot.features.EventReceiver; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; + +public class NumericScoreFeature extends ListenerAdapter implements EventReceiver { + private final List numericScoreConfig; + + public NumericScoreFeature(List numericScoreConfig) { + this.numericScoreConfig = numericScoreConfig; + } + + /** + * Runs a function with the config found for the given thread channel. If the given channel + * isn't a thread channel in a forum or no config is found for it, nothing will happen. + * + * @param channel the supposed thread channel + * @param configConsumer the function to run with the config found on the post message + */ + private void withConfig(Channel channel, + BiConsumer configConsumer) { + if (channel instanceof ThreadChannel threadChannel + && threadChannel.getParentChannel() instanceof ForumChannel) { + numericScoreConfig.stream() + .filter(c -> c.forumId() == threadChannel.getParentChannel().getIdLong()) + .findFirst() + .ifPresent(c -> threadChannel.retrieveStartMessage() + .queue(m -> configConsumer.accept(m, c))); + } + } + + /** + * Runs a function with the config found for the given thread channel. If the given message + * isn't a post in a forum or no config is found for it, nothing will happen. + * + * @param message the supposed forum post + * @param configConsumer the function to run with the config found on the post message + */ + private void withConfig(Message message, + BiConsumer configConsumer) { + withConfig(message.getChannel(), configConsumer); + } + + /** + * Runs a function with the config found for the given thread channel. If the given message + * isn't a post in a forum or no config is found for it, nothing will happen. + * + * @param event the supposed forum post reaction event + * @param configConsumer the function to run with the config found on the post message + */ + private void withConfig(GenericMessageEvent event, + BiConsumer configConsumer) { + event.getChannel() + .retrieveMessageById(event.getMessageId()) + .queue(message -> withConfig(message, configConsumer)); + } + + private String findStringEmojiFromScore(NumericScoreConfig config, int score) { + if (score > 0) { + score = Math.min(score, config.positiveScoresEmojis().size() - 1); + return config.positiveScoresEmojis().get(score); + } else if (score < 0) { + score = Math.min(-score, config.negativeScoresEmojis().size() - 1); + return config.negativeScoresEmojis().get(score); + } else { + return config.zeroScoreEmoji(); + } + } + + private RestAction calculateScore(NumericScoreConfig config, Message message) { + EmojiUnion upvoteEmoji = findEmoji(message.getGuild(), config.upvoteEmoji()); + EmojiUnion downvoteEmoji = findEmoji(message.getGuild(), config.downvoteEmoji()); + var upvotesReaction = message.getReaction(upvoteEmoji); + var downvotesReaction = message.getReaction(downvoteEmoji); + + RestAction> retrieveUpvotesUsers = upvotesReaction == null + ? new CompletedRestAction<>(message.getJDA(), new ArrayList<>()) + : upvotesReaction.retrieveUsers(); + RestAction> retrieveDownvotesUsers = downvotesReaction == null + ? new CompletedRestAction<>(message.getJDA(), new ArrayList<>()) + : downvotesReaction.retrieveUsers(); + + return RestAction.allOf(retrieveUpvotesUsers, retrieveDownvotesUsers).map(reactionUsers -> { + List upvotesUsers = reactionUsers.get(0); + List downvotesUsers = reactionUsers.get(1); + int score = 1; + score += (int) upvotesUsers.stream() + .filter(u -> filterReaction(message.getAuthor(), u)) + .count(); + score -= (int) downvotesUsers.stream() + .filter(u -> filterReaction(message.getAuthor(), u)) + .count(); + return score; + }); + } + + private void updateEmojis(NumericScoreConfig config, Message message) { + addVoteEmojis(config, message); + calculateScore(config, message).queue(score -> { + String stringEmoji = findStringEmojiFromScore(config, score); + Emoji emoji = findEmoji(message.getGuild(), stringEmoji); + clearEmojis(config, message, () -> message.addReaction(emoji).queue()); + }); + } + + private boolean filterReaction(User author, User reacted) { + return !reacted.isBot() && reacted.getIdLong() != author.getIdLong(); + } + + private EmojiUnion findEmoji(Guild guild, String nameOrUnicode) { + return guild.getEmojisByName(nameOrUnicode, true) + .stream() + .findAny() + .map(e -> (EmojiUnion) e) + .orElse((EmojiUnion) Emoji.fromUnicode(nameOrUnicode)); + } + + private void clearEmojis(NumericScoreConfig config, Message message, Runnable postAction) { + List> actions = config.streamAllBlacklistedEmojis() + .map(e -> (Emoji) findEmoji(message.getGuild(), e)) + .map(e -> { + MessageReaction reaction = message.getReaction(e); + return reaction == null ? null : message.clearReactions(e); + }) + .filter(Objects::nonNull) + .toList(); + if (actions.isEmpty()) { + postAction.run(); + } else { + RestAction.allOf(actions).queue(_ -> postAction.run()); + } + } + + private void addVoteEmojis(NumericScoreConfig config, Message message) { + Emoji upvote = findEmoji(message.getGuild(), config.upvoteEmoji()); + Emoji downvote = findEmoji(message.getGuild(), config.downvoteEmoji()); + MessageReaction reactionUpvote = message.getReaction(upvote); + MessageReaction reactionDownvote = message.getReaction(downvote); + if (reactionUpvote != null && !reactionUpvote.isSelf()) { + message.addReaction(upvote).queue(); + } + if (reactionDownvote != null && !reactionDownvote.isSelf()) { + message.addReaction(downvote).queue(); + } + } + + @Override + public void onChannelCreate(@NotNull ChannelCreateEvent event) { + withConfig(event.getChannel(), (post, config) -> { + updateEmojis(config, post); + addVoteEmojis(config, post); + }); + } + + /** + * Handles ADD/REMOVE reaction only + * + * @param event the message event + */ + @Override + public void onGenericMessageReaction(@NotNull GenericMessageReactionEvent event) { + event.retrieveUser().queue(user -> { + if (!user.isBot()) { + withConfig(event, (post, config) -> updateEmojis(config, post)); + } + }); + } +} From cf1821f0dd2bee7f4ff6a0ad18417624995fa911 Mon Sep 17 00:00:00 2001 From: Alathreon Date: Mon, 13 Apr 2026 23:17:42 +0200 Subject: [PATCH 2/2] [feature/numeric-score] Done, needs improvements --- application/config.json.template | 15 +- .../tjbot/config/NumericScoreConfig.java | 11 +- .../togetherjava/tjbot/features/Features.java | 2 +- .../numericscore/NumericScoreFeature.java | 206 ++++++++---------- .../db/V19__Add_Vote_Score_System.sql | 7 + 5 files changed, 109 insertions(+), 132 deletions(-) create mode 100644 application/src/main/resources/db/V19__Add_Vote_Score_System.sql diff --git a/application/config.json.template b/application/config.json.template index 1c79990ab2..22c7b7988b 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -215,15 +215,18 @@ "cleanChannelsAmount": 20, "minimumChannelsAmount": 40 }, - "numericScoreConfig": { - "12345678901234567890": { - "zeroScore": "0️⃣", - "positiveScores": [ + "numericScoreConfig": [ + { + "forumId": 123456789123456789, + "upvoteEmoji": "upvote", + "downvoteEmoji": "downvote", + "zeroScoreEmoji": "0️⃣", + "positiveScoresEmojis": [ "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟" ], - "negativeScores": [ + "negativeScoresEmojis": [ "🟡", "🟠", "🔴" ] } - } + ] } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/NumericScoreConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/NumericScoreConfig.java index 73deab08b6..47674d8b76 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/NumericScoreConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/NumericScoreConfig.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; -import java.util.stream.Stream; /** * Configuration of the numeric score feature. @@ -14,7 +13,6 @@ * @param zeroScoreEmoji the emoji for the score zero * @param positiveScoresEmojis the emojis for positive scores starting at 1 ascending * @param negativeScoresEmojis the emojis for negative scores starting at -1 descending - * @param addedEmojiBlackList the blacklisted emojis on top of score emojis */ public record NumericScoreConfig(@JsonProperty(value = "forumId", required = true) long forumId, @JsonProperty(value = "upvoteEmoji", required = true) String upvoteEmoji, @@ -23,12 +21,5 @@ public record NumericScoreConfig(@JsonProperty(value = "forumId", required = tru @JsonProperty(value = "positiveScoresEmojis", required = true) List positiveScoresEmojis, @JsonProperty(value = "negativeScoresEmojis", - required = true) List negativeScoresEmojis, - @JsonProperty(value = "addedEmojiBlackList", - required = true) List addedEmojiBlackList) { - public Stream streamAllBlacklistedEmojis() { - return Stream.concat( - Stream.concat(positiveScoresEmojis.stream(), negativeScoresEmojis.stream()), - Stream.concat(Stream.of(zeroScoreEmoji), addedEmojiBlackList.stream())); - } + required = true) List negativeScoresEmojis) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index caf266c95a..398689e631 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -220,7 +220,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new JShellCommand(jshellEval)); features.add(new MessageCommand()); features.add(new RewriteCommand(chatGptService)); - features.add(new NumericScoreFeature(config.getNumericScoreConfig())); + features.add(new NumericScoreFeature(database, config.getNumericScoreConfig())); FeatureBlacklist> blacklist = blacklistConfig.normal(); return blacklist.filterStream(features.stream(), Object::getClass).toList(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/numericscore/NumericScoreFeature.java b/application/src/main/java/org/togetherjava/tjbot/features/numericscore/NumericScoreFeature.java index 7a69b18786..240f956c7f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/numericscore/NumericScoreFeature.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/numericscore/NumericScoreFeature.java @@ -2,34 +2,51 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.MessageReaction; -import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.entities.emoji.EmojiUnion; import net.dv8tion.jda.api.events.channel.ChannelCreateEvent; -import net.dv8tion.jda.api.events.message.GenericMessageEvent; import net.dv8tion.jda.api.events.message.react.*; import net.dv8tion.jda.api.hooks.ListenerAdapter; -import net.dv8tion.jda.api.requests.RestAction; -import net.dv8tion.jda.internal.requests.CompletedRestAction; import org.jetbrains.annotations.NotNull; +import org.jooq.Result; import org.togetherjava.tjbot.config.NumericScoreConfig; +import org.togetherjava.tjbot.db.Database; +import org.togetherjava.tjbot.db.generated.tables.VoteScore; +import org.togetherjava.tjbot.db.generated.tables.records.VoteScoreRecord; import org.togetherjava.tjbot.features.EventReceiver; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.function.BiConsumer; +import java.util.stream.Collectors; public class NumericScoreFeature extends ListenerAdapter implements EventReceiver { + + private final Database database; private final List numericScoreConfig; + /** + * Map> + */ + private final Map> reverseEmojiScoreConfig; - public NumericScoreFeature(List numericScoreConfig) { + public NumericScoreFeature(Database database, List numericScoreConfig) { + this.database = database; this.numericScoreConfig = numericScoreConfig; + this.reverseEmojiScoreConfig = this.numericScoreConfig.stream() + .collect(Collectors.toMap(NumericScoreConfig::forumId, c -> { + Map map = new HashMap<>(); + map.put(c.zeroScoreEmoji(), 0); + for (int i = 0; i < c.positiveScoresEmojis().size(); i++) { + map.put(c.positiveScoresEmojis().get(i), i + 1); + } + for (int i = 0; i < c.negativeScoresEmojis().size(); i++) { + map.put(c.negativeScoresEmojis().get(i), -i - 1); + } + return map; + })); } /** @@ -51,82 +68,50 @@ private void withConfig(Channel channel, } } - /** - * Runs a function with the config found for the given thread channel. If the given message - * isn't a post in a forum or no config is found for it, nothing will happen. - * - * @param message the supposed forum post - * @param configConsumer the function to run with the config found on the post message - */ - private void withConfig(Message message, - BiConsumer configConsumer) { - withConfig(message.getChannel(), configConsumer); - } - - /** - * Runs a function with the config found for the given thread channel. If the given message - * isn't a post in a forum or no config is found for it, nothing will happen. - * - * @param event the supposed forum post reaction event - * @param configConsumer the function to run with the config found on the post message - */ - private void withConfig(GenericMessageEvent event, - BiConsumer configConsumer) { - event.getChannel() - .retrieveMessageById(event.getMessageId()) - .queue(message -> withConfig(message, configConsumer)); - } - private String findStringEmojiFromScore(NumericScoreConfig config, int score) { if (score > 0) { - score = Math.min(score, config.positiveScoresEmojis().size() - 1); - return config.positiveScoresEmojis().get(score); + score = Math.min(score, config.positiveScoresEmojis().size()); + return config.positiveScoresEmojis().get(score - 1); } else if (score < 0) { - score = Math.min(-score, config.negativeScoresEmojis().size() - 1); - return config.negativeScoresEmojis().get(score); + score = Math.min(-score, config.negativeScoresEmojis().size()); + return config.negativeScoresEmojis().get(score - 1); } else { return config.zeroScoreEmoji(); } } - private RestAction calculateScore(NumericScoreConfig config, Message message) { - EmojiUnion upvoteEmoji = findEmoji(message.getGuild(), config.upvoteEmoji()); - EmojiUnion downvoteEmoji = findEmoji(message.getGuild(), config.downvoteEmoji()); - var upvotesReaction = message.getReaction(upvoteEmoji); - var downvotesReaction = message.getReaction(downvoteEmoji); - - RestAction> retrieveUpvotesUsers = upvotesReaction == null - ? new CompletedRestAction<>(message.getJDA(), new ArrayList<>()) - : upvotesReaction.retrieveUsers(); - RestAction> retrieveDownvotesUsers = downvotesReaction == null - ? new CompletedRestAction<>(message.getJDA(), new ArrayList<>()) - : downvotesReaction.retrieveUsers(); - - return RestAction.allOf(retrieveUpvotesUsers, retrieveDownvotesUsers).map(reactionUsers -> { - List upvotesUsers = reactionUsers.get(0); - List downvotesUsers = reactionUsers.get(1); - int score = 1; - score += (int) upvotesUsers.stream() - .filter(u -> filterReaction(message.getAuthor(), u)) - .count(); - score -= (int) downvotesUsers.stream() - .filter(u -> filterReaction(message.getAuthor(), u)) - .count(); - return score; - }); + private int calculateScore(Message message) { + Result votes = database + .read(ctx -> ctx.selectFrom(VoteScore.VOTE_SCORE) + .where(VoteScore.VOTE_SCORE.MESSAGE_ID.eq(message.getIdLong()))) + .fetch(); + int upvotes = (int) votes.stream().filter(v -> v.getVote() == 1).count(); + int downvotes = (int) votes.stream().filter(v -> v.getVote() == -1).count(); + + return 1 + upvotes - downvotes; } private void updateEmojis(NumericScoreConfig config, Message message) { - addVoteEmojis(config, message); - calculateScore(config, message).queue(score -> { - String stringEmoji = findStringEmojiFromScore(config, score); - Emoji emoji = findEmoji(message.getGuild(), stringEmoji); - clearEmojis(config, message, () -> message.addReaction(emoji).queue()); - }); - } + Map emojiToScoreMap = reverseEmojiScoreConfig.get(config.forumId()); + int score = calculateScore(message); + + if (message.getReactions() + .stream() + .map(e -> e.getEmoji().getName()) + .anyMatch(e -> Objects.equals(emojiToScoreMap.get(e), score))) { + // If the score emoji is the same + return; + } - private boolean filterReaction(User author, User reacted) { - return !reacted.isBot() && reacted.getIdLong() != author.getIdLong(); + Emoji scoreEmoji = findEmoji(message.getGuild(), findStringEmojiFromScore(config, score)); + Emoji upvote = findEmoji(message.getGuild(), config.upvoteEmoji()); + Emoji downvote = findEmoji(message.getGuild(), config.downvoteEmoji()); + + message.clearReactions() + .flatMap(_ -> message.addReaction(scoreEmoji)) + .flatMap(_ -> message.addReaction(upvote)) + .flatMap(_ -> message.addReaction(downvote)) + .queue(); } private EmojiUnion findEmoji(Guild guild, String nameOrUnicode) { @@ -137,54 +122,45 @@ private EmojiUnion findEmoji(Guild guild, String nameOrUnicode) { .orElse((EmojiUnion) Emoji.fromUnicode(nameOrUnicode)); } - private void clearEmojis(NumericScoreConfig config, Message message, Runnable postAction) { - List> actions = config.streamAllBlacklistedEmojis() - .map(e -> (Emoji) findEmoji(message.getGuild(), e)) - .map(e -> { - MessageReaction reaction = message.getReaction(e); - return reaction == null ? null : message.clearReactions(e); - }) - .filter(Objects::nonNull) - .toList(); - if (actions.isEmpty()) { - postAction.run(); - } else { - RestAction.allOf(actions).queue(_ -> postAction.run()); - } - } - - private void addVoteEmojis(NumericScoreConfig config, Message message) { - Emoji upvote = findEmoji(message.getGuild(), config.upvoteEmoji()); - Emoji downvote = findEmoji(message.getGuild(), config.downvoteEmoji()); - MessageReaction reactionUpvote = message.getReaction(upvote); - MessageReaction reactionDownvote = message.getReaction(downvote); - if (reactionUpvote != null && !reactionUpvote.isSelf()) { - message.addReaction(upvote).queue(); - } - if (reactionDownvote != null && !reactionDownvote.isSelf()) { - message.addReaction(downvote).queue(); - } - } - @Override public void onChannelCreate(@NotNull ChannelCreateEvent event) { - withConfig(event.getChannel(), (post, config) -> { - updateEmojis(config, post); - addVoteEmojis(config, post); - }); + withConfig(event.getChannel(), (post, config) -> updateEmojis(config, post)); } - /** - * Handles ADD/REMOVE reaction only - * - * @param event the message event - */ @Override - public void onGenericMessageReaction(@NotNull GenericMessageReactionEvent event) { - event.retrieveUser().queue(user -> { - if (!user.isBot()) { - withConfig(event, (post, config) -> updateEmojis(config, post)); + public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) { + withConfig(event.getChannel(), (post, config) -> { + long user = Objects.requireNonNull(event.getUser()).getIdLong(); + if (user == event.getJDA().getSelfUser().getIdLong() || user == post.getIdLong()) { + return; } + + String emoji = event.getEmoji().getName(); + int vote = 0; + if (emoji.equals(config.upvoteEmoji())) { + vote = 1; + } + if (emoji.equals(config.downvoteEmoji())) { + vote = -1; + } + + if (vote == 0) { + database.write(ctx -> ctx.deleteFrom(VoteScore.VOTE_SCORE) + .where(VoteScore.VOTE_SCORE.MESSAGE_ID.eq(event.getMessageIdLong())) + .and(VoteScore.VOTE_SCORE.USER_ID.eq(user)) + .execute()); + } else { + int vote2 = vote; + database.write(ctx -> ctx.insertInto(VoteScore.VOTE_SCORE) + .set(VoteScore.VOTE_SCORE.MESSAGE_ID, event.getMessageIdLong()) + .set(VoteScore.VOTE_SCORE.USER_ID, user) + .set(VoteScore.VOTE_SCORE.VOTE, vote2) + .onConflict(VoteScore.VOTE_SCORE.MESSAGE_ID, VoteScore.VOTE_SCORE.USER_ID) + .doUpdate() + .set(VoteScore.VOTE_SCORE.VOTE, vote2) + .execute()); + } + updateEmojis(config, post); }); } } diff --git a/application/src/main/resources/db/V19__Add_Vote_Score_System.sql b/application/src/main/resources/db/V19__Add_Vote_Score_System.sql new file mode 100644 index 0000000000..c2977a7965 --- /dev/null +++ b/application/src/main/resources/db/V19__Add_Vote_Score_System.sql @@ -0,0 +1,7 @@ +CREATE TABLE vote_score +( + message_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + vote INTEGER NOT NULL, + PRIMARY KEY (message_id, user_id) +) \ No newline at end of file