added jda utilities dependency

added modmail with some simple features (chat between user and moderator), and initial server selection (currently allows all servers, and does not consider the modmail feature)
added the initial commands, not all of them hold logic currently
added some checks to not crash when a command has null configuration
changed systemconfig to also have a long value, because double is not fit for all cases
added locking mechanism, to effectively lock the whole table without using the ... proper locking mechanism (needs to be reviewed)
changed interface in channel service to be a message channel instead of a textChannel
changed starboard post to be a user in a server instead of only user
This commit is contained in:
Sheldan
2020-05-06 14:17:44 +02:00
parent cf713cc561
commit a06006d763
99 changed files with 1506 additions and 48 deletions

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>modmail-impl</artifactId>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail-int</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.modmail.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
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.modmail.commands.condition.RequiresModMailCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class AnonReply extends AbstractConditionableCommand {
@Autowired
private RequiresModMailCondition requiresModMailCondition;
@Override
public CommandResult execute(CommandContext commandContext) {
return null;
}
@Override
public CommandConfiguration getConfiguration() {
return null;
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MODMAIL;
}
@Override
public List<CommandCondition> getConditions() {
List<CommandCondition> conditions = super.getConditions();
conditions.add(requiresModMailCondition);
return conditions;
}
}

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.modmail.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
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.modmail.commands.condition.RequiresModMailCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class Close extends AbstractConditionableCommand {
@Autowired
private RequiresModMailCondition requiresModMailCondition;
@Override
public CommandResult execute(CommandContext commandContext) {
return null;
}
@Override
public CommandConfiguration getConfiguration() {
return null;
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MODMAIL;
}
@Override
public List<CommandCondition> getConditions() {
List<CommandCondition> conditions = super.getConditions();
conditions.add(requiresModMailCondition);
return conditions;
}
}

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.modmail.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
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.modmail.commands.condition.RequiresModMailCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class CloseSilently extends AbstractConditionableCommand {
@Autowired
private RequiresModMailCondition requiresModMailCondition;
@Override
public CommandResult execute(CommandContext commandContext) {
return null;
}
@Override
public CommandConfiguration getConfiguration() {
return null;
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MODMAIL;
}
@Override
public List<CommandCondition> getConditions() {
List<CommandCondition> conditions = super.getConditions();
conditions.add(requiresModMailCondition);
return conditions;
}
}

View File

@@ -0,0 +1,28 @@
package dev.sheldan.abstracto.modmail.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
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.modmail.config.ModMailFeatures;
import org.springframework.stereotype.Component;
@Component
public class Contact extends AbstractConditionableCommand {
@Override
public CommandResult execute(CommandContext commandContext) {
return null;
}
@Override
public CommandConfiguration getConfiguration() {
return null;
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MODMAIL;
}
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.modmail.commands;
import dev.sheldan.abstracto.core.command.config.ModuleInfo;
import dev.sheldan.abstracto.core.command.config.ModuleInterface;
import org.springframework.stereotype.Component;
@Component
public class ModMailModuleInterface implements ModuleInterface {
public static final String MODMAIL = "modMail";
@Override
public ModuleInfo getInfo() {
return ModuleInfo.builder().name(MODMAIL).description("Commands to be used for modmail.").build();
}
@Override
public String getParentModule() {
return "default";
}
}

View File

@@ -0,0 +1,67 @@
package dev.sheldan.abstracto.modmail.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
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.modmail.commands.condition.RequiresModMailCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class Reply extends AbstractConditionableCommand {
@Autowired
private RequiresModMailCondition requiresModMailCondition;
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
ModMailThread thread = modMailThreadManagementService.getByChannel(commandContext.getUserInitiatedContext().getChannel());
modMailThreadService.relayMessageToDm(thread, commandContext.getMessage());
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter responseText = Parameter.builder().name("text").type(String.class).description("The text to reply with").build();
List<Parameter> parameters = Arrays.asList(responseText);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("reply")
.module(ModMailModuleInterface.MODMAIL)
.parameters(parameters)
.help(helpInfo)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MODMAIL;
}
@Override
public List<CommandCondition> getConditions() {
List<CommandCondition> conditions = super.getConditions();
conditions.add(requiresModMailCondition);
return conditions;
}
}

View File

@@ -0,0 +1,52 @@
package dev.sheldan.abstracto.modmail.commands;
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.modmail.config.ModMailFeatures;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class SetModMailCategory extends AbstractConditionableCommand {
@Autowired
private ModMailThreadService modMailThreadService;
@Override
public CommandResult execute(CommandContext commandContext) {
Long categoryId = (Long) commandContext.getParameters().getParameters().get(0);
modMailThreadService.setModMailCategoryTo(commandContext.getUserInitiatedContext().getServer(), categoryId);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter categoryId = Parameter.builder().name("categoryId").type(Long.class).description("The category id to be used for modmail.").build();
List<Parameter> parameters = Arrays.asList(categoryId);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
List<String> aliases = Arrays.asList("modMailCat");
return CommandConfiguration.builder()
.name("setModMailCategory")
.module(ModMailModuleInterface.MODMAIL)
.aliases(aliases)
.parameters(parameters)
.help(helpInfo)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MODMAIL;
}
}

View File

