Compare commits

..

13 Commits

Author SHA1 Message Date
Sheldan
10bae80377 [AB-xxx] adding quick replies feature to modmail similar to 2026-06-29 19:07:38 +02:00
Sheldan
7ba34a97e3 [AB-xxx] grrr takes remainder of input 2026-05-15 14:58:46 +02:00
release-bot
cdd2e15969 Commit from GitHub Actions (Publishes a new version of abstracto) 2026-05-15 11:43:53 +00:00
release-bot
8de0a578db [maven-release-plugin] prepare for next development iteration 2026-05-15 11:29:36 +00:00
release-bot
072c192e4f [maven-release-plugin] prepare release v1.6.28 2026-05-15 11:29:33 +00:00
Sheldan
be5cb132ea [AB-xxx] fixing offset for grrr meme 2026-05-15 13:25:47 +02:00
release-bot
6deb5aad77 Commit from GitHub Actions (Publishes a new version of abstracto) 2026-05-15 10:55:51 +00:00
release-bot
32e0c1dc83 [maven-release-plugin] prepare for next development iteration 2026-05-15 10:43:07 +00:00
release-bot
f0a5fbd059 [maven-release-plugin] prepare release v1.6.27 2026-05-15 10:43:05 +00:00
Sheldan
8024eb56b0 [AB-xxx] fixing typo 2026-05-15 12:40:20 +02:00
release-bot
87937f48b7 Commit from GitHub Actions (Publishes a new version of abstracto) 2026-05-15 09:16:26 +00:00
release-bot
6dda4132f6 [maven-release-plugin] prepare for next development iteration 2026-05-15 09:00:15 +00:00
release-bot
67fdfe3b54 [maven-release-plugin] prepare release v1.6.26 2026-05-15 09:00:12 +00:00
113 changed files with 1023 additions and 125 deletions

2
.env
View File

@@ -1,2 +1,2 @@
REGISTRY_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.6.25
VERSION=1.6.28

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assignable-roles-int</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>dynamic-activity</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>dynamic-activity</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>giveaway-impl</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>giveaway-int</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>giveaway</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>image-generation-impl</artifactId>

View File

@@ -51,7 +51,7 @@ public class Grrr extends AbstractConditionableCommand {
@Autowired
private SlashCommandParameterService slashCommandParameterService;
private static final String GRRR_EMBED_TEMPLATE_KEY = "grr_response";
private static final String GRRR_EMBED_TEMPLATE_KEY = "grrr_response";
public static final String TEXT_PARAMETER_KEY = "text";
@Override
@@ -98,6 +98,7 @@ public class Grrr extends AbstractConditionableCommand {
.name(TEXT_PARAMETER_KEY)
.type(String.class)
.templated(true)
.remainder(true)
.build();
parameters.add(textParameter);
HelpInfo helpInfo = HelpInfo

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>image-generation-int</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>image-generation</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,7 +18,7 @@
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -53,7 +53,7 @@ public class AnonReply extends AbstractConditionableCommand {
}
Long threadId = modMailThread.getId();
return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenCompose(user ->
modMailThreadService.loadExecutingMemberAndRelay(threadId, text, commandContext.getMessage(), true, user, commandContext.getGuild())
modMailThreadService.relayMessageToDm(threadId, text, commandContext.getMessage(), true, user, commandContext.getGuild(), commandContext.getAuthor())
).thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -0,0 +1,175 @@
package dev.sheldan.abstracto.modmail.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandAutoCompleteService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.SnowflakeUtils;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.QuickReplyService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import dev.sheldan.abstracto.modmail.service.management.QuickReplyManagementService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class QuickReplyCommand extends AbstractConditionableCommand {
private static final String QUICK_REPLY_COMMAND = "quickReply";
private static final String QUICK_REPLY_NAME_PARAMETER = "name";
private static final String QUICK_REPLY_ANONYMOUS_PARAMETER = "anonymous";
private static final String QUICK_REPLY_RESPONSE_TEMPLATE_KEY = "quickReply_response";
private static final String QUICK_REPLY_NO_QUICK_REPLY_FOUND_TEMPLATE_KEY = "quickReply_no_quick_reply_response";
@Autowired
private InteractionService interactionService;
@Autowired
private QuickReplyService quickReplyService;
@Autowired
private QuickReplyManagementService quickReplyManagementService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private SlashCommandAutoCompleteService slashCommandAutoCompleteService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private UserService userService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String name = slashCommandParameterService.getCommandOption(QUICK_REPLY_NAME_PARAMETER, event, String.class);
Optional<QuickReply> quickReplyOptional = quickReplyService.getQuickReply(name, event.getGuild());
if(quickReplyOptional.isEmpty()) {
return interactionService.replyEmbed(QUICK_REPLY_NO_QUICK_REPLY_FOUND_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(event.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
Long threadId = modMailThread.getId();
QuickReply quickReply = quickReplyOptional.get();
Boolean anonymousOverride = null;
if(slashCommandParameterService.hasCommandOption(QUICK_REPLY_ANONYMOUS_PARAMETER, event)) {
anonymousOverride = slashCommandParameterService.getCommandOption(QUICK_REPLY_ANONYMOUS_PARAMETER, event, Boolean.class);
}
boolean anonymous;
if(anonymousOverride != null) {
anonymous = anonymousOverride;
} else {
anonymous = quickReply.getAnonymous();
}
Long snowFlake = SnowflakeUtils.createSnowFlake();
Long targetUserId = modMailThread.getUser().getUserReference().getId();
event.deferReply(true).queue();
return
userService.retrieveUserForId(targetUserId).thenCompose(user ->
modMailThreadService.relayMessageToDm(threadId, quickReply.getAdditionalMessage(), snowFlake, anonymous, user,
event.getGuild(), event.getMember())
).thenCompose(unused -> FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(QUICK_REPLY_RESPONSE_TEMPLATE_KEY, new Object(), event.getHook())))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public List<String> performAutoComplete(CommandAutoCompleteInteractionEvent event) {
if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), QUICK_REPLY_NAME_PARAMETER)) {
String input = event.getFocusedOption().getValue();
AServer server = serverManagementService.loadServer(event.getGuild());
return quickReplyManagementService.getQuickRepliesContaining(input, server)
.stream().map(quickReply -> quickReply.getName().toLowerCase())
.toList();
}
return new ArrayList<>();
}
@Override
public FeatureDefinition getFeature() {
return ModMailFeatureDefinition.MOD_MAIL;
}
@Override
public CommandConfiguration getConfiguration() {
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
Parameter replyContentParameter = Parameter
.builder()
.name(QUICK_REPLY_NAME_PARAMETER)
.templated(true)
.supportsAutoComplete(true)
.type(String.class)
.build();
Parameter replyAnonymousparameter = Parameter
.builder()
.name(QUICK_REPLY_ANONYMOUS_PARAMETER)
.templated(true)
.optional(true)
.type(Boolean.class)
.build();
List<Parameter> parameters = Arrays.asList(replyContentParameter, replyAnonymousparameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModMailSlashCommandNames.MODMAIL)
.commandName("quickReply")
.build();
return CommandConfiguration.builder()
.name(QUICK_REPLY_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.async(true)
.slashCommandOnly(true)
.slashCommandConfig(slashCommandConfig)
.causesReaction(true)
.parameters(parameters)
.supportsEmbedException(true)
.help(helpInfo)
.build();
}
}

View File

@@ -0,0 +1,115 @@
package dev.sheldan.abstracto.modmail.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandPrivilegeLevels;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.service.QuickReplyService;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class QuickReplyCreate extends AbstractConditionableCommand {
private static final String CREATE_QUICK_REPLY_COMMAND = "createQuickReply";
private static final String QUICK_REPLY_NAME_PARAMETER = "name";
private static final String QUICK_REPLY_CONTENT_PARAMETER = "response";
private static final String QUICK_REPLY_ANONYMOUS_PARAMETER = "anonymous";
private static final String CREATE_QUICK_REPLY_RESPONSE_TEMPLATE_KEY = "createQuickReply_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private QuickReplyService quickReplyService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String name = slashCommandParameterService.getCommandOption(QUICK_REPLY_NAME_PARAMETER, event, String.class);
String content = slashCommandParameterService.getCommandOption(QUICK_REPLY_CONTENT_PARAMETER, event, String.class);
boolean anonymous;
if(slashCommandParameterService.hasCommandOption(QUICK_REPLY_ANONYMOUS_PARAMETER, event)) {
anonymous = slashCommandParameterService.getCommandOption(QUICK_REPLY_ANONYMOUS_PARAMETER, event, Boolean.class);
} else {
anonymous = false;
}
quickReplyService.createQuickReply(name, content, event.getMember(), anonymous);
return interactionService.replyEmbed(CREATE_QUICK_REPLY_RESPONSE_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter commandNameParameter = Parameter
.builder()
.name(QUICK_REPLY_NAME_PARAMETER)
.templated(true)
.type(String.class)
.build();
Parameter replyContentParameter = Parameter
.builder()
.name(QUICK_REPLY_CONTENT_PARAMETER)
.templated(true)
.type(String.class)
.build();
Parameter replyAnonymousparameter = Parameter
.builder()
.name(QUICK_REPLY_ANONYMOUS_PARAMETER)
.templated(true)
.optional(true)
.type(Boolean.class)
.build();
List<Parameter> parameters = Arrays.asList(commandNameParameter, replyContentParameter, replyAnonymousparameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.defaultPrivilege(SlashCommandPrivilegeLevels.ADMIN)
.rootCommandName(ModMailSlashCommandNames.MODMAIL)
.commandName("createQuickReply")
.build();
return CommandConfiguration.builder()
.name(CREATE_QUICK_REPLY_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.async(true)
.slashCommandOnly(true)
.slashCommandConfig(slashCommandConfig)
.causesReaction(true)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModMailFeatureDefinition.MOD_MAIL;
}
}

View File

@@ -0,0 +1,119 @@
package dev.sheldan.abstracto.modmail.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandPrivilegeLevels;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandAutoCompleteService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.service.QuickReplyService;
import dev.sheldan.abstracto.modmail.service.management.QuickReplyManagementService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class QuickReplyDelete extends AbstractConditionableCommand {
private static final String DELETE_QUICK_REPLY_COMMAND = "deleteQuickReply";
private static final String DELETE_QUICK_REPLY_RESPONSE_TEMPLATE_KEY = "deleteQuickReply_response";
private static final String QUICK_REPLY_NAME_PARAMETER = "name";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private QuickReplyService customCommandService;
@Autowired
private QuickReplyManagementService quickReplyManagementService;
@Autowired
private SlashCommandAutoCompleteService slashCommandAutoCompleteService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private SlashCommandService slashCommandService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String name = slashCommandParameterService.getCommandOption(QUICK_REPLY_NAME_PARAMETER, event, String.class);
customCommandService.deleteQuickReply(name, event.getGuild());
return slashCommandService.completeConfirmableCommand(event, DELETE_QUICK_REPLY_RESPONSE_TEMPLATE_KEY)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public List<String> performAutoComplete(CommandAutoCompleteInteractionEvent event) {
if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), QUICK_REPLY_NAME_PARAMETER)) {
String input = event.getFocusedOption().getValue();
AServer server = serverManagementService.loadServer(event.getGuild());
return quickReplyManagementService.getQuickRepliesContaining(input, server)
.stream().map(quickReply -> quickReply.getName().toLowerCase())
.toList();
}
return new ArrayList<>();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter quickReplyNameParameter = Parameter
.builder()
.name(QUICK_REPLY_NAME_PARAMETER)
.templated(true)
.supportsAutoComplete(true)
.type(String.class)
.build();
List<Parameter> parameters = Arrays.asList(quickReplyNameParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.defaultPrivilege(SlashCommandPrivilegeLevels.INVITER)
.rootCommandName(ModMailSlashCommandNames.MODMAIL)
.commandName("deleteQuickReply")
.build();
return CommandConfiguration.builder()
.name(DELETE_QUICK_REPLY_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.causesReaction(true)
.slashCommandOnly(true)
.requiresConfirmation(true)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModMailFeatureDefinition.MOD_MAIL;
}
}

View File

@@ -0,0 +1,86 @@
package dev.sheldan.abstracto.modmail.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.service.PaginatorService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import dev.sheldan.abstracto.modmail.model.template.QuickRepliesListResponseModel;
import dev.sheldan.abstracto.modmail.service.QuickReplyService;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class QuickReplyList extends AbstractConditionableCommand {
private static final String LIST_QUICK_REPLIES_COMMAND = "listQuickReplies";
private static final String LIST_QUICK_REPLIES_TEMPLATE_KEY = "listQuickReplies_response";
private static final String NO_QUICK_REPLIES_TEMPLATE_KEY = "listQuickReplies_no_quick_replies_response";
@Autowired
private QuickReplyService quickReplyService;
@Autowired
private InteractionService interactionService;
@Autowired
private PaginatorService paginatorService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
List<QuickReply> quickReplies = quickReplyService.getQuickReplies(event.getGuild());
if(quickReplies.isEmpty()) {
return interactionService.replyEmbed(NO_QUICK_REPLIES_TEMPLATE_KEY, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
QuickRepliesListResponseModel model = QuickRepliesListResponseModel.fromQuickReplies(quickReplies);
return paginatorService.createPaginatorFromTemplate(LIST_QUICK_REPLIES_TEMPLATE_KEY, model, event)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public FeatureDefinition getFeature() {
return ModMailFeatureDefinition.MOD_MAIL;
}
@Override
public CommandConfiguration getConfiguration() {
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModMailSlashCommandNames.MODMAIL)
.commandName("listQuickReply")
.build();
return CommandConfiguration.builder()
.name(LIST_QUICK_REPLIES_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.async(true)
.slashCommandOnly(true)
.slashCommandConfig(slashCommandConfig)
.causesReaction(true)
.supportsEmbedException(true)
.help(helpInfo)
.build();
}
}

View File

@@ -8,7 +8,6 @@ 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.FeatureDefinition;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
@@ -52,7 +51,7 @@ public class Reply extends AbstractConditionableCommand {
}
Long threadId = modMailThread.getId();
return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenCompose(user ->
modMailThreadService.loadExecutingMemberAndRelay(threadId, text, commandContext.getMessage(), false, user, commandContext.getGuild())
modMailThreadService.relayMessageToDm(threadId, text, commandContext.getMessage(), false, user, commandContext.getGuild(), commandContext.getAuthor())
).thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -6,7 +6,6 @@ import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageUpdatedListener;
import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.listener.MessageUpdatedModel;
@@ -125,7 +124,6 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
.builder()
.text(newText)
.modMailThread(modMailMessage.getThreadReference())
.postedMessage(loadedMessage)
.attachedImageUrls(imageUrls)
.remainingAttachments(otherAttachments)
.anonymous(modMailMessage.getAnonymous())

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.modmail.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface QuickReplyRepository extends JpaRepository<QuickReply, Long> {
Optional<QuickReply> getByNameIgnoreCaseAndServer(String name, AServer server);
void deleteByNameAndServer(String name, AServer server);
List<QuickReply> findByServer(AServer server);
List<QuickReply> findByNameStartsWithIgnoreCaseAndServer(String prefix, AServer server);
List<QuickReply> findByNameContainingIgnoreCaseAndServer(String name, AServer server);
}

View File

@@ -50,6 +50,7 @@ import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.apache.commons.lang3.RandomStringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@@ -748,34 +749,32 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
}
@Override
@Transactional
public CompletableFuture<Void> loadExecutingMemberAndRelay(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, User user, Guild guild) {
log.info("Relaying message {} to user {} in modmail thread {} on server {}.", replyCommandMessage.getId(), user.getId(), modmailThreadId, guild.getId());
return memberService.getMemberInServerAsync(replyCommandMessage.getGuild().getIdLong(), replyCommandMessage.getAuthor().getIdLong())
.thenCompose(executingMember -> self.relayMessageToDm(modmailThreadId, text, replyCommandMessage, anonymous, user, executingMember));
public CompletableFuture<Void> relayMessageToDm(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, User user, Guild guild, Member executingMember) {
List<String> imageUrls = replyCommandMessage
.getAttachments()
.stream()
.filter(Message.Attachment::isImage)
.map(Message.Attachment::getProxyUrl)
.collect(Collectors.toList());
Map<String, String> otherAttachments = replyCommandMessage
.getAttachments()
.stream()
.filter(attachment -> !attachment.isImage())
.collect(Collectors.toMap(Message.Attachment::getFileName, Message.Attachment::getUrl));
return relayMessageToDMInternal(modmailThreadId, text, replyCommandMessage.getIdLong(), imageUrls, otherAttachments, anonymous, user, guild, executingMember);
}
@Transactional
public CompletableFuture<Void> relayMessageToDm(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, User user, Member executingMember) {
private CompletableFuture<Void> relayMessageToDMInternal(Long modmailThreadId, String text, Long messageId, List<String> imageUrls, Map<String, String> otherAttachments,
boolean anonymous, User user, Guild guild, Member executingMember) {
log.info("Relaying message {} to user {} in modmail thread {} on server {}.", messageId, user.getId(), modmailThreadId, guild.getId());
metricService.incrementCounter(MDOMAIL_THREAD_MESSAGE_SENT);
ModMailThread modMailThread = modMailThreadManagementService.getById(modmailThreadId);
List<String> imageUrls = replyCommandMessage
.getAttachments()
.stream()
.filter(Message.Attachment::isImage)
.map(Message.Attachment::getProxyUrl)
.collect(Collectors.toList());
Map<String, String> otherAttachments = replyCommandMessage
.getAttachments()
.stream()
.filter(attachment -> !attachment.isImage())
.collect(Collectors.toMap(Message.Attachment::getFileName, Message.Attachment::getUrl));
ModMailModeratorReplyModel.ModMailModeratorReplyModelBuilder modMailModeratorReplyModelBuilder = ModMailModeratorReplyModel
.builder()
.text(text)
.modMailThread(modMailThread)
.postedMessage(replyCommandMessage)
.remainingAttachments(otherAttachments)
.attachedImageUrls(imageUrls)
.anonymous(anonymous)
@@ -796,10 +795,17 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
sameThreadMessageFuture = CompletableFuture.completedFuture(null);
}
return CompletableFuture.allOf(future, sameThreadMessageFuture).thenAccept(avoid ->
self.saveSendMessagesAndUpdateState(modmailThreadId, anonymous, future.join(), replyCommandMessage.getMember(), replyCommandMessage.getIdLong(), sameThreadMessageFuture.join())
self.saveSendMessagesAndUpdateState(modmailThreadId, anonymous, future.join(), executingMember, messageId,
sameThreadMessageFuture.join())
);
}
@Override
public CompletableFuture<Void> relayMessageToDm(Long threadId, String text, Long uniqueMessageId, boolean anonymous, User targetUser, Guild guild,
Member executingMember) {
return relayMessageToDMInternal(threadId, text, uniqueMessageId, new ArrayList<>(), new HashMap<>(), anonymous, targetUser, guild, executingMember);
}
@Override
public CompletableFuture<Void> closeModMailThreadEvaluateLogging(ModMailThread modMailThread, ClosingContext closingConfig, List<UndoActionInstance> undoActions) {
boolean loggingMode = featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, modMailThread.getServer(), ModMailMode.LOGGING);

View File

@@ -0,0 +1,65 @@
package dev.sheldan.abstracto.modmail.service;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.modmail.exception.QuickReplyExistsException;
import dev.sheldan.abstracto.modmail.exception.QuickReplyNotFoundException;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import dev.sheldan.abstracto.modmail.service.management.QuickReplyManagementService;
import java.util.List;
import java.util.Optional;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class QuickReplyServiceBean implements QuickReplyService {
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private QuickReplyManagementService quickReplyManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public QuickReply createQuickReply(String name, String content, Member creator, boolean anonymous) {
if(quickReplyManagementService.getQuickReplyByName(name, creator.getGuild().getIdLong()).isPresent()) {
throw new QuickReplyExistsException();
}
AUserInAServer creatorUser = userInServerManagementService.loadOrCreateUser(creator);
return quickReplyManagementService.createQuickReply(name, content, creatorUser, anonymous);
}
@Override
public void deleteQuickReply(String name, Guild guild) {
if(quickReplyManagementService.getQuickReplyByName(name, guild.getIdLong()).isEmpty()) {
throw new QuickReplyNotFoundException();
}
AServer server = serverManagementService.loadServer(guild);
quickReplyManagementService.deleteQuickReply(name, server);
}
@Override
public List<QuickReply> getQuickReplies(Guild guild) {
AServer server = serverManagementService.loadServer(guild);
return quickReplyManagementService.getQuickReplies(server);
}
@Override
public Optional<QuickReply> getQuickReply(String name, Guild guild) {
return quickReplyManagementService.getQuickReplyByName(name, guild.getIdLong());
}
@Override
public List<QuickReply> getQuickRepliesContaining(String name, Guild guild) {
AServer server = serverManagementService.loadServer(guild);
return quickReplyManagementService.getQuickRepliesContaining(name, server);
}
}

View File

@@ -0,0 +1,57 @@
package dev.sheldan.abstracto.modmail.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import dev.sheldan.abstracto.modmail.repository.QuickReplyRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class QuickReplyManagementServiceBean implements QuickReplyManagementService {
@Autowired
private QuickReplyRepository repository;
@Autowired
private ServerManagementService serverManagementService;
@Override
public Optional<QuickReply> getQuickReplyByName(String name, Long serverId) {
AServer server = serverManagementService.loadServer(serverId);
return repository.getByNameIgnoreCaseAndServer(name, server);
}
@Override
public QuickReply createQuickReply(String name, String content, AUserInAServer creator, boolean anonymous) {
QuickReply quickReply = QuickReply
.builder()
.name(name)
.additionalMessage(content)
.anonymous(anonymous)
.server(creator.getServerReference())
.creator(creator)
.build();
return repository.save(quickReply);
}
@Override
public void deleteQuickReply(String name, AServer server) {
repository.deleteByNameAndServer(name, server);
}
@Override
public List<QuickReply> getQuickReplies(AServer server) {
return repository.findByServer(server);
}
@Override
public List<QuickReply> getQuickRepliesContaining(String name, AServer server) {
return repository.findByNameContainingIgnoreCaseAndServer(name, server);
}
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,31 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<property name="modmailModule" value="(SELECT id FROM module WHERE name = 'modmail')"/>
<property name="modmailFeature" value="(SELECT id FROM feature WHERE key = 'modmail')"/>
<changeSet author="Sheldan" id="quickReply-commands">
<insert tableName="command">
<column name="name" value="quickReply"/>
<column name="module_id" valueComputed="${modmailModule}"/>
<column name="feature_id" valueComputed="${modmailFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="createQuickReply"/>
<column name="module_id" valueComputed="${modmailModule}"/>
<column name="feature_id" valueComputed="${modmailFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="deleteQuickReply"/>
<column name="module_id" valueComputed="${modmailModule}"/>
<column name="feature_id" valueComputed="${modmailFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="listQuickReplies"/>
<column name="module_id" valueComputed="${modmailModule}"/>
<column name="feature_id" valueComputed="${modmailFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,6 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,44 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="quick_reply-table">
<createTable tableName="quick_reply">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints nullable="false" primaryKey="true" primaryKeyName="quick_reply_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="creator_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="additional_message" type="VARCHAR(2048)">
<constraints nullable="false"/>
</column>
<column name="name" type="VARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="anonymous" type="BOOLEAN"/>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="quick_reply"
constraintName="fk_quick_reply_server" deferrable="false" initiallyDeferred="false"
onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="creator_user_in_server_id" baseTableName="quick_reply"
constraintName="fk_quick_reply_creator" deferrable="false" initiallyDeferred="false"
onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS quick_reply_update_trigger ON quick_reply;
CREATE TRIGGER quick_reply_update_trigger BEFORE UPDATE ON quick_reply FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS quick_reply_insert_trigger ON quick_reply;
CREATE TRIGGER quick_reply_insert_trigger BEFORE INSERT ON quick_reply FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,6 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="quick_reply.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -7,4 +7,5 @@
<include file="1.5.51/collection.xml" relativeToChangelogFile="true"/>
<include file="1.6.22/collection.xml" relativeToChangelogFile="true"/>
<include file="1.6.23/collection.xml" relativeToChangelogFile="true"/>
<include file="1.6.29/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.modmail.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class QuickReplyExistsException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "quick_reply_exists_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.modmail.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class QuickReplyNotFoundException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "quick_reply_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,61 @@
package dev.sheldan.abstracto.modmail.model.database;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.io.Serializable;
import java.time.Instant;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "quick_reply")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class QuickReply implements Serializable {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "additional_message")
private String additionalMessage;
@Column(name = "name")
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id")
private AServer server;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "creator_user_in_server_id")
private AUserInAServer creator;
@Column(name = "anonymous")
private Boolean anonymous;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
}

View File

@@ -6,7 +6,6 @@ import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import java.util.List;
import java.util.Map;
@@ -23,15 +22,7 @@ public class ModMailModeratorReplyModel {
* The staff {@link Member} which replied to the thread, be it anonymously or normal.
*/
private Member moderator;
/**
* The text which was used to reply. This is necessary, because the reply is triggered via a command, so
* we would need re-parse the {@link Message} in order to find the value to display
*/
private String text;
/**
* The {@link Message} which contained the command to reply to the user. This is needed for attachments.
*/
private Message postedMessage;
/**
* Whether or not the reply should be shown anonymous
*/

View File

@@ -0,0 +1,23 @@
package dev.sheldan.abstracto.modmail.model.template;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class QuickRepliesListResponseModel {
private List<QuickReplyListItem> quickReplies;
public static QuickRepliesListResponseModel fromQuickReplies(List<QuickReply> quickReplies) {
return QuickRepliesListResponseModel
.builder()
.quickReplies(quickReplies
.stream()
.map(QuickReplyListItem::fromQuickReply)
.collect(Collectors.toList()))
.build();
}
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.modmail.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class QuickReplyListItem {
private String name;
private String content;
private boolean anonymous;
private MemberDisplay creator;
public static QuickReplyListItem fromQuickReply(QuickReply quickReply) {
MemberDisplay creatorObj = MemberDisplay.fromAUserInAServer(quickReply.getCreator());
return QuickReplyListItem
.builder()
.name(quickReply.getName())
.content(quickReply.getAdditionalMessage())
.creator(creatorObj)
.anonymous(quickReply.getAnonymous())
.build();
}
}

View File

@@ -72,12 +72,14 @@ public interface ModMailThreadService {
* @param threadId The id of the {@link ModMailThread} to which the reply was sent to
* @param text The parsed text of the reply
* @param message The pure {@link Message} containing the command which caused the reply
* @param anonymous Whether or nor the message should be send anonymous
* @param anonymous Whether or nor the message should be sent anonymous
* @param targetUser The {@link User} the {@link ModMailThread} is about.
* @param guild The guild the reply is created in
* @param executingMember The member that initiates the reply
* @return A {@link CompletableFuture future} which completes when the message has been relayed to the DM
*/
CompletableFuture<Void> loadExecutingMemberAndRelay(Long threadId, String text, Message message, boolean anonymous, User targetUser, Guild guild);
CompletableFuture<Void> relayMessageToDm(Long threadId, String text, Message message, boolean anonymous, User targetUser, Guild guild, Member executingMember);
CompletableFuture<Void> relayMessageToDm(Long threadId, String content, Long uniqueMessageId, boolean anonymous, User targetUser, Guild guild, Member executingMember);
/**
* Closes the mod mail thread which means: deletes the {@link net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel} associated with the mod mail thread,

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.modmail.service;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import java.util.List;
import java.util.Optional;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
public interface QuickReplyService {
QuickReply createQuickReply(String name, String content, Member creator, boolean anonymous);
void deleteQuickReply(String name, Guild guild);
List<QuickReply> getQuickReplies(Guild guild);
Optional<QuickReply> getQuickReply(String name, Guild guild);
List<QuickReply> getQuickRepliesContaining(String name, Guild guild);
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.modmail.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.modmail.model.database.QuickReply;
import java.util.List;
import java.util.Optional;
public interface QuickReplyManagementService {
Optional<QuickReply> getQuickReplyByName(String name, Long serverId);
QuickReply createQuickReply(String name, String content, AUserInAServer creator, boolean anonymous);
void deleteQuickReply(String name, AServer server);
List<QuickReply> getQuickReplies(AServer server);
List<QuickReply> getQuickRepliesContaining(String prefix, AServer server);
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>profanity-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>profanity-filter</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>sticky-roles</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>sticky-roles-impl</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>sticky-roles</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>sticky-roles-int</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>suggestion</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>suggestion</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>twitch</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>twitch</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices</artifactId>
<version>1.6.26-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Some files were not shown because too many files have changed in this diff Show More