Compare commits

...

30 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
Sheldan
0586ade8d8 [AB-xxx] filtering current section from past sections in twitch stream notifications to not duplicate the information in case of a refresh 2026-05-15 00:26:52 +02:00
Sheldan
ae91c8ee4d [AB-xxx] adding grrr image generation 2026-05-15 00:20:52 +02:00
release-bot
0cec5284b4 Commit from GitHub Actions (Publishes a new version of abstracto) 2026-05-02 22:46:19 +00:00
release-bot
51e0da06ae [maven-release-plugin] prepare for next development iteration 2026-05-02 22:35:37 +00:00
release-bot
f4b048d18b [maven-release-plugin] prepare release v1.6.25 2026-05-02 22:35:35 +00:00
Sheldan
6addd5939b [AB-xxx] adding honeypot message feature to ban members that message a certain channel 2026-05-03 00:31:56 +02:00
Sheldan
dafde2d8f6 [AB-xxx] refactoring enable/disableMode to the modes coming from the feature instead of the ones already present in the server 2026-05-03 00:29:28 +02:00
Sheldan
96cbe2db87 [AB-xxx] adding ability to refresh twitch stream notification texts 2026-05-03 00:28:02 +02:00
Sheldan
2ae2542c71 [AB-xxx] changing timestamp used for updated in modmail reminder job 2026-05-02 13:58:51 +02:00
release-bot
a69d4c25ff Commit from GitHub Actions (Publishes a new version of abstracto) 2026-04-26 21:26:13 +00:00
release-bot
fba5adf573 [maven-release-plugin] prepare for next development iteration 2026-04-26 21:14:22 +00:00
release-bot
dd21390a60 [maven-release-plugin] prepare release v1.6.24 2026-04-26 21:14:20 +00:00
Sheldan
790cca7278 [AB-xxx] changing duration for reminder snooze, so that it doesnt add the duration _after_ the snooze again, the snooze is intended to be the duration at which the reminders start again
fixing message embed cleanup job not being able to deal with missing channels
2026-04-26 22:58:08 +02:00
release-bot
52f6fd148e Commit from GitHub Actions (Publishes a new version of abstracto) 2026-04-25 21:41:51 +00:00
release-bot
708703fc1f [maven-release-plugin] prepare for next development iteration 2026-04-25 21:30:06 +00:00
release-bot
c5f47cf6e5 [maven-release-plugin] prepare release v1.6.23 2026-04-25 21:30:04 +00:00
Sheldan
f157b1edd7 [AB-xxx] refactoring to use a separate updated column for the auto closing
wrapping modmail thread actions into a separate transaction
2026-04-25 23:27:22 +02:00
147 changed files with 1500 additions and 169 deletions

2
.env
View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<artifactId>image-generation-impl</artifactId>

View File