@@ -0,0 +1,26 @@
package dev.sheldan.abstracto.modmail.commands.condition;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.condition.ConditionResult;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RequiresModMailCondition implements CommandCondition {
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Override
public ConditionResult shouldExecute(CommandContext commandContext, Command command) {
ModMailThread thread = modMailThreadManagementService.getByChannel(commandContext.getUserInitiatedContext().getChannel());
if(thread != null) {
return ConditionResult.builder().result(true).build();
}
return ConditionResult.builder().result(false).reason("Not in a mod mail thread.").build();
}
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.modmail.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import org.springframework.stereotype.Component;
@Component
public class ModMailFeature implements FeatureConfig {
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MODMAIL;
}
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.modmail.config;
import dev.sheldan.abstracto.core.config.FeatureEnum;
public enum ModMailFeatures implements FeatureEnum {
MODMAIL("modmail");
private String key;
ModMailFeatures(String key) {
this.key = key;
}
@Override
public String getKey() {
return this.key;
}
}

View File

@@ -0,0 +1,52 @@
package dev.sheldan.abstracto.modmail.listener;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.listener.PrivateMessageReceivedListener;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.modmail.config.ModMailFeature;
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Slf4j
public class ModMailMessageListener implements PrivateMessageReceivedListener {
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private UserManagementService userManagementService;
@Autowired
private TemplateService templateService;
@Override
@Transactional
public void execute(Message message) {
AUser user = userManagementService.loadUser(message.getAuthor().getIdLong());
ModMailThread existingThread = modMailThreadManagementService.getOpenModmailThreadForUser(user);
if(existingThread != null) {
modMailThreadService.relayMessageToModMailThread(existingThread, message);
} else {
modMailThreadService.createModMailPrompt(user, message.getChannel());
}
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MODMAIL;
}
}

View File

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

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.modmail.repository;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.models.database.ModMailThreadState;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ModMailThreadRepository extends JpaRepository<ModMailThread, Long> {
ModMailThread findByChannel(AChannel channel);
List<ModMailThread> findByUser(AUserInAServer aUserInAServer);
ModMailThread findByUser_UserReferenceAndStateNot(AUser user, ModMailThreadState state);
List<ModMailThread> findByServerAndState(AServer server, ModMailThreadState state);
ModMailThread findByUserAndStateNot(AUserInAServer userInAServer, ModMailThreadState state);
List<ModMailThread> findByUserAndState(AUserInAServer userInAServer, ModMailThreadState state);
}

View File

@@ -0,0 +1,243 @@
package dev.sheldan.abstracto.modmail.service;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import com.jagrosh.jdautilities.menu.ButtonMenu;
import dev.sheldan.abstracto.core.models.FullGuild;
import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.core.models.cache.CachedReaction;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.models.dto.ServerChoice;
import dev.sheldan.abstracto.modmail.models.template.ModMailModeratorReplyModel;
import dev.sheldan.abstracto.modmail.models.template.ModMailServerChooserModel;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
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.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@Component
@Slf4j
public class ModMailThreadServiceBean implements ModMailThreadService {
public static final String MODMAIL_CATEGORY = "modmailCategory";
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private ConfigService configService;
@Autowired
private ChannelService channelService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private BotService botService;
@Autowired
private TemplateService templateService;
@Autowired
private ModMailMessageManagementService modMailMessageManagementService;
@Autowired
private ModMailThreadServiceBean self;
private List<String> NUMBER_EMOJI = Arrays.asList("\u0031\u20e3", "\u0032\u20e3", "\u0033\u20e3",
"\u0034\u20e3", "\u0035\u20e3", "\u0036\u20e3",
"\u0037\u20e3", "\u0038\u20e3", "\u0039\u20e3",
"\u0040\u20e3");
@Override
public void createModMailThreadForUser(FullUser aUserInAServer) {
Long categoryId = configService.getLongValue(MODMAIL_CATEGORY, aUserInAServer.getAUserInAServer().getServerReference().getId());
User user = aUserInAServer.getMember().getUser();
CompletableFuture<TextChannel> textChannel = channelService.createTextChannel(user.getName() + user.getDiscriminator(), aUserInAServer.getAUserInAServer().getServerReference(), categoryId);
textChannel.thenAccept(channel -> {
self.createThreadObject(channel, aUserInAServer);
self.sendWelcomeMessage(channel, aUserInAServer);
});
}
@Transactional
public void createThreadObject(TextChannel channel, FullUser user) {
AChannel channel2 = channelManagementService.createChannel(channel.getIdLong(), AChannelType.TEXT, user.getAUserInAServer().getServerReference());
log.info("Creating mod mail thread in channel {} with db channel {}", channel.getIdLong(), channel2.getId());
modMailThreadManagementService.createModMailThread(user.getAUserInAServer(), channel2);
}
@Override
public boolean hasOpenThread(AUserInAServer aUserInAServer) {
return modMailThreadManagementService.getOpenModmailThreadForUser(aUserInAServer) != null;
}
@Override
public boolean hasOpenThread(AUser user) {
return modMailThreadManagementService.getOpenModmailThreadForUser(user) != null;
}
@Override
public void setModMailCategoryTo(AServer server, Long categoryId) {
configService.setLongValue(MODMAIL_CATEGORY, server.getId(), categoryId);
}
@Override
public void createModMailPrompt(AUser user, MessageChannel channel) {
List<AUserInAServer> knownServers = userInServerManagementService.getUserInAllServers(user.getId());
if(knownServers.size() > 0) {
List<ServerChoice> availableGuilds = new ArrayList<>();
HashMap<String, AUserInAServer> choices = new HashMap<>();
for (int i = 0; i < knownServers.size(); i++) {
AUserInAServer aUserInAServer = knownServers.get(i);
AServer serverReference = aUserInAServer.getServerReference();
FullGuild guild = FullGuild
.builder()
.guild(botService.getGuildByIdNullable(serverReference.getId()))
.server(serverReference)
.build();
String reactionEmote = NUMBER_EMOJI.get(i);
ServerChoice serverChoice = ServerChoice.builder().guild(guild).reactionEmote(reactionEmote).build();
choices.put(reactionEmote, aUserInAServer);
availableGuilds.add(serverChoice);
}
ModMailServerChooserModel modMailServerChooserModel = ModMailServerChooserModel
.builder()
.commonGuilds(availableGuilds)
.build();
String text = templateService.renderTemplate("modmail_modal_server_choice", modMailServerChooserModel);
// todo dont instantiate directly
EventWaiter waiter = new EventWaiter();
botService.getInstance().addEventListener(waiter);
ButtonMenu menu = new ButtonMenu.Builder()
.setChoices(choices.keySet().toArray(new String[0]))
.setEventWaiter(waiter)
.setDescription(text)
.setAction(reactionEmote -> {
AUserInAServer chosenServer = choices.get(reactionEmote.getEmoji());
Member memberInServer = botService.getMemberInServer(chosenServer);
FullUser fullUser = FullUser.builder().member(memberInServer).aUserInAServer(chosenServer).build();
self.createModMailThreadForUser(fullUser);
botService.getInstance().removeEventListener(waiter);
})
.build();
menu.display(channel);
}
}
@Override
public void sendWelcomeMessage(TextChannel channel, FullUser aUserInAServer) {
String text = templateService.renderTemplate("modmail_welcome_message", new Object());
channel.sendMessage(text).queue();
}
@Override
public void relayMessageToModMailThread(ModMailThread modMailThread, Message message) {
Optional<TextChannel> textChannelFromServer = botService.getTextChannelFromServer(modMailThread.getServer().getId(), modMailThread.getChannel().getId());
if(textChannelFromServer.isPresent()) {
TextChannel textChannel = textChannelFromServer.get();
self.sendUserReply(textChannel, modMailThread, message);
}
}
@Transactional
public void sendUserReply(TextChannel textChannel, ModMailThread modMailThread, Message message) {
FullUser fullUser = FullUser
.builder()
.aUserInAServer(modMailThread.getUser())
.member(botService.getMemberInServer(modMailThread.getUser()))
.build();
ModMailModeratorReplyModel modMailUserReplyModel = ModMailModeratorReplyModel
.builder()
.modMailThread(modMailThread)
.postedMessage(message)
.threadUser(fullUser)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_moderator_message", modMailUserReplyModel);
List<CompletableFuture<Message>> completableFutures = channelService.sendMessageToEndInTextChannel(messageToSend, textChannel);
List<Message> messages = new ArrayList<>();
completableFutures.forEach(messageCompletableFuture -> {
try {
Message messageToAdd = messageCompletableFuture.get();
messages.add(messageToAdd);
} catch (InterruptedException | ExecutionException e) {
log.error("Error while executing future to retrieve reaction.", e);
}
self.saveMessageIds(messages, modMailThread, modMailThread.getUser(), false);
});
}
@Override
public void relayMessageToDm(ModMailThread modMailThread, Message message) {
User userById = botService.getInstance().getUserById(modMailThread.getUser().getUserReference().getId());
if(userById != null) {
userById.openPrivateChannel().queue(privateChannel -> {
self.sendReply(modMailThread, message, privateChannel);
});
}
}
@Transactional
public void sendReply(ModMailThread modMailThread, Message message, PrivateChannel privateChannel) {
AUserInAServer moderator = userInServerManagementService.loadUser(message.getMember());
Member userInGuild = botService.getMemberInServer(modMailThread.getUser());
FullUser moderatorUser = FullUser
.builder()
.aUserInAServer(moderator)
.member(message.getMember())
.build();
FullUser fullThreadUser = FullUser
.builder()
.aUserInAServer(modMailThread.getUser())
.member(userInGuild)
.build();
ModMailModeratorReplyModel modMailUserReplyModel = ModMailModeratorReplyModel
.builder()
.modMailThread(modMailThread)
.postedMessage(message)
.threadUser(fullThreadUser)
.moderator(moderatorUser)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_staff_message", modMailUserReplyModel);
List<CompletableFuture<Message>> completableFutures = channelService.sendMessageToEndInTextChannel(messageToSend, privateChannel);
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).thenAccept(aVoid -> {
List<Message> messages = new ArrayList<>();
completableFutures.forEach(messageCompletableFuture -> {
try {
Message messageToAdd = messageCompletableFuture.get();
messages.add(messageToAdd);
} catch (InterruptedException | ExecutionException e) {
log.error("Error while executing send message to reply to user.", e);
}
});
self.saveMessageIds(messages, modMailThread, moderator, false);
});
}
@Transactional
public void saveMessageIds(List<Message> messages, ModMailThread modMailThread, AUserInAServer author, Boolean anonymous) {
messages.forEach(message -> {
modMailMessageManagementService.addMessageToThread(modMailThread, message, author, false);
});
}
}

