[AB-150] creating repost detection feature including configuration and documentation

adding http and hash service
adding ability to add default emotes to a message to message service
adding message embedded listener to wrap the embedded event
adding custom channel groups which can be defined by modules, in case a change on a channel group (only created and updated) happens a listener is available in order to sync the state in dependant areas
changing command receiver re-throwing abstracto runtime exceptions in order to display them better
changing channel group parameter handler to throw an exception in case the channel group was not found
adding User in a server parameter handler
split channel not found exception to be able to differentiate between not found in database and not found in guild
changing exception handling in command received handler to handle the case for only one parameter handler future which failed (the whole single future failed, which was not reported)
changing parameter type of `removeFromChannelGroup` to AChannel in order to be able to delete channels in the database via ID
moving method to mock utils for mocking consumer
removing parameter validation from commands, as it should be done in the command received handler and parameter handlers anyway
This commit is contained in:
Sheldan
2020-12-04 00:38:18 +01:00
parent e966c710ce
commit 325264a325
249 changed files with 5310 additions and 686 deletions

View File

@@ -32,7 +32,6 @@ public class ShowAvatar extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
checkParameters(commandContext);
List<Object> parameters = commandContext.getParameters().getParameters();
Member memberToShow = parameters.size() == 1 ? (Member) parameters.get(0) : commandContext.getUserInitiatedContext().getMember();
ShowAvatarModel model = (ShowAvatarModel) ContextConverter.fromCommandContext(commandContext, ShowAvatarModel.class);

View File

@@ -29,7 +29,6 @@ public class ShowEmote extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
checkParameters(commandContext);
List<Object> parameters = commandContext.getParameters().getParameters();
Emote emoteParameter = (Emote) parameters.get(0);
ShowEmoteLog emoteLog = (ShowEmoteLog) ContextConverter.fromCommandContext(commandContext, ShowEmoteLog.class);

View File

@@ -39,7 +39,6 @@ public class UserInfo extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
checkParameters(commandContext);
List<Object> parameters = commandContext.getParameters().getParameters();
Member memberToShow = parameters.size() == 1 ? (Member) parameters.get(0) : commandContext.getAuthor();
UserInfoModel model = (UserInfoModel) ContextConverter.slimFromCommandContext(commandContext, UserInfoModel.class);

View File

@@ -37,7 +37,6 @@ public class Remind extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
checkParameters(commandContext);
List<Object> parameters = commandContext.getParameters().getParameters();
Duration remindTime = (Duration) parameters.get(0);
String text = (String) parameters.get(1);

View File

@@ -29,7 +29,6 @@ public class UnRemind extends AbstractConditionableCommand {
@Override
public CommandResult execute(CommandContext commandContext) {
checkParameters(commandContext);
Long reminderId = (Long) commandContext.getParameters().getParameters().get(0);
reminderService.unRemind(reminderId, commandContext.getUserInitiatedContext().getAUserInAServer());
return CommandResult.fromSuccess();

View File

@@ -0,0 +1,67 @@
package dev.sheldan.abstracto.utility.commands.repost;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.utility.config.RepostDetectionModuleInterface;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.service.RepostCheckChannelService;
import dev.sheldan.abstracto.utility.service.RepostServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class DisableRepostCheck extends AbstractConditionableCommand {
@Autowired
private RepostCheckChannelService repostCheckChannelService;
@Autowired
private ChannelGroupManagementService channelGroupManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
AChannelGroup fakeChannelGroup = (AChannelGroup) parameters.get(0);
AServer actualServer = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
AChannelGroup actualChannelGroup = channelGroupManagementService.findByNameAndServerAndType(fakeChannelGroup.getGroupName(), actualServer, RepostServiceBean.REPOST_CHECK_CHANNEL_GROUP_TYPE);
repostCheckChannelService.setRepostCheckDisabledForChannelGroup(actualChannelGroup);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter channelToSet = Parameter.builder().name("channelGroup").type(AChannelGroup.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(channelToSet);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("disableRepostCheck")
.module(RepostDetectionModuleInterface.REPOST_DETECTION)
.templated(true)
.async(false)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return UtilityFeature.REPOST_DETECTION;
}
}

View File

@@ -0,0 +1,67 @@
package dev.sheldan.abstracto.utility.commands.repost;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.utility.config.RepostDetectionModuleInterface;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.service.RepostCheckChannelService;
import dev.sheldan.abstracto.utility.service.RepostServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class EnableRepostCheck extends AbstractConditionableCommand {
@Autowired
private RepostCheckChannelService repostCheckChannelService;
@Autowired
private ChannelGroupManagementService channelGroupManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
AChannelGroup fakeChannelGroup = (AChannelGroup) parameters.get(0);
AServer actualServer = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
AChannelGroup actualChannelGroup = channelGroupManagementService.findByNameAndServerAndType(fakeChannelGroup.getGroupName(), actualServer, RepostServiceBean.REPOST_CHECK_CHANNEL_GROUP_TYPE);
repostCheckChannelService.setRepostCheckEnabledForChannelGroup(actualChannelGroup);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter channelToSet = Parameter.builder().name("channelGroup").type(AChannelGroup.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(channelToSet);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("enableRepostCheck")
.module(RepostDetectionModuleInterface.REPOST_DETECTION)
.templated(true)
.async(false)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return UtilityFeature.REPOST_DETECTION;
}
}

View File

@@ -0,0 +1,64 @@
package dev.sheldan.abstracto.utility.commands.repost;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.utility.config.RepostDetectionModuleInterface;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.service.PostedImageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class PurgeImagePosts extends AbstractConditionableCommand {
@Autowired
private PostedImageService postedImageService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
if(!parameters.isEmpty()) {
AUserInAServer fakeUser = (AUserInAServer) parameters.get(0);
AUserInAServer actualUser = userInServerManagementService.loadUser(fakeUser.getUserInServerId());
postedImageService.purgePostedImages(actualUser);
} else {
postedImageService.purgePostedImages(commandContext.getGuild());
}
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter channelToSet = Parameter.builder().name("member").type(AUserInAServer.class).templated(true).optional(true).build();
List<Parameter> parameters = Arrays.asList(channelToSet);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("purgeImagePosts")
.module(RepostDetectionModuleInterface.REPOST_DETECTION)
.templated(true)
.async(false)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return UtilityFeature.REPOST_DETECTION;
}
}

View File

@@ -0,0 +1,64 @@
package dev.sheldan.abstracto.utility.commands.repost;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.utility.config.RepostDetectionModuleInterface;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.service.RepostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class PurgeReposts extends AbstractConditionableCommand {
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private RepostService repostService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
if(!parameters.isEmpty()) {
AUserInAServer fakeUser = (AUserInAServer) parameters.get(0);
AUserInAServer actualUser = userInServerManagementService.loadUser(fakeUser.getUserInServerId());
repostService.purgeReposts(actualUser);
} else {
repostService.purgeReposts(commandContext.getGuild());
}
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter channelToSet = Parameter.builder().name("member").type(AUserInAServer.class).templated(true).optional(true).build();
List<Parameter> parameters = Arrays.asList(channelToSet);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("purgeReposts")
.module(RepostDetectionModuleInterface.REPOST_DETECTION)
.templated(true)
.async(false)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return UtilityFeature.REPOST_DETECTION;
}
}

