diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/pom.xml b/abstracto-application/abstracto-modules/modmail/modmail-impl/pom.xml
new file mode 100644
index 000000000..63fc4ca07
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+ dev.sheldan.abstracto.modules
+ utility
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ modmail-impl
+
+
+
+ dev.sheldan.abstracto.modules
+ modmail-int
+ ${project.version}
+
+
+
+
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/AnonReply.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/AnonReply.java
new file mode 100644
index 000000000..420ef6eb7
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/AnonReply.java
@@ -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 getConditions() {
+ List conditions = super.getConditions();
+ conditions.add(requiresModMailCondition);
+ return conditions;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Close.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Close.java
new file mode 100644
index 000000000..acd34da6f
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Close.java
@@ -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 getConditions() {
+ List conditions = super.getConditions();
+ conditions.add(requiresModMailCondition);
+ return conditions;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/CloseSilently.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/CloseSilently.java
new file mode 100644
index 000000000..64a4d5d9d
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/CloseSilently.java
@@ -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 getConditions() {
+ List conditions = super.getConditions();
+ conditions.add(requiresModMailCondition);
+ return conditions;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Contact.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Contact.java
new file mode 100644
index 000000000..2f9c0c005
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Contact.java
@@ -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;
+ }
+
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/ModMailModuleInterface.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/ModMailModuleInterface.java
new file mode 100644
index 000000000..915fe0e18
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/ModMailModuleInterface.java
@@ -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";
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Reply.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Reply.java
new file mode 100644
index 000000000..46aa83832
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/Reply.java
@@ -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 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 getConditions() {
+ List conditions = super.getConditions();
+ conditions.add(requiresModMailCondition);
+ return conditions;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/SetModMailCategory.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/SetModMailCategory.java
new file mode 100644
index 000000000..f6b185326
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/SetModMailCategory.java
@@ -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 parameters = Arrays.asList(categoryId);
+ HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
+ List 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;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/condition/RequiresModMailCondition.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/condition/RequiresModMailCondition.java
new file mode 100644
index 000000000..e69fd2a31
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/commands/condition/RequiresModMailCondition.java
@@ -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();
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeature.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeature.java
new file mode 100644
index 000000000..5fc2b3239
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeature.java
@@ -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;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeatures.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeatures.java
new file mode 100644
index 000000000..8f3e18a8e
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeatures.java
@@ -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;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/listener/ModMailMessageListener.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/listener/ModMailMessageListener.java
new file mode 100644
index 000000000..efee69997
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/listener/ModMailMessageListener.java
@@ -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;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/repository/ModMailMessageRepository.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/repository/ModMailMessageRepository.java
new file mode 100644
index 000000000..d572d05ec
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/repository/ModMailMessageRepository.java
@@ -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 {
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/repository/ModMailThreadRepository.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/repository/ModMailThreadRepository.java
new file mode 100644
index 000000000..0dc2403dc
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/repository/ModMailThreadRepository.java
@@ -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 findByChannel(AChannel channel);
+ List findByUser(AUserInAServer aUserInAServer);
+ ModMailThread findByUser_UserReferenceAndStateNot(AUser user, ModMailThreadState state);
+ List findByServerAndState(AServer server, ModMailThreadState state);
+ ModMailThread findByUserAndStateNot(AUserInAServer userInAServer, ModMailThreadState state);
+ List findByUserAndState(AUserInAServer userInAServer, ModMailThreadState state);
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java
new file mode 100644
index 000000000..a5296d4f1
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java
@@ -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 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 = 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 knownServers = userInServerManagementService.getUserInAllServers(user.getId());
+ if(knownServers.size() > 0) {
+ List availableGuilds = new ArrayList<>();
+ HashMap 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 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> completableFutures = channelService.sendMessageToEndInTextChannel(messageToSend, textChannel);
+ List 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> completableFutures = channelService.sendMessageToEndInTextChannel(messageToSend, privateChannel);
+ CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).thenAccept(aVoid -> {
+ List 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 messages, ModMailThread modMailThread, AUserInAServer author, Boolean anonymous) {
+ messages.forEach(message -> {
+ modMailMessageManagementService.addMessageToThread(modMailThread, message, author, false);
+ });
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailMessageManagementServiceBean.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailMessageManagementServiceBean.java
new file mode 100644
index 000000000..8c26929de
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailMessageManagementServiceBean.java
@@ -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;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementServiceBean.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementServiceBean.java
new file mode 100644
index 000000000..a301a2b7a
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementServiceBean.java
@@ -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 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 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);
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/serverChooser/modmail_modal_server_choice_en_US.ftl b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/serverChooser/modmail_modal_server_choice_en_US.ftl
new file mode 100644
index 000000000..e40aba800
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/serverChooser/modmail_modal_server_choice_en_US.ftl
@@ -0,0 +1,5 @@
+<#include "server_chooser_server_list_description">
+
+<#list commonGuilds as guild>
+ ${guild.reactionEmote} ${guild.guild.guild.name}
+#list>
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/threadContent/modmail_staff_message_embed_en_US.ftl b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/threadContent/modmail_staff_message_embed_en_US.ftl
new file mode 100644
index 000000000..aeacafc66
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/threadContent/modmail_staff_message_embed_en_US.ftl
@@ -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>
+}
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/threadContent/modmail_user_message_embed_en_US.ftl b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/threadContent/modmail_user_message_embed_en_US.ftl
new file mode 100644
index 000000000..a8bd51305
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/threadContent/modmail_user_message_embed_en_US.ftl
@@ -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>
+}
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/welcomeMessage/modmail_welcome_message_en_US.ftl b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/welcomeMessage/modmail_welcome_message_en_US.ftl
new file mode 100644
index 000000000..dd117fc58
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/templates/welcomeMessage/modmail_welcome_message_en_US.ftl
@@ -0,0 +1 @@
+You opened a mod mail thread on the server reply in this channel to send messages to a staff only channel.
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/pom.xml b/abstracto-application/abstracto-modules/modmail/modmail-int/pom.xml
new file mode 100644
index 000000000..2779c72e8
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/pom.xml
@@ -0,0 +1,22 @@
+
+
+
+ dev.sheldan.abstracto.modules
+ utility
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ modmail-int
+
+
+
+ dev.sheldan.abstracto.templating
+ templating-interface
+ ${project.version}
+
+
+
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailMessage.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailMessage.java
new file mode 100644
index 000000000..3c23a0d8a
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailMessage.java
@@ -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;
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailThread.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailThread.java
new file mode 100644
index 000000000..d5df152c4
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailThread.java
@@ -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 messages = new ArrayList<>();
+
+ @Enumerated(EnumType.STRING)
+ @Column
+ private ModMailThreadState state;
+
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailThreadState.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailThreadState.java
new file mode 100644
index 000000000..0b09e2ebb
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/database/ModMailThreadState.java
@@ -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;
+ }
+ }
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/dto/ServerChoice.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/dto/ServerChoice.java
new file mode 100644
index 000000000..278f68791
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/dto/ServerChoice.java
@@ -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;
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailModeratorReplyModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailModeratorReplyModel.java
new file mode 100644
index 000000000..4df512247
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailModeratorReplyModel.java
@@ -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;
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailServerChooserModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailServerChooserModel.java
new file mode 100644
index 000000000..9147b790b
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailServerChooserModel.java
@@ -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 commonGuilds;
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailUserReplyModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailUserReplyModel.java
new file mode 100644
index 000000000..3cc4b2477
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/models/template/ModMailUserReplyModel.java
@@ -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;
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadService.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadService.java
new file mode 100644
index 000000000..93080d5d8
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadService.java
@@ -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);
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailMessageManagementService.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailMessageManagementService.java
new file mode 100644
index 000000000..273ca69fa
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailMessageManagementService.java
@@ -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);
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementService.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementService.java
new file mode 100644
index 000000000..1d614716e
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementService.java
@@ -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 getThreadByUserAndState(AUserInAServer userInAServer, ModMailThreadState state);
+ ModMailThread getOpenModmailThreadForUser(AUserInAServer userInAServer);
+ ModMailThread getOpenModmailThreadForUser(AUser user);
+ List getModMailThreadForUser(AUserInAServer aUserInAServer);
+ void createModMailThread(AUserInAServer userInAServer, AChannel channel);
+
+}
diff --git a/abstracto-application/abstracto-modules/modmail/pom.xml b/abstracto-application/abstracto-modules/modmail/pom.xml
new file mode 100644
index 000000000..4e26d118f
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/pom.xml
@@ -0,0 +1,29 @@
+
+
+
+ dev.sheldan.abstracto
+ abstracto-modules
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dev.sheldan.abstracto.modules
+ modmail
+ pom
+
+
+ modmail-int
+ modmail-impl
+
+
+
+
+ dev.sheldan.abstracto.core
+ core-interface
+ ${project.version}
+ compile
+
+
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/pom.xml b/abstracto-application/abstracto-modules/pom.xml
index 4f75f0029..94bb2bd2f 100644
--- a/abstracto-application/abstracto-modules/pom.xml
+++ b/abstracto-application/abstracto-modules/pom.xml
@@ -15,6 +15,7 @@
moderation
utility
experience-tracking
+ modmail
diff --git a/abstracto-application/abstracto-modules/utility/utility-impl/src/main/java/dev/sheldan/abstracto/utility/listener/starboard/StarboardPostDeletedListener.java b/abstracto-application/abstracto-modules/utility/utility-impl/src/main/java/dev/sheldan/abstracto/utility/listener/starboard/StarboardPostDeletedListener.java
index 1d13d1840..eea1271c5 100644
--- a/abstracto-application/abstracto-modules/utility/utility-impl/src/main/java/dev/sheldan/abstracto/utility/listener/starboard/StarboardPostDeletedListener.java
+++ b/abstracto-application/abstracto-modules/utility/utility-impl/src/main/java/dev/sheldan/abstracto/utility/listener/starboard/StarboardPostDeletedListener.java
@@ -24,7 +24,8 @@ public class StarboardPostDeletedListener implements MessageDeletedListener {
Optional 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);
}
}
diff --git a/abstracto-application/abstracto-modules/utility/utility-impl/src/main/java/dev/sheldan/abstracto/utility/service/management/StarboardPostManagementServiceBean.java b/abstracto-application/abstracto-modules/utility/utility-impl/src/main/java/dev/sheldan/abstracto/utility/service/management/StarboardPostManagementServiceBean.java
index c856c7830..411c07f6d 100644
--- a/abstracto-application/abstracto-modules/utility/utility-impl/src/main/java/dev/sheldan/abstracto/utility/service/management/StarboardPostManagementServiceBean.java
+++ b/abstracto-application/abstracto-modules/utility/utility-impl/src/main/java/dev/sheldan/abstracto/utility/service/management/StarboardPostManagementServiceBean.java
@@ -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())
diff --git a/abstracto-application/abstracto-modules/utility/utility-int/src/main/java/dev/sheldan/abstracto/utility/models/database/StarboardPost.java b/abstracto-application/abstracto-modules/utility/utility-int/src/main/java/dev/sheldan/abstracto/utility/models/database/StarboardPost.java
index b3a90a361..fbd019a2f 100644
--- a/abstracto-application/abstracto-modules/utility/utility-int/src/main/java/dev/sheldan/abstracto/utility/models/database/StarboardPost.java
+++ b/abstracto-application/abstracto-modules/utility/utility-int/src/main/java/dev/sheldan/abstracto/utility/models/database/StarboardPost.java
@@ -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;
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/config/CommandConfigListener.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/config/CommandConfigListener.java
index 148dc4b39..d0371fd6f 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/config/CommandConfigListener.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/config/CommandConfigListener.java
@@ -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);
+ }
}
});
}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/config/CommandCreationListener.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/config/CommandCreationListener.java
index eadff86ac..306621729 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/config/CommandCreationListener.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/config/CommandCreationListener.java
@@ -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());
}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandManager.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandManager.java
index 81dc11803..d167b54dd 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandManager.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandManager.java
@@ -33,6 +33,9 @@ public class CommandManager implements CommandRegistry {
public Command findCommandByParameters(String name, UnParsedCommandParameter unParsedCommandParameter) {
Optional 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 getAllCommandsFromModule(ModuleInterface moduleInterface) {
List 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);
}
});
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandServiceBean.java
index 97cef6b67..52768c10a 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandServiceBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandServiceBean.java
@@ -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);
}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetNumber.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetFloat.java
similarity index 95%
rename from abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetNumber.java
rename to abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetFloat.java
index d82c564f5..aa84a6926 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetNumber.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetFloat.java
@@ -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 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)
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetLong.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetLong.java
new file mode 100644
index 000000000..1f1e79a15
--- /dev/null
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/SetLong.java
@@ -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 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;
+ }
+}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/ChannelListener.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/ChannelListener.java
index 612840358..fb132ff3a 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/ChannelListener.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/ChannelListener.java
@@ -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);
}
}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/MessageReceivedListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/MessageReceivedListenerBean.java
index 27a957ccc..93bdb359a 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/MessageReceivedListenerBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/MessageReceivedListenerBean.java
@@ -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 listenerList;
+ @Autowired
+ private List 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);
+ }
+ });
+ }
}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/model/database/ALock.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/model/database/ALock.java
new file mode 100644
index 000000000..68c733b11
--- /dev/null
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/model/database/ALock.java
@@ -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;
+}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/repository/LockRepository.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/repository/LockRepository.java
new file mode 100644
index 000000000..75b2a09c1
--- /dev/null
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/repository/LockRepository.java
@@ -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 {
+
+ @Lock(LockModeType.PESSIMISTIC_WRITE)
+ @Query("select a from ALock a where a.id = :id")
+ ALock findArticleForRead(@Param("id") Long id);
+}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/repository/UserInServerRepository.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/repository/UserInServerRepository.java
index 89ecddf56..dd050d1d1 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/repository/UserInServerRepository.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/repository/UserInServerRepository.java
@@ -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 {
@@ -17,4 +18,7 @@ public interface UserInServerRepository extends JpaRepository findByUserReference(AUser user);
}
\ No newline at end of file
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java
index 149ba0200..7f6815379 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java
@@ -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 sendTextInAChannelFuture(String text, TextChannel channel) {
+ public CompletableFuture sendTextInAChannelFuture(String text, MessageChannel channel) {
return channel.sendMessage(text).submit();
}
@@ -77,7 +75,7 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
- public CompletableFuture sendEmbedInAChannelFuture(MessageEmbed embed, TextChannel channel) {
+ public CompletableFuture sendEmbedInAChannelFuture(MessageEmbed embed, MessageChannel channel) {
return channel.sendMessage(embed).submit();
}
@@ -91,7 +89,7 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
- public List> sendMessageToEndInTextChannel(MessageToSend messageToSend, TextChannel textChannel) {
+ public List> sendMessageToEndInTextChannel(MessageToSend messageToSend, MessageChannel textChannel) {
String messageText = messageToSend.getMessage();
List> 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 createTextChannel(String name, AServer server, Long categoryId) {
+ Optional 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;
+ }
}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ConfigServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ConfigServiceBean.java
index 7f4bf4972..86143bbd1 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ConfigServiceBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ConfigServiceBean.java
@@ -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)) {
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/LockServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/LockServiceBean.java
new file mode 100644
index 000000000..d27edabe0
--- /dev/null
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/LockServiceBean.java
@@ -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);
+ }
+}
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/StartupServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/StartupServiceBean.java
index a3a9bd09a..b1b7bb855 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/StartupServiceBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/StartupServiceBean.java
@@ -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);
});
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/ChannelManagementServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/ChannelManagementServiceBean.java
index 7a6edafe7..f17f352ed 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/ChannelManagementServiceBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/ChannelManagementServiceBean.java
@@ -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);
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/ConfigManagementServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/ConfigManagementServiceBean.java
index 7682dc9fe..71d16ecf6 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/ConfigManagementServiceBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/ConfigManagementServiceBean.java
@@ -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);
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/UserInServerManagementServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/UserInServerManagementServiceBean.java
index 157be0eca..60201bf2b 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/UserInServerManagementServiceBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/management/UserInServerManagementServiceBean.java
@@ -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 getUserInAllServers(Long userId) {
+ AUser user = userManagementService.loadUser(userId);
+ return userInServerRepository.findByUserReference(user);
+ }
}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/models/TableLocks.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/models/TableLocks.java
new file mode 100644
index 000000000..d5f9462f3
--- /dev/null
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/command/models/TableLocks.java
@@ -0,0 +1,5 @@
+package dev.sheldan.abstracto.core.command.models;
+
+public enum TableLocks {
+ CHANNELS
+}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/listener/PrivateMessageReceivedListener.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/listener/PrivateMessageReceivedListener.java
new file mode 100644
index 000000000..fa18ee289
--- /dev/null
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/listener/PrivateMessageReceivedListener.java
@@ -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);
+}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/FullGuild.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/FullGuild.java
new file mode 100644
index 000000000..b1e6ca4e4
--- /dev/null
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/FullGuild.java
@@ -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;
+}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AChannel.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AChannel.java
index 746860cbf..8a084b271 100644
--- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AChannel.java
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AChannel.java
@@ -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) {
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AChannelType.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AChannelType.java
index 2182b8cdd..0f1d00173 100644
--- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AChannelType.java
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AChannelType.java
@@ -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;
+ }
+ }
}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AConfig.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AConfig.java
index 554895c0b..9aec3938b 100644
--- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AConfig.java
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/models/database/AConfig.java
@@ -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
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java
index eb105bcb7..9c7a8c1b4 100644
--- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java
@@ -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 sendTextInAChannelFuture(String text, AChannel channel);
- CompletableFuture sendTextInAChannelFuture(String text, TextChannel channel);
+ CompletableFuture sendTextInAChannelFuture(String text, MessageChannel channel);
CompletableFuture sendEmbedInAChannelFuture(MessageEmbed embed, AChannel channel);
- CompletableFuture sendEmbedInAChannelFuture(MessageEmbed embed, TextChannel channel);
+ CompletableFuture sendEmbedInAChannelFuture(MessageEmbed embed, MessageChannel channel);
List> sendMessageToEndInAChannel(MessageToSend messageToSend, AChannel channel);
- List> sendMessageToEndInTextChannel(MessageToSend messageToSend, TextChannel textChannel);
+ List> sendMessageToEndInTextChannel(MessageToSend messageToSend, MessageChannel textChannel);
Optional 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 createTextChannel(String name, AServer server, Long categoryId);
}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/ConfigService.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/ConfigService.java
index a07bcc0be..dd2529a69 100644
--- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/ConfigService.java
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/ConfigService.java
@@ -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);
}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/LockService.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/LockService.java
new file mode 100644
index 000000000..5c44743b1
--- /dev/null
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/LockService.java
@@ -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);
+}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/ChannelManagementService.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/ChannelManagementService.java
index 3542f5764..19be169ff 100644
--- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/ChannelManagementService.java
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/ChannelManagementService.java
@@ -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);
}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/ConfigManagementService.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/ConfigManagementService.java
index 57da10ef2..4ff5c896a 100644
--- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/ConfigManagementService.java
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/ConfigManagementService.java
@@ -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);
}
diff --git a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/UserInServerManagementService.java b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/UserInServerManagementService.java
index 511ce56f1..7b0c72cbc 100644
--- a/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/UserInServerManagementService.java
+++ b/abstracto-application/core/core-interface/src/main/java/dev/sheldan/abstracto/core/service/management/UserInServerManagementService.java
@@ -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 getUserInAllServers(Long userId);
}
diff --git a/abstracto-application/executable/pom.xml b/abstracto-application/executable/pom.xml
index d3557f365..8a03f681c 100644
--- a/abstracto-application/executable/pom.xml
+++ b/abstracto-application/executable/pom.xml
@@ -63,6 +63,12 @@
${project.version}
+
+ dev.sheldan.abstracto.modules
+ modmail-impl
+ ${project.version}
+
+
dev.sheldan.abstracto.modules
utility-impl
diff --git a/abstracto-application/pom.xml b/abstracto-application/pom.xml
index ec4969802..b2a239a76 100644
--- a/abstracto-application/pom.xml
+++ b/abstracto-application/pom.xml
@@ -37,6 +37,12 @@
+
+ com.jagrosh
+ jda-utilities-menu
+ ${jda.utilities.version}
+ compile
+
org.apache.commons
commons-lang3
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setNumber/help/setNumber_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setFloat/help/setFloat_description_en_US.ftl
similarity index 100%
rename from abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setNumber/help/setNumber_description_en_US.ftl
rename to abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setFloat/help/setFloat_description_en_US.ftl
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setNumber/help/setNumber_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setFloat/help/setFloat_long_help_en_US.ftl
similarity index 100%
rename from abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setNumber/help/setNumber_long_help_en_US.ftl
rename to abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setFloat/help/setFloat_long_help_en_US.ftl
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setNumber/help/setNumber_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setFloat/help/setFloat_usage_en_US.ftl
similarity index 100%
rename from abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setNumber/help/setNumber_usage_en_US.ftl
rename to abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setFloat/help/setFloat_usage_en_US.ftl
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_description_en_US.ftl
new file mode 100644
index 000000000..cd98b92a6
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_description_en_US.ftl
@@ -0,0 +1 @@
+Changes the value of a numerical value of the bots configuration
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_long_help_en_US.ftl
new file mode 100644
index 000000000..a62f53500
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_long_help_en_US.ftl
@@ -0,0 +1 @@
+Sets a numerical value in the bots configuration to the given value.
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_usage_en_US.ftl
new file mode 100644
index 000000000..cab8588e0
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/core/setLong/help/setLong_usage_en_US.ftl
@@ -0,0 +1 @@
+setNumber
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_description_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_description_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_long_help_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_long_help_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_usage_en_US.ftl
new file mode 100644
index 000000000..b675504f6
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/anonReply/help/anonReply_usage_en_US.ftl
@@ -0,0 +1 @@
+setModMailCategory
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_description_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_description_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_long_help_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_long_help_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_usage_en_US.ftl
new file mode 100644
index 000000000..f8b4a41ef
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/close/help/close_usage_en_US.ftl
@@ -0,0 +1 @@
+close [note]
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_description_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_description_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_long_help_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_long_help_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_usage_en_US.ftl
new file mode 100644
index 000000000..94547b312
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/closeSilently/help/closeSilently_usage_en_US.ftl
@@ -0,0 +1 @@
+closeSilenty [note]
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_description_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_description_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_long_help_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_long_help_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_usage_en_US.ftl
new file mode 100644
index 000000000..0aea4edc2
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/contact/help/contact_usage_en_US.ftl
@@ -0,0 +1 @@
+contact
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_description_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_description_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_long_help_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_long_help_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_usage_en_US.ftl
new file mode 100644
index 000000000..21145747a
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/reply/help/reply_usage_en_US.ftl
@@ -0,0 +1 @@
+reply
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_description_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_description_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_long_help_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_long_help_en_US.ftl
new file mode 100644
index 000000000..f3a34851d
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_long_help_en_US.ftl
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_usage_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_usage_en_US.ftl
new file mode 100644
index 000000000..b675504f6
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/commands/modmail/setModMailCategory/help/setModMailCategory_usage_en_US.ftl
@@ -0,0 +1 @@
+setModMailCategory
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/config/modmail_feature_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/config/modmail_feature_en_US.ftl
new file mode 100644
index 000000000..076a26637
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/config/modmail_feature_en_US.ftl
@@ -0,0 +1 @@
+Mod mail
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/listener/server_chooser/server_chooser_server_list_description_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/listener/server_chooser/server_chooser_server_list_description_en_US.ftl
new file mode 100644
index 000000000..b9e29496f
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/listener/server_chooser/server_chooser_server_list_description_en_US.ftl
@@ -0,0 +1,2 @@
+For which server do you want to open a modmail?
+The available servers are:
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/modMail/modmail_thread_staff_message_title_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/modMail/modmail_thread_staff_message_title_en_US.ftl
new file mode 100644
index 000000000..36aef3c39
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/modMail/modmail_thread_staff_message_title_en_US.ftl
@@ -0,0 +1 @@
+Moderator replied
\ No newline at end of file
diff --git a/abstracto-application/template-config/src/main/resources/templates/en_US/modMail/modmail_thread_user_message_title_en_US.ftl b/abstracto-application/template-config/src/main/resources/templates/en_US/modMail/modmail_thread_user_message_title_en_US.ftl
new file mode 100644
index 000000000..0c0611288
--- /dev/null
+++ b/abstracto-application/template-config/src/main/resources/templates/en_US/modMail/modmail_thread_user_message_title_en_US.ftl
@@ -0,0 +1 @@
+User replied
\ No newline at end of file
diff --git a/abstracto-application/templating/templating-interface/pom.xml b/abstracto-application/templating/templating-interface/pom.xml
index 1057af57a..34d6028d3 100644
--- a/abstracto-application/templating/templating-interface/pom.xml
+++ b/abstracto-application/templating/templating-interface/pom.xml
@@ -22,6 +22,11 @@
JDA
+
+ com.jagrosh
+ jda-utilities-menu
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 06b8208fd..1d3037a89 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,8 @@
- 4.0.0_73
+ 4.1.1_126
+ 3.0.3