View File

@@ -0,0 +1,30 @@
package dev.sheldan.abstracto.modmail.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.modmail.models.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.repository.ModMailMessageRepository;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ModMailMessageManagementServiceBean implements ModMailMessageManagementService {
@Autowired
private ModMailMessageRepository modMailMessageRepository;
@Override
public ModMailMessage addMessageToThread(ModMailThread modMailThread, Message message, AUserInAServer author, Boolean anonymous) {
ModMailMessage modMailMessage = ModMailMessage
.builder()
.author(author)
.messageId(message.getIdLong())
.threadReference(modMailThread)
.anonymous(anonymous)
.build();
modMailMessageRepository.save(modMailMessage);
return modMailMessage;
}
}

View File

@@ -0,0 +1,70 @@
package dev.sheldan.abstracto.modmail.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.models.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.repository.ModMailThreadRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.List;
@Component
public class ModMailThreadManagementServiceBean implements ModMailThreadManagementService {
@Autowired
private ModMailThreadRepository modMailThreadRepository;
@Autowired
private ChannelManagementService channelManagementService;
@Override
public ModMailThread getByChannelId(Long channelId) {
AChannel channel = channelManagementService.loadChannel(channelId);
return getByChannel(channel);
}
@Override
public ModMailThread getByChannel(AChannel channel) {
return modMailThreadRepository.findByChannel(channel);
}
@Override
public List<ModMailThread> getThreadByUserAndState(AUserInAServer userInAServer, ModMailThreadState state) {
return modMailThreadRepository.findByUserAndState(userInAServer, state);
}
@Override
public ModMailThread getOpenModmailThreadForUser(AUserInAServer userInAServer) {
return modMailThreadRepository.findByUserAndStateNot(userInAServer, ModMailThreadState.CLOSED);
}
@Override
public ModMailThread getOpenModmailThreadForUser(AUser user) {
return modMailThreadRepository.findByUser_UserReferenceAndStateNot(user, ModMailThreadState.CLOSED);
}
@Override
public List<ModMailThread> getModMailThreadForUser(AUserInAServer aUserInAServer) {
return modMailThreadRepository.findByUser(aUserInAServer);
}
@Override
public void createModMailThread(AUserInAServer userInAServer, AChannel channel) {
ModMailThread thread = ModMailThread
.builder()
.channel(channel)
.created(Instant.now())
.user(userInAServer)
.server(userInAServer.getServerReference())
.state(ModMailThreadState.INITIAL)
.updated(Instant.now())
.build();
modMailThreadRepository.save(thread);
}
}