@@ -0,0 +1,135 @@
package dev.sheldan.abstracto.imagegeneration.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.config.UserCommandConfig;
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.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.templating.model.AttachedFile;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FileService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationFeatureDefinition;
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames;
import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Grrr extends AbstractConditionableCommand {
@Autowired
private ImageGenerationService imageGenerationService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private FileService fileService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
private static final String GRRR_EMBED_TEMPLATE_KEY = "grrr_response";
public static final String TEXT_PARAMETER_KEY = "text";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
String text = (String) parameters.get(0);
File grrrFile = imageGenerationService.getGrrrImage(text);
MessageToSend messageToSend = templateService.renderEmbedTemplate(GRRR_EMBED_TEMPLATE_KEY, new Object(), commandContext.getGuild().getIdLong());
// template support does not support binary files
AttachedFile file = AttachedFile
.builder()
.file(grrrFile)
.fileName("grrr.png")
.build();
messageToSend.getAttachedFiles().add(file);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
event.deferReply().queue();
String text = slashCommandParameterService.getCommandOption(TEXT_PARAMETER_KEY, event, String.class);
File grrrFile = imageGenerationService.getGrrrImage(text);
MessageToSend messageToSend = templateService.renderEmbedTemplate(GRRR_EMBED_TEMPLATE_KEY, new Object(), event.getGuild().getIdLong());
// template support does not support binary files
AttachedFile file = AttachedFile
.builder()
.file(grrrFile)
.fileName("grrr.png")
.build();
messageToSend.getAttachedFiles().add(file);
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(messageToSend, event.getHook()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter textParameter = Parameter
.builder()
.name(TEXT_PARAMETER_KEY)
.type(String.class)
.templated(true)
.remainder(true)
.build();
parameters.add(textParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.userInstallable(true)
.userCommandConfig(UserCommandConfig.all())
.rootCommandName(ImageGenerationSlashCommandNames.IMAGE_GENERATION)
.groupName("memes")
.commandName("grrr")
.build();
return CommandConfiguration.builder()
.name("grrr")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.supportsEmbedException(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ImageGenerationFeatureDefinition.IMAGE_GENERATION;
}
}

View File

@@ -27,6 +27,10 @@ public class ImageGenerationServiceBean implements ImageGenerationService {
@Value("${abstracto.feature.imagegeneration.amongusText.url}")
private String amongusTextUrl;
@Value("${abstracto.feature.imagegeneration.grrr.url}")
private String grrrUrl;
@Autowired
private HttpService httpService;
@@ -68,4 +72,13 @@ public class ImageGenerationServiceBean implements ImageGenerationService {
}
}
@Override
public File getGrrrImage(String text) {
try {
return httpService.downloadFileToTempFile(grrrUrl.replace("{1}", text));
} catch (IOException | RequestException e) {
throw new AbstractoRunTimeException(String.format("Failed to download grrr image for text %s with error %s", text, e.getMessage()));
}
}
}

View File

@@ -11,3 +11,5 @@ abstracto.feature.imagegeneration.bonk.url=http://${PRIVATE_REST_API_HOST}:${PRI
abstracto.feature.imagegeneration.bonk.imagesize=128
abstracto.feature.imagegeneration.amongusText.url=http://${PRIVATE_REST_API_HOST}:${PRIVATE_REST_API_PORT}/memes/amongus/text?text={1}
abstracto.feature.imagegeneration.grrr.url=http://${PRIVATE_REST_API_HOST}:${PRIVATE_REST_API_PORT}/memes/grrr/text?text={1}

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="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,16 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns: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="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="imageGenerationFeature" value="(SELECT id FROM feature WHERE key = 'imageGeneration')"/>
<changeSet author="Sheldan" id="grrr-command">
<insert tableName="command">
<column name="name" value="grrr"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${imageGenerationFeature}"/>
</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

@@ -5,4 +5,5 @@
<include file="1.5.15/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.19/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.22/collection.xml" relativeToChangelogFile="true"/>
<include file="1.6.26/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

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

View File

@@ -7,4 +7,5 @@ public interface ImageGenerationService {
File getPatGif(String imageUrl);
File getBonkGif(String imageUrl);
File getAmongusTextImage(String text);
File getGrrrImage(String text);
}

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.23-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.23-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.23-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.23-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.23-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.23-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -26,9 +26,11 @@ import dev.sheldan.abstracto.linkembed.service.management.MessageEmbedPostManage
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.interactions.commands.CommandInteraction;
import org.apache.commons.lang3.tuple.Pair;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -191,11 +193,20 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
.builder()
.message(embeddedMessage)
.build();
MessageToSend messageToSend =
templateService.renderEmbedTemplate(MESSAGE_EMBED_CLEANUP_REPLACEMENT_TEMPLATE, model, embeddingMessage.getServerId());
return channelService.editMessageInAChannelFuture(messageToSend, embeddingMessage.getServerId(), embeddingMessage.getChannelId(),
embeddingMessage.getMessageId());
}).toList();
Optional<GuildChannel> existingChannel =
channelService.getGuildChannelFromServerOptional(embeddingMessage.getServerId(), embeddingMessage.getChannelId());
// if the channel doesnt exist, we dont need to cleanup
if(existingChannel.isPresent()) {
MessageToSend messageToSend =
templateService.renderEmbedTemplate(MESSAGE_EMBED_CLEANUP_REPLACEMENT_TEMPLATE, model, embeddingMessage.getServerId());
return channelService.editMessageInAChannelFuture(messageToSend, embeddingMessage.getServerId(), embeddingMessage.getChannelId(),
embeddingMessage.getMessageId());
} else {
return null;
}
})
.filter(Objects::nonNull)
.toList();
return FutureUtils.toSingleFutureGeneric(editList).whenComplete((unused, throwable) -> {
if(throwable != null) {
log.warn("Failed to cleanup embedded messages..", throwable);

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-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.23-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.23-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.23-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.23-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.23-SNAPSHOT</version>
<version>1.6.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -18,7 +18,7 @@ import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.listener.HoneyPotServiceBean;
import dev.sheldan.abstracto.moderation.service.HoneyPotServiceBean;
import dev.sheldan.abstracto.moderation.model.template.command.HoneyPotBanResponseModel;
import java.time.Duration;
import java.time.Instant;
@@ -98,7 +98,7 @@ public class HoneyPotBan extends AbstractConditionableCommand {
.toList();
Role honeyPotRole = guild.getRoleById(honeyPotServiceBean.getHoneyPotRoleId(guild.getIdLong()));
List<CompletableFuture<Void>> futures = currentMembersWithHoneypotRole.stream().map(member ->
honeyPotServiceBean.banForHoneyPot(member, honeyPotRole)
honeyPotServiceBean.banForHoneyPotRole(member, honeyPotRole)
).toList();
Integer memberCount = currentMembersWithHoneypotRole.size();
CompletableFutureList<Void> futureList = new CompletableFutureList<>(futures);

View File

@@ -0,0 +1,61 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.mode.HoneypotMode;
import dev.sheldan.abstracto.moderation.service.HoneyPotServiceBean;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class HoneyPotMessageListener implements AsyncMessageReceivedListener {
@Autowired
private ConfigService configService;
@Autowired
private HoneyPotServiceBean honeyPotServiceBean;
@Autowired
private MessageService messageService;
@Override
public DefaultListenerResult execute(MessageReceivedModel model) {
Long honeyPotChannel = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_CHANNEL, model.getServerId());
if(honeyPotChannel == 0) {
return DefaultListenerResult.IGNORED;
}
if(model.getMessage().getChannelId().equals(honeyPotChannel.toString())) {
boolean honeyPotActivated = honeyPotServiceBean.fellIntoHoneyPotIgnoringJoinDate(model.getServerId(), model.getMessage().getMember());
if(honeyPotActivated) {
log.info("Banning user {} in guild {} due to a message in channel {}.", model.getMessage().getAuthor().getIdLong(), model.getServerId(), honeyPotChannel);
honeyPotServiceBean.banForHoneyPotMessage(model.getMessage().getMember(), honeyPotChannel);
} else {
log.info("NOT banning user {} in guild {} due to a message in honeypot channel {}.", model.getMessage().getAuthor().getIdLong(), model.getServerId(), honeyPotChannel);
messageService.deleteMessage(model.getMessage());
}
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.HONEYPOT;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return List.of(HoneypotMode.MESSAGE);
}
}

View File

@@ -1,12 +1,15 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.sync.jda.RoleAddedListener;
import dev.sheldan.abstracto.core.models.listener.RoleAddedModel;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.mode.HoneypotMode;
import dev.sheldan.abstracto.moderation.service.HoneyPotServiceBean;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -37,7 +40,7 @@ public class HoneyPotRoleAddedListener implements RoleAddedListener {
boolean fellIntoHoneyPot = honeyPotServiceBean.fellIntoHoneyPot(model.getServerId(), model.getTargetMember());
if (fellIntoHoneyPot) {
log.info("Banning user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId());
honeyPotServiceBean.banForHoneyPot(model.getTargetMember(), model.getRole());
honeyPotServiceBean.banForHoneyPotRole(model.getTargetMember(), model.getRole());
} else {
log.info("User {} in server {} will not get banned by honeypot. All existing roles besides {} will be removed.",
model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), honeyPotRoleId);
@@ -83,4 +86,8 @@ public class HoneyPotRoleAddedListener implements RoleAddedListener {
return ListenerPriority.MEDIUM;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return List.of(HoneypotMode.ROLE);
}
}

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.moderation.listener;
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ConditionContextInstance;
import dev.sheldan.abstracto.core.models.ServerUser;
@@ -13,7 +13,6 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig;
import dev.sheldan.abstracto.moderation.model.listener.HoneyPotReasonModel;
import dev.sheldan.abstracto.moderation.service.BanService;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -68,11 +67,33 @@ public class HoneyPotServiceBean {
return !allowed;
}
public boolean fellIntoHoneyPotIgnoringJoinDate(Long serverId, Member member) {
Integer levelToSkipBan = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_IGNORED_LEVEL, serverId).intValue();
boolean allowed = userHasLevel(member, levelToSkipBan);
return !allowed;
}
public List<Member> getCurrentMembersWithHoneypotRole(Guild guild) {
return memberService.getMembersWithRole(guild.getIdLong(), getHoneyPotRoleId(guild.getIdLong()));
}
public CompletableFuture<Void> banForHoneyPot(Member targetMember, Role role) {
public CompletableFuture<Void> banForHoneyPotMessage(Member targetMember, Long channelId) {
HoneyPotReasonModel reasonModel = HoneyPotReasonModel
.builder()
.memberDisplay(MemberDisplay.fromMember(targetMember))
.build();
ServerUser bannedUser = ServerUser.fromMember(targetMember);
String banReason = templateService.renderTemplate(HONEYPOT_BAN_REASON_TEMPLATE, reasonModel, bannedUser.getServerId());
return banService.banUserWithNotification(bannedUser, banReason, ServerUser.fromMember(targetMember.getGuild().getSelfMember()),
targetMember.getGuild(), Duration.ofDays(7)).thenAccept(banResult -> {
log.info("Banned user {} in guild {} due to a message in channel {}.", bannedUser.getUserId(), bannedUser.getServerId(), channelId);
}).exceptionally(throwable -> {
log.error("Failed to ban user {} in guild {} due to a message in channel {}.", bannedUser.getUserId(), bannedUser.getServerId(), channelId, throwable);
return null;
});
}
public CompletableFuture<Void> banForHoneyPotRole(Member targetMember, Role role) {
HoneyPotReasonModel reasonModel = HoneyPotReasonModel
.builder()
.memberDisplay(MemberDisplay.fromMember(targetMember))
@@ -91,7 +112,7 @@ public class HoneyPotServiceBean {
}
private boolean userHasLevel(Member member, Integer level) {
log.info("Checking if member {} is ignored to click on the honeypot in server {}.", member.getIdLong(),member.getGuild().getIdLong());
log.info("Checking if member {} is ignored by the honeypot in server {}.", member.getIdLong(),member.getGuild().getIdLong());
Map<String, Object> parameters = new HashMap<>();
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member);
parameters.put(LEVEL_CONDITION_USER_ID_PARAMETER, userInAServer.getUserInServerId());

View File

@@ -99,3 +99,14 @@ abstracto.systemConfigs.honeypotIgnoredJoinDurationSeconds.longValue=86400
abstracto.featureFlags.honeypot.featureName=honeypot
abstracto.featureFlags.honeypot.enabled=false
abstracto.featureModes.honeypotRole.featureName=honeypot
abstracto.featureModes.honeypotRole.mode=honeypotRole
abstracto.featureModes.honeypotRole.enabled=false
abstracto.featureModes.honeypotMessage.featureName=honeypot
abstracto.featureModes.honeypotMessage.mode=honeypotMessage
abstracto.featureModes.honeypotMessage.enabled=false
abstracto.systemConfigs.honeypotChannel.name=honeypotChannel
abstracto.systemConfigs.honeypotChannel.longValue=0

View File

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

View File

@@ -2,6 +2,8 @@ package dev.sheldan.abstracto.moderation.config.feature;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.moderation.config.feature.mode.HoneypotMode;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@@ -12,6 +14,7 @@ public class HoneyPotFeatureConfig implements FeatureConfig {
public static final String HONEYPOT_ROLE_ID = "honeypotRoleId";
public static final String HONEYPOT_IGNORED_LEVEL = "honeypotIgnoredLevel";
public static final String HONEYPOT_CHANNEL = "honeypotChannel";
public static final String HONEYPOT_IGNORED_JOIN_DURATION_SECONDS = "honeypotIgnoredJoinDurationSeconds";
@Override
@@ -21,6 +24,11 @@ public class HoneyPotFeatureConfig implements FeatureConfig {
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(HONEYPOT_ROLE_ID, HONEYPOT_IGNORED_LEVEL, HONEYPOT_IGNORED_JOIN_DURATION_SECONDS);
return Arrays.asList(HONEYPOT_ROLE_ID, HONEYPOT_IGNORED_LEVEL, HONEYPOT_IGNORED_JOIN_DURATION_SECONDS, HONEYPOT_CHANNEL);
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(HoneypotMode.values());
}
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.abstracto.moderation.config.feature.mode;
import dev.sheldan.abstracto.core.config.FeatureMode;
import lombok.Getter;
@Getter
public enum HoneypotMode implements FeatureMode {
ROLE("honeypotRole"),
MESSAGE("honeypotMessage");
private final String key;
HoneypotMode(String key) {
this.key = key;
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.23-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.23-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

@@ -92,8 +92,8 @@ public class ModmailAutoCloseListener implements ModmailThreadActionListener {
}
private static Instant getTimeStampToConsider(ModMailThread thread) {
if(thread.getUpdated() != null) {
return thread.getUpdated();
if(thread.getLastUpdated() != null) {
return thread.getLastUpdated();
}
return thread.getCreated();
}

View File

@@ -75,7 +75,7 @@ public class ModmailReminderListener implements ModmailThreadActionListener {
log.debug("Thread {} is closed - ignoring.", model.getThreadId());
return ModmailThreadActionListenerResult.IGNORED;
}
Instant timeStampToConsider = getTimestampToUse(thread);
Instant timeStampToConsider = getTimestampToUse(thread, duration);
boolean mustBeReminded = timeInPastDuration.isAfter(timeStampToConsider);
if (mustBeReminded) {
sendReminder(thread)
@@ -97,14 +97,17 @@ public class ModmailReminderListener implements ModmailThreadActionListener {
}
private static Instant getTimestampToUse(ModMailThread thread) {
private static Instant getTimestampToUse(ModMailThread thread, Duration configuredDuration) {
if (thread.getRemindersSnoozedUntil() != null) {
return thread.getRemindersSnoozedUntil();
return thread.getRemindersSnoozedUntil().minus(configuredDuration);
}
return getUpdatedOrCrated(thread);
}
private static Instant getUpdatedOrCrated(ModMailThread thread) {
if(thread.getLastUpdated() != null) {
return thread.getLastUpdated();
}
if(thread.getUpdated() != null) {
return thread.getUpdated();
}

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,9 +50,11 @@ 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;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
@@ -287,26 +289,33 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.serverId(thread.getServer().getId())
.serverUser(ServerUser.fromAUserInAServer(thread.getUser()))
.messageCount(thread.getMessages() != null ? thread.getMessages().size() : 0)
.updated(thread.getUpdated())
.updated(thread.getLastUpdated())
.created(thread.getCreated())
.subscriberCount(thread.getSubscribers() != null ? thread.getSubscribers().size() : 0)
.build();
for (ModmailThreadActionListener modmailThreadActionListener : threadActionListeners) {
try {
log.info("Executing action {} for thread {}.", modmailThreadActionListener.getClass().getSimpleName(), model.getThreadId());
ModmailThreadActionListener.ModmailThreadActionListenerResult result = modmailThreadActionListener.execute(model);
ModmailThreadActionListener.ModmailThreadActionListenerResult result =
self.executeThreadAction(modmailThreadActionListener, model);
if(ModmailThreadActionListener.ModmailThreadActionListenerResult.FINAL == result) {
log.info("Listener {} terminated for thread {}.", modmailThreadActionListener.getClass().getSimpleName(), model.getThreadId());
break;
}
} catch (Exception exception) {
log.error("Action failed to execute.", exception);
log.error("Action failed to execute for thread {}.", thread.getId(), exception);
}
}
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public ModmailThreadActionListener.ModmailThreadActionListenerResult executeThreadAction(
ModmailThreadActionListener modmailThreadActionListener, ModmailThreadActionListenerModel model) {
return modmailThreadActionListener.execute(model);
}
/**
* This method is responsible for creating the instance in the database, sending the header in the newly created text channel and forwarding the initial message
* by the user (if any), after this is complete, this method executes the method to perform the mod mail notification.
@@ -740,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)
@@ -788,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

@@ -116,6 +116,7 @@ public class ModMailThreadManagementServiceBean implements ModMailThreadManageme
.user(userInAServer)
.server(userInAServer.getServerReference())
.state(ModMailThreadState.INITIAL)
.lastUpdated(Instant.now())
.updated(Instant.now())
.appeal(appeal)
.build();
@@ -133,6 +134,7 @@ public class ModMailThreadManagementServiceBean implements ModMailThreadManageme
} else {
modMailThread.setState(newState);
}
modMailThread.setLastUpdated(Instant.now());
modMailThread.setUpdated(Instant.now());
modMailThreadRepository.save(modMailThread);
}

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,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="tables/tables.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,12 @@
<?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="mod_mail_thread-add_last_updated_column">
<addColumn tableName="mod_mail_thread">
<column name="last_updated" type="TIMESTAMP WITHOUT TIME ZONE" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="true" />
</column>
</addColumn>
</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="modmail_thread.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

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

@@ -6,4 +6,6 @@
<include file="1.5.37/collection.xml" relativeToChangelogFile="true"/>
<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.23-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

@@ -56,6 +56,9 @@ public class ModMailThread implements Serializable {
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
@Column(name = "last_updated")
private Instant lastUpdated;
@Column(name = "closed")
private Instant closed;

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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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.23-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