Skip to content

Commit 9389156

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

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-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,141 @@
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 Emoji coolEmoji;
34+
private final Predicate<String> boardChannelNamePredicate;
35+
private final CoolMessagesBoardConfig config;
36+
37+
public CoolMessagesBoardManager(Config config) {
38+
this.config = config.getCoolMessagesConfig();
39+
this.coolEmoji = Emoji.fromUnicode(this.config.reactionEmoji());
40+
41+
boardChannelNamePredicate =
42+
Pattern.compile(this.config.boardChannelPattern()).asMatchPredicate();
43+
}
44+
45+
@Override
46+
public void onMessageReactionAdd(MessageReactionAddEvent event) {
47+
final MessageReaction messageReaction = event.getReaction();
48+
int originalReactionsCount = messageReaction.hasCount() ? messageReaction.getCount() : 0;
49+
boolean isCoolEmoji = messageReaction.getEmoji().equals(coolEmoji);
50+
long guildId = event.getGuild().getIdLong();
51+
Optional<TextChannel> boardChannel = getBoardChannel(event.getJDA(), guildId);
52+
53+
if (boardChannel.isEmpty()) {
54+
logger.warn(
55+
"Could not find board channel with pattern '{}' in server with ID '{}'. Skipping reaction handling...",
56+
this.config.boardChannelPattern(), guildId);
57+
return;
58+
}
59+
60+
// If the bot has already reacted to this message, then this means that
61+
// the message has been quoted to the cool messages board, so skip it.
62+
if (hasBotReacted(event.getJDA(), messageReaction)) {
63+
return;
64+
}
65+
66+
final int newReactionsCount = originalReactionsCount + 1;
67+
if (isCoolEmoji && newReactionsCount >= config.minimumReactions()) {
68+
event.retrieveMessage()
69+
.queue(message -> message.addReaction(coolEmoji)
70+
.flatMap(v -> insertCoolMessage(boardChannel.get(), message))
71+
.queue(),
72+
e -> logger.warn("Tried to retrieve cool message but got: {}",
73+
e.getMessage()));
74+
}
75+
}
76+
77+
/**
78+
* Gets the board text channel where the quotes go to, wrapped in an optional.
79+
*
80+
* @param jda the JDA
81+
* @param guildId the guild ID
82+
* @return the board text channel
83+
*/
84+
private Optional<TextChannel> getBoardChannel(JDA jda, long guildId) {
85+
return jda.getGuildById(guildId)
86+
.getTextChannelCache()
87+
.stream()
88+
.filter(channel -> boardChannelNamePredicate.test(channel.getName()))
89+
.findAny();
90+
}
91+
92+
/**
93+
* Inserts a message to the specified text channel
94+
*
95+
* @return a {@link MessageCreateAction} of the call to make
96+
*/
97+
private static MessageCreateAction insertCoolMessage(TextChannel boardChannel,
98+
Message message) {
99+
return boardChannel.sendMessageEmbeds(Collections.singleton(createQuoteEmbed(message)));
100+
}
101+
102+
/**
103+
* Wraps a text message into a properly formatted quote message used for the board text channel.
104+
*/
105+
private static MessageEmbed createQuoteEmbed(Message message) {
106+
final User author = message.getAuthor();
107+
EmbedBuilder embedBuilder = new EmbedBuilder();
108+
109+
// If the message contains image(s), include the first one
110+
var firstImageAttachment = message.getAttachments()
111+
.stream()
112+
.parallel()
113+
.filter(Message.Attachment::isImage)
114+
.findAny()
115+
.orElse(null);
116+
117+
if (firstImageAttachment != null) {
118+
embedBuilder.setThumbnail(firstImageAttachment.getUrl());
119+
}
120+
121+
return embedBuilder.setDescription(message.getContentDisplay())
122+
.appendDescription("%n%n[Jump to Message](%s)".formatted(message.getJumpUrl()))
123+
.setColor(Color.orange)
124+
.setAuthor(author.getName(), null, author.getAvatarUrl())
125+
.setTimestamp(message.getTimeCreated())
126+
.build();
127+
}
128+
129+
/**
130+
* Checks a {@link MessageReaction} to see if the bot has reacted to it.
131+
*/
132+
private boolean hasBotReacted(JDA jda, MessageReaction messageReaction) {
133+
if (!coolEmoji.equals(messageReaction.getEmoji())) {
134+
return false;
135+
}
136+
137+
return messageReaction.retrieveUsers()
138+
.parallelStream()
139+
.anyMatch(user -> jda.getSelfUser().getIdLong() == user.getIdLong());
140+
}
141+
}

0 commit comments

Comments
 (0)