View File

@@ -0,0 +1,5 @@
<#include "server_chooser_server_list_description">
<#list commonGuilds as guild>
${guild.reactionEmote} ${guild.guild.guild.name}
</#list>

View File

@@ -0,0 +1,20 @@
{
"author": {
"name": "${threadUser.member.effectiveName}",
"avatar": "${threadUser.member.user.effectiveAvatarUrl}"
},
"title": {
"title": "<#include "modmail_thread_staff_message_title">"
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
<#if postedMessage.contentRaw?has_content>
"description": "${postedMessage.contentRaw}"
</#if>
<#if postedMessage.attachments?size gt 0>
,"imageUrl": "${postedMessage.attachments[0].proxyUrl}"
</#if>
}

View File

@@ -0,0 +1,20 @@
{
"author": {
"name": "${threadUser.member.effectiveName}",
"avatar": "${threadUser.member.user.effectiveAvatarUrl}"
},
"title": {
"title": "<#include "modmail_thread_user_message_title">"
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
<#if postedMessage.contentRaw?has_content>
"description": "${postedMessage.contentRaw}"
</#if>
<#if postedMessage.attachments?size gt 0>
,"imageUrl": "${postedMessage.attachments[0].proxyUrl}"
</#if>
}

View File

@@ -0,0 +1 @@
You opened a mod mail thread on the server reply in this channel to send messages to a staff only channel.

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>modmail-int</artifactId>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.templating</groupId>
<artifactId>templating-interface</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,32 @@
package dev.sheldan.abstracto.modmail.models.database;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "modmail_messages")
@Cacheable
@Getter
@Setter
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ModMailMessage {
@Id
private Long messageId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "modmail_message_author", nullable = false)
private AUserInAServer author;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "threadReference", nullable = false)
private ModMailThread threadReference;
private Boolean anonymous;
}

View File