View File

@@ -0,0 +1,102 @@
package dev.sheldan.abstracto.utility.commands.repost;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.templating.service.TemplateService;
import dev.sheldan.abstracto.utility.config.RepostDetectionModuleInterface;
import dev.sheldan.abstracto.utility.config.features.RepostDetectionFeatureMode;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.converter.RepostLeaderBoardConverter;
import dev.sheldan.abstracto.utility.models.RepostLeaderboardEntryModel;
import dev.sheldan.abstracto.utility.models.RepostLeaderboardModel;
import dev.sheldan.abstracto.utility.models.database.result.RepostLeaderboardResult;
import dev.sheldan.abstracto.utility.service.RepostService;
import dev.sheldan.abstracto.utility.service.management.RepostManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class RepostLeaderboard extends AbstractConditionableCommand {
public static final String REPOST_LEADERBOARD_RESPONSE_TEMPLATE_KEY = "repostLeaderboard_response";
@Autowired
private RepostService repostService;
@Autowired
private RepostManagementService repostManagementService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private RepostLeaderBoardConverter converter;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Integer page = !parameters.isEmpty() ? (Integer) parameters.get(0) : 1;
AUserInAServer aUserInAServer = userInServerManagementService.loadUser(commandContext.getAuthor());
List<RepostLeaderboardResult> topRepostingUsersOfServer = repostManagementService.findTopRepostingUsersOfServer(commandContext.getGuild().getIdLong(), page, 5);
RepostLeaderboardResult resultOfUser = repostManagementService.getRepostRankOfUser(aUserInAServer);
CompletableFuture<List<RepostLeaderboardEntryModel>> leaderBoardFuture = converter.fromLeaderBoardResults(topRepostingUsersOfServer);
CompletableFuture<RepostLeaderboardEntryModel> userFuture = converter.convertSingleUser(resultOfUser);
return CompletableFuture.allOf(leaderBoardFuture, userFuture).thenCompose(unused -> {
List<RepostLeaderboardEntryModel> entries = leaderBoardFuture.join();
RepostLeaderboardModel model = RepostLeaderboardModel
.builder()
.guild(commandContext.getGuild())
.entries(entries)
.userExecuting(userFuture.join())
.member(commandContext.getAuthor())
.build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInChannel(REPOST_LEADERBOARD_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()));
}).thenApply(o -> CommandResult.fromIgnored());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter channelToSet = Parameter.builder().name("page").type(Integer.class).templated(true).optional(true).build();
List<Parameter> parameters = Arrays.asList(channelToSet);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("repostLeaderboard")
.module(RepostDetectionModuleInterface.REPOST_DETECTION)
.templated(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return UtilityFeature.REPOST_DETECTION;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(RepostDetectionFeatureMode.LEADERBOARD);
}
}

View File

