Skip to content

Commit 6fb7720

Browse files
committed
feat(cool-messages): add primary logic
1 parent 9e16276 commit 6fb7720

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.togetherjava.tjbot.config.FeatureBlacklist;
77
import org.togetherjava.tjbot.config.FeatureBlacklistConfig;
88
import org.togetherjava.tjbot.db.Database;
9+
import org.togetherjava.tjbot.features.basic.CoolMessagesBoardManager;
910
import org.togetherjava.tjbot.features.basic.MemberCountDisplayRoutine;
1011
import org.togetherjava.tjbot.features.basic.PingCommand;
1112
import org.togetherjava.tjbot.features.basic.RoleSelectCommand;
@@ -150,6 +151,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
150151
features.add(new CodeMessageManualDetection(codeMessageHandler));
151152
features.add(new SlashCommandEducator());
152153
features.add(new PinnedNotificationRemover(config));
154+
features.add(new CoolMessagesBoardManager(config));
153155

154156
// Event receivers
155157
features.add(new RejoinModerationRoleListener(actionsStore, config));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package org.togetherjava.tjbot.features.basic;
2+
3+
import net.dv8tion.jda.api.EmbedBuilder;
4+
import net.dv8tion.jda.api.JDA;
5+
import net.dv8tion.jda.api.entities.Message;
6+
import net.dv8tion.jda.api.entities.MessageEmbed;
7+
import net.dv8tion.jda.api.entities.MessageReaction;
8+
import net.dv8tion.jda.api.entities.User;
9+
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
10+
import net.dv8tion.jda.api.entities.emoji.Emoji;
11+
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
12+
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
import org.togetherjava.tjbot.config.Config;
17+
import org.togetherjava.tjbot.config.CoolMessagesBoardConfig;
18+
import org.togetherjava.tjbot.features.MessageReceiverAdapter;
19+
20+
import java.awt.Color;
21+
import java.util.Collections;
22+
import java.util.Optional;
23+
import java.util.function.Predicate;
24+
import java.util.regex.Pattern;
25+
26+
/**
27+
* Manager for the cool messages board. It appends highly-voted text messages to a separate channel
28+
* where members of the guild can see a list of all of them.
29+
*/
30+
public final class CoolMessagesBoardManager extends MessageReceiverAdapter {
31+
32+
private static final Logger logger = LoggerFactory.getLogger(CoolMessagesBoardManager.class);
33+
private final Emoji coolEmoji;
34+
private final Predicate<String> boardChannelNamePredicate;
35+
private final CoolMessagesBoardConfig config;
36+
37+
/**
38+
* Constructs a new instance of CoolMessagesBoardManager.
39+
*
40+
* @param config the configuration containing settings specific to the cool messages board,
41+
* including the reaction emoji and the pattern to match board channel names
42+
*/
43+
public CoolMessagesBoardManager(Config config) {
44+
this.config = config.getCoolMessagesConfig();
45+
this.coolEmoji = Emoji.fromUnicode(this.config.reactionEmoji());
46+
47+
boardChannelNamePredicate =
48+
Pattern.compile(this.config.boardChannelPattern()).asMatchPredicate();
49+
}
50+
51+
@Override
52+
public void onMessageReactionAdd(MessageReactionAddEvent event) {
53+
final MessageReaction messageReaction = event.getReaction();
54+
int originalReactionsCount = messageReaction.hasCount() ? messageReaction.getCount() : 0;
55+
boolean isCoolEmoji = messageReaction.getEmoji().equals(coolEmoji);
56+
long guildId = event.getGuild().getIdLong();
57+
Optional<TextChannel> boardChannel = getBoardChannel(event.getJDA(), guildId);
58+
59+
if (boardChannel.isEmpty()) {
60+
logger.warn(
61+
"Could not find board channel with pattern '{}' in server with ID '{}'. Skipping reaction handling...",
62+
this.config.boardChannelPattern(), guildId);
63+
return;
64+
}
65+
66+
// If the bot has already reacted to this message, then this means that
67+
// the message has been quoted to the cool messages board, so skip it.
68+
if (hasBotReacted(event.getJDA(), messageReaction)) {
69+
return;
70+
}
71+
72+
final int newReactionsCount = originalReactionsCount + 1;
73+
if (isCoolEmoji && newReactionsCount >= config.minimumReactions()) {
74+
event.retrieveMessage()
75+
.queue(message -> message.addReaction(coolEmoji)
76+
.flatMap(v -> insertCoolMessage(boardChannel.get(), message))
77+
.queue(),
78+
e -> logger.warn("Tried to retrieve cool message but got: {}",
79+
e.getMessage()));
80+
}
81+
}
82+
83+
/**
84+
* Gets the board text channel where the quotes go to, wrapped in an optional.
85+
*
86+
* @param jda the JDA
87+
* @param guildId the guild ID
88+
* @return the board text channel
89+
*/
90+
private Optional<TextChannel> getBoardChannel(JDA jda, long guildId) {
91+
return jda.getGuildById(guildId)
92+
.getTextChannelCache()
93+
.stream()
94+
.filter(channel -> boardChannelNamePredicate.test(channel.getName()))
95+
.findAny();
96+
}
97+
98+
/**
99+
* Inserts a message to the specified text channel
100+
*
101+
* @return a {@link MessageCreateAction} of the call to make
102+
*/
103+
private static MessageCreateAction insertCoolMessage(TextChannel boardChannel,
104+
Message message) {
105+
return boardChannel.sendMessageEmbeds(Collections.singleton(createQuoteEmbed(message)));
106+
}
107+
108+
/**
109+
* Wraps a text message into a properly formatted quote message used for the board text channel.
110+
*/
111+
private static MessageEmbed createQuoteEmbed(Message message) {
112+
final User author = message.getAuthor();
113+
EmbedBuilder embedBuilder = new EmbedBuilder();
114+
115+
// If the message contains image(s), include the first one
116+
var firstImageAttachment = message.getAttachments()
117+
.stream()
118+
.parallel()
119+
.filter(Message.Attachment::isImage)
120+
.findAny()
121+
.orElse(null);
122+
123+
if (firstImageAttachment != null) {
124+
embedBuilder.setThumbnail(firstImageAttachment.getUrl());
125+
}
126+
127+
return embedBuilder.setDescription(message.getContentDisplay())
128+
.appendDescription("%n%n[Jump to Message](%s)".formatted(message.getJumpUrl()))
129+
.setColor(Color.orange)
130+
.setAuthor(author.getName(), null, author.getAvatarUrl())
131+
.setTimestamp(message.getTimeCreated())
132+
.build();
133+
}
134+
135+
/**
136+
* Checks a {@link MessageReaction} to see if the bot has reacted to it.
137+
*/
138+
private boolean hasBotReacted(JDA jda, MessageReaction messageReaction) {
139+
if (!coolEmoji.equals(messageReaction.getEmoji())) {
140+
return false;
141+
}
142+
143+
return messageReaction.retrieveUsers()
144+
.parallelStream()
145+
.anyMatch(user -> jda.getSelfUser().getIdLong() == user.getIdLong());
146+
}
147+
}

0 commit comments

Comments
 (0)