Skip to content

Commit 75bbd9f

Browse files
committed
feat(coolMessages): implement working version
1 parent 6c3cc81 commit 75bbd9f

File tree

7 files changed

+195
-1
lines changed

7 files changed

+195
-1
lines changed

application/src/main/java/org/togetherjava/tjbot/config/Config.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public final class Config {
4545
private final FeatureBlacklistConfig featureBlacklistConfig;
4646
private final String selectRolesChannelPattern;
4747
private final String memberCountCategoryPattern;
48+
private final CoolMessagesBoardConfig coolMessagesConfig;
4849

4950
@SuppressWarnings("ConstructorWithTooManyParameters")
5051
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
@@ -94,7 +95,9 @@ private Config(@JsonProperty(value = "token", required = true) String token,
9495
@JsonProperty(value = "featureBlacklist",
9596
required = true) FeatureBlacklistConfig featureBlacklistConfig,
9697
@JsonProperty(value = "selectRolesChannelPattern",
97-
required = true) String selectRolesChannelPattern) {
98+
required = true) String selectRolesChannelPattern,
99+
@JsonProperty(value = "coolMessagesConfig",
100+
required = true) CoolMessagesBoardConfig coolMessagesConfig) {
98101
this.token = Objects.requireNonNull(token);
99102
this.githubApiKey = Objects.requireNonNull(githubApiKey);
100103
this.databasePath = Objects.requireNonNull(databasePath);
@@ -127,6 +130,7 @@ private Config(@JsonProperty(value = "token", required = true) String token,
127130
this.helperPruneConfig = Objects.requireNonNull(helperPruneConfig);
128131
this.featureBlacklistConfig = Objects.requireNonNull(featureBlacklistConfig);
129132
this.selectRolesChannelPattern = Objects.requireNonNull(selectRolesChannelPattern);
133+
this.coolMessagesConfig = Objects.requireNonNull(coolMessagesConfig);
130134
}
131135

132136
/**
@@ -410,6 +414,15 @@ public String getSelectRolesChannelPattern() {
410414
return selectRolesChannelPattern;
411415
}
412416

417+
/**
418+
* The configuration of the cool messages config.
419+
*
420+
* @return configuration of cool messages config
421+
*/
422+
public CoolMessagesBoardConfig getCoolMessagesConfig() {
423+
return coolMessagesConfig;
424+
}
425+
413426
/**
414427
* Gets the pattern matching the category that is used to display the total member count.
415428
*
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.togetherjava.tjbot.config;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.annotation.JsonRootName;
5+
6+
/**
7+
* Configuration for the cool messages board feature, see
8+
* {@link org.togetherjava.tjbot.features.basic.CoolMessagesBoardManager}.
9+
*/
10+
@JsonRootName("coolMessagesConfig")
11+
public final class CoolMessagesBoardConfig {
12+
private final String boardChannelPattern;
13+
private final int minimumReactions;
14+
15+
private CoolMessagesBoardConfig(
16+
@JsonProperty(value = "minimumReactions", required = true) int minimumReactions,
17+
@JsonProperty(value = "boardChannelPattern",
18+
required = true) String boardChannelPattern) {
19+
this.minimumReactions = minimumReactions;
20+
this.boardChannelPattern = boardChannelPattern;
21+
}
22+
23+
/**
24+
* Gets the minimum amount of reactions needed for a message to be considered as a quote.
25+
*
26+
* @return the minimum amount of reactions
27+
*/
28+
public int getMinimumReactions() {
29+
return minimumReactions;
30+
}
31+
32+
/**
33+
* Gets the REGEX pattern used to identify the quotes text channel
34+
*
35+
* @return the channel name pattern
36+
*/
37+
public String getBoardChannelPattern() {
38+
return boardChannelPattern;
39+
}
40+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.togetherjava.tjbot.config.FeatureBlacklist;
99
import org.togetherjava.tjbot.config.FeatureBlacklistConfig;
1010
import org.togetherjava.tjbot.db.Database;
11+
import org.togetherjava.tjbot.features.basic.CoolMessagesBoardManager;
1112
import org.togetherjava.tjbot.features.basic.MemberCountDisplayRoutine;
1213
import org.togetherjava.tjbot.features.basic.PingCommand;
1314
import org.togetherjava.tjbot.features.basic.RoleSelectCommand;
@@ -125,6 +126,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
125126
features.add(new CodeMessageManualDetection(codeMessageHandler));
126127
features.add(new SlashCommandEducator());
127128
features.add(new PinnedNotificationRemover(config));
129+
features.add(new CoolMessagesBoardManager(config));
128130

129131
// Event receivers
130132
features.add(new RejoinModerationRoleListener(actionsStore, config));

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
44
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
55
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
6+
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
67

78
import java.util.regex.Pattern;
89

@@ -56,4 +57,13 @@ public interface MessageReceiver extends Feature {
5657
* message that was deleted
5758
*/
5859
void onMessageDeleted(MessageDeleteEvent event);
60+
61+
/**
62+
* Triggered by the core system whenever a new reaction was added to a message in a text channel
63+
* of a guild the bot has been added to.
64+
*
65+
* @param event the event that triggered this, containing information about the corresponding
66+
* reaction that was added
67+
*/
68+
void onMessageReactionAdd(MessageReactionAddEvent event);
5969
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
44
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
55
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
6+
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
67

78
import java.util.regex.Pattern;
89

@@ -57,4 +58,10 @@ public void onMessageUpdated(MessageUpdateEvent event) {
5758
public void onMessageDeleted(MessageDeleteEvent event) {
5859
// Adapter does not react by default, subclasses may change this behavior
5960
}
61+
62+
@SuppressWarnings("NoopMethodInAbstractClass")
63+
@Override
64+
public void onMessageReactionAdd(MessageReactionAddEvent event) {
65+
// Adapter does not react by default, subclasses may change this behavior
66+
}
6067
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
import org.togetherjava.tjbot.config.Config;
16+
import org.togetherjava.tjbot.config.CoolMessagesBoardConfig;
17+
import org.togetherjava.tjbot.features.MessageReceiverAdapter;
18+
19+
import java.awt.*;
20+
import java.util.Collections;
21+
import java.util.Optional;
22+
import java.util.function.Predicate;
23+
import java.util.regex.Pattern;
24+
25+
/**
26+
* Manager for the cool messages board. It appends highly-voted text messages to a separate channel
27+
* where members of the guild can see a list of all of them.
28+
*/
29+
public final class CoolMessagesBoardManager extends MessageReceiverAdapter {
30+
31+
private static final Logger logger = LoggerFactory.getLogger(CoolMessagesBoardManager.class);
32+
private static final Emoji REACT_EMOJI = Emoji.fromUnicode("🌟");
33+
private final Predicate<String> boardChannelNamePredicate;
34+
private final CoolMessagesBoardConfig config;
35+
36+
public CoolMessagesBoardManager(Config config) {
37+
this.config = config.getCoolMessagesConfig();
38+
39+
boardChannelNamePredicate =
40+
Pattern.compile(this.config.getBoardChannelPattern()).asMatchPredicate();
41+
}
42+
43+
@Override
44+
public void onMessageReactionAdd(MessageReactionAddEvent event) {
45+
final MessageReaction messageReaction = event.getReaction();
46+
int originalReactionsCount = messageReaction.hasCount() ? messageReaction.getCount() : 0;
47+
boolean isCoolEmoji = messageReaction.getEmoji().getName().equals(REACT_EMOJI.getName());
48+
long guildId = event.getGuild().getIdLong();
49+
Optional<TextChannel> boardChannel = getBoardChannel(event.getJDA(), guildId);
50+
51+
if (boardChannel.isEmpty()) {
52+
logger.warn("Tried to find the board channel in server {} but was unable to do so",
53+
guildId);
54+
return;
55+
}
56+
57+
if (isCoolEmoji && originalReactionsCount + 1 >= config.getMinimumReactions()) {
58+
event.retrieveMessage()
59+
.queue(message -> insertCoolMessage(boardChannel.orElseThrow(), message),
60+
e -> logger.warn("Tried to retrieve cool message but got: {}",
61+
e.getMessage()));
62+
}
63+
}
64+
65+
/**
66+
* Gets the board text channel where the quotes go to, wrapped in an optional.
67+
*
68+
* @param jda the JDA
69+
* @param guildId the guild ID
70+
* @return the board text channel
71+
*/
72+
private Optional<TextChannel> getBoardChannel(JDA jda, long guildId) {
73+
return jda.getGuildById(guildId)
74+
.getTextChannelCache()
75+
.stream()
76+
.filter(channel -> boardChannelNamePredicate.test(channel.getName()))
77+
.findAny();
78+
}
79+
80+
/**
81+
* Inserts a message to the specified text channel
82+
*/
83+
private static void insertCoolMessage(TextChannel boardChannel, Message message) {
84+
boardChannel.sendMessageEmbeds(Collections.singleton(createQuoteEmbed(message))).queue();
85+
}
86+
87+
/**
88+
* Wraps a text message into a properly formatted quote message used for the board text channel.
89+
*/
90+
private static MessageEmbed createQuoteEmbed(Message message) {
91+
final User author = message.getAuthor();
92+
EmbedBuilder embedBuilder = new EmbedBuilder();
93+
94+
// If the message contains image(s), include the first one
95+
var imageAttachment = message.getAttachments()
96+
.stream()
97+
.parallel()
98+
.filter(Message.Attachment::isImage)
99+
.findAny()
100+
.orElse(null);
101+
102+
if (imageAttachment != null) {
103+
embedBuilder.setThumbnail(imageAttachment.getUrl());
104+
}
105+
106+
return embedBuilder.setDescription(message.getContentDisplay())
107+
.appendDescription("%n%n[Jump to Message](%s)".formatted(message.getJumpUrl()))
108+
.setColor(Color.orange)
109+
.setAuthor(author.getName(), null, author.getAvatarUrl())
110+
.setTimestamp(message.getTimeCreated())
111+
.build();
112+
}
113+
}

application/src/main/java/org/togetherjava/tjbot/features/system/BotCore.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
1414
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
1515
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
16+
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
1617
import net.dv8tion.jda.api.hooks.ListenerAdapter;
1718
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
1819
import net.dv8tion.jda.api.interactions.components.ComponentInteraction;
@@ -223,6 +224,14 @@ public void onMessageDelete(final MessageDeleteEvent event) {
223224
}
224225
}
225226

227+
@Override
228+
public void onMessageReactionAdd(final MessageReactionAddEvent event) {
229+
if (event.isFromGuild()) {
230+
getMessageReceiversSubscribedTo(event.getChannel())
231+
.forEach(messageReceiver -> messageReceiver.onMessageReactionAdd(event));
232+
}
233+
}
234+
226235
private Stream<MessageReceiver> getMessageReceiversSubscribedTo(Channel channel) {
227236
String channelName = channel.getName();
228237
return channelNameToMessageReceiver.entrySet()

0 commit comments

Comments
 (0)