@@ -0,0 +1,62 @@
package dev.sheldan.abstracto.utility.commands.repost;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.utility.config.RepostDetectionModuleInterface;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.converter.RepostCheckChannelModelConverter;
import dev.sheldan.abstracto.utility.models.database.RepostCheckChannelGroup;
import dev.sheldan.abstracto.utility.models.template.commands.RepostCheckChannelsModel;
import dev.sheldan.abstracto.utility.service.RepostCheckChannelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class ShowRepostCheckChannels extends AbstractConditionableCommand {
public static final String SHOW_REPOST_CHECK_CHANNELS_RESPONSE_TEMPLATE_KEY = "showRepostCheckChannels_response";
@Autowired
private RepostCheckChannelModelConverter converter;
@Autowired
private RepostCheckChannelService checkChannelService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<RepostCheckChannelGroup> channelGroups = checkChannelService.getChannelGroupsWithEnabledCheck(commandContext.getGuild().getIdLong());
RepostCheckChannelsModel model = converter.fromRepostCheckChannelGroups(channelGroups, commandContext.getGuild());
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInChannel(SHOW_REPOST_CHECK_CHANNELS_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CommandConfiguration getConfiguration() {
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("showRepostCheckChannels")
.module(RepostDetectionModuleInterface.REPOST_DETECTION)
.templated(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return UtilityFeature.REPOST_DETECTION;
}
}

View File

@@ -30,7 +30,6 @@ public class Accept extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
checkParameters(commandContext);
List<Object> parameters = commandContext.getParameters().getParameters();
Long suggestionId = (Long) parameters.get(0);
String text = parameters.size() == 2 ? (String) parameters.get(1) : "";

View File

@@ -30,7 +30,6 @@ public class Reject extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
checkParameters(commandContext);
List<Object> parameters = commandContext.getParameters().getParameters();
Long suggestionId = (Long) parameters.get(0);
String text = parameters.size() == 2 ? (String) parameters.get(1) : "";

View File

@@ -25,7 +25,6 @@ public class Suggest extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
checkParameters(commandContext);
List<Object> parameters = commandContext.getParameters().getParameters();
String text = (String) parameters.get(0);
SuggestionLog suggestLogModel = (SuggestionLog) ContextConverter.fromCommandContext(commandContext, SuggestionLog.class);

View File

@@ -0,0 +1,42 @@
package dev.sheldan.abstracto.utility.converter;
import dev.sheldan.abstracto.core.models.FullChannel;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.utility.models.database.RepostCheckChannelGroup;
import dev.sheldan.abstracto.utility.models.template.commands.RepostCheckChannelGroupDisplayModel;
import dev.sheldan.abstracto.utility.models.template.commands.RepostCheckChannelsModel;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class RepostCheckChannelModelConverter {
@Autowired
private BotService botService;
public RepostCheckChannelsModel fromRepostCheckChannelGroups(List<RepostCheckChannelGroup> channelGroups, Guild guild) {
List<RepostCheckChannelGroupDisplayModel> repostCheckChannelGroups = new ArrayList<>();
channelGroups.forEach(repostCheckChannelGroup -> {
List<FullChannel> fullChannels = repostCheckChannelGroup.getChannelGroup().getChannels().stream().map(channel ->
FullChannel
.builder()
.channel(channel)
.serverChannel(botService.getTextChannelFromServerNullable(guild, channel.getId()))
.build()
).collect(Collectors.toList());
repostCheckChannelGroups.add(
RepostCheckChannelGroupDisplayModel
.builder()
.channelGroup(repostCheckChannelGroup)
.channels(fullChannels)
.build());
});
return RepostCheckChannelsModel.builder().repostCheckChannelGroups(repostCheckChannelGroups).build();
}
}

View File

@@ -0,0 +1,62 @@
package dev.sheldan.abstracto.utility.converter;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.utility.models.RepostLeaderboardEntryModel;
import dev.sheldan.abstracto.utility.models.database.result.RepostLeaderboardResult;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
public class RepostLeaderBoardConverter {
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private BotService botService;
@Autowired
private RepostLeaderBoardConverter self;
public CompletableFuture<List<RepostLeaderboardEntryModel>> fromLeaderBoardResults(List<RepostLeaderboardResult> results) {
if(results.isEmpty()) {
return CompletableFuture.completedFuture(new ArrayList<>());
}
List<CompletableFuture<RepostLeaderboardEntryModel>> modelFutures =
results.stream().map(this::convertSingleUser).collect(Collectors.toList());
return FutureUtils.toSingleFutureGeneric(modelFutures).thenApply(unused ->
modelFutures.stream().map(CompletableFuture::join).collect(Collectors.toList())
);
}
public CompletableFuture<RepostLeaderboardEntryModel> convertSingleUser(RepostLeaderboardResult result) {
AUserInAServer user = userInServerManagementService.loadUser(result.getUserInServerId());
Integer count = result.getRepostCount();
Long userInServerId = result.getUserInServerId();
Integer rank = result.getRank();
return botService.getMemberInServerAsync(user).thenApply(member ->
self.loadUserFromDatabase(member, count, userInServerId, rank)
);
}
@Transactional
public RepostLeaderboardEntryModel loadUserFromDatabase(Member member, Integer count, Long userInServerId, Integer rank) {
return RepostLeaderboardEntryModel
.builder()
.member(member)
.user(userInServerManagementService.loadUser(userInServerId))
.count(count)
.rank(rank)
.build();
}
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.utility.listener.repost;
import dev.sheldan.abstracto.core.listener.entity.ChannelGroupCreatedListener;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.utility.service.RepostServiceBean;
import dev.sheldan.abstracto.utility.service.management.RepostCheckChannelGroupManagement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RepostCheckChannelGroupCreatedListener implements ChannelGroupCreatedListener {
@Autowired
private RepostCheckChannelGroupManagement checkChannelGroupManagement;
@Override
public void channelGroupCreated(AChannelGroup channelGroup) {
if(channelGroup.getChannelGroupType().getGroupTypeKey().equals(RepostServiceBean.REPOST_CHECK_CHANNEL_GROUP_TYPE)) {
checkChannelGroupManagement.createRepostCheckChannelGroup(channelGroup);
}
}
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.utility.listener.repost;
import dev.sheldan.abstracto.core.listener.entity.ChannelGroupDeletedListener;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.utility.service.RepostServiceBean;
import dev.sheldan.abstracto.utility.service.management.RepostCheckChannelGroupManagement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RepostCheckChannelGroupDeletedListener implements ChannelGroupDeletedListener {
@Autowired
private RepostCheckChannelGroupManagement checkChannelGroupManagement;
@Override
public void channelGroupDeleted(AChannelGroup channelGroup) {
if(channelGroup.getChannelGroupType().getGroupTypeKey().equals(RepostServiceBean.REPOST_CHECK_CHANNEL_GROUP_TYPE)) {
checkChannelGroupManagement.deleteRepostCheckChannelGroup(channelGroup);
}
}
}

View File

@@ -0,0 +1,58 @@
package dev.sheldan.abstracto.utility.listener.repost;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.MessageEmbeddedListener;
import dev.sheldan.abstracto.core.models.listener.GuildMessageEmbedEventModel;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.service.RepostCheckChannelService;
import dev.sheldan.abstracto.utility.service.RepostService;
import dev.sheldan.abstracto.utility.service.management.PostedImageManagement;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.EmbedType;
import net.dv8tion.jda.api.entities.MessageEmbed;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
@Component
@Slf4j
public class RepostEmbedListener implements MessageEmbeddedListener {
@Autowired
private RepostCheckChannelService repostCheckChannelService;
@Autowired
private RepostService repostService;
@Autowired
private PostedImageManagement repostManagement;
@Override
public void execute(GuildMessageEmbedEventModel eventModel) {
if(repostCheckChannelService.duplicateCheckEnabledForChannel(eventModel.getChannel())) {
if(repostManagement.messageEmbedsHaveBeenCovered(eventModel.getMessageId())) {
log.info("The embeds of the message {} in channel {} in server {} have already been covered by repost check -- ignoring.",
eventModel.getMessageId(), eventModel.getChannel().getIdLong(), eventModel.getChannel().getGuild().getIdLong());
return;
}
eventModel.getChannel().retrieveMessageById(eventModel.getMessageId()).queue(message -> {
List<MessageEmbed> imageEmbeds = eventModel.getEmbeds().stream().filter(messageEmbed -> messageEmbed.getType().equals(EmbedType.IMAGE)).collect(Collectors.toList());
repostService.processMessageEmbedsRepostCheck(imageEmbeds, message);
});
}
}
@Override
public FeatureEnum getFeature() {
return UtilityFeature.REPOST_DETECTION;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -0,0 +1,48 @@
package dev.sheldan.abstracto.utility.listener.repost;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.MessageReceivedListener;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.service.RepostCheckChannelService;
import dev.sheldan.abstracto.utility.service.RepostService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.EmbedType;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
@Component
@Slf4j
public class RepostMessageReceivedListener implements MessageReceivedListener {
@Autowired
private RepostCheckChannelService repostCheckChannelService;
@Autowired
private RepostService repostService;
@Override
public void execute(Message message) {
if(repostCheckChannelService.duplicateCheckEnabledForChannel(message.getTextChannel())) {
repostService.processMessageAttachmentRepostCheck(message);
List<MessageEmbed> imageEmbeds = message.getEmbeds().stream().filter(messageEmbed -> messageEmbed.getType().equals(EmbedType.IMAGE)).collect(Collectors.toList());
repostService.processMessageEmbedsRepostCheck(imageEmbeds, message);
}
}
@Override
public FeatureEnum getFeature() {
return UtilityFeature.REPOST_DETECTION;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.utility.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.models.database.PostedImage;
import dev.sheldan.abstracto.utility.models.database.embed.PostIdentifier;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface PostedImageRepository extends JpaRepository<PostedImage, PostIdentifier> {
boolean existsByImageHashAndServerId(String hash, Long serverId);
Optional<PostedImage> findByImageHashAndServerId(String hash, Long serverId);
boolean existsByPostId_MessageId(Long messageId);
boolean existsByPostId_MessageIdAndPostId_PositionGreaterThan(Long messageId, Integer position);
List<PostedImage> findByPostId_MessageId(Long messageId);
void deleteByServer(AServer server);
void deleteByPoster(AUserInAServer aUserInAServer);
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.utility.repository;
import dev.sheldan.abstracto.utility.models.database.RepostCheckChannelGroup;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface RepostCheckChannelRepository extends JpaRepository<RepostCheckChannelGroup, Long> {
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.abstracto.utility.repository;
import dev.sheldan.abstracto.utility.models.database.Repost;
import dev.sheldan.abstracto.utility.models.database.embed.RepostIdentifier;
import dev.sheldan.abstracto.utility.models.database.result.RepostLeaderboardResult;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface RepostRepository extends JpaRepository<Repost, RepostIdentifier> {
@Query(value = "WITH repost_rank AS " +
"( " +
"SELECT user_in_server_id as user_in_server_id, SUM(count) as repost_count, ROW_NUMBER() OVER ( ORDER BY SUM(count) DESC ) \n" +
"FROM repost \n" +
"WHERE server_id = :server_id " +
"GROUP BY user_in_server_id \n" +
"ORDER BY SUM(count) DESC \n" +
" )" +
"SELECT rank.user_in_server_id as userInServerId, rank.repost_count as repostCount, rank.row_number as rank " +
"FROM repost_rank rank ",
countQuery = "SELECT COUNT(1) FROM repost WHERE server_id = :server_id GROUP BY user_in_server_id",
nativeQuery = true)
List<RepostLeaderboardResult> findTopRepostingUsers(@Param("server_id") Long serverId, Pageable pageable);
@Query(value = "WITH repost_rank AS " +
"( " +
"SELECT user_in_server_id as user_in_server_id, SUM(count) as repost_count, ROW_NUMBER() OVER ( ORDER BY SUM(count) DESC ) \n" +
"FROM repost \n" +
"WHERE server_id = :server_id " +
"GROUP BY user_in_server_id \n" +
"ORDER BY SUM(count) DESC \n" +
" )" +
"SELECT rank.user_in_server_id as userInServerId, rank.repost_count as repostCount, rank.row_number as rank " +
"FROM repost_rank rank " +
"WHERE rank.user_in_server_id = :user_in_server_id " +
"UNION ALL " +
"SELECT :user_in_server_id as userInServerId, 0 as repostCount, 0 as rank " +
"WHERE NOT EXISTS " +
"(SELECT 1 " +
"FROM repost_rank rank WHERE rank.user_in_server_id = :user_in_server_id" +
")",
nativeQuery = true)
RepostLeaderboardResult getRepostRankOfUserInServer(@Param("user_in_server_id") Long useInServerId, @Param("server_id") Long serverId);
void deleteByServerId(Long serverId);
void deleteByRepostId_UserInServerIdAndServerId(Long userInServerId, Long serverId);
}

View File

@@ -37,6 +37,7 @@ public class StarStatsUserConverter {
.builder()
.starCount(starStatsUserResult.getStarCount())
.member(member)
// TODO properly load this instance instead of just building one
.user(AUser.builder().id(starStatsUserResult.getUserId()).build())
.build()
);

View File

@@ -0,0 +1,30 @@
package dev.sheldan.abstracto.utility.service;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.utility.service.management.PostedImageManagement;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class PostedImageServiceBean implements PostedImageService {
@Autowired
private PostedImageManagement postedImageManagement;
@Autowired
private ServerManagementService serverManagementService;
@Override
public void purgePostedImages(AUserInAServer aUserInAServer) {
postedImageManagement.removePostedImagesOf(aUserInAServer);
}
@Override
public void purgePostedImages(Guild guild) {
AServer server = serverManagementService.loadServer(guild.getIdLong());
postedImageManagement.removedPostedImagesIn(server);
}
}

View File

@@ -0,0 +1,104 @@
package dev.sheldan.abstracto.utility.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelGroupService;
import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.utility.models.database.RepostCheckChannelGroup;
import dev.sheldan.abstracto.utility.service.management.RepostCheckChannelGroupManagement;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static dev.sheldan.abstracto.utility.service.RepostServiceBean.REPOST_CHECK_CHANNEL_GROUP_TYPE;
@Component
public class RepostCheckChannelServiceBean implements RepostCheckChannelService {
@Autowired
private RepostCheckChannelGroupManagement repostCheckChannelManagement;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private ChannelGroupService channelGroupService;
@Autowired
private ChannelGroupManagementService channelGroupManagementService;
@Override
public void setRepostCheckEnabledForChannelGroup(AChannelGroup channelGroup) {
RepostCheckChannelGroup group = repostCheckChannelManagement.loadRepostChannelGroupByChannelGroup(channelGroup);
setRepostCheckEnabledForChannelGroup(group);
}
@Override
public void setRepostCheckEnabledForChannelGroup(RepostCheckChannelGroup channelGroup) {
channelGroup.setCheckEnabled(true);
}
@Override
public void setRepostCheckDisabledForChannelGroup(AChannelGroup channelGroup) {
RepostCheckChannelGroup group = repostCheckChannelManagement.loadRepostChannelGroupByChannelGroup(channelGroup);
setRepostCheckDisabledForChannelGroup(group);
}
@Override
public void setRepostCheckDisabledForChannelGroup(RepostCheckChannelGroup channelGroup) {
channelGroup.setCheckEnabled(false);
}
@Override
public boolean duplicateCheckEnabledForChannel(TextChannel textChannel) {
AChannel channel = channelManagementService.loadChannel(textChannel.getIdLong());
return duplicateCheckEnabledForChannel(channel);
}
@Override
public boolean duplicateCheckEnabledForChannel(AChannel channel) {
List<AChannelGroup> channelGroups = channelGroupService.getChannelGroupsOfChannelWithType(channel, REPOST_CHECK_CHANNEL_GROUP_TYPE);
if(!channelGroups.isEmpty()) {
List<RepostCheckChannelGroup> repostChannelGroups = channelGroups.stream().map(aChannelGroup ->
repostCheckChannelManagement.loadRepostChannelGroupById(aChannelGroup.getId())
).collect(Collectors.toList());
return repostChannelGroups.stream().anyMatch(RepostCheckChannelGroup::getCheckEnabled);
}
return false;
}
@Override
public List<RepostCheckChannelGroup> getRepostCheckChannelGroupsForServer(AServer server) {
return getRepostCheckChannelGroupsForServer(server.getId());
}
@Override
public List<RepostCheckChannelGroup> getRepostCheckChannelGroupsForServer(Long serverId) {
List<AChannelGroup> channelGroups = channelGroupManagementService.findAllInServerWithType(serverId, REPOST_CHECK_CHANNEL_GROUP_TYPE);
if(!channelGroups.isEmpty()) {
return channelGroups.stream().map(aChannelGroup ->
repostCheckChannelManagement.loadRepostChannelGroupById(aChannelGroup.getId())
).collect(Collectors.toList());
}
return new ArrayList<>();
}
@Override
public List<RepostCheckChannelGroup> getChannelGroupsWithEnabledCheck(AServer server) {
return getChannelGroupsWithEnabledCheck(server.getId());
}
@Override
public List<RepostCheckChannelGroup> getChannelGroupsWithEnabledCheck(Long serverId) {
return getRepostCheckChannelGroupsForServer(serverId)
.stream()
.filter(RepostCheckChannelGroup::getCheckEnabled)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,235 @@
package dev.sheldan.abstracto.utility.service;
import dev.sheldan.abstracto.core.models.AServerAChannelAUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.HashService;
import dev.sheldan.abstracto.core.service.HttpService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FileUtils;
import dev.sheldan.abstracto.utility.config.features.RepostDetectionFeatureMode;
import dev.sheldan.abstracto.utility.config.features.UtilityFeature;
import dev.sheldan.abstracto.utility.converter.RepostLeaderBoardConverter;
import dev.sheldan.abstracto.utility.models.RepostLeaderboardEntryModel;
import dev.sheldan.abstracto.utility.models.database.PostedImage;
import dev.sheldan.abstracto.utility.models.database.Repost;
import dev.sheldan.abstracto.utility.models.database.result.RepostLeaderboardResult;
import dev.sheldan.abstracto.utility.service.management.PostedImageManagement;
import dev.sheldan.abstracto.utility.service.management.RepostManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class RepostServiceBean implements RepostService {
public static final Integer LEADER_BOARD_PAGE_SIZE = 5;
@Autowired
private HttpService httpService;
@Autowired
private HashService hashService;
@Autowired
private FileUtils fileUtils;
@Autowired
private PostedImageManagement postedImageManagement;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private MessageService messageService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private RepostManagementService repostManagementService;
@Autowired
private RepostLeaderBoardConverter leaderBoardConverter;
@Autowired
private RepostServiceBean self;
public static final List<String> NUMBER_EMOJI = Arrays.asList("\u0031\u20e3", "\u0032\u20e3", "\u0033\u20e3",
"\u0034\u20e3", "\u0035\u20e3", "\u0036\u20e3",
"\u0037\u20e3", "\u0038\u20e3", "\u0039\u20e3");
public static final String REPOST_CHECK_CHANNEL_GROUP_TYPE = "repostDetection";
// any embedded post will create an repost instance with a position higher than this
public static final Integer EMBEDDED_LINK_POSITION_START_INDEX = 1000;
public static final String REPOST_MARKER_EMOTE_KEY = "repostMarker";
@Override
public boolean isRepost(Message message, MessageEmbed messageEmbed, Integer index) {
return getRepostFor(message, messageEmbed, index).isPresent();
}
@Override
public Optional<PostedImage> getRepostFor(Message message, MessageEmbed messageEmbed, Integer embedIndex) {
if(messageEmbed.getThumbnail() == null && messageEmbed.getImage() == null) {
return Optional.empty();
}
String urlToUse = null;
if(messageEmbed.getThumbnail() != null) {
urlToUse = messageEmbed.getThumbnail().getProxyUrl();
} else if (messageEmbed.getImage() != null) {
urlToUse = messageEmbed.getImage().getProxyUrl();
}
return checkForDuplicates(message, EMBEDDED_LINK_POSITION_START_INDEX + embedIndex, urlToUse);
}
private Optional<PostedImage> checkForDuplicates(Message message, Integer index, String fileUrl) {
String fileHash = calculateHashForPost(fileUrl, message.getGuild().getIdLong());
AServer aServer = serverManagementService.loadServer(message.getGuild().getIdLong());
Optional<PostedImage> potentialRepost = postedImageManagement.getPostWithHash(fileHash, aServer);
if(potentialRepost.isPresent()) {
PostedImage existingRepost = potentialRepost.get();
return existingRepost.getPostId().getMessageId() != message.getIdLong() ? Optional.of(existingRepost) : Optional.empty();
} else {
AUserInAServer aUserInAServer = userInServerManagementService.loadUser(message.getMember());
AServerAChannelAUser cause = AServerAChannelAUser
.builder()
.aUserInAServer(aUserInAServer)
.channel(channelManagementService.loadChannel(message.getTextChannel().getIdLong()))
.guild(aServer)
.user(aUserInAServer.getUserReference())
.build();
postedImageManagement.createPost(cause, message, fileHash, index);
return Optional.empty();
}
}
@Override
public boolean isRepost(Message message, Message.Attachment attachment, Integer index) {
return getRepostFor(message, attachment, index).isPresent();
}
@Override
public Optional<PostedImage> getRepostFor(Message message, Message.Attachment attachment, Integer index) {
return checkForDuplicates(message, index, attachment.getProxyUrl());
}
@Override
public String calculateHashForPost(String url, Long serverId) {
File downloadedFile = null;
try {
if(featureModeService.featureModeActive(UtilityFeature.REPOST_DETECTION, serverId, RepostDetectionFeatureMode.DOWNLOAD)) {
downloadedFile = httpService.downloadFileToTempFile(url);
return hashService.sha256HashFileContent(downloadedFile);
} else {
return hashService.sha256HashString(url);
}
} catch (IOException e) {
log.error("Failed to download attachment for repost check.", e);
} finally {
if(downloadedFile != null) {
try {
fileUtils.safeDelete(downloadedFile);
} catch (IOException e) {
log.error("Failed to delete downloaded repost check file.", e);
}
}
}
return null;
}
@Override
public void processMessageAttachmentRepostCheck(Message message) {
boolean canThereBeMultipleReposts = message.getAttachments().size() > 1;
for (int imageIndex = 0; imageIndex < message.getAttachments().size(); imageIndex++) {
executeRepostCheckForAttachment(message, message.getAttachments().get(imageIndex), imageIndex, canThereBeMultipleReposts);
}
}
private void executeRepostCheckForAttachment(Message message, Message.Attachment attachment, Integer index, boolean moreRepostsPossible) {
Optional<PostedImage> originalPostOptional = getRepostFor(message, attachment, index);
originalPostOptional.ifPresent(postedImage -> markMessageAndPersist(message, index, moreRepostsPossible, postedImage));
}
private void markMessageAndPersist(Message message, Integer index, boolean moreRepostsPossible, PostedImage originalPost) {
log.info("Detected repost in message embed {} of message {} in channel {} in server {}.", index, message.getIdLong(), message.getTextChannel().getIdLong(), message.getGuild().getIdLong());
CompletableFuture<Void> markerFuture = messageService.addReactionToMessageWithFuture(REPOST_MARKER_EMOTE_KEY, message.getGuild().getIdLong(), message);
CompletableFuture<Void> counterFuture;
if (moreRepostsPossible) {
counterFuture = messageService.addDefaultReactionToMessageAsync(NUMBER_EMOJI.get(index), message);
} else {
counterFuture = CompletableFuture.completedFuture(null);
}
Long messageId = originalPost.getPostId().getMessageId();
Integer position = originalPost.getPostId().getPosition();
Long serverId = message.getGuild().getIdLong();
Long userId = message.getAuthor().getIdLong();
CompletableFuture.allOf(markerFuture, counterFuture).thenAccept(unused ->
self.persistRepost(messageId, position, serverId, userId)
);
}
@Transactional
public void persistRepost(Long messageId, Integer position, Long serverId, Long userId) {
PostedImage postedImage = postedImageManagement.getPostFromMessageAndPosition(messageId, position);
AUserInAServer userInAServer = userInServerManagementService.loadUser(serverId, userId);
Optional<Repost> existingPost = repostManagementService.findRepostOptional(postedImage, userInAServer);
if(existingPost.isPresent()) {
Repost previousRepost = existingPost.get();
existingPost.get().setCount(previousRepost.getCount() + 1);
} else {
repostManagementService.createRepost(postedImage, userInAServer);
}
}
@Override
public void processMessageEmbedsRepostCheck(List<MessageEmbed> embeds, Message message) {
boolean canThereBeMultipleReposts = embeds.size() > 1 || !message.getAttachments().isEmpty();
for (int imageIndex = 0; imageIndex < embeds.size(); imageIndex++) {
executeRepostCheckForMessageEmbed(message, embeds.get(imageIndex), imageIndex + message.getAttachments().size(), canThereBeMultipleReposts);
}
}
@Override
public CompletableFuture<List<RepostLeaderboardEntryModel>> retrieveRepostLeaderboard(Guild guild, Integer page) {
AServer server = serverManagementService.loadServer(guild.getIdLong());
List<RepostLeaderboardResult> topRepostingUsersOfServer = repostManagementService.findTopRepostingUsersOfServer(server, page, LEADER_BOARD_PAGE_SIZE);
return leaderBoardConverter.fromLeaderBoardResults(topRepostingUsersOfServer);
}
@Override
public void purgeReposts(AUserInAServer userInAServer) {
repostManagementService.deleteRepostsFromUser(userInAServer);
}
@Override
public void purgeReposts(Guild guild) {
AServer server = serverManagementService.loadServer(guild.getIdLong());
repostManagementService.deleteRepostsFromServer(server);
}
private void executeRepostCheckForMessageEmbed(Message message, MessageEmbed messageEmbed, Integer index, boolean moreRepostsPossible) {
Optional<PostedImage> originalPostOptional = getRepostFor(message, messageEmbed, index);
originalPostOptional.ifPresent(postedImage -> markMessageAndPersist(message, index, moreRepostsPossible, postedImage));
}
}

View File

@@ -0,0 +1,86 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.AServerAChannelAUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.exception.PostedImageNotFoundException;
import dev.sheldan.abstracto.utility.models.database.PostedImage;
import dev.sheldan.abstracto.utility.models.database.embed.PostIdentifier;
import dev.sheldan.abstracto.utility.repository.PostedImageRepository;
import dev.sheldan.abstracto.utility.service.RepostServiceBean;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class PostedImageManagementBean implements PostedImageManagement {
@Autowired
private PostedImageRepository postedImageRepository;
@Autowired
private RepostCheckChannelGroupManagement checkChannelBean;
@Override
public PostedImage createPost(AServerAChannelAUser creation, Message source, String hash, Integer index) {
PostedImage post = PostedImage
.builder()
.imageHash(hash)
.postId(new PostIdentifier(source.getIdLong(), index))
.poster(creation.getAUserInAServer())
.server(creation.getGuild())
.postedChannel(creation.getChannel())
.build();
postedImageRepository.save(post);
return post;
}
@Override
public boolean postWitHashExists(String hash, AServer server) {
return postedImageRepository.existsByImageHashAndServerId(hash, server.getId());
}
@Override
public Optional<PostedImage> getPostWithHash(String hash, AServer server) {
return postedImageRepository.findByImageHashAndServerId(hash, server.getId());
}
@Override
public boolean messageHasBeenCovered(Long messageId) {
return postedImageRepository.existsByPostId_MessageId(messageId);
}
@Override
public boolean messageEmbedsHaveBeenCovered(Long messageId) {
return postedImageRepository.existsByPostId_MessageIdAndPostId_PositionGreaterThan(messageId, RepostServiceBean.EMBEDDED_LINK_POSITION_START_INDEX - 1);
}
@Override
public List<PostedImage> getAllFromMessage(Long messageId) {
return postedImageRepository.findByPostId_MessageId(messageId);
}
@Override
public Optional<PostedImage> getPostFromMessageAndPositionOptional(Long messageId, Integer position) {
return postedImageRepository.findById(new PostIdentifier(messageId, position));
}
@Override
public PostedImage getPostFromMessageAndPosition(Long messageId, Integer position) {
return getPostFromMessageAndPositionOptional(messageId, position).orElseThrow(() -> new PostedImageNotFoundException(messageId, position));
}
@Override
public void removePostedImagesOf(AUserInAServer poster) {
postedImageRepository.deleteByPoster(poster);
}
@Override
public void removedPostedImagesIn(AServer aServer) {
postedImageRepository.deleteByServer(aServer);
}
}

View File

@@ -0,0 +1,61 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.utility.exception.RepostCheckChannelGroupNotFoundException;
import dev.sheldan.abstracto.utility.models.database.RepostCheckChannelGroup;
import dev.sheldan.abstracto.utility.repository.RepostCheckChannelRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class RepostCheckChannelGroupManagementBean implements RepostCheckChannelGroupManagement {
@Autowired
private RepostCheckChannelRepository repository;
@Override
public RepostCheckChannelGroup loadRepostChannelGroupById(Long channelGroupId) {
return loadRepostChanelGroupByIdOptional(channelGroupId).orElseThrow(() -> new RepostCheckChannelGroupNotFoundException(channelGroupId));
}
@Override
public Optional<RepostCheckChannelGroup> loadRepostChanelGroupByIdOptional(Long channelGroupId) {
return repository.findById(channelGroupId);
}
@Override
public boolean repostCheckChannelGroupExists(Long channelGroupId) {
return loadRepostChanelGroupByIdOptional(channelGroupId).isPresent();
}
@Override
public Optional<RepostCheckChannelGroup> loadRepostChannelGroupByChannelGroupOptional(AChannelGroup channelGroup) {
return loadRepostChanelGroupByIdOptional(channelGroup.getId());
}
@Override
public RepostCheckChannelGroup loadRepostChannelGroupByChannelGroup(AChannelGroup channelGroup) {
return loadRepostChannelGroupById(channelGroup.getId());
}
@Override
public RepostCheckChannelGroup createRepostCheckChannelGroup(AChannelGroup channelGroup) {
RepostCheckChannelGroup repostCheckChannelGroup = RepostCheckChannelGroup
.builder()
.checkEnabled(true)
.channelGroup(channelGroup)
.id(channelGroup.getId())
.build();
repository.save(repostCheckChannelGroup);
return repostCheckChannelGroup;
}
@Override
public void deleteRepostCheckChannelGroup(AChannelGroup channelGroup) {
RepostCheckChannelGroup repostCheckChannelGroup = loadRepostChannelGroupByChannelGroup(channelGroup);
repository.delete(repostCheckChannelGroup);
}
}

View File

@@ -0,0 +1,85 @@
package dev.sheldan.abstracto.utility.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.exception.RepostNotFoundException;
import dev.sheldan.abstracto.utility.models.database.PostedImage;
import dev.sheldan.abstracto.utility.models.database.Repost;
import dev.sheldan.abstracto.utility.models.database.embed.RepostIdentifier;
import dev.sheldan.abstracto.utility.models.database.result.RepostLeaderboardResult;
import dev.sheldan.abstracto.utility.repository.RepostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class RepostManagementServiceBean implements RepostManagementService {
@Autowired
private RepostRepository repostRepository;
@Override
public Repost createRepost(PostedImage postedImage, AUserInAServer poster) {
Repost repost = Repost
.builder()
.originalPost(postedImage)
.poster(poster)
.repostId(buildRepostIdentifier(postedImage, poster))
.server(postedImage.getServer())
.count(1)
.build();
repostRepository.save(repost);
return repost;
}
@Override
public Repost setRepostCount(PostedImage postedImage, AUserInAServer poster, Integer newCount) {
Repost repost = findRepost(postedImage, poster);
repost.setCount(newCount);
return repost;
}
@Override
public Repost findRepost(PostedImage postedImage, AUserInAServer poster) {
return findRepostOptional(postedImage, poster)
.orElseThrow(() -> new RepostNotFoundException(postedImage.getPostId().getMessageId(), postedImage.getPostId().getPosition(), poster.getUserInServerId()));
}
@Override
public Optional<Repost> findRepostOptional(PostedImage postedImage, AUserInAServer poster) {
return repostRepository.findById(buildRepostIdentifier(postedImage, poster));
}
@Override
public List<RepostLeaderboardResult> findTopRepostingUsersOfServer(AServer server, Integer page, Integer pageSize) {
return findTopRepostingUsersOfServer(server.getId(), page, pageSize);
}
@Override
public List<RepostLeaderboardResult> findTopRepostingUsersOfServer(Long serverId, Integer page, Integer pageSize) {
return repostRepository.findTopRepostingUsers(serverId, PageRequest.of(page - 1, pageSize));
}
@Override
public RepostLeaderboardResult getRepostRankOfUser(AUserInAServer aUserInAServer) {
return repostRepository.getRepostRankOfUserInServer(aUserInAServer.getUserInServerId(), aUserInAServer.getServerReference().getId());
}
@Override
public void deleteRepostsFromUser(AUserInAServer aUserInAServer) {
repostRepository.deleteByRepostId_UserInServerIdAndServerId(aUserInAServer.getUserInServerId(), aUserInAServer.getServerReference().getId());
}
@Override
public void deleteRepostsFromServer(AServer server) {
repostRepository.deleteByServerId(server.getId());
}
private RepostIdentifier buildRepostIdentifier(PostedImage postedImage, AUserInAServer poster) {
return new RepostIdentifier(postedImage.getPostId().getMessageId(), postedImage.getPostId().getPosition(), poster.getUserInServerId());
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="repost_channel_group_type-insertion">
<insert tableName="channel_group_type">
<column name="group_type_key" value="repostDetection"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -7,10 +7,12 @@
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<property name="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="repostDetectionModule" value="(SELECT id FROM module WHERE name = 'repostDetection')"/>
<property name="remindFeature" value="(SELECT id FROM feature WHERE key = 'remind')"/>
<property name="starboardFeature" value="(SELECT id FROM feature WHERE key = 'starboard')"/>
<property name="suggestionFeature" value="(SELECT id FROM feature WHERE key = 'suggestion')"/>
<property name="utilityFeature" value="(SELECT id FROM feature WHERE key = 'utility')"/>
<property name="repostDetectionFeature" value="(SELECT id FROM feature WHERE key = 'repostDetection')"/>
<property name="today" value="(SELECT NOW())"/>
<changeSet author="Sheldan" id="utility_remind-commands">
@@ -87,6 +89,43 @@
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
<changeSet author="Sheldan" id="utility_repostDetection-commands">
<insert tableName="command">
<column name="name" value="disableRepostCheck"/>
<column name="module_id" valueComputed="${repostDetectionModule}"/>
<column name="feature_id" valueComputed="${repostDetectionFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="enableRepostCheck"/>
<column name="module_id" valueComputed="${repostDetectionModule}"/>
<column name="feature_id" valueComputed="${repostDetectionFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="purgeImagePosts"/>
<column name="module_id" valueComputed="${repostDetectionModule}"/>
<column name="feature_id" valueComputed="${repostDetectionFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="purgeReposts"/>
<column name="module_id" valueComputed="${repostDetectionModule}"/>
<column name="feature_id" valueComputed="${repostDetectionFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="repostLeaderboard"/>
<column name="module_id" valueComputed="${repostDetectionModule}"/>
<column name="feature_id" valueComputed="${repostDetectionFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="showRepostCheckChannels"/>
<column name="module_id" valueComputed="${repostDetectionModule}"/>
<column name="feature_id" valueComputed="${remindFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -13,4 +13,6 @@
<include file="default_posttarget.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
<include file="reminder_job.xml" relativeToChangelogFile="true"/>
<include file="default_feature_mode.xml" relativeToChangelogFile="true"/>
<include file="channel_group_types.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -63,5 +63,10 @@
<column name="name" value="🗑"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="default_emote">
<column name="emote_key" value="repostMarker"/>
<column name="name" value="♻️"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -11,6 +11,7 @@
<property name="suggestionFeature" value="(SELECT id FROM feature WHERE key = 'suggestion')"/>
<property name="utilityFeature" value="(SELECT id FROM feature WHERE key = 'utility')"/>
<property name="linkEmbedFeature" value="(SELECT id FROM feature WHERE key = 'link_embeds')"/>
<property name="repostDetectionFeature" value="(SELECT id FROM feature WHERE key = 'repostDetection')"/>
<property name="today" value="(SELECT NOW())"/>
<changeSet author="Sheldan" id="utility_default_feature_flag-insertion">
<insert tableName="default_feature_flag">
@@ -38,5 +39,10 @@
<column name="feature_id" valueComputed="${linkEmbedFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="default_feature_flag">
<column name="enabled" value="false"/>
<column name="feature_id" valueComputed="${repostDetectionFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,25 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<property name="repostDetectionFeature" value="(SELECT id FROM feature WHERE key = 'repostDetection')"/>
<property name="today" value="(SELECT NOW())"/>
<changeSet author="Sheldan" id="utilities_default_feature_mode-insertion">
<insert tableName="default_feature_mode">
<column name="enabled" value="true"/>
<column name="mode" value="download"/>
<column name="feature_id" valueComputed="${repostDetectionFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="default_feature_mode">
<column name="enabled" value="true"/>
<column name="mode" value="leaderboard"/>
<column name="feature_id" valueComputed="${repostDetectionFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -28,5 +28,9 @@
<column name="key" value="link_embeds"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="feature">
<column name="key" value="repostDetection"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,16 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<property name="today" value="(SELECT NOW())"/>
<changeSet author="Sheldan" id="utility-module-insertion">
<insert tableName="module">
<column name="name" value="repostDetection"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,46 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="posted_image-table">
<createTable tableName="posted_image">
<column name="message_id" type="BIGINT" />
<column name="position" type="INTEGER" />
<column name="posting_user_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="posted_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="image_hash" type="VARCHAR(512)">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addPrimaryKey columnNames="message_id, position" tableName="posted_image" constraintName="pk_posted_image" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="posted_image-fk_posted_image_user">
<addForeignKeyConstraint baseColumnNames="posting_user_id" baseTableName="posted_image" constraintName="fk_posted_image_user"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="posted_image-fk_posted_image_channel">
<addForeignKeyConstraint baseColumnNames="posted_channel_id" baseTableName="posted_image" constraintName="fk_posted_image_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="channel" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="posted_image-fk_posted_image_server">
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="posted_image" constraintName="fk_posted_image_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="server" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,44 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="repost-table">
<createTable tableName="repost">
<column name="message_id" type="BIGINT" />
<column name="position" type="INTEGER" />
<column name="user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="count" type="INTEGER">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addPrimaryKey columnNames="message_id, position, user_in_server_id" tableName="repost" constraintName="pk_repost" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="repost-fk_repost_server">
<addForeignKeyConstraint baseColumnNames="user_in_server_id" baseTableName="repost" constraintName="fk_repost_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="server" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="repost-fk_repost_posted_image">
<addForeignKeyConstraint baseColumnNames="message_id, position" baseTableName="repost" constraintName="fk_repost_posted_image"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="message_id, position"
referencedTableName="posted_image" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="repost-fk_repost_user_in_server">
<addForeignKeyConstraint baseColumnNames="user_in_server_id" baseTableName="repost" constraintName="fk_repost_user_in_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id"
referencedTableName="user_in_server" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,23 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="repost_check_channel_group-table">
<createTable tableName="repost_check_channel_group">
<column name="id" type="BIGINT" />
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="enabled" type="BOOLEAN"/>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="repost_check_channel_group-fk_repost_check_channel_group_group">
<addForeignKeyConstraint baseColumnNames="id" baseTableName="repost_check_channel_group" constraintName="fk_repost_check_channel_group_group"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="channel_group" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -1,42 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="starboard-table">
<createTable tableName="starboard_post">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="starboard_post_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="ignored" type="BOOLEAN"/>
<column name="post_message_id" type="BIGINT"/>
<column name="starboard_message_id" type="BIGINT"/>
<column name="starred_date" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="poster" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="source_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="starboard-fk_starboard_post_poster">
<addForeignKeyConstraint baseColumnNames="poster" baseTableName="starboard_post" constraintName="fk_starboard_post_poster" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="starboard-fk_starboard_post_channel">
<addForeignKeyConstraint baseColumnNames="channel_id" baseTableName="starboard_post" constraintName="fk_starboard_post_channel" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="channel" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="starboard-fk_starboard_post_source_channel">
<addForeignKeyConstraint baseColumnNames="source_channel_id" baseTableName="starboard_post" constraintName="fk_starboard_post_source_channel" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="channel" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -6,23 +6,37 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="starboard_post_reaction-table">
<createTable tableName="starboard_post_reaction">
<changeSet author="Sheldan" id="starboard-table">
<createTable tableName="starboard_post">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="starboard_post_reaction_pkey"/>
<constraints nullable="false" primaryKey="true" primaryKeyName="starboard_post_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="reactor_id" type="BIGINT">
<column name="ignored" type="BOOLEAN"/>
<column name="post_message_id" type="BIGINT"/>
<column name="starboard_message_id" type="BIGINT"/>
<column name="starred_date" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="poster" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="source_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="post_id" type="BIGINT"/>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="starboard_post_reaction-fk_starboard_post_reaction_reactor">
<addForeignKeyConstraint baseColumnNames="reactor_id" baseTableName="starboard_post_reaction" constraintName="fk_starboard_post_reaction_reactor" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<changeSet author="Sheldan" id="starboard-fk_starboard_post_poster">
<addForeignKeyConstraint baseColumnNames="poster" baseTableName="starboard_post" constraintName="fk_starboard_post_poster" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="starboard_post_reaction-fk_starboard_post_reaction_post">
<addForeignKeyConstraint baseColumnNames="post_id" baseTableName="starboard_post_reaction" constraintName="fk_starboard_post_reaction_post" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="starboard_post" validate="true"/>
<changeSet author="Sheldan" id="starboard-fk_starboard_post_channel">
<addForeignKeyConstraint baseColumnNames="channel_id" baseTableName="starboard_post" constraintName="fk_starboard_post_channel" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="channel" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="starboard-fk_starboard_post_source_channel">
<addForeignKeyConstraint baseColumnNames="source_channel_id" baseTableName="starboard_post" constraintName="fk_starboard_post_source_channel" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="channel" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,28 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="starboard_post_reaction-table">
<createTable tableName="starboard_post_reaction">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="starboard_post_reaction_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="reactor_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="post_id" type="BIGINT"/>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="starboard_post_reaction-fk_starboard_post_reaction_reactor">
<addForeignKeyConstraint baseColumnNames="reactor_id" baseTableName="starboard_post_reaction" constraintName="fk_starboard_post_reaction_reactor" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="starboard_post_reaction-fk_starboard_post_reaction_post">
<addForeignKeyConstraint baseColumnNames="post_id" baseTableName="starboard_post_reaction" constraintName="fk_starboard_post_reaction_post" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="starboard_post" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -8,7 +8,10 @@
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<include file="embedded_message.xml" relativeToChangelogFile="true"/>
<include file="reminder.xml" relativeToChangelogFile="true"/>
<include file="starboard.xml" relativeToChangelogFile="true"/>
<include file="starboard_post.xml" relativeToChangelogFile="true"/>
<include file="starboard_post_reaction.xml" relativeToChangelogFile="true"/>
<include file="suggestion.xml" relativeToChangelogFile="true"/>
<include file="repost_check_channel_group.xml" relativeToChangelogFile="true"/>
<include file="posted_image.xml" relativeToChangelogFile="true"/>
<include file="repost.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>