@@ -0,0 +1,62 @@
package dev.sheldan.abstracto.modmail.models.database;
import dev.sheldan.abstracto.core.models.database.*;
import lombok.*;
import net.dv8tion.jda.api.entities.ChannelType;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "modmail_threads")
@Cacheable
@Getter
@Setter
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ModMailThread {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "modmail_user", nullable = false)
private AUserInAServer user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "modmail_thread_channel", nullable = false)
private AChannel channel;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "modmail_thread_server", nullable = false)
private AServer server;
@Column
private Instant created;
@Column
private Instant updated;
@Column
private Instant closed;
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
@JoinColumn(name = "threadReference")
@Builder.Default
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<ModMailMessage> messages = new ArrayList<>();
@Enumerated(EnumType.STRING)
@Column
private ModMailThreadState state;
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.abstracto.modmail.models.database;
public enum ModMailThreadState {
INITIAL, USER_REPLIED, MOD_REPLIED, CLOSED, CLOSING;
public static ModMailThreadState getState(ModMailThreadState type) {
switch (type) {
case INITIAL: return ModMailThreadState.INITIAL;
case USER_REPLIED: return ModMailThreadState.USER_REPLIED;
case CLOSED: return ModMailThreadState.CLOSED;
case CLOSING: return ModMailThreadState.CLOSING;
default:
case MOD_REPLIED: return ModMailThreadState.MOD_REPLIED;
}
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.modmail.models.dto;
import dev.sheldan.abstracto.core.models.FullGuild;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class ServerChoice {
private FullGuild guild;
private String reactionEmote;
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.modmail.models.template;
import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Message;
@Getter
@Setter
@Builder
public class ModMailModeratorReplyModel {
private FullUser threadUser;
private FullUser moderator;
private Message postedMessage;
private ModMailThread modMailThread;
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.modmail.models.template;
import dev.sheldan.abstracto.modmail.models.dto.ServerChoice;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class ModMailServerChooserModel {
private List<ServerChoice> commonGuilds;
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.abstracto.modmail.models.template;
import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Message;
@Getter
@Setter
@Builder
public class ModMailUserReplyModel {
private FullUser threadUser;
private Message postedMessage;
private ModMailThread modMailThread;
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.modmail.service;
import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.TextChannel;
public interface ModMailThreadService {
void createModMailThreadForUser(FullUser userInAServer);
boolean hasOpenThread(AUserInAServer aUserInAServer);
boolean hasOpenThread(AUser user);
void setModMailCategoryTo(AServer server, Long categoryId);
void createModMailPrompt(AUser user, MessageChannel messageChannel);
void sendWelcomeMessage(TextChannel channel, FullUser aUserInAServer);
void relayMessageToModMailThread(ModMailThread modMailThread, Message message);
void relayMessageToDm(ModMailThread modMailThread, Message message);
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.modmail.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.modmail.models.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import net.dv8tion.jda.api.entities.Message;
public interface ModMailMessageManagementService {
ModMailMessage addMessageToThread(ModMailThread modMailThread, Message message, AUserInAServer author, Boolean anonymous);
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.modmail.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.models.database.ModMailThreadState;
import net.dv8tion.jda.api.entities.TextChannel;
import java.util.List;
public interface ModMailThreadManagementService {
ModMailThread getByChannelId(Long channelId);
ModMailThread getByChannel(AChannel channel);
List<ModMailThread> getThreadByUserAndState(AUserInAServer userInAServer, ModMailThreadState state);
ModMailThread getOpenModmailThreadForUser(AUserInAServer userInAServer);
ModMailThread getOpenModmailThreadForUser(AUser user);
List<ModMailThread> getModMailThreadForUser(AUserInAServer aUserInAServer);
void createModMailThread(AUserInAServer userInAServer, AChannel channel);
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<packaging>pom</packaging>
<modules>
<module>modmail-int</module>
<module>modmail-impl</module>
</modules>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-interface</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -15,6 +15,7 @@
<module>moderation</module>
<module>utility</module>
<module>experience-tracking</module>
<module>modmail</module>
</modules>

View File

@@ -24,7 +24,8 @@ public class StarboardPostDeletedListener implements MessageDeletedListener {
Optional<StarboardPost> byStarboardPostId = starboardPostManagementService.findByStarboardPostId(messageBefore.getMessageId());
if(byStarboardPostId.isPresent()) {
StarboardPost post = byStarboardPostId.get();
log.info("Removing starboard post: message {}, channel {}, server {}, because the message was deleted", post.getPostMessageId(), post.getSourceChanel().getId(), post.getAuthor().getId());
log.info("Removing starboard post: message {}, channel {}, server {}, because the message was deleted",
post.getPostMessageId(), post.getSourceChanel().getId(), post.getAuthor().getUserReference().getId());
starboardPostManagementService.setStarboardPostIgnored(messageBefore.getMessageId(), true);
}
}

View File

@@ -25,7 +25,7 @@ public class StarboardPostManagementServiceBean implements StarboardPostManageme
public StarboardPost createStarboardPost(CachedMessage starredMessage, AUserInAServer starredUser, AUserInAServer starringUser, AServerAChannelMessage starboardPost) {
StarboardPost post = StarboardPost
.builder()
.author(starredUser.getUserReference())
.author(starredUser)
.postMessageId(starredMessage.getMessageId())
.starboardMessageId(starboardPost.getMessageId())
.starboardChannel(starboardPost.getChannel())

View File

@@ -1,7 +1,7 @@
package dev.sheldan.abstracto.utility.models.database;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -27,7 +27,7 @@ public class StarboardPost {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "poster", nullable = false)
private AUser author;
private AUserInAServer author;
@Column
private Long starboardMessageId;

View File

@@ -26,9 +26,11 @@ public class CommandConfigListener implements ServerConfigListener {
@Override
public void updateServerConfig(AServer server) {
commandList.forEach(command -> {
ACommand aCommand = commandManagementService.findCommandByName(command.getConfiguration().getName());
if(!commandInServerManagementService.doesCommandExistInServer(aCommand, server)) {
commandInServerManagementService.crateCommandInServer(aCommand, server);
if(command.getConfiguration() != null) {
ACommand aCommand = commandManagementService.findCommandByName(command.getConfiguration().getName());
if(!commandInServerManagementService.doesCommandExistInServer(aCommand, server)) {
commandInServerManagementService.crateCommandInServer(aCommand, server);
}
}
});
}

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
@@ -13,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Component
@Slf4j
public class CommandCreationListener {
@Autowired
@@ -38,6 +40,10 @@ public class CommandCreationListener {
}
});
commandList.forEach(command -> {
if(command.getConfiguration() == null) {
log.warn("Command {} has null configuration.", command);
return;
}
if(!commandService.doesCommandExist(command.getConfiguration().getName())) {
commandService.createCommand(command.getConfiguration().getName(), command.getConfiguration().getModule(), command.getFeature());
}

View File

@@ -33,6 +33,9 @@ public class CommandManager implements CommandRegistry {
public Command findCommandByParameters(String name, UnParsedCommandParameter unParsedCommandParameter) {
Optional<Command> commandOptional = commands.stream().filter((Command o )-> {
CommandConfiguration commandConfiguration = o.getConfiguration();
if(commandConfiguration == null) {
return false;
}
if(!commandNameMatches(name, commandConfiguration)) {
return false;
}
@@ -88,7 +91,8 @@ public class CommandManager implements CommandRegistry {
public List<Command> getAllCommandsFromModule(ModuleInterface moduleInterface) {
List<Command> commandsFromModule = new ArrayList<>();
this.getAllCommands().forEach(command -> {
if(command.getConfiguration().getModule().equals(moduleInterface.getInfo().getName())){
CommandConfiguration configuration = command.getConfiguration();
if(configuration != null && configuration.getModule().equals(moduleInterface.getInfo().getName())){
commandsFromModule.add(command);
}
});

View File

@@ -11,10 +11,12 @@ import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class CommandServiceBean implements CommandService {
@Autowired
@@ -32,6 +34,10 @@ public class CommandServiceBean implements CommandService {
@Override
public ACommand createCommand(String name, String moduleName, FeatureEnum featureEnum) {
AModule module = moduleManagementService.getOrCreate(moduleName);
if(featureEnum == null) {
log.warn("Command {} in module {} has no feature.", name, moduleName);
return null;
}
AFeature feature = featureManagementService.getFeature(featureEnum.getKey());
return commandManagementService.createCommand(name, module, feature);
}

View File

@@ -16,7 +16,7 @@ import java.util.Arrays;
import java.util.List;
@Component
public class SetNumber extends AbstractConditionableCommand {
public class SetFloat extends AbstractConditionableCommand {
@Autowired
private ConfigService configService;
@@ -37,7 +37,7 @@ public class SetNumber extends AbstractConditionableCommand {
List<Parameter> parameters = Arrays.asList(channelGroupName, channelToAdd);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("setNumber")
.name("setFloat")
.module(ConfigModuleInterface.CONFIG)
.parameters(parameters)
.templated(true)

View File

@@ -0,0 +1,53 @@
package dev.sheldan.abstracto.core.commands.config;
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.features.CoreFeatures;
import dev.sheldan.abstracto.core.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class SetLong extends AbstractConditionableCommand {
@Autowired
private ConfigService configService;
@Override
public CommandResult execute(CommandContext commandContext) {
String key = (String) commandContext.getParameters().getParameters().get(0);
Long value = (Long) commandContext.getParameters().getParameters().get(1);
configService.setLongValue(key, commandContext.getGuild().getIdLong(), value);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter channelGroupName = Parameter.builder().name("key").type(String.class).description("The key to change.").build();
Parameter channelToAdd = Parameter.builder().name("value").type(Long.class).description("The numeric value to use for the config.").build();
List<Parameter> parameters = Arrays.asList(channelGroupName, channelToAdd);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("setLong")
.module(ConfigModuleInterface.CONFIG)
.parameters(parameters)
.templated(true)
.help(helpInfo)
.causesReaction(true)
.build();
}
@Override
public FeatureEnum getFeature() {
return CoreFeatures.CORE_FEATURE;
}
}

View File

@@ -39,7 +39,7 @@ public class ChannelListener extends ListenerAdapter {
log.info("Handling channel created event. Channel {}, Server {}", event.getChannel().getIdLong(), event.getGuild().getIdLong());
AServer serverObject = serverRepository.getOne(event.getGuild().getIdLong());
TextChannel createdChannel = event.getChannel();
AChannelType type = AChannel.getAChannelType(createdChannel.getType());
AChannelType type = AChannelType.getAChannelType(createdChannel.getType());
channelManagementService.createChannel(createdChannel.getIdLong(), type, serverObject);
}
}

View File

@@ -1,14 +1,15 @@
package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import java.util.List;
@@ -23,9 +24,15 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
@Autowired
private List<MessageReceivedListener> listenerList;
@Autowired
private List<PrivateMessageReceivedListener> privateMessageReceivedListeners;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private BotService botService;
@Override
public void onGuildMessageReceived(@Nonnull GuildMessageReceivedEvent event) {
messageCache.putMessageInCache(event.getMessage());
@@ -42,5 +49,17 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
});
}
@Override
public void onPrivateMessageReceived(@Nonnull PrivateMessageReceivedEvent event) {
if(event.getAuthor().getId().equals(botService.getInstance().getSelfUser().getId())) {
return;
}
privateMessageReceivedListeners.forEach(messageReceivedListener -> {
try {
messageReceivedListener.execute(event.getMessage());
} catch (Exception e) {
log.error("Listener {} had exception when executing.", messageReceivedListener, e);
}
});
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.core.model.database;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Table(name = "lock")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class ALock {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.model.database.ALock;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import javax.persistence.LockModeType;
@Repository
public interface LockRepository extends JpaRepository<ALock, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select a from ALock a where a.id = :id")
ALock findArticleForRead(@Param("id") Long id);
}

View File

@@ -8,6 +8,7 @@ import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
@Repository
public interface UserInServerRepository extends JpaRepository<AUserInAServer, Long> {
@@ -17,4 +18,7 @@ public interface UserInServerRepository extends JpaRepository<AUserInAServer, Lo
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByServerReferenceAndUserReference(AServer server, AUser user);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<AUserInAServer> findByUserReference(AUser user);
}

View File

@@ -3,13 +3,11 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.ChannelException;
import dev.sheldan.abstracto.core.exception.GuildException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.models.database.AChannel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.requests.restaction.MessageAction;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,7 +31,7 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public void sendTextInAChannel(String text, TextChannel channel) {
public void sendTextInAChannel(String text, MessageChannel channel) {
sendTextInAChannelFuture(text, channel);
}
@@ -55,7 +53,7 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public CompletableFuture<Message> sendTextInAChannelFuture(String text, TextChannel channel) {
public CompletableFuture<Message> sendTextInAChannelFuture(String text, MessageChannel channel) {
return channel.sendMessage(text).submit();
}
@@ -77,7 +75,7 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public CompletableFuture<Message> sendEmbedInAChannelFuture(MessageEmbed embed, TextChannel channel) {
public CompletableFuture<Message> sendEmbedInAChannelFuture(MessageEmbed embed, MessageChannel channel) {
return channel.sendMessage(embed).submit();
}
@@ -91,7 +89,7 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public List<CompletableFuture<Message>> sendMessageToEndInTextChannel(MessageToSend messageToSend, TextChannel textChannel) {
public List<CompletableFuture<Message>> sendMessageToEndInTextChannel(MessageToSend messageToSend, MessageChannel textChannel) {
String messageText = messageToSend.getMessage();
List<CompletableFuture<Message>> futures = new ArrayList<>();
if(StringUtils.isBlank(messageText)) {
@@ -130,7 +128,7 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public void editMessageInAChannel(MessageToSend messageToSend, TextChannel channel, Long messageId) {
public void editMessageInAChannel(MessageToSend messageToSend, MessageChannel channel, Long messageId) {
MessageAction messageAction;
if(!StringUtils.isBlank(messageToSend.getMessage())) {
messageAction = channel.editMessageById(messageId, messageToSend.getMessage());
@@ -146,4 +144,18 @@ public class ChannelServiceBean implements ChannelService {
}
messageAction.queue();
}
@Override
public CompletableFuture<TextChannel> createTextChannel(String name, AServer server, Long categoryId) {
Optional<Guild> guildById = botService.getGuildById(server.getId());
if(guildById.isPresent()) {
Guild guild = guildById.get();
Category categoryById = guild.getCategoryById(categoryId);
if(categoryById != null) {
return categoryById.createTextChannel(name).submit();
}
return null;
}
return null;
}
}

View File

@@ -17,6 +17,11 @@ public class ConfigServiceBean implements ConfigService{
return getDoubleValue(name, serverId, 0D);
}
@Override
public Long getLongValue(String name, Long serverId) {
return getLongValue(name, serverId, 0L);
}
@Override
public Double getDoubleValue(String name, Long serverId, Double defaultValue) {
AConfig config = configManagementService.loadConfig(serverId, name);
@@ -35,6 +40,15 @@ public class ConfigServiceBean implements ConfigService{
return config.getStringValue();
}
@Override
public Long getLongValue(String name, Long serverId, Long defaultValue) {
AConfig config = configManagementService.loadConfig(serverId, name);
if(config == null) {
return defaultValue;
}
return config.getLongValue();
}
@Override
public void setDoubleValue(String name, Long serverId, Double value) {
if(configManagementService.configExists(serverId, name)) {
@@ -44,6 +58,15 @@ public class ConfigServiceBean implements ConfigService{
}
}
@Override
public void setLongValue(String name, Long serverId, Long value) {
if(configManagementService.configExists(serverId, name)) {
configManagementService.setLongValue(serverId, name, value);
} else {
throw new ConfigurationException(String.format("Key %s does not exist.", name));
}
}
@Override
public void setStringValue(String name, Long serverId, String value) {
if(configManagementService.configExists(serverId, name)) {

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.command.models.TableLocks;
import dev.sheldan.abstracto.core.repository.LockRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class LockServiceBean implements LockService {
@Autowired
private LockRepository lockRepository;
@Override
public void lockTable(TableLocks toLock) {
int ordinal = toLock.ordinal();
lockRepository.findArticleForRead((long) ordinal);
}
}

View File

@@ -103,7 +103,7 @@ public class StartupServiceBean implements Startup {
newChannels.forEach(aLong -> {
GuildChannel channel1 = available.stream().filter(channel -> channel.getIdLong() == aLong).findFirst().get();
log.trace("Adding new channel: {}", aLong);
AChannelType type = AChannel.getAChannelType(channel1.getType());
AChannelType type = AChannelType.getAChannelType(channel1.getType());
channelManagementService.createChannel(channel1.getIdLong(), type, existingServer);
});

View File

@@ -1,9 +1,11 @@
package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.command.models.TableLocks;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AChannelType;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.repository.ChannelRepository;
import dev.sheldan.abstracto.core.service.LockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -15,6 +17,9 @@ public class ChannelManagementServiceBean implements ChannelManagementService {
@Autowired
private ChannelRepository repository;
@Autowired
private LockService lockService;
@Override
public AChannel loadChannel(Long id) {
return repository.getOne(id);
@@ -22,15 +27,20 @@ public class ChannelManagementServiceBean implements ChannelManagementService {
@Override
public AChannel createChannel(Long id, AChannelType type, AServer server) {
log.info("Creating channel {} with type {}", id, type);
AChannel build = AChannel
.builder()
.id(id)
.type(type)
.server(server)
.deleted(false)
.build();
return repository.save(build);
lockService.lockTable(TableLocks.CHANNELS);
if(!channelExists(id)) {
log.info("Creating channel {} with type {}", id, type);
AChannel build = AChannel
.builder()
.id(id)
.type(type)
.server(server)
.deleted(false)
.build();
return repository.save(build);
} else {
return loadChannel(id);
}
}
@Override
@@ -40,6 +50,11 @@ public class ChannelManagementServiceBean implements ChannelManagementService {
return channel;
}
@Override
public boolean channelExists(Long id) {
return repository.existsById(id);
}
@Override
public void removeChannel(Long id) {
log.info("Deleting channel {}", id);

View File

@@ -65,6 +65,19 @@ public class ConfigManagementServiceBean implements ConfigManagementService {
return config;
}
@Override
public AConfig createConfig(Long serverId, String name, Long value) {
AServer server = serverManagementService.loadOrCreate(serverId);
AConfig config = AConfig
.builder()
.longValue(value)
.server(server)
.name(name)
.build();
configRepository.save(config);
return config;
}
@Override
public AConfig createIfNotExists(Long serverId, String name, String value) {
AConfig config = loadConfig(serverId, name);
@@ -100,6 +113,13 @@ public class ConfigManagementServiceBean implements ConfigManagementService {
return config;
}
@Override
public AConfig setLongValue(Long serverId, String name, Long value) {
AConfig config = loadConfig(serverId, name);
config.setLongValue(value);
return config;
}
@Override
public AConfig setStringValue(Long serverId, String name, String value) {
AConfig config = loadConfig(serverId, name);

View File

@@ -9,6 +9,8 @@ import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class UserInServerManagementServiceBean implements UserInServerManagementService {
@@ -55,4 +57,10 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
userInServerRepository.save(aUserInAServer);
return aUserInAServer;
}
@Override
public List<AUserInAServer> getUserInAllServers(Long userId) {
AUser user = userManagementService.loadUser(userId);
return userInServerRepository.findByUserReference(user);
}
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.core.command.models;
public enum TableLocks {
CHANNELS
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.core.listener;
import net.dv8tion.jda.api.entities.Message;
public interface PrivateMessageReceivedListener extends FeatureAware {
void execute(Message message);
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.core.models;
import dev.sheldan.abstracto.core.models.database.AServer;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Guild;
@Setter
@Getter
@Builder
public class FullGuild {
private AServer server;
private Guild guild;
}

View File

@@ -43,15 +43,6 @@ public class AChannel implements SnowFlake {
@Column
private Boolean deleted;
public static AChannelType getAChannelType(ChannelType type) {
switch (type) {
case TEXT: return AChannelType.TEXT;
case PRIVATE: return AChannelType.DM;
case VOICE: return AChannelType.VOICE;
case CATEGORY: return AChannelType.CATEGORY;
default: return AChannelType.UNKOWN;
}
}
@Override
public boolean equals(Object o) {

View File

@@ -1,5 +1,17 @@
package dev.sheldan.abstracto.core.models.database;
import net.dv8tion.jda.api.entities.ChannelType;
public enum AChannelType {
TEXT, DM, VOICE, NEWS, CATEGORY, UNKOWN
TEXT, DM, VOICE, NEWS, CATEGORY, UNKOWN;
public static AChannelType getAChannelType(ChannelType type) {
switch (type) {
case TEXT: return AChannelType.TEXT;
case PRIVATE: return AChannelType.DM;
case VOICE: return AChannelType.VOICE;
case CATEGORY: return AChannelType.CATEGORY;
default: return AChannelType.UNKOWN;
}
}
}

View File

@@ -31,6 +31,10 @@ public class AConfig {
@Setter
private Double doubleValue;
@Column
@Setter
private Long longValue;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", nullable = false)
@Getter

View File

@@ -1,8 +1,10 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.models.database.AChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.TextChannel;
@@ -12,14 +14,16 @@ import java.util.concurrent.CompletableFuture;
public interface ChannelService {
void sendTextInAChannel(String text, AChannel channel);
void sendTextInAChannel(String text, TextChannel channel);
void sendTextInAChannel(String text, MessageChannel channel);
CompletableFuture<Message> sendTextInAChannelFuture(String text, AChannel channel);
CompletableFuture<Message> sendTextInAChannelFuture(String text, TextChannel channel);
CompletableFuture<Message> sendTextInAChannelFuture(String text, MessageChannel channel);
CompletableFuture<Message> sendEmbedInAChannelFuture(MessageEmbed embed, AChannel channel);
CompletableFuture<Message> sendEmbedInAChannelFuture(MessageEmbed embed, TextChannel channel);
CompletableFuture<Message> sendEmbedInAChannelFuture(MessageEmbed embed, MessageChannel channel);
List<CompletableFuture<Message>> sendMessageToEndInAChannel(MessageToSend messageToSend, AChannel channel);
List<CompletableFuture<Message>> sendMessageToEndInTextChannel(MessageToSend messageToSend, TextChannel textChannel);
List<CompletableFuture<Message>> sendMessageToEndInTextChannel(MessageToSend messageToSend, MessageChannel textChannel);
Optional<TextChannel> getTextChannelInGuild(Long serverId, Long channelId);
void editMessageInAChannel(MessageToSend messageToSend, AChannel channel, Long messageId);
void editMessageInAChannel(MessageToSend messageToSend, TextChannel channel, Long messageId);
void editMessageInAChannel(MessageToSend messageToSend, MessageChannel channel, Long messageId);
CompletableFuture<TextChannel> createTextChannel(String name, AServer server, Long categoryId);
}

View File

@@ -2,9 +2,12 @@ package dev.sheldan.abstracto.core.service;
public interface ConfigService {
Double getDoubleValue(String name, Long serverId);
Long getLongValue(String name, Long serverId);
Double getDoubleValue(String name, Long serverId, Double defaultValue);
String getStringValue(String name, Long serverId, String defaultValue);
Long getLongValue(String name, Long serverId, Long defaultValue);
void setDoubleValue(String name, Long serverId, Double value);
void setLongValue(String name, Long serverId, Long value);
void setStringValue(String name, Long serverId, String value);
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.command.models.TableLocks;
public interface LockService {
void lockTable(TableLocks toLock);
}

View File

@@ -8,5 +8,6 @@ public interface ChannelManagementService {
AChannel loadChannel(Long id);
AChannel createChannel(Long id, AChannelType type, AServer server);
AChannel markAsDeleted(Long id);
boolean channelExists(Long id);
void removeChannel(Long id);
}

View File

@@ -7,10 +7,12 @@ public interface ConfigManagementService {
AConfig setOrCreateDoubleValue(Long serverId, String name, Double value);
AConfig createConfig(Long serverId, String name, String value);
AConfig createConfig(Long serverId, String name, Double value);
AConfig createConfig(Long serverId, String name, Long value);
AConfig createIfNotExists(Long serverId, String name, String value);
AConfig createIfNotExists(Long serverId, String name, Double value);
AConfig loadConfig(Long serverId, String name);
boolean configExists(Long serverId, String name);
AConfig setDoubleValue(Long serverId, String name, Double value);
AConfig setLongValue(Long serverId, String name, Long value);
AConfig setStringValue(Long serverId, String name, String value);
}

View File

@@ -5,10 +5,13 @@ import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;
public interface UserInServerManagementService {
AUserInAServer loadUser(Long serverId, Long userId);
AUserInAServer loadUser(AServer server, AUser user);
AUserInAServer loadUser(Member member);
AUserInAServer createUserInServer(Member member);
AUserInAServer createUserInServer(Long guildId, Long userId);
List<AUserInAServer> getUserInAllServers(Long userId);
}

View File

@@ -63,6 +63,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail-impl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility-impl</artifactId>

View File

@@ -37,6 +37,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.jagrosh</groupId>
<artifactId>jda-utilities-menu</artifactId>
<version>${jda.utilities.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>

View File

@@ -0,0 +1 @@
Changes the value of a numerical value of the bots configuration

View File

@@ -0,0 +1 @@
Sets a numerical value in the bots configuration to the given value.

View File

@@ -0,0 +1,2 @@
For which server do you want to open a modmail?
The available servers are:

View File

@@ -22,6 +22,11 @@
<artifactId>JDA</artifactId>
</dependency>
<dependency>
<groupId>com.jagrosh</groupId>
<artifactId>jda-utilities-menu</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -18,7 +18,8 @@
<properties>
<jda.version>4.0.0_73</jda.version>
<jda.version>4.1.1_126</jda.version>
<jda.utilities.version>3.0.3</jda.utilities.version>
</properties>
<modules>