added starboard functionality (starboard/starstats)

added module to store/retrieve configuration (double and string values)
replaced a few null values with optionals (emote loading)
fixed creating channels on startup
added delete message/get emote utility to bot service
extended emote service to have utility methods to use emotes
added reactions to message cache
added empty message handling to  post target service, in case the template evaluates to empty
added ability to edit a message in a post target (standard message and embed)
added principle of config listeners, so default config can be created, for example starboard thresholds
added abstract reaction listeners for adding/removing/clearing
fixed foreign keys between channel and server
added emote utils to handle AEmote and Emotes
renamed emotes to be camelCase, so they are easier to type
This commit is contained in:
Sheldan
2020-04-01 14:16:04 +02:00
parent 03e81a025b
commit 089862bf15
72 changed files with 1795 additions and 121 deletions

View File

@@ -0,0 +1,51 @@
package dev.sheldan.abstracto.utility.command;
import dev.sheldan.abstracto.command.Command;
import dev.sheldan.abstracto.command.HelpInfo;
import dev.sheldan.abstracto.command.execution.CommandConfiguration;
import dev.sheldan.abstracto.command.execution.CommandContext;
import dev.sheldan.abstracto.command.execution.Parameter;
import dev.sheldan.abstracto.command.execution.Result;
import dev.sheldan.abstracto.core.models.embed.MessageToSend;
import dev.sheldan.abstracto.templating.TemplateService;
import dev.sheldan.abstracto.utility.Utility;
import dev.sheldan.abstracto.utility.models.template.starboard.StarStatsModel;
import dev.sheldan.abstracto.utility.service.StarboardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class StarStats implements Command {
public static final String STARSTATS_RESPONSE_TEMPLATE = "starStats_response";
@Autowired
private StarboardService starboardService;
@Autowired
private TemplateService templateService;
@Override
public Result execute(CommandContext commandContext) {
StarStatsModel result = starboardService.retrieveStarStats(commandContext.getGuild().getIdLong());
MessageToSend messageToSend = templateService.renderEmbedTemplate(STARSTATS_RESPONSE_TEMPLATE, result);
commandContext.getChannel().sendMessage(messageToSend.getEmbed()).queue();
return Result.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("starStats")
.module(Utility.UTILITY)
.templated(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.utility.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "abstracto.starboard")
public class StarboardConfig {
private List<Integer> lvl = new ArrayList<>();
private List<String> badge = new ArrayList<>();
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.abstracto.utility.config;
import dev.sheldan.abstracto.core.listener.ServerConfigListener;
import dev.sheldan.abstracto.core.management.ServerManagementService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.management.ConfigManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Slf4j
public class StarboardConfigListener implements ServerConfigListener {
@Autowired
private StarboardConfig starboardConfig;
@Autowired
private ConfigManagementService configManagementService;
@Override
public void updateServerConfig(AServer server) {
for (int i = 0; i < starboardConfig.getLvl().size(); i++) {
Integer value = starboardConfig.getLvl().get(i);
configManagementService.createIfNotExists(server.getId(), "starLvl" + ( i + 1 ), Double.valueOf(value));
}
}
}

View File

@@ -0,0 +1,142 @@
package dev.sheldan.abstracto.utility.listener;
import dev.sheldan.abstracto.core.listener.ReactedAddedListener;
import dev.sheldan.abstracto.core.listener.ReactedRemovedListener;
import dev.sheldan.abstracto.core.management.EmoteManagementService;
import dev.sheldan.abstracto.core.management.UserManagementService;
import dev.sheldan.abstracto.core.models.CachedMessage;
import dev.sheldan.abstracto.core.models.CachedReaction;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.Bot;
import dev.sheldan.abstracto.core.management.ConfigManagementService;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.utils.EmoteUtils;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import dev.sheldan.abstracto.utility.service.StarboardService;
import dev.sheldan.abstracto.utility.service.management.StarboardPostManagementService;
import dev.sheldan.abstracto.utility.service.management.StarboardPostReactorManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Emote;
import net.dv8tion.jda.api.entities.MessageReaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Component
@Slf4j
public class StarboardListener implements ReactedAddedListener, ReactedRemovedListener {
public static final String STAR_EMOTE = "STAR";
@Autowired
private EmoteManagementService emoteManagementService;
@Autowired
private Bot bot;
@Autowired
private MessageCache messageCache;
@Autowired
private ConfigManagementService configManagementService;
@Autowired
private StarboardService starboardService;
@Autowired
private StarboardPostManagementService starboardPostManagementService;
@Autowired
private StarboardPostReactorManagementService starboardPostReactorManagementService;
@Autowired
private UserManagementService userManagementService;
@Override
@Transactional
public void executeReactionAdded(CachedMessage message, MessageReaction addedReaction, AUserInAServer userAdding) {
if(userAdding.getUserReference().getId().equals(message.getAuthorId())) {
return;
}
Long guildId = message.getServerId();
Optional<AEmote> aEmote = emoteManagementService.loadEmoteByName(STAR_EMOTE, guildId);
if(aEmote.isPresent()) {
AEmote emote = aEmote.get();
MessageReaction.ReactionEmote reactionEmote = addedReaction.getReactionEmote();
Emote emoteInGuild = bot.getEmote(guildId, emote);
if(EmoteUtils.isReactionEmoteAEmote(reactionEmote, emote, Optional.ofNullable(emoteInGuild))) {
Optional<CachedReaction> reactionOptional = EmoteUtils.getReactionFromMessageByEmote(message, emote);
updateStarboardPost(message, reactionOptional.orElse(null), userAdding, true);
}
} else {
log.warn("Emote {} is not defined for guild {}. Starboard not functional.", STAR_EMOTE, guildId);
}
}
private void updateStarboardPost(CachedMessage message, CachedReaction reaction, AUserInAServer userReacting, boolean adding) {
Optional<StarboardPost> starboardPostOptional = starboardPostManagementService.findByMessageId(message.getMessageId());
if(reaction != null) {
List<AUser> userExceptAuthor = getUsersExcept(reaction.getUsers(), message.getAuthorId());
Double starMinimum = getFromConfig("starLvl1", message.getServerId());
if (userExceptAuthor.size() >= starMinimum) {
AUserInAServer author = userManagementService.loadUser(message.getServerId(), message.getAuthorId());
if(starboardPostOptional.isPresent()) {
StarboardPost starboardPost = starboardPostOptional.get();
starboardService.updateStarboardPost(starboardPost, message, userExceptAuthor);
if(adding) {
starboardPostReactorManagementService.addReactor(starboardPost, userReacting.getUserReference());
} else {
starboardPostReactorManagementService.removeReactor(starboardPost, userReacting.getUserReference());
}
} else {
starboardService.createStarboardPost(message, userExceptAuthor, userReacting, author);
}
} else {
starboardPostOptional.ifPresent(starboardPost -> {
starboardService.removeStarboardPost(starboardPost);
starboardPostReactorManagementService.removeReactors(starboardPost);
});
}
} else {
starboardPostOptional.ifPresent(starboardPost -> {
starboardService.removeStarboardPost(starboardPost);
starboardPostReactorManagementService.removeReactors(starboardPost);
});
}
}
@Override
@Transactional
public void executeReactionRemoved(CachedMessage message, MessageReaction removedReaction, AUserInAServer userRemoving) {
if(message.getAuthorId().equals(userRemoving.getUserReference().getId())) {
return;
}
Long guildId = message.getServerId();
Optional<AEmote> aEmote = emoteManagementService.loadEmoteByName(STAR_EMOTE, guildId);
if(aEmote.isPresent()) {
AEmote emote = aEmote.get();
MessageReaction.ReactionEmote reactionEmote = removedReaction.getReactionEmote();
Emote emoteInGuild = bot.getEmote(guildId, emote);
if(EmoteUtils.isReactionEmoteAEmote(reactionEmote, emote, Optional.ofNullable(emoteInGuild))) {
Optional<CachedReaction> reactionOptional = EmoteUtils.getReactionFromMessageByEmote(message, emote);
updateStarboardPost(message, reactionOptional.orElse(null), userRemoving, false);
}
} else {
log.warn("Emote {} is not defined for guild {}. Starboard not functional.", STAR_EMOTE, guildId);
}
}
private Double getFromConfig(String key, Long guildId) {
return configManagementService.loadConfig(guildId, key).getDoubleValue();
}
private List<AUser> getUsersExcept(List<AUser> users, Long userId) {
return users.stream().filter(user -> !user.getId().equals(userId)).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,6 @@
package dev.sheldan.abstracto.utility.repository;
public interface StarStatsUserResult {
Long getUserId();
Integer getStarCount();
}

View File

@@ -0,0 +1,42 @@
package dev.sheldan.abstracto.utility.repository;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import dev.sheldan.abstracto.utility.models.StarboardPostReaction;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface StarboardPostReactionRepository extends JpaRepository<StarboardPostReaction, Long> {
void deleteByReactorAndStarboardPost(AUser user, StarboardPost post);
void deleteByStarboardPost(StarboardPost post);
@Query(value = "SELECT r.reactor_id as userId, COUNT(*) AS starCount \n" +
"FROM starboard_post_reaction r \n" +
"INNER JOIN starboard_post p ON p.id = r.post_id\n" +
"INNER JOIN channel c ON c.id = p.channel_id\n" +
"WHERE c.server_id = :serverId\n" +
"GROUP BY r.reactor_id \n" +
"ORDER BY starCount DESC \n" +
"LIMIT :count", nativeQuery = true)
List<StarStatsUserResult> findTopStarGiverInServer(Long serverId, Integer count);
@Query(value = "SELECT COUNT(*) \n" +
"FROM starboard_post_reaction r \n" +
"INNER JOIN starboard_post p ON p.id = r.post_id\n" +
"INNER JOIN channel c ON c.id = p.channel_id\n" +
"WHERE c.server_id = :serverId\n"
, nativeQuery = true)
Integer getReactionCountByServer(Long serverId);
@Query(value = "SELECT p.poster as userId, COUNT(*) AS starCount \n" +
"FROM starboard_post_reaction r \n" +
"INNER JOIN starboard_post p ON p.id = r.post_id\n" +
"INNER JOIN channel c ON c.id = p.channel_id\n" +
"WHERE c.server_id = :serverId\n" +
"GROUP BY p.poster \n" +
"ORDER BY starCount DESC \n" +
"LIMIT :count", nativeQuery = true)
List<StarStatsUserResult> retrieveTopStarReceiverInServer(Long serverId, Integer count);
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.utility.repository;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface StarboardPostRepository extends JpaRepository<StarboardPost, Long> {
StarboardPost findByPostMessageId(Long messageId);
List<StarboardPost> findByStarboardChannelServerId(Long serverId);
}

View File

@@ -0,0 +1,36 @@
package dev.sheldan.abstracto.utility.repository.converter;
import dev.sheldan.abstracto.core.management.UserManagementService;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.service.Bot;
import dev.sheldan.abstracto.utility.models.template.starboard.StarStatsUser;
import dev.sheldan.abstracto.utility.repository.StarStatsUserResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class StarStatsUserConverter {
@Autowired
private UserManagementService userManagementService;
@Autowired
private Bot bot;
public List<StarStatsUser> convertToStarStatsUser(List<StarStatsUserResult> users, Long serverId) {
List<StarStatsUser> result = new ArrayList<>();
users.forEach(starStatsUserResult -> {
StarStatsUser newUser = StarStatsUser
.builder()
.starCount(starStatsUserResult.getStarCount())
.member(bot.getMemberInServer(serverId, starStatsUserResult.getUserId()))
.user(AUser.builder().id(starStatsUserResult.getUserId()).build())
.build();
result.add(newUser);
});
return result;
}
}

View File

@@ -17,6 +17,7 @@ import dev.sheldan.abstracto.utility.service.management.ReminderManagementServic
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobDataMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -66,7 +67,12 @@ public class RemindServiceBean implements ReminderService {
Reminder reminder = reminderManagementService.createReminder(aServerAChannelAUser, remindText, remindAt, reminderModel.getMessage().getIdLong());
reminderModel.setReminder(reminder);
MessageToSend message = templateService.renderEmbedTemplate(REMINDER_EMBED_KEY, reminderModel);
reminderModel.getTextChannel().sendMessage(message.getMessage()).embed(message.getEmbed()).queue();
String messageText = message.getMessage();
if(StringUtils.isBlank(messageText)) {
reminderModel.getTextChannel().sendMessage(message.getEmbed()).queue();
} else {
reminderModel.getTextChannel().sendMessage(messageText).embed(message.getEmbed()).queue();
}
if(remindIn.getSeconds() < 60) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

View File

@@ -0,0 +1,181 @@
package dev.sheldan.abstracto.utility.service;
import dev.sheldan.abstracto.core.management.EmoteManagementService;
import dev.sheldan.abstracto.core.management.PostTargetManagement;
import dev.sheldan.abstracto.core.management.UserManagementService;
import dev.sheldan.abstracto.core.models.AServerChannelMessage;
import dev.sheldan.abstracto.core.models.CachedMessage;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.models.embed.MessageToSend;
import dev.sheldan.abstracto.core.service.Bot;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.EmoteService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.templating.TemplateService;
import dev.sheldan.abstracto.utility.config.StarboardConfig;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import dev.sheldan.abstracto.utility.models.template.starboard.StarStatsModel;
import dev.sheldan.abstracto.utility.models.template.starboard.StarStatsPost;
import dev.sheldan.abstracto.utility.models.template.starboard.StarStatsUser;
import dev.sheldan.abstracto.utility.models.template.starboard.StarboardPostModel;
import dev.sheldan.abstracto.utility.service.management.StarboardPostManagementService;
import dev.sheldan.abstracto.utility.service.management.StarboardPostReactorManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
public class StarboardServiceBean implements StarboardService {
@Autowired
private Bot bot;
@Autowired
private PostTargetService postTargetService;
@Autowired
private TemplateService templateService;
@Autowired
private EmoteManagementService emoteManagementService;
@Autowired
private ConfigService configService;
@Autowired
private StarboardPostManagementService starboardPostManagementService;
@Autowired
private StarboardConfig starboardConfig;
@Autowired
private UserManagementService userManagementService;
@Autowired
private StarboardPostReactorManagementService starboardPostReactorManagementService;
@Autowired
private PostTargetManagement postTargetManagement;
@Autowired
private EmoteService emoteService;
@Override
public void createStarboardPost(CachedMessage message, List<AUser> userExceptAuthor, AUserInAServer userReacting, AUserInAServer starredUser) {
StarboardPostModel starboardPostModel = buildStarboardPostModel(message, userExceptAuthor.size());
MessageToSend messageToSend = templateService.renderEmbedTemplate("starboard_post", starboardPostModel);
PostTarget starboard = postTargetManagement.getPostTarget("starboard", message.getServerId());
postTargetService.sendEmbedInPostTarget(messageToSend, "starboard", message.getServerId()).thenAccept(message1 -> {
AServerChannelMessage aServerChannelMessage = AServerChannelMessage
.builder()
.messageId(message1.getIdLong())
.channel(starboard.getChannelReference())
.server(userReacting.getServerReference())
.build();
StarboardPost starboardPost = starboardPostManagementService.createStarboardPost(message, starredUser, userReacting, aServerChannelMessage);
// TODO maybe in bulk, but numbers should be small enough
userExceptAuthor.forEach(user -> {
starboardPostReactorManagementService.addReactor(starboardPost, user);
});
});
}
private StarboardPostModel buildStarboardPostModel(CachedMessage message, Integer starCount) {
Member member = bot.getMemberInServer(message.getServerId(), message.getAuthorId());
TextChannel channel = bot.getTextChannelFromServer(message.getServerId(), message.getChannelId());
Guild guild = bot.getGuildById(message.getServerId());
AChannel aChannel = AChannel.builder().id(message.getChannelId()).build();
AUser user = AUser.builder().id(message.getAuthorId()).build();
AServer server = AServer.builder().id(message.getServerId()).build();
Optional<AEmote> appropriateEmoteOptional = getAppropriateEmote(message.getServerId(), starCount);
String emoteText;
if(appropriateEmoteOptional.isPresent()) {
AEmote emote = appropriateEmoteOptional.get();
emoteText = emoteService.getEmoteAsMention(emote, message.getServerId(), "");
} else {
log.warn("No emote defined to be used for starboard post. Falling back to default.");
emoteText = "";
}
return StarboardPostModel
.builder()
.message(message)
.author(member)
.channel(channel)
.aChannel(aChannel)
.starCount(starCount)
.guild(guild)
.user(user)
.server(server)
.starLevelEmote(emoteText)
.build();
}
@Override
public void updateStarboardPost(StarboardPost post, CachedMessage message, List<AUser> userExceptAuthor) {
StarboardPostModel starboardPostModel = buildStarboardPostModel(message, userExceptAuthor.size());
MessageToSend messageToSend = templateService.renderEmbedTemplate("starboard_post", starboardPostModel);
CompletableFuture<Message> future = new CompletableFuture<>();
postTargetService.editOrCreatedInPostTarget(post.getStarboardMessageId(), messageToSend, "starboard", message.getServerId(), future);
future.thenAccept(newOrOldMessage -> {
starboardPostManagementService.setStarboardPostMessageId(post, newOrOldMessage.getIdLong());
});
}
@Override
public void removeStarboardPost(StarboardPost message) {
AChannel starboardChannel = message.getStarboardChannel();
bot.deleteMessage(starboardChannel.getServer().getId(), starboardChannel.getId(), message.getStarboardMessageId());
}
@Override
public StarStatsModel retrieveStarStats(Long serverId) {
int count = 3;
List<StarboardPost> starboardPosts = starboardPostManagementService.retrieveTopPosts(serverId, count);
List<StarStatsUser> topStarGivers = starboardPostReactorManagementService.retrieveTopStarGiver(serverId, count);
List<StarStatsPost> starStatsPosts = starboardPosts.stream().map(StarStatsPost::fromStarboardPost).collect(Collectors.toList());
List<StarStatsUser> topStarReceiver = starboardPostReactorManagementService.retrieveTopStarReceiver(serverId, count);
Integer postCount = starboardPostManagementService.getPostCount(serverId);
Integer reactionCount = starboardPostReactorManagementService.getStarCount(serverId);
List<String> emotes = new ArrayList<>();
for (int i = 1; i < count + 1; i++) {
Optional<AEmote> starboardRankingEmote = getStarboardRankingEmote(serverId, i);
AEmote emote = starboardRankingEmote.orElse(null);
String defaultEmoji = starboardConfig.getBadge().get(i - 1);
emotes.add(emoteService.getEmoteAsMention(emote, serverId, defaultEmoji));
}
return StarStatsModel
.builder()
.badgeEmotes(emotes)
.starGiver(topStarGivers)
.starReceiver(topStarReceiver)
.topPosts(starStatsPosts)
.starredMessages(postCount)
.totalStars(reactionCount)
.build();
}
private Optional<AEmote> getStarboardRankingEmote(Long serverId, Integer position) {
return emoteManagementService.loadEmoteByName("starboardBadge" + position, serverId);
}
private Optional<AEmote> getAppropriateEmote(Long serverId, Integer starCount) {
for(int i = starboardConfig.getLvl().size(); i > 0; i--) {
Double starMinimum = configService.getDoubleValue("starLvl" + i, serverId);
if(starCount >= starMinimum) {
return emoteManagementService.loadEmoteByName("star" + i, serverId);
}
}
return emoteManagementService.loadEmoteByName("star0", serverId);
}
}

View File

@@ -27,8 +27,8 @@ import java.util.Optional;
public class SuggestionServiceBean implements SuggestionService {
public static final String SUGGESTION_LOG_TEMPLATE = "suggest_log";
private static final String SUGGESTION_YES_EMOTE = "SUGGESTION_YES";
private static final String SUGGESTION_NO_EMOTE = "SUGGESTION_NO";
private static final String SUGGESTION_YES_EMOTE = "suggestionYes";
private static final String SUGGESTION_NO_EMOTE = "suggestionNo";
public static final String SUGGESTIONS_TARGET = "suggestions";
@Autowired
private SuggestionManagementService suggestionManagementService;

View File

@@ -0,0 +1,72 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.AServerChannelMessage;
import dev.sheldan.abstracto.core.models.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import dev.sheldan.abstracto.utility.repository.StarboardPostRepository;
import dev.sheldan.abstracto.utility.repository.converter.StarStatsUserConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
@Component
public class StarboardPostManagementServiceBean implements StarboardPostManagementService {
@Autowired
private StarboardPostRepository repository;
@Autowired
private StarStatsUserConverter converter;
@Override
public StarboardPost createStarboardPost(CachedMessage starredMessage, AUserInAServer starredUser, AUserInAServer starringUser, AServerChannelMessage starboardPost) {
StarboardPost post = StarboardPost
.builder()
.author(starredUser.getUserReference())
.deleted(false)
.postMessageId(starredMessage.getMessageId())
.starboardMessageId(starboardPost.getMessageId())
.starboardChannel(starboardPost.getChannel())
.starredDate(Instant.now())
.build();
repository.save(post);
return post;
}
@Override
public void setStarboardPostMessageId(StarboardPost post, Long messageId) {
post.setStarboardMessageId(messageId);
repository.save(post);
}
@Override
public List<StarboardPost> retrieveTopPosts(Long serverId, Integer count) {
List<StarboardPost> posts = retrieveAllPosts(serverId);
posts.sort(Comparator.comparingInt(o -> o.getReactions().size()));
Collections.reverse(posts);
return posts.subList(0, Math.min(count, posts.size()));
}
@Override
public List<StarboardPost> retrieveAllPosts(Long serverId) {
return repository.findByStarboardChannelServerId(serverId);
}
@Override
public Integer getPostCount(Long serverId) {
return retrieveAllPosts(serverId).size();
}
@Override
public Optional<StarboardPost> findByMessageId(Long messageId) {
return Optional.ofNullable(repository.findByPostMessageId(messageId));
}
}

View File

@@ -0,0 +1,63 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import dev.sheldan.abstracto.utility.models.StarboardPostReaction;
import dev.sheldan.abstracto.utility.models.template.starboard.StarStatsUser;
import dev.sheldan.abstracto.utility.repository.StarStatsUserResult;
import dev.sheldan.abstracto.utility.repository.StarboardPostReactionRepository;
import dev.sheldan.abstracto.utility.repository.converter.StarStatsUserConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@Component
public class StarboardPostReactorManagementServiceBean implements StarboardPostReactorManagementService {
@Autowired
private StarboardPostReactionRepository repository;
@Autowired
private StarStatsUserConverter converter;
@Override
public void addReactor(StarboardPost post, AUser user) {
StarboardPostReaction reactor = StarboardPostReaction
.builder()
.starboardPost(post)
.reactor(user)
.build();
repository.save(reactor);
}
@Override
public void removeReactor(StarboardPost post, AUser user) {
repository.deleteByReactorAndStarboardPost(user, post);
}
@Override
public void removeReactors(StarboardPost post) {
repository.deleteByStarboardPost(post);
}
@Override
public Integer getStarCount(Long serverId) {
return repository.getReactionCountByServer(serverId);
}
@Override
public List<StarStatsUser> retrieveTopStarGiver(Long serverId, Integer count) {
List<StarStatsUserResult> starGivers = repository.findTopStarGiverInServer(serverId, count);
return converter.convertToStarStatsUser(starGivers, serverId);
}
@Override
public List<StarStatsUser> retrieveTopStarReceiver(Long serverId, Integer count) {
List<StarStatsUserResult> starReceivers = repository.retrieveTopStarReceiverInServer(serverId, count);
return converter.convertToStarStatsUser(starReceivers, serverId);
}
}

View File

@@ -0,0 +1,51 @@
{
"title": {
"title": "Server starboard stats"
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
"description": "${starredMessages} starred messages with ${totalStars} stars in total",
"fields": [
{
"name": "Top starred posts",
"value": "
<#list topPosts as post>
${badgeEmotes[post?index]} - ${post.starCount} :star: [Jump!](${post.messageUrl})
<#else>
No starred messages.
</#list>
"
},
{
"name": "Top starrer",
"value": "
<#list starGiver as starrer>
<#if starrer.member?has_content>
${badgeEmotes[starrer?index]} - ${starrer.starCount} :star: ${starrer.member.asMention}
<#else>
${badgeEmotes[starrer?index]} - ${starrer.starCount} :star: ${starrer.user.id?c} (Left the guild)
</#if>
<#else>
No starred messages.
</#list>
"
},
{
"name": "Top star receiver",
"value": "
<#list starReceiver as starred>
<#if starred.member?has_content>
${badgeEmotes[starred?index]} - ${starred.starCount} :star: ${starred.member.asMention}
<#else>
${badgeEmotes[starred?index]} - ${starred.starCount} :star: ${starred.user.id?c} (Left the guild)
</#if>
<#else>
No starred messages.
</#list>
"
}
]
}

View File

@@ -0,0 +1,44 @@
{
"author": {
<#if author?has_content>
"name": "${author.effectiveName}",
"avatar": "${author.user.effectiveAvatarUrl}"
<#else>
"name": "${user.id?c} (Has left the server)"
</#if>
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
<#if message.content?has_content || message.embeds?size gt 0>
"description": "${message.content}
<#list message.embeds>
Embeds:
<#items as embed>
Description: ${embed.description} <#if embed.imageUrl?has_content> ImageUrl: ${embed.imageUrl} </#if>
</#items>
</#list>
",
</#if>
<#if channel?has_content>
"additionalMessage": "${starLevelEmote} ${starCount} ${channel.asMention} ID: ${message.messageId?c}",
<#else>
"additionalMessage": "${starLevelEmote} ${starCount} ${aChannel.id?c} ID: ${message.messageId?c}",
</#if>
<#if message.attachmentUrls?size gt 0>
"imageUrl": "${message.attachmentUrls[0]}",
</#if>
"fields": [
{
"name": "Original",
<#if channel?has_content>
"value": "[${channel.name}](${message.messageUrl})"
<#else>
"value": "[${aChannel.id?c}](${message.messageUrl})"
</#if>
}
],
"timeStamp": "${message.timeCreated}"
}

View File

@@ -1,8 +1,16 @@
abstracto.postTargets.utility=suggestions
abstracto.emoteNames.suggestion=SUGGESTION_YES,SUGGESTION_NO
abstracto.postTargets.utility=suggestions,starboard
abstracto.emoteNames.suggestion=suggestionYes,suggestionNo
abstracto.emoteNames.starboard=star,star1,star2,star3,star4,starboardBadge1,starboardBadge2,starboardBadge3
abstracto.starboard.lvl[0]=5
abstracto.starboard.lvl[1]=8
abstracto.starboard.lvl[2]=13
abstracto.starboard.lvl[3]=17
abstracto.starboard.badge[0]=\ud83e\udd47
abstracto.starboard.badge[1]=\ud83e\udd48
abstracto.starboard.badge[2]=\ud83e\udd49
abstracto.scheduling.jobs.reminderJob.name=reminderJob
abstracto.scheduling.jobs.reminderJob.group=utility
abstracto.scheduling.jobs.reminderJob.clazz=dev.sheldan.abstracto.utility.jobs.ReminderJob
abstracto.scheduling.jobs.reminderJob.standAlone=false
abstracto.scheduling.jobs.reminderJob.active=true
abstracto.scheduling.jobs.reminderJob.active=true

View File

@@ -13,7 +13,8 @@ import java.time.Instant;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter @Setter
@Getter
@Setter
public class Reminder {
@Id

View File

@@ -0,0 +1,63 @@
package dev.sheldan.abstracto.utility.models;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
import java.util.List;
@Entity
@Table(name="starboard_post")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class StarboardPost {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "poster")
private AUser author;
@Column
private Long starboardMessageId;
@Column
private Long postMessageId;
@ManyToOne
@JoinColumn(name = "channelId")
private AChannel starboardChannel;
@Transient
private Integer reactionCount;
@PostLoad
private void onLoad() {
this.reactionCount = this.reactions.size();
}
@Getter
@OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
@JoinColumn(name="postId")
private List<StarboardPostReaction> reactions;
@Column
private Instant starredDate;
@Column
private boolean deleted;
public int getReactionCount() {
if(this.reactions == null) {
return 0;
}
return this.reactions.size();
}
}

View File

@@ -0,0 +1,29 @@
package dev.sheldan.abstracto.utility.models;
import dev.sheldan.abstracto.core.models.database.AUser;
import lombok.*;
import javax.persistence.*;
@Entity
@Table(name="starboard_post_reaction")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class StarboardPostReaction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "reactorId")
private AUser reactor;
@ManyToOne
@JoinColumn(name = "postId")
private StarboardPost starboardPost;
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.utility.models.template.starboard;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class StarStatsModel {
private List<StarStatsPost> topPosts;
private List<StarStatsUser> starReceiver;
private List<StarStatsUser> starGiver;
private Integer totalStars;
private List<String> badgeEmotes;
private Integer starredMessages;
}

View File

@@ -0,0 +1,33 @@
package dev.sheldan.abstracto.utility.models.template.starboard;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.utils.MessageUtils;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class StarStatsPost {
private Long serverId;
private Long channelId;
private Long messageId;
private Integer starCount;
public String getMessageUrl() {
return MessageUtils.buildMessageUrl(serverId ,channelId, messageId);
}
public static StarStatsPost fromStarboardPost(StarboardPost starboardPost) {
AChannel channel = starboardPost.getStarboardChannel();
return StarStatsPost
.builder()
.serverId(channel.getServer().getId())
.channelId(channel.getId())
.messageId(starboardPost.getPostMessageId())
.starCount(starboardPost.getReactions().size())
.build();
}
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.utility.models.template.starboard;
import dev.sheldan.abstracto.core.models.database.AUser;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
@Getter
@Setter
@Builder
public class StarStatsUser {
private AUser user;
private Member member;
private Integer starCount;
}

View File

@@ -0,0 +1,24 @@
package dev.sheldan.abstracto.utility.models.template.starboard;
import dev.sheldan.abstracto.core.models.CachedMessage;
import dev.sheldan.abstracto.core.models.ServerContext;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
@Getter
@Setter
@SuperBuilder
public class StarboardPostModel extends ServerContext {
private Member author;
private TextChannel channel;
private AUser user;
private AChannel aChannel;
private CachedMessage message;
private Integer starCount;
private String starLevelEmote;
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.abstracto.utility.service;
import dev.sheldan.abstracto.core.models.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import dev.sheldan.abstracto.utility.models.template.starboard.StarStatsModel;
import java.util.List;
public interface StarboardService {
void createStarboardPost(CachedMessage message, List<AUser> userExceptAuthor, AUserInAServer userReacting, AUserInAServer starredUser);
void updateStarboardPost(StarboardPost post, CachedMessage message, List<AUser> userExceptAuthor);
void removeStarboardPost(StarboardPost message);
StarStatsModel retrieveStarStats(Long serverId);
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.AServerChannelMessage;
import dev.sheldan.abstracto.core.models.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import java.util.List;
import java.util.Optional;
public interface StarboardPostManagementService {
StarboardPost createStarboardPost(CachedMessage starredMessage, AUserInAServer starredUser, AUserInAServer starringUser, AServerChannelMessage starboardPost);
void setStarboardPostMessageId(StarboardPost post, Long messageId);
List<StarboardPost> retrieveTopPosts(Long serverId, Integer count);
List<StarboardPost> retrieveAllPosts(Long serverId);
Integer getPostCount(Long serverId);
Optional<StarboardPost> findByMessageId(Long messageId);
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.utility.models.StarboardPost;
import dev.sheldan.abstracto.utility.models.template.starboard.StarStatsUser;
import java.util.List;
public interface StarboardPostReactorManagementService {
void addReactor(StarboardPost post, AUser user);
void removeReactor(StarboardPost post, AUser user);
void removeReactors(StarboardPost post);
Integer getStarCount(Long serverId);
List<StarStatsUser> retrieveTopStarGiver(Long serverId, Integer count);
List<StarStatsUser> retrieveTopStarReceiver(Long serverId, Integer count);
}