diff --git a/application/config.json.template b/application/config.json.template index 84ac087cb0..d05574f1a4 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -87,6 +87,10 @@ "wsh" ], "logInfoChannelWebhook": "", - "logErrorChannelWebhook": "" + "logErrorChannelWebhook": "", + "starboard": { + "emojiNames" : ["star"], + "channelName": "starboard" + } "openaiApiKey": "" } 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 e5933ca262..05f6f5b5ba 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -38,6 +38,8 @@ public final class Config { private final String logErrorChannelWebhook; private final String openaiApiKey; + private final StarboardConfig starboard; + @SuppressWarnings("ConstructorWithTooManyParameters") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) private Config(@JsonProperty(value = "token", required = true) String token, @@ -72,7 +74,8 @@ private Config(@JsonProperty(value = "token", required = true) String token, required = true) String logInfoChannelWebhook, @JsonProperty(value = "logErrorChannelWebhook", required = true) String logErrorChannelWebhook, - @JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey) { + @JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey, + @JsonProperty(value = "starboard", required = true) StarboardConfig starboard) { this.token = Objects.requireNonNull(token); this.gistApiKey = Objects.requireNonNull(gistApiKey); this.databasePath = Objects.requireNonNull(databasePath); @@ -96,6 +99,7 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.logInfoChannelWebhook = Objects.requireNonNull(logInfoChannelWebhook); this.logErrorChannelWebhook = Objects.requireNonNull(logErrorChannelWebhook); this.openaiApiKey = Objects.requireNonNull(openaiApiKey); + this.starboard = Objects.requireNonNull(starboard); } /** @@ -316,4 +320,14 @@ public String getLogErrorChannelWebhook() { public String getOpenaiApiKey() { return openaiApiKey; } + + /** + * Gets the config for the Starboard Contains the List of emoji names recognized by the + * starboard as well as the name of the channel with the starboard. + * + * @return the config of the Starboard + */ + public StarboardConfig getStarboard() { + return starboard; + } } diff --git a/application/src/main/java/org/togetherjava/tjbot/config/StarboardConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/StarboardConfig.java new file mode 100644 index 0000000000..82568a7674 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/config/StarboardConfig.java @@ -0,0 +1,44 @@ +package org.togetherjava.tjbot.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import java.util.List; +import java.util.Objects; + +@JsonRootName("starboard") +public final class StarboardConfig { + private final List emojiNames; + private final String channelName; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public StarboardConfig( + @JsonProperty(value = "emojiNames", required = true) List emojiNames, + @JsonProperty(value = "channelName", required = true) String channelName) { + this.emojiNames = Objects.requireNonNull(emojiNames); + this.channelName = Objects.requireNonNull(channelName); + } + + /** + * Gets the list of emotes that are recognized by the starboard feature. A message that is + * reacted on with an emote in this list will be reposted in a special channel. + * + * Empty to deactivate the feature. + * + * @return The List of emojis recognized by the starboard + */ + public List getEmojiNames() { + return emojiNames; + } + + /** + * Gets the name of the channel with the starboard Deactivate by using a non-existent channel + * name + * + * @return the name of the channel with the starboard + */ + public String getChannelName() { + return channelName; + } +} 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 7f1f3af046..ece3020bc8 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -111,6 +111,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new GuildLeaveCloseThreadListener(config)); features.add(new LeftoverBookmarksListener(bookmarksSystem)); features.add(new HelpThreadCreatedListener(helpSystemHelper)); + features.add(new Starboard(config)); // Message context commands diff --git a/application/src/main/java/org/togetherjava/tjbot/features/basic/Starboard.java b/application/src/main/java/org/togetherjava/tjbot/features/basic/Starboard.java new file mode 100644 index 0000000000..f13f3ad253 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/basic/Starboard.java @@ -0,0 +1,66 @@ +package org.togetherjava.tjbot.features.basic; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.togetherjava.tjbot.config.Config; +import org.togetherjava.tjbot.config.StarboardConfig; +import org.togetherjava.tjbot.features.EventReceiver; + +import java.util.Optional; + +public class Starboard extends ListenerAdapter implements EventReceiver { + + private static final Logger logger = LoggerFactory.getLogger(Starboard.class); + private final StarboardConfig config; + + public Starboard(Config config) { + this.config = config.getStarboard(); + } + + @Override + public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) { + String emojiName = event.getEmoji().asCustom().getName(); + Guild guild = event.getGuild(); + if (ignoreMessage(emojiName, guild, event.getGuildChannel())) { + return; + } + Optional starboardChannel = getStarboardChannel(guild); + if (starboardChannel.isEmpty()) { + logger.warn("There is no channel for the starboard in the guild with the name {}", + config.getChannelName()); + return; + } + event.retrieveMessage() + .flatMap( + message -> starboardChannel.orElseThrow().sendMessageEmbeds(formEmbed(message))) + .queue(); + } + + private boolean ignoreMessage(String emojiName, Guild guild, GuildChannel channel) { + return !config.getEmojiNames().contains(emojiName) + || !guild.getPublicRole().hasPermission(channel, Permission.VIEW_CHANNEL); + } + + private Optional getStarboardChannel(Guild guild) { + return guild.getTextChannelsByName(config.getChannelName(), false).stream().findFirst(); + } + + private static MessageEmbed formEmbed(Message message) { + User author = message.getAuthor(); + return new EmbedBuilder().setAuthor(author.getName(), null, author.getAvatarUrl()) + .setDescription(message.getContentDisplay()) + .build(); // TODO make footer with link and reacted emojis + } +}