Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Starboard support #881

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions application/config.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
"special": [
]
},
"starboard": {
"emojiNames" : ["⭐"],
"channelPattern": "starboard"
},
"selectRolesChannelPattern": "select-your-roles",
"rssConfig": {
"feeds": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ public final class Config {
private final String openaiApiKey;
private final String sourceCodeBaseUrl;
private final JShellConfig jshell;
private final StarboardConfig starboard;
private final FeatureBlacklistConfig featureBlacklistConfig;
private final RSSFeedsConfig rssFeedsConfig;
private final String selectRolesChannelPattern;
private final String memberCountCategoryPattern;


@SuppressWarnings("ConstructorWithTooManyParameters")
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
private Config(@JsonProperty(value = "token", required = true) String token,
Expand Down Expand Up @@ -94,7 +96,8 @@ private Config(@JsonProperty(value = "token", required = true) String token,
required = true) FeatureBlacklistConfig featureBlacklistConfig,
@JsonProperty(value = "rssConfig", required = true) RSSFeedsConfig rssFeedsConfig,
@JsonProperty(value = "selectRolesChannelPattern",
required = true) String selectRolesChannelPattern) {
required = true) String selectRolesChannelPattern,
@JsonProperty(value = "starboard", required = true) StarboardConfig starboard) {
this.token = Objects.requireNonNull(token);
this.githubApiKey = Objects.requireNonNull(githubApiKey);
this.databasePath = Objects.requireNonNull(databasePath);
Expand Down Expand Up @@ -127,6 +130,7 @@ private Config(@JsonProperty(value = "token", required = true) String token,
this.featureBlacklistConfig = Objects.requireNonNull(featureBlacklistConfig);
this.rssFeedsConfig = Objects.requireNonNull(rssFeedsConfig);
this.selectRolesChannelPattern = Objects.requireNonNull(selectRolesChannelPattern);
this.starboard = Objects.requireNonNull(starboard);
}

/**
Expand Down Expand Up @@ -391,6 +395,17 @@ public FeatureBlacklistConfig getFeatureBlacklistConfig() {
return featureBlacklistConfig;
}

/**
* Gets the config for the Starboard. The starboard displays certain messages in a special
* emojis{@link StarboardConfig#emojiNames()} channel {@link StarboardConfig#channelPattern()}
* if a user reacts with one of the recognized
*
* @return the config of the Starboard
*/
public StarboardConfig getStarboard() {
return starboard;
}

/**
* Gets the REGEX pattern used to identify the channel in which users can select their helper
* roles.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.togetherjava.tjbot.config;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonRootName;

import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;

/**
* Starboard Config
*
* @param emojiNames the List of emojis which are recognized by the starboard
* @param channelPattern the pattern of the channel with the starboard
*/
@JsonRootName("starboard")
public record StarboardConfig(List<String> emojiNames, Pattern channelPattern) {
/**
* Creates a Starboard config.
*
* @param emojiNames the List of emojis which are recognized by the starboard
* @param channelPattern the pattern of the channel with the starboard
*/
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public StarboardConfig {
Objects.requireNonNull(emojiNames);
Objects.requireNonNull(channelPattern);
}

/**
* 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
* {@link #channelPattern()}.
* <p>
* Empty to deactivate the feature.
*
* @return The List of emojis recognized by the starboard
*/
@Override
public List<String> emojiNames() {
return emojiNames;
}

/**
* Gets the pattern of the channel with the starboard. Deactivate by using a non-existent
* channel name.
*
* @return the pattern of the channel with the starboard
*/
public Pattern channelPattern() {
return channelPattern;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.togetherjava.tjbot.features.basic.PingCommand;
import org.togetherjava.tjbot.features.basic.RoleSelectCommand;
import org.togetherjava.tjbot.features.basic.SlashCommandEducator;
import org.togetherjava.tjbot.features.basic.Starboard;
import org.togetherjava.tjbot.features.basic.SuggestionsUpDownVoter;
import org.togetherjava.tjbot.features.bookmarks.BookmarksCommand;
import org.togetherjava.tjbot.features.bookmarks.BookmarksSystem;
Expand Down Expand Up @@ -131,6 +132,7 @@ public static Collection<Feature> 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, database));

// Message context commands
features.add(new TransferQuestionCommand(config));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.togetherjava.tjbot.features.basic;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.*;
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.events.message.react.MessageReactionRemoveEvent;
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.db.Database;
import org.togetherjava.tjbot.features.EventReceiver;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static org.togetherjava.tjbot.db.generated.tables.StarboardMessages.STARBOARD_MESSAGES;

public class Starboard extends ListenerAdapter implements EventReceiver {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing javadoc, class should be final


private static final Logger logger = LoggerFactory.getLogger(Starboard.class);
private final StarboardConfig config;
private final Database database;

private final Cache<Long, Object> messageCache;

public Starboard(Config config, Database database) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing javadoc

this.config = config.getStarboard();
this.database = database;
this.messageCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterAccess(24, TimeUnit.HOURS) // TODO make these constants
.build();
}

@Override
public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) {
String emojiName = event.getEmoji().getName();
Guild guild = event.getGuild();
long messageId = event.getMessageIdLong();
if (shouldIgnoreMessage(emojiName, guild, event.getGuildChannel(), messageId, true)) {
return;
}
Optional<TextChannel> starboardChannel = getStarboardChannel(guild);
if (starboardChannel.isEmpty()) {
logger.warn("There is no channel for the starboard in the guild with the name {}",
config.channelPattern());
return;
}
database.write(context -> context.newRecord(STARBOARD_MESSAGES).setMessageId(messageId));
messageCache.put(messageId, new Object());
event.retrieveMessage()
.flatMap(
message -> starboardChannel.orElseThrow().sendMessageEmbeds(formEmbed(message)))
.queue();
}

@Override
public void onMessageReactionRemove(@NotNull MessageReactionRemoveEvent event) {
String emojiName = event.getEmoji().getName();
Guild guild = event.getGuild();
long messageId = event.getMessageIdLong();
if (shouldIgnoreMessage(emojiName, guild, event.getGuildChannel(), messageId, false)) {
return;
}
event.retrieveMessage()
.map(m -> m.getReactions()
.stream()
.map(reaction -> reaction.getEmoji().getName())
.noneMatch(config.emojiNames()::contains))
.onSuccess(noGoodReactions -> {
if (noGoodReactions) {
database.write(context -> context.data().remove(messageId));
messageCache.invalidate(messageId);
}
})
.queue();
}

private boolean shouldIgnoreMessage(String emojiName, Guild guild, GuildChannel channel,
long messageId, boolean addingMessage) {
return !config.emojiNames().contains(emojiName)
|| !guild.getPublicRole().hasPermission(channel, Permission.VIEW_CHANNEL)
|| (addingMessage == (messageCache.getIfPresent(messageId) != null || database
.read(context -> context.fetchExists(context.selectFrom(STARBOARD_MESSAGES)
.where(STARBOARD_MESSAGES.MESSAGE_ID.eq(messageId))))));
}

private Optional<TextChannel> getStarboardChannel(Guild guild) {
return guild.getTextChannels()
.stream()
.filter(channel -> config.channelPattern().matcher(channel.getName()).find())
.findFirst();
}

private static MessageEmbed formEmbed(Message message) {
User author = message.getAuthor();
return new EmbedBuilder().setAuthor(author.getName(), null, author.getAvatarUrl())
.setDescription(message.getContentDisplay())
.appendDescription("%n [Link](%s)".formatted(message.getJumpUrl()))
.build();
}
}
4 changes: 4 additions & 0 deletions application/src/main/resources/db/V15__Add_Starboard.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE starboard_messages
(
message_id BIGINT NOT NULL PRIMARY KEY
)
Loading