Compare commits

...

43 Commits

Author SHA1 Message Date
release-bot
27466b7333 [maven-release-plugin] prepare release v1.5.38 2024-06-13 16:21:31 +00:00
Sheldan
bfb8969d1f [AB-xxx] fixing passing wrong ID in contact slash command 2024-06-13 18:18:51 +02:00
Sheldan
0097ff801a [AB-xxx] changing hikari max live time 2024-06-12 20:07:55 +02:00
Sheldan
b6a188c04d [AB-xxx] using the suggestion message as the thread starter for suggestion threads 2024-06-03 22:24:35 +02:00
release-bot
2fa1adde02 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-05-30 23:29:17 +00:00
release-bot
cb8b64cc01 [maven-release-plugin] prepare for next development iteration 2024-05-30 23:19:23 +00:00
release-bot
3b3dd0dbb7 [maven-release-plugin] prepare release v1.5.37 2024-05-30 23:19:22 +00:00
Sheldan
388fead2a6 [AB-xxx] typo change
java doc update
removal of todo
2024-05-31 01:14:26 +02:00
Sheldan
336c3d0bd8 [AB-xxx] adding modmail support for ban appeals
refactoring modmail to use user objects instead of member in various places
2024-05-31 01:14:26 +02:00
Sheldan
4991ad8f1c [AB-xxx] making intents, cache policies, and cache flags configurable 2024-05-18 18:22:33 +02:00
Sheldan
c5136a1808 [AB-xxx] removing example implementation repository from readme 2024-05-17 17:34:33 +02:00
release-bot
446d882eec Commit from GitHub Actions (Publishes a new version of abstracto) 2024-05-05 22:24:18 +00:00
release-bot
b3e207a967 [maven-release-plugin] prepare for next development iteration 2024-05-05 22:12:03 +00:00
release-bot
5c25345cf8 [maven-release-plugin] prepare release v1.5.36 2024-05-05 22:12:01 +00:00
Sheldan
a01a5055a0 [AB-xxx] refactoring handling of bans and mutes: commands actively log, the reason for this is that the command is the only place who actually knows how executed the command. the event itself only sees the bot performing the action
adding event based logging of kicks
2024-05-06 00:07:24 +02:00
Sheldan
234aae3783 [AB-xxx] reworking ban logging to use audit log instead of actively logging or using the banned event
partially fixing broken infraction handling
adding CompletableFutureMap to handle futures easier
updating user display object to also hold name
replaced some references to UserObjects in models with UserDisplay objects
2024-05-05 01:58:26 +02:00
Sheldan
ca45137cc6 [AB-xxx] reworking mute logging to use audit log events instead of active logging and member update events 2024-05-04 20:35:56 +02:00
Sheldan
bc3d16b40e [AB-xxx] fixing using the wrong user for unmute notifications 2024-05-03 18:24:10 +02:00
Sheldan
66e212d30c [AB-xxx] updating documentation 2024-05-03 18:07:29 +02:00
Sheldan
b69811479f [AB-xxx] adding more documentation in the moderation area
removing not used feature modes from configuration
2024-04-19 23:57:33 +02:00
Sheldan
43c5d041ef [AB-xxx] adding/updating documentation for core and experience module
updating asciidoctor plugin version
adding check to not allow duplicate level action configurations
limiting experience level up notification toggle command to be only available if the feature mode is enabled
2024-04-18 23:09:56 +02:00
Sheldan
dfe9792330 [AB-xxx] fixing combined parameters not providing the appropriate option types for slash commands 2024-04-13 00:20:02 +02:00
Sheldan
250df57bd0 [AB-xxx] adding tests for command management services
some code improvements
2024-04-07 14:59:22 +02:00
Sheldan
02b8ed2b5d [AB-xxx] adding unit test for server controller
refactoring parameter parsing tests to use assertj
2024-04-07 11:49:06 +02:00
Sheldan
71c1445439 [AB-112] adding command parameter alternatives to bonk/pat to use the message author the command was replied to 2024-04-05 17:30:06 +02:00
release-bot
d86299cdf6 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-04-05 13:22:47 +00:00
release-bot
1b86fba3e0 [maven-release-plugin] prepare for next development iteration 2024-04-05 13:08:51 +00:00
release-bot
3ee7c92cdd [maven-release-plugin] prepare release v1.5.35 2024-04-05 13:08:49 +00:00
Sheldan
6c6cd130aa [AB-xxx] changing types of ids to be string instead for javascript purposes 2024-04-05 15:06:10 +02:00
release-bot
65a1d44069 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-04-05 12:43:58 +00:00
release-bot
11312a5e27 [maven-release-plugin] prepare for next development iteration 2024-04-05 12:29:23 +00:00
release-bot
6b13958ac0 [maven-release-plugin] prepare release v1.5.34 2024-04-05 12:29:20 +00:00
Sheldan
3142daafd3 [AB-xxx] renaming leaderboard url property 2024-04-05 01:58:43 +02:00
Sheldan
4bef78f847 [AB-xxx] adding link to the leaderboard to the leaderboard command response 2024-04-05 01:40:06 +02:00
Sheldan
82be86e086 [AB-xxx] adding locking mechanism for role assignments to work around discord lack of role update locking 2024-04-04 22:54:18 +02:00
Sheldan
bff505ef25 [AB-xxx] fixing not using the ban reason for moderation actions 2024-03-27 23:17:06 +01:00
release-bot
533f5671c2 [maven-release-plugin] prepare for next development iteration 2024-03-27 21:26:56 +00:00
release-bot
8c7547b485 [maven-release-plugin] prepare release v1.5.33 2024-03-27 21:26:54 +00:00
Sheldan
741c194bb8 [AB-xxx] changing styling for smaller screens for member display to truncate the name
prepare for release
2024-03-27 22:24:38 +01:00
release-bot
d2bdfd8dac [maven-release-plugin] prepare for next development iteration 2024-03-26 22:54:50 +00:00
release-bot
36c67fbe20 [maven-release-plugin] prepare release v1.5.32 2024-03-26 22:54:47 +00:00
Sheldan
8fd1aede2a [AB-xxx] changing style of leaderboard table
preparing for release
2024-03-26 23:48:22 +01:00
release-bot
287ae1f0b1 [maven-release-plugin] prepare for next development iteration 2024-03-26 21:40:02 +00:00
227 changed files with 2859 additions and 1668 deletions

2
.env
View File

@@ -1,2 +1,2 @@
REGISTRY_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.5.31
VERSION=1.5.37

View File

@@ -8,7 +8,6 @@ Abstracto represents a framework to be used as a basis for a Discord bot. It use
and provides an extensive tool set to create new commands and a wide range of commands out of the box.
This repository does not provide the full functionality in order to start a discord bot, because it requires a Main class.
An example implementation of this bot can be seen [here](https://github.com/Sheldan/Crimson). This repository contains the required configuration in order to run a bot and example customizations.
## Technologies

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.entertainment.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CombinedParameterEntry;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
@@ -86,7 +87,7 @@ public class Mock extends AbstractConditionableCommand {
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Map<String, Object> parameterAlternatives = new HashMap<>();
parameterAlternatives.put(ADDITIONAL_TYPES_KEY, Arrays.asList(Message.class, String.class));
parameterAlternatives.put(ADDITIONAL_TYPES_KEY, Arrays.asList(CombinedParameterEntry.messageParameter(Message.class), CombinedParameterEntry.parameter(String.class)));
Parameter messageParameter = Parameter
.builder()

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -56,7 +56,7 @@ public class ExperienceConfigController {
} else {
roleDisplay = RoleDisplay
.builder()
.id(levelRole.getRoleId())
.id(String.valueOf(levelRole.getRoleId()))
.build();
}
return ExperienceRoleDisplay

View File

@@ -68,7 +68,7 @@ public class LeaderboardController {
}
return UserExperienceDisplay
.builder()
.id(userId)
.id(String.valueOf(userId))
.messages(aUserExperience.getMessageCount())
.level(aUserExperience.getLevelOrDefault())
.rank((int) pageable.getOffset() + page.getContent().indexOf(aUserExperience) + 1)

View File

@@ -18,6 +18,7 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureMode;
import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames;
import dev.sheldan.abstracto.experience.exception.LevelActionAlreadyExistsException;
import dev.sheldan.abstracto.experience.exception.LevelActionNotFoundException;
import dev.sheldan.abstracto.experience.listener.LevelActionListener;
import dev.sheldan.abstracto.experience.model.LevelActionPayload;
@@ -99,6 +100,9 @@ public class AddLevelAction extends AbstractConditionableCommand {
return userExperienceManagementService.saveUser(user);
});
}
if(levelActionManagementService.getLevelAction(actionName, level, server, userExperience).isPresent()) {
throw new LevelActionAlreadyExistsException();
}
levelActionManagementService.createLevelAction(level, server, actionName, userExperience, payload);
return interactionService.replyEmbed(RESPONSE_TEMPLATE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());

View File

@@ -7,12 +7,14 @@ 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.config.FeatureMode;
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.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureMode;
import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import net.dv8tion.jda.api.entities.Member;
@@ -100,4 +102,10 @@ public class ExpLevelUpNotification extends AbstractConditionableCommand {
.help(helpInfo)
.build();
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(ExperienceFeatureMode.LEVEL_UP_NOTIFICATION);
}
}

View File

@@ -28,7 +28,9 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@@ -70,6 +72,9 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
@Autowired
private InteractionService interactionService;
@Value("${abstracto.experience.leaderboard.externalUrl}")
private String leaderboardExternalURL;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
@@ -91,11 +96,18 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
futures.add(userRankFuture);
String leaderboardUrl;
if(!StringUtils.isBlank(leaderboardExternalURL)) {
leaderboardUrl = String.format("%s/experience/leaderboards/%s", leaderboardExternalURL, actorUser.getGuild().getIdLong());
} else {
leaderboardUrl = null;
}
return FutureUtils.toSingleFuture(futures).thenCompose(aVoid -> {
List<LeaderBoardEntryModel> finalModels = completableFutures.join();
LeaderBoardModel leaderBoardModel = LeaderBoardModel
.builder()
.userExperiences(finalModels)
.leaderboardUrl(leaderboardUrl)
.userExecuting(userRankFuture.join().get(0))
.build();
return CompletableFuture.completedFuture(templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, actorUser.getGuild().getIdLong()));

View File

@@ -10,7 +10,7 @@ import lombok.Getter;
@Builder
public class UserExperienceDisplay {
private UserDisplay member;
private Long id;
private String id;
private Integer rank;
private Integer level;
private Long experience;

View File

@@ -17,4 +17,6 @@ abstracto.featureModes.levelUpNotification.enabled=false
abstracto.featureModes.levelAction.featureName=experience
abstracto.featureModes.levelAction.mode=levelAction
abstracto.featureModes.levelAction.enabled=false
abstracto.featureModes.levelAction.enabled=false
abstracto.experience.leaderboard.externalUrl=${FRONTEND_BASE:}

View File

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

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.experience.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
public class LevelActionAlreadyExistsException extends AbstractoRunTimeException implements Templatable {
public LevelActionAlreadyExistsException() {
super("Level action already exists.");
}
@Override
public String getTemplateName() {
return "level_action_already_exists_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -24,4 +24,5 @@ public class LeaderBoardModel extends SlimUserInitiatedServerContext {
* The {@link LeaderBoardEntryModel} containing the leaderboard information executing the command.
*/
private LeaderBoardEntryModel userExecuting;
private String leaderboardUrl;
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</version>
</parent>
<artifactId>giveaway-impl</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</version>
</parent>
<artifactId>giveaway</artifactId>

View File

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

View File

@@ -2,11 +2,13 @@ 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.CombinedParameterEntry;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.handler.parameter.CombinedParameter;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
@@ -21,16 +23,18 @@ import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationFeatureDefini
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames;
import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.command.config.Parameter.ADDITIONAL_TYPES_KEY;
@Component
public class Bonk extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member";
@@ -65,7 +69,11 @@ public class Bonk extends AbstractConditionableCommand {
if(parameters.isEmpty()) {
member = commandContext.getAuthor();
} else {
member = (Member) parameters.get(0);
if(parameters.get(0) instanceof Message) {
member = ((Message) parameters.get(0)).getMember();
} else {
member = (Member) parameters.get(0);
}
}
File bonkGifFile = imageGenerationService.getBonkGif(member.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(BONK_EMBED_TEMPLATE_KEY, new Object());
@@ -107,10 +115,13 @@ public class Bonk extends AbstractConditionableCommand {
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Map<String, Object> parameterAlternatives = new HashMap<>();
parameterAlternatives.put(ADDITIONAL_TYPES_KEY, Arrays.asList(CombinedParameterEntry.messageParameter(Message.class), CombinedParameterEntry.parameter(Member.class)));
Parameter memberParameter = Parameter
.builder()
.name(MEMBER_PARAMETER_KEY)
.type(Member.class)
.type(CombinedParameter.class)
.additionalInfo(parameterAlternatives)
.templated(true)
.optional(true)
.build();

View File

@@ -2,11 +2,13 @@ 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.CombinedParameterEntry;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.handler.parameter.CombinedParameter;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
@@ -21,16 +23,18 @@ import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationFeatureDefini
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames;
import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.command.config.Parameter.ADDITIONAL_TYPES_KEY;
@Component
public class Pat extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member";
@@ -65,7 +69,11 @@ public class Pat extends AbstractConditionableCommand {
if(parameters.isEmpty()) {
member = commandContext.getAuthor();
} else {
member = (Member) parameters.get(0);
if(parameters.get(0) instanceof Message) {
member = ((Message) parameters.get(0)).getMember();
} else {
member = (Member) parameters.get(0);
}
}
File patGifFile = imageGenerationService.getPatGif(member.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(PAT_EMBED_TEMPLATE_KEY, new Object());
@@ -107,10 +115,13 @@ public class Pat extends AbstractConditionableCommand {
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Map<String, Object> parameterAlternatives = new HashMap<>();
parameterAlternatives.put(ADDITIONAL_TYPES_KEY, Arrays.asList(CombinedParameterEntry.messageParameter(Message.class), CombinedParameterEntry.parameter(Member.class)));
Parameter memberParameter = Parameter
.builder()
.name(MEMBER_PARAMETER_KEY)
.type(Member.class)
.type(CombinedParameter.class)
.additionalInfo(parameterAlternatives)
.templated(true)
.optional(true)
.build();

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,7 +18,7 @@
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>1.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,13 +5,14 @@ import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncLeaveListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.MemberLeaveModel;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MemberLeaveLogModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -40,10 +41,10 @@ public class LeaveLogger implements AsyncLeaveListener {
.userId(listenerModel.getUser().getIdLong())
.serverId(listenerModel.getServerId())
.build();
MemberLeaveModel model = MemberLeaveModel
MemberLeaveLogModel model = MemberLeaveLogModel
.builder()
.leavingUser(leavingUser)
.user(listenerModel.getUser())
.user(UserDisplay.fromUser(listenerModel.getUser()))
.build();
log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), listenerModel.getServerId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_LEAVE_TEMPLATE, model, listenerModel.getServerId());

View File

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

View File

@@ -1,13 +1,15 @@
package dev.sheldan.abstracto.logging.model.template;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
@Getter
@Setter
@Builder
public class MemberLeaveLogModel {
private Member member;
private ServerUser leavingUser;
private UserDisplay user;
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -116,7 +116,7 @@ public class Infractions extends AbstractConditionableCommand {
} else if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.STRING)){
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
Long userId = Long.parseLong(userIdStr);
AUserInAServer userInServer = userInServerManagementService.createUserInServer(event.getGuild().getIdLong(), userId);
AUserInAServer userInServer = userInServerManagementService.loadOrCreateUser(event.getGuild().getIdLong(), userId);
infractions = infractionManagementService.getInfractionsForUser(userInServer);
} else {

View File

@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -73,11 +74,15 @@ public class Mute extends AbstractConditionableCommand {
Duration duration = (Duration) parameters.get(1);
String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason;
Instant oldTimeoutDate = null;
if(member.getTimeOutEnd() != null && member.isTimedOut()) {
oldTimeoutDate = member.getTimeOutEnd().toInstant();
}
ServerUser userToMute = ServerUser.fromMember(member);
ServerUser mutingUser = ServerUser.fromMember(commandContext.getAuthor());
Long serverId = commandContext.getGuild().getIdLong();
ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(commandContext.getMessage());
return muteService.muteMemberWithLog(userToMute, mutingUser, reason, duration, commandContext.getGuild(), serverChannelMessage)
return muteService.muteMemberWithLog(userToMute, mutingUser, reason, duration, commandContext.getGuild(), serverChannelMessage, oldTimeoutDate)
.thenCompose(muteResult -> {
if(muteResult == NOTIFICATION_FAILED) {
MessageToSend errorNotification = templateService.renderEmbedTemplate(MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY, new Object(), serverId);

View File

@@ -11,7 +11,6 @@ import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
@@ -51,7 +50,7 @@ public class UnBan extends AbstractConditionableCommand {
String userIdStr = (String) parameters.get(0);
Long userId = Long.parseLong(userIdStr);
return userService.retrieveUserForId(userId)
.thenCompose(user -> banService.unBanUserWithNotification(userId, ServerUser.fromMember(commandContext.getAuthor()), commandContext.getGuild()))
.thenCompose(user -> banService.unbanUser(commandContext.getGuild(), user, commandContext.getAuthor()))
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@@ -60,7 +59,7 @@ public class UnBan extends AbstractConditionableCommand {
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, String.class);
Long userId = Long.parseLong(userIdStr);
return userService.retrieveUserForId(userId)
.thenCompose(user -> banService.unBanUserWithNotification(userId, ServerUser.fromMember(event.getMember()), event.getGuild()))
.thenCompose(user -> banService.unbanUser(event.getGuild(), user, event.getMember()))
.thenCompose(unused -> interactionService.replyEmbed(UN_BAN_RESPONSE, event))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}

View File

@@ -12,7 +12,6 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
@@ -42,9 +41,6 @@ public class UnMute extends AbstractConditionableCommand {
@Autowired
private InteractionService interactionService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();

View File

@@ -76,7 +76,7 @@ public class BanModerationActionModalListener implements ModalInteractionListene
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberKickedListener;
import dev.sheldan.abstracto.core.models.listener.MemberKickedModel;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
import dev.sheldan.abstracto.moderation.service.KickService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MemberKickedListener implements AsyncMemberKickedListener {
@Autowired
private KickService kickService;
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
}
@Override
public DefaultListenerResult execute(MemberKickedModel eventModel) {
log.info("Notifying about kicked of user {} in guild {}.", eventModel.getKickedServerUser().getUserId(), eventModel.getServerId());
if(eventModel.getKickingServerUser().getUserId() == eventModel.getGuild().getJDA().getSelfUser().getIdLong()) {
log.info("Skipping logging kicked event about user {} in guild {}, because it was done by us.", eventModel.getKickedServerUser().getUserId(), eventModel.getGuild().getIdLong());
return DefaultListenerResult.IGNORED;
}
KickLogModel model = KickLogModel
.builder()
.kickedUser(eventModel.getKickedUser() != null ? UserDisplay.fromUser(eventModel.getKickedUser()) : UserDisplay.fromId(eventModel.getKickedServerUser().getUserId()))
.kickingUser(eventModel.getKickingUser() != null ? UserDisplay.fromUser(eventModel.getKickingUser()) : UserDisplay.fromServerUser(eventModel.getKickingServerUser()))
.reason(eventModel.getReason())
.build();
kickService.sendKicklog(model, eventModel.getServerId());
return DefaultListenerResult.PROCESSED;
}
}

View File

@@ -1,139 +0,0 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberTimeoutUpdatedListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.MemberTimeoutUpdatedModel;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel;
import dev.sheldan.abstracto.moderation.service.MuteServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.audit.ActionType;
import net.dv8tion.jda.api.audit.AuditLogEntry;
import net.dv8tion.jda.api.audit.AuditLogKey;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class MemberTimeoutListener implements AsyncMemberTimeoutUpdatedListener {
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private MemberTimeoutListener self;
@Autowired
private MemberService memberService;
@Override
public DefaultListenerResult execute(MemberTimeoutUpdatedModel model) {
Guild guild = model.getGuild();
guild.retrieveAuditLogs()
.type(ActionType.MEMBER_UPDATE)
.limit(10)
.queue(auditLogEntries -> {
CompletableFuture<Void> notificationFuture = null;
if(auditLogEntries.isEmpty()) {
log.info("Did not find recent timeouts in guild {}.", model.getServerId());
notificationFuture = self.sendMutingUpdateNotification(model, null);
} else {
Optional<AuditLogEntry> timeoutEntryOptional = auditLogEntries
.stream()
.filter(auditLogEntry -> auditLogEntry.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT) != null
&& auditLogEntry.getTargetIdLong() == model.getTimeoutUser().getUserId())
.findFirst();
if(timeoutEntryOptional.isPresent()) {
AuditLogEntry auditLogEntry = timeoutEntryOptional.get();
User responsibleUser = auditLogEntry.getUser();
if(guild.getSelfMember().getIdLong() != responsibleUser.getIdLong()) {
notificationFuture = self.sendMutingUpdateNotification(model, auditLogEntry);
}
} else {
notificationFuture = self.sendMutingUpdateNotification(model, null);
}
}
if(notificationFuture != null) {
notificationFuture.thenAccept(unused -> {
log.info("Sent notification about timeout change {} -> {} of user {} in server {}.",
model.getOldTimeout(), model.getNewTimeout(), model.getTimeoutUser().getUserId(), model.getServerId());
}).exceptionally(throwable -> {
log.info("Sent notification about timeout change {} -> {} of user {} in server {}.",
model.getOldTimeout(), model.getNewTimeout(), model.getTimeoutUser().getUserId(), model.getServerId());
return null;
});
}
});
return DefaultListenerResult.PROCESSED;
}
@Transactional
public CompletableFuture<Void> sendMutingUpdateNotification(MemberTimeoutUpdatedModel model, AuditLogEntry logEntry) {
User responsibleUser;
Guild guild = model.getGuild();
CompletableFuture<Member> future;
String reason;
if(logEntry != null) {
responsibleUser = logEntry.getUser();
if(responsibleUser != null) {
ServerUser responsibleServerUser = ServerUser
.builder()
.serverId(guild.getIdLong())
.isBot(responsibleUser.isBot())
.userId(responsibleUser.getIdLong())
.build();
future = memberService.retrieveMemberInServer(responsibleServerUser);
} else {
future = CompletableFuture.completedFuture(null);
}
reason = logEntry.getReason();
} else {
future = CompletableFuture.completedFuture(null);
reason = null;
}
CompletableFuture<Void> returningFuture = new CompletableFuture<>();
future.whenComplete((aVoid, throwable) -> {
try {
MuteListenerModel muteLogModel = MuteListenerModel
.builder()
.muteTargetDate(model.getNewTimeout() != null ? model.getNewTimeout().toInstant() : null)
.oldMuteTargetDate(model.getOldTimeout() != null ? model.getOldTimeout().toInstant() : null)
.mutingUser(future.isCompletedExceptionally() ? null : MemberDisplay.fromMember(future.join()))
.mutedUser(MemberDisplay.fromMember(model.getMember()))
.reason(reason)
.build();
MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, muteLogModel, guild.getIdLong());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, model.getServerId()));
returningFuture.complete(null);
} catch (Exception exception) {
log.error("Failed to log timeout update event for user {} in guild {}.", model.getTimeoutUser().getUserId(), model.getServerId(), exception);
}
});
return returningFuture;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
}
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberTimeoutUpdatedListener;
import dev.sheldan.abstracto.core.models.listener.MemberTimeoutUpdatedModel;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.template.command.MuteLogModel;
import dev.sheldan.abstracto.moderation.service.MuteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
@Component
@Slf4j
public class MemberTimeoutLoggerListener implements AsyncMemberTimeoutUpdatedListener {
@Autowired
private MuteService muteService;
@Override
public DefaultListenerResult execute(MemberTimeoutUpdatedModel model) {
log.info("Notifying about timeout of user {} in guild {}.", model.getMutedUser().getUserId(), model.getServerId());
if(model.getMutingUser().getUserId() == model.getGuild().getSelfMember().getIdLong()) {
log.info("Skipping logging timeout event about user {} in guild {}, because it was done by us.", model.getMutedUser().getUserId(), model.getGuild().getIdLong());
return DefaultListenerResult.IGNORED;
}
MemberDisplay mutedMemberDisplay = model.getMutedMember() != null ? MemberDisplay.fromMember(model.getMutedMember()) : MemberDisplay.fromServerUser(model.getMutedUser());
MemberDisplay mutingMemberDisplay = model.getMutingMember() != null ? MemberDisplay.fromMember(model.getMutingMember()) : MemberDisplay.fromServerUser(model.getMutingUser());
Duration duration = null;
if(model.getNewTimeout() != null) {
duration = Duration.between(Instant.now(), model.getNewTimeout());
}
MuteLogModel muteLogModel = MuteLogModel
.builder()
.muteTargetDate(model.getNewTimeout() != null ? model.getNewTimeout().toInstant() : null)
.oldMuteTargetDate(model.getOldTimeout() != null ? model.getOldTimeout().toInstant() : null)
.mutingMember(mutingMemberDisplay)
.mutedMember(mutedMemberDisplay)
.duration(duration)
.reason(model.getReason())
.build();
muteService.sendMuteLogMessage(muteLogModel, model.getServerId());
return DefaultListenerResult.PROCESSED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
}
}

View File

@@ -9,7 +9,6 @@ import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionLis
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
@@ -38,9 +37,6 @@ public class MuteModerationActionModalListener implements ModalInteractionListen
@Autowired
private InteractionService interactionService;
@Autowired
private TemplateService templateService;
@Autowired
private InteractionExceptionService interactionExceptionService;

View File

@@ -3,82 +3,37 @@ package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserBannedListener;
import dev.sheldan.abstracto.core.models.listener.UserBannedModel;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.models.listener.UserBannedListenerModel;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedListenerModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
import dev.sheldan.abstracto.moderation.service.BanService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.audit.ActionType;
import net.dv8tion.jda.api.audit.AuditLogEntry;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class UserBannedListener implements AsyncUserBannedListener {
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private UserBannedListener self;
private static final String USER_BANNED_NOTIFICATION_TEMPLATE = "userBanned_listener_notification";
private BanService banService;
@Override
public DefaultListenerResult execute(UserBannedModel model) {
model.getGuild()
.retrieveAuditLogs()
.type(ActionType.BAN)
.limit(5)
.queue(auditLogEntries -> {
if(auditLogEntries.isEmpty()) {
log.info("Did not find recent bans in guild {}.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
return;
}
Optional<AuditLogEntry> banEntryOptional = auditLogEntries
.stream()
.filter(auditLogEntry -> auditLogEntry.getTargetIdLong() == model.getBannedUser().getUserId())
.findFirst();
if(banEntryOptional.isPresent()) {
AuditLogEntry auditLogEntry = banEntryOptional.get();
if(!model.getGuild().getJDA().getSelfUser().equals(auditLogEntry.getUser())) {
self.sendBannedNotification(model.getUser(), auditLogEntry.getUser(), auditLogEntry.getReason(), model.getServerId());
}
} else {
log.info("Did not find the banned user in the most recent bans for guild {}. Not adding audit log information.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
}
}, throwable -> {
log.error("Retrieving bans for guild {} failed - logging ban regardless.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
});
return DefaultListenerResult.PROCESSED;
}
@Transactional
public CompletableFuture<Void> sendBannedNotification(User bannedUser, User banningUser, String reason, Long serverId) {
UserBannedListenerModel model = UserBannedListenerModel
public DefaultListenerResult execute(UserBannedListenerModel eventModel) {
log.info("Notifying about ban of user {} in guild {}.", eventModel.getBannedServerUser().getUserId(), eventModel.getServerId());
if(eventModel.getBanningServerUser().getUserId() == eventModel.getGuild().getJDA().getSelfUser().getIdLong()) {
log.info("Skipping logging banned event about user {} in guild {}, because it was done by us.", eventModel.getBannedServerUser().getUserId(), eventModel.getGuild().getIdLong());
return DefaultListenerResult.IGNORED;
}
UserBannedLogModel model = UserBannedLogModel
.builder()
.bannedUser(bannedUser)
.banningUser(banningUser)
.reason(reason)
.bannedUser(eventModel.getBannedUser() != null ? UserDisplay.fromUser(eventModel.getBannedUser()) : UserDisplay.fromId(eventModel.getBannedServerUser().getUserId()))
.banningUser(eventModel.getBanningUser() != null ? UserDisplay.fromUser(eventModel.getBanningUser()) : UserDisplay.fromServerUser(eventModel.getBanningServerUser()))
.reason(eventModel.getReason())
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
banService.sendBanLogMessage(model, eventModel.getServerId());
return DefaultListenerResult.PROCESSED;
}
@Override

View File

@@ -3,88 +3,37 @@ package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserUnBannedListener;
import dev.sheldan.abstracto.core.models.listener.UserUnBannedModel;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.models.listener.UserUnBannedListenerModel;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedListenerModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedLogModel;
import dev.sheldan.abstracto.moderation.service.BanService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.audit.ActionType;
import net.dv8tion.jda.api.audit.AuditLogEntry;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class UserUnBannedListener implements AsyncUserUnBannedListener {
@Autowired
private FeatureModeService featureModeService;
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private UserUnBannedListener self;
private static final String USER_UN_BANNED_NOTIFICATION_TEMPLATE = "userUnBanned_listener_notification";
private BanService banService;
@Override
public DefaultListenerResult execute(UserUnBannedModel model) {
log.info("Notifying about unban of user {} in guild {}.", model.getUnbannedUser().getUserId(), model.getServerId());
model.getGuild()
.retrieveAuditLogs()
.type(ActionType.UNBAN)
.limit(5)
.queue(auditLogEntries -> {
try {
if(auditLogEntries.isEmpty()) {
log.info("Did not find recent bans in guild {}.", model.getServerId());
return;
}
Optional<AuditLogEntry> banEntryOptional = auditLogEntries
.stream()
.filter(auditLogEntry -> auditLogEntry.getTargetIdLong() == model.getUnbannedUser().getUserId())
.findFirst();
if(banEntryOptional.isPresent()) {
AuditLogEntry auditLogEntry = banEntryOptional.get();
if(!model.getGuild().getJDA().getSelfUser().equals(auditLogEntry.getUser())) {
self.sendUnBannedNotification(model.getUser(), auditLogEntry.getUser(), model.getServerId());
}
} else {
log.info("Did not find the un-banned user in the most recent un-bans for guild {}. Not adding audit log information.", model.getServerId());
self.sendUnBannedNotification(model.getUser(), null, model.getServerId());
}
} catch (Exception exception) {
log.error("Failed to properly send un ban log with error.", exception);
}
}, throwable -> {
log.error("Failed to retrieve audit log entries for unban log.", throwable);
self.sendUnBannedNotification(model.getUser(), null, model.getServerId());
});
return DefaultListenerResult.PROCESSED;
}
@Transactional
public CompletableFuture<Void> sendUnBannedNotification(User unbannedUser, User unbanningUser, Long serverId) {
UserUnBannedListenerModel model = UserUnBannedListenerModel
public DefaultListenerResult execute(UserUnBannedListenerModel eventModel) {
log.info("Notifying about unban of user {} in guild {}.", eventModel.getUnBannedServerUser().getUserId(), eventModel.getServerId());
if(eventModel.getUnBanningUser().getIdLong() == eventModel.getGuild().getSelfMember().getIdLong()) {
log.info("Skipping logging banned event about user {} in guild {}, because it was done by us.", eventModel.getUnBannedServerUser().getUserId(), eventModel.getGuild().getIdLong());
return DefaultListenerResult.IGNORED;
}
UserUnBannedLogModel model = UserUnBannedLogModel
.builder()
.unBannedUser(unbannedUser)
.unBanningUser(unbanningUser)
.unBannedUser(eventModel.getUnBannedUser() != null ? UserDisplay.fromUser(eventModel.getUnBannedUser()) : UserDisplay.fromId(eventModel.getUnBannedServerUser().getUserId()))
.unBanningUser(eventModel.getUnBanningUser() != null ? UserDisplay.fromUser(eventModel.getUnBanningUser()) : UserDisplay.fromServerUser(eventModel.getUnBanningServerUser()))
.reason(eventModel.getReason())
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_UN_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
banService.sendUnBanLogMessage(model, eventModel.getServerId());
return DefaultListenerResult.PROCESSED;
}
@Override

View File

@@ -3,34 +3,31 @@ package dev.sheldan.abstracto.moderation.listener.infraction;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionListener;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.InfractionParameter;
import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel;
import dev.sheldan.abstracto.moderation.model.template.command.BanLog;
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
import dev.sheldan.abstracto.moderation.service.BanService;
import dev.sheldan.abstracto.moderation.service.BanServiceBean;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionListener {
@@ -45,7 +42,7 @@ public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionLis
private UserService userService;
@Autowired
private BanServiceBean banServiceBean;
private TemplateService templateService;
@Autowired
private BanReasonUpdatedListener self;
@@ -60,7 +57,7 @@ public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionLis
public CompletableFuture<DefaultListenerResult> execute(InfractionDescriptionEventModel model) {
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
CompletableFuture<User> infractionUser = userService.retrieveUserForId(infraction.getUser().getUserReference().getId());
CompletableFuture<Member> creatorUser = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(infraction.getInfractionCreator()));
CompletableFuture<User> creatorUser = userService.retrieveUserForId(infraction.getInfractionCreator().getUserReference().getId());
CompletableFuture<DefaultListenerResult> returningFuture = new CompletableFuture<>();
Long infractionId = infraction.getId();
CompletableFuture.allOf(infractionUser, creatorUser)
@@ -74,26 +71,17 @@ public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionLis
}
@Transactional
public void handleBanUpdate(InfractionDescriptionEventModel model, CompletableFuture<User> infractionUser, CompletableFuture<Member> infractionCreator, CompletableFuture<DefaultListenerResult> returningFuture) {
public void handleBanUpdate(InfractionDescriptionEventModel model, CompletableFuture<User> infractionUser, CompletableFuture<User> infractionCreator, CompletableFuture<DefaultListenerResult> returningFuture) {
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId());
Duration deletionDuration = infraction
.getParameters()
.stream()
.filter(infractionParameter -> infractionParameter.getInfractionParameterId().getName().equals(BanService.INFRACTION_PARAMETER_DELETION_DURATION_KEY))
.findAny()
.map(InfractionParameter::getValue)
.map(Duration::parse)
.orElse(Duration.ZERO);
BanLog banLog = BanLog
UserBannedLogModel banLog = UserBannedLogModel
.builder()
.bannedUser(infractionUser.isCompletedExceptionally() ? null : UserDisplay.fromUser(infractionUser.join()))
.banningMember(infractionCreator.isCompletedExceptionally() ? null : MemberDisplay.fromMember(infractionCreator.join()))
.deletionDuration(deletionDuration)
.banningUser(infractionCreator.isCompletedExceptionally() ? null : UserDisplay.fromUser(infractionCreator.join()))
.reason(model.getNewDescription())
.build();
MessageToSend message = banServiceBean.renderBanMessage(banLog, model.getServerId());
MessageToSend message = templateService.renderEmbedTemplate(BanServiceBean.USER_BANNED_NOTIFICATION_TEMPLATE, banLog, model.getServerId());
messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId())
.thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED))
.exceptionally(throwable1 -> {

View File

@@ -2,48 +2,47 @@ package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.CompletableFutureMap;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.moderation.model.BanResult;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.template.command.BanLog;
import dev.sheldan.abstracto.moderation.model.template.command.BanNotificationModel;
import dev.sheldan.abstracto.moderation.model.template.command.UnBanLog;
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedLogModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.UserSnowflake;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class BanServiceBean implements BanService {
public static final String BAN_LOG_TEMPLATE = "ban_log";
public static final String UN_BAN_LOG_TEMPLATE = "unBan_log";
public static final String BAN_NOTIFICATION = "ban_notification";
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private BanServiceBean self;
@@ -62,15 +61,17 @@ public class BanServiceBean implements BanService {
@Autowired
private InfractionService infractionService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private UserService userService;
public static final String USER_BANNED_NOTIFICATION_TEMPLATE = "userBanned_listener_notification";
private static final String USER_UN_BANNED_NOTIFICATION_TEMPLATE = "userUnBanned_listener_notification";
@Override
public CompletableFuture<BanResult> banUserWithNotification(ServerUser userToBeBanned, String reason, ServerUser banningUser, Guild guild, Duration deletionDuration) {
BanLog banLog = BanLog
.builder()
.bannedUser(UserDisplay.fromServerUser(userToBeBanned))
.banningMember(MemberDisplay.fromServerUser(banningUser))
.deletionDuration(deletionDuration)
.reason(reason)
.build();
BanResult[] result = {BanResult.SUCCESSFUL};
return sendBanNotification(userToBeBanned, reason, guild)
.exceptionally(throwable -> {
@@ -78,13 +79,13 @@ public class BanServiceBean implements BanService {
return null;
})
.thenCompose(unused -> banUser(guild, userToBeBanned, deletionDuration, reason))
.thenCompose(unused -> sendBanLogMessage(banLog, guild.getIdLong()))
.thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(userToBeBanned, guild, reason, banningUser, banLogMessage, deletionDuration))
.thenCompose(unused -> self.composeAndSendBanLogMessage(userToBeBanned, banningUser, reason))
.thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(userToBeBanned, guild, reason, banningUser, deletionDuration))
.thenApply(unused -> result[0]);
}
@Transactional
public CompletableFuture<Long> evaluateAndStoreInfraction(ServerUser user, Guild guild, String reason, ServerUser banningMember, Message banLogMessage, Duration deletionDuration) {
public CompletableFuture<Long> evaluateAndStoreInfraction(ServerUser user, Guild guild, String reason, ServerUser banningMember, Duration deletionDuration) {
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) {
Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.BAN_INFRACTION_POINTS, guild.getIdLong());
AUserInAServer bannedUser = userInServerManagementService.loadOrCreateUser(guild.getIdLong(), user.getUserId());
@@ -94,7 +95,7 @@ public class BanServiceBean implements BanService {
deletionDuration = Duration.ZERO;
}
parameters.put(INFRACTION_PARAMETER_DELETION_DURATION_KEY, deletionDuration.toString());
return infractionService.createInfractionWithNotification(bannedUser, infractionPoints, BAN_INFRACTION_TYPE, reason, banningUser, parameters, banLogMessage)
return infractionService.createInfractionWithNotification(bannedUser, infractionPoints, BAN_INFRACTION_TYPE, reason, banningUser, parameters)
.thenApply(Infraction::getId);
} else {
return CompletableFuture.completedFuture(null);
@@ -111,17 +112,6 @@ public class BanServiceBean implements BanService {
return messageService.sendMessageToUser(serverUser, message).thenAccept(message1 -> {});
}
@Override
public CompletableFuture<Void> unBanUserWithNotification(Long userId, ServerUser unBanningMember, Guild guild) {
UnBanLog banLog = UnBanLog
.builder()
.bannedUser(UserDisplay.fromId(userId))
.unBanningMember(MemberDisplay.fromServerUser(unBanningMember))
.build();
return unbanUser(guild, userId)
.thenCompose(unused -> self.sendUnBanLogMessage(banLog, guild.getIdLong(), UN_BAN_LOG_TEMPLATE));
}
@Override
public CompletableFuture<Void> banUser(Guild guild, ServerUser userToBeBanned, Duration deletionDuration, String reason) {
log.info("Banning user {} in guild {}.", userToBeBanned.getUserId(), guild.getId());
@@ -132,31 +122,60 @@ public class BanServiceBean implements BanService {
}
@Override
public CompletableFuture<Void> unbanUser(Guild guild, Long userId) {
log.info("Unbanning user {} in guild {}.", userId, guild.getId());
return guild.unban(UserSnowflake.fromId(userId)).submit();
public CompletableFuture<Void> unbanUser(Guild guild, User user, Member memberPerforming) {
log.info("Unbanning user {} in guild {}.", user.getIdLong(), guild.getId());
return guild.unban(user).submit().thenCompose(unused -> self.composeAndSendUnBanLogMessage(guild, user, memberPerforming));
}
@Transactional
public CompletionStage<Void> composeAndSendUnBanLogMessage(Guild guild, User user, Member memberPerforming) {
UserUnBannedLogModel model = UserUnBannedLogModel
.builder()
.unBannedUser(UserDisplay.fromUser(user))
.unBanningUser(UserDisplay.fromUser(memberPerforming.getUser()))
.reason(null)
.build();
return sendUnBanLogMessage(model, guild.getIdLong());
}
@Transactional
public CompletableFuture<Void> composeAndSendBanLogMessage(ServerUser serverUserToBeBanned, ServerUser serverUserBanning, String reason) {
CompletableFutureMap<Long, User> userMap = userService.retrieveUsersMapped(Arrays.asList(serverUserToBeBanned.getUserId(), serverUserBanning.getUserId()));
return userMap.getMainFuture().thenCompose(unused -> {
User userToBeBanned = userMap.getElement(serverUserToBeBanned.getUserId());
User banningUser = userMap.getElement(serverUserBanning.getUserId());
UserBannedLogModel model = UserBannedLogModel
.builder()
.bannedUser(UserDisplay.fromUser(userToBeBanned))
.banningUser(UserDisplay.fromUser(banningUser))
.reason(reason)
.build();
return self.sendBanLogMessage(model, serverUserToBeBanned.getServerId());
}).exceptionally(throwable -> {
log.warn("Failed to load users ({}, {}) for ban log message.", serverUserToBeBanned.getUserId(), serverUserBanning.getUserId(), throwable);
UserBannedLogModel model = UserBannedLogModel
.builder()
.bannedUser(UserDisplay.fromId(serverUserToBeBanned.getUserId()))
.banningUser(UserDisplay.fromId(serverUserBanning.getUserId()))
.reason(reason)
.build();
self.sendBanLogMessage(model, serverUserToBeBanned.getServerId());
return null;
});
}
@Override
public CompletableFuture<Void> softBanUser(Guild guild, ServerUser user, Duration delDays) {
return banUser(guild, user, delDays, "")
.thenCompose(unused -> unbanUser(guild, user.getUserId()));
@Transactional
public CompletableFuture<Void> sendUnBanLogMessage(UserUnBannedLogModel model, Long serverId) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_UN_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
}
public CompletableFuture<Message> sendBanLogMessage(BanLog banLog, Long guildId) {
MessageToSend banLogMessage = renderBanMessage(banLog, guildId);
log.debug("Sending ban log message in guild {}.", guildId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId);
return FutureUtils.toSingleFutureGeneric(messageFutures).thenApply(unused -> messageFutures.get(0).join());
@Override
@Transactional
public CompletableFuture<Void> sendBanLogMessage(UserBannedLogModel model, Long serverId) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
}
public MessageToSend renderBanMessage(BanLog banLog, Long guildId) {
return templateService.renderEmbedTemplate(BAN_LOG_TEMPLATE, banLog, guildId);
}
public CompletableFuture<Void> sendUnBanLogMessage(UnBanLog banLog, Long guildId, String template) {
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
log.debug("Sending unban log message in guild {}.", guildId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.UN_BAN_LOG, guildId));
}
}

View File

@@ -81,7 +81,7 @@ public class InfractionServiceBean implements InfractionService {
List<Infraction> infractions = infractionManagementService.getActiveInfractionsForUser(aUserInAServer);
log.info("Calculating points for user {} in server {} with {} infractions.",
aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId(), infractions.size());
return infractions.stream().collect(Collectors.summarizingLong(Infraction::getPoints)).getCount();
return infractions.stream().collect(Collectors.summarizingLong(Infraction::getPoints)).getSum();
}
@Override
@@ -112,8 +112,8 @@ public class InfractionServiceBean implements InfractionService {
public CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points, String type, String description) {
Long serverId = aUserInAServer.getServerReference().getId();
Long currentPoints = getActiveInfractionPointsForUser(aUserInAServer);
Long newPoints = currentPoints + points;
Pair<Integer, Integer> levelChange = infractionLevelChanged(serverId, newPoints, currentPoints);
Long oldPoints = currentPoints - points;
Pair<Integer, Integer> levelChange = infractionLevelChanged(serverId, currentPoints, oldPoints);
Integer oldLevel = levelChange.getFirst();
Integer newLevel = levelChange.getSecond();
if(!oldLevel.equals(newLevel)) {
@@ -124,10 +124,10 @@ public class InfractionServiceBean implements InfractionService {
.oldLevel(oldLevel)
.type(type)
.description(description)
.oldPoints(currentPoints)
.newPoints(newPoints)
.oldPoints(oldPoints)
.newPoints(currentPoints)
.build();
infractionLevelChangedListenerManager.sendInfractionLevelChangedEvent(newLevel, oldLevel, newPoints, currentPoints, ServerUser.fromAUserInAServer(aUserInAServer));
infractionLevelChangedListenerManager.sendInfractionLevelChangedEvent(newLevel, oldLevel, currentPoints, oldPoints, ServerUser.fromAUserInAServer(aUserInAServer));
MessageToSend messageToSend = templateService.renderEmbedTemplate(INFRACTION_NOTIFICATION_TEMPLATE_KEY, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, InfractionPostTarget.INFRACTION_NOTIFICATION, serverId));
} else {

View File

@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureMap;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
@@ -16,14 +18,12 @@ import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.UserSnowflake;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -51,6 +51,9 @@ public class KickServiceBean implements KickService {
@Autowired
private InfractionService infractionService;
@Autowired
private UserService userService;
@Autowired
private KickServiceBean self;
@@ -59,7 +62,7 @@ public class KickServiceBean implements KickService {
Guild guild = kickedMember.getGuild();
log.info("Kicking user {} from guild {}", kickedMember.getUser().getIdLong(), guild.getIdLong());
CompletableFuture<Void> kickFuture = guild.kick(kickedMember, reason).submit();
CompletableFuture<Message> logFuture = sendKickLog(kickedMember, kickingMember, reason, guild.getIdLong());
CompletableFuture<Message> logFuture = sendKickLog(kickedMember.getUser(), ServerUser.fromMember(kickedMember), kickingMember.getUser(), ServerUser.fromMember(kickingMember), reason, guild.getIdLong());
return CompletableFuture.allOf(kickFuture, logFuture)
.thenAccept(unused -> self.storeInfraction(kickedMember, kickingMember, reason, logFuture.join(), guild.getIdLong()));
}
@@ -96,27 +99,30 @@ public class KickServiceBean implements KickService {
}
}
private CompletableFuture<Message> sendKickLog(Member kickedMember, Member kickingMember, String reason, Long serverId) {
public CompletableFuture<Message> sendKickLog(User kickedUser, ServerUser kickedServerUser, User kickingUser, ServerUser kickingServerUser, String reason, Long serverId) {
KickLogModel kickLogModel = KickLogModel
.builder()
.kickedMember(MemberDisplay.fromMember(kickedMember))
.kickingMember(MemberDisplay.fromMember(kickingMember))
.kickedUser(kickedUser != null ? UserDisplay.fromUser(kickedUser) : UserDisplay.fromServerUser(kickedServerUser))
.kickingUser(kickingUser != null ? UserDisplay.fromUser(kickingUser) : UserDisplay.fromServerUser(kickingServerUser))
.reason(reason)
.build();
return sendKicklog(serverId, kickLogModel);
return sendKicklog(kickLogModel, serverId);
}
private CompletableFuture<Message> sendKickLog(ServerUser kickedMember, ServerUser kickingMember, String reason, Long serverId) {
KickLogModel kickLogModel = KickLogModel
.builder()
.kickedMember(MemberDisplay.fromServerUser(kickedMember))
.kickingMember(MemberDisplay.fromServerUser(kickingMember))
.reason(reason)
.build();
return sendKicklog(serverId, kickLogModel);
public CompletableFuture<Message> sendKickLog(ServerUser kickedMember, ServerUser kickingMember, String reason, Long serverId) {
CompletableFutureMap<Long, User> userMap = userService.retrieveUsersMapped(Arrays.asList(kickedMember.getUserId(), kickingMember.getUserId()));
return userMap.getMainFuture().thenCompose(unused -> {
User kickedUser = userMap.getElement(kickedMember.getUserId());
User kickingUser = userMap.getElement(kickingMember.getUserId());
return self.sendKickLog(kickedUser, kickedMember, kickingUser, kickingMember, reason, serverId);
}).exceptionally(throwable -> {
log.warn("Failed to fetch users ({}, {}) for kick event logging in server {}.", kickingMember.getUserId(), kickedMember.getUserId(), serverId, throwable);
self.sendKickLog(null, kickedMember, null, kickingMember, reason, serverId);
return null;
});
}
private CompletableFuture<Message> sendKicklog(Long serverId, KickLogModel kickLogModel) {
public CompletableFuture<Message> sendKicklog(KickLogModel kickLogModel, Long serverId) {
MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, serverId);
log.debug("Sending kick log message in guild {}.", serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, serverId);

View File

@@ -21,15 +21,14 @@ import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException;
import dev.sheldan.abstracto.moderation.model.MuteResult;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel;
import dev.sheldan.abstracto.moderation.model.template.command.MuteLogModel;
import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification;
import dev.sheldan.abstracto.moderation.model.template.command.UnMuteLog;
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
import dev.sheldan.abstracto.scheduling.model.JobParameters;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@@ -168,21 +167,60 @@ public class MuteServiceBean implements MuteService {
@Override
public CompletableFuture<MuteResult> muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin) {
return muteMemberWithLog(userToMute, mutingUser, reason, duration, guild, origin, null);
}
@Override
public CompletableFuture<MuteResult> muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin, Instant oldTimeout) {
Long serverId = userToMute.getServerId();
Instant targetDate = Instant.now().plus(duration);
log.debug("Muting member {} in server {}.", userToMute.getUserId(), serverId);
log.info("Muting member {} in server {}.", userToMute.getUserId(), serverId);
AServer server = serverManagementService.loadOrCreate(serverId);
Long muteId = counterService.getNextCounterValue(server, MUTE_COUNTER_KEY);
CompletableFuture<MuteResult> result = muteUserInServer(guild, userToMute, reason, duration);
return result
.thenCompose(unused -> self.sendMuteLog(userToMute, mutingUser, duration, reason))
.thenCompose(logMessage -> self.evaluateAndStoreInfraction(userToMute, mutingUser, reason, targetDate, logMessage))
.thenCompose(muteResult -> self.composeAndLogMute(userToMute, mutingUser, reason, duration, guild, oldTimeout))
.thenCompose(logMessage -> self.evaluateAndStoreInfraction(userToMute, mutingUser, reason, targetDate))
.thenAccept(infractionId -> self.persistMute(userToMute, mutingUser, targetDate, muteId, reason, infractionId, origin))
.thenApply(unused -> result.join());
}
@Transactional
public CompletableFuture<Long> evaluateAndStoreInfraction(ServerUser userToMute, ServerUser mutingUser, String reason, Instant targetDate, Message logMessage) {
public CompletableFuture<Void> composeAndLogMute(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, Instant oldTimeout) {
CompletableFuture<Member> mutedMemberFuture = memberService.retrieveMemberInServer(userToMute);
CompletableFuture<Member> mutingMemberFuture = memberService.retrieveMemberInServer(mutingUser);
Instant targetDate = Instant.now().plus(duration);
return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture).thenCompose(unused -> {
Member mutedMember = mutedMemberFuture.join();
Member mutingMember = mutingMemberFuture.join();
MuteLogModel muteLogModel = MuteLogModel
.builder()
.muteTargetDate(targetDate)
.oldMuteTargetDate(oldTimeout)
.mutingMember(MemberDisplay.fromMember(mutingMember))
.mutedMember(MemberDisplay.fromMember(mutedMember))
.duration(duration)
.reason(reason)
.build();
return self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to load users for mute log ({}, {}) in guild {}.", userToMute.getUserId(), mutingUser.getUserId(), guild.getIdLong(), throwable);
MuteLogModel muteLogModel = MuteLogModel
.builder()
.muteTargetDate(targetDate)
.oldMuteTargetDate(null)
.mutingMember(MemberDisplay.fromServerUser(mutingUser))
.mutedMember(MemberDisplay.fromServerUser(userToMute))
.duration(duration)
.reason(reason)
.build();
self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
return null;
});
}
@Transactional
public CompletableFuture<Long> evaluateAndStoreInfraction(ServerUser userToMute, ServerUser mutingUser, String reason, Instant targetDate) {
Long serverId = userToMute.getServerId();
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) {
Long infractionPoints = configService.getLongValueOrConfigDefault(MutingFeatureConfig.MUTE_INFRACTION_POINTS, serverId);
@@ -190,7 +228,7 @@ public class MuteServiceBean implements MuteService {
AUserInAServer mutingUserInAServer = userInServerManagementService.loadOrCreateUser(mutingUser);
Map<String, String> parameters = new HashMap<>();
parameters.put(INFRACTION_PARAMETER_DURATION_KEY, templateService.renderDuration(Duration.between(Instant.now(), targetDate), serverId));
return infractionService.createInfractionWithNotification(mutedUserInAServer, infractionPoints, MUTE_INFRACTION_TYPE, reason, mutingUserInAServer, parameters, logMessage)
return infractionService.createInfractionWithNotification(mutedUserInAServer, infractionPoints, MUTE_INFRACTION_TYPE, reason, mutingUserInAServer, parameters)
.thenApply(Infraction::getId);
} else {
return CompletableFuture.completedFuture(null);
@@ -204,48 +242,13 @@ public class MuteServiceBean implements MuteService {
createMuteObject(userToMute, mutingUser, reason, targetDate, muteId, triggerKey, infractionId, origin);
}
@Transactional
public CompletableFuture<Message> sendMuteLog(ServerUser userBeingMuted, ServerUser mutingUser, Duration duration, String reason) {
Instant targetDate = Instant.now().plus(duration);
MuteListenerModel model = MuteListenerModel
.builder()
.mutedUser(MemberDisplay.fromServerUser(userBeingMuted))
.mutingUser(MemberDisplay.fromServerUser(mutingUser))
.oldMuteTargetDate(null)
.duration(duration)
.muteTargetDate(targetDate)
.reason(reason)
.build();
log.debug("Sending mute log to the mute post target.");
Long serverId = userBeingMuted.getServerId();
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, model, serverId);
List<CompletableFuture<Message>> futures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, serverId);
return FutureUtils.toSingleFutureGeneric(futures).thenApply(unused -> futures.get(0).join());
}
private CompletableFuture<Void> sendUnMuteLogMessage(UnMuteLog muteLogModel, AServer server) {
MuteListenerModel model = MuteListenerModel
.builder()
.mutedUser(muteLogModel.getUnMutedUser())
.mutingUser(muteLogModel.getMutingUser())
.oldMuteTargetDate(muteLogModel.getMute() != null ? muteLogModel.getMute().getMuteTargetDate() : null)
.muteTargetDate(null)
.build();
if(muteLogModel.getMute() != null) {
log.debug("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId());
}
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, model, server.getId());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId()));
}
@Override
@Transactional
public CompletableFuture<Void> unMuteUser(ServerUser userToUnmute, ServerUser unMutingUser, Guild guild) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(userToUnmute);
boolean muteActive = muteManagementService.hasActiveMute(aUserInAServer);
if(!muteActive) {
return memberService.removeTimeout(guild, userToUnmute, null)
.thenCompose(unused -> self.sendUnmuteLog(null, guild, userToUnmute, unMutingUser));
return memberService.removeTimeout(guild, userToUnmute, null);
} else {
Mute mute = muteManagementService.getAMuteOf(aUserInAServer);
return endMute(mute, guild);
@@ -261,31 +264,34 @@ public class MuteServiceBean implements MuteService {
Long muteId = mute.getMuteId().getId();
AServer mutingServer = mute.getServer();
ServerUser mutedUser = ServerUser.fromAUserInAServer(mute.getMutedUser());
ServerUser mutingUser = ServerUser.fromAUserInAServer(mute.getMutedUser());
ServerUser mutingUser = ServerUser.fromAUserInAServer(mute.getMutingUser());
log.info("UnMuting {} in server {}", mute.getMutedUser().getUserReference().getId(), mutingServer.getId());
return memberService.removeTimeout(guild, mutedUser, null)
.thenCompose(unused -> self.sendUnmuteLog(muteId, guild, mutedUser, mutingUser));
.thenCompose(unused -> self.composeAndLogUnmute(mutedUser, mutingUser, guild))
.thenAccept(unused -> {
if(muteId != null) {
self.endMuteInDatabase(muteId, guild.getIdLong());
}
});
}
@Transactional
public CompletableFuture<Void> sendUnmuteLog(Long muteId, Guild guild, ServerUser unMutedMember, ServerUser mutingMember) {
Mute mute = null;
if(muteId != null) {
mute = muteManagementService.findMute(muteId, guild.getIdLong());
}
AServer mutingServer = serverManagementService.loadServer(guild.getIdLong());
UnMuteLog unMuteLog = UnMuteLog
.builder()
.mute(mute)
.mutingUser(MemberDisplay.fromServerUser(mutingMember))
.unMutedUser(MemberDisplay.fromServerUser(unMutedMember))
.build();
CompletableFuture<Void> notificationFuture = sendUnMuteLogMessage(unMuteLog, mutingServer);
return CompletableFuture.allOf(notificationFuture).thenAccept(aVoid -> {
if(muteId != null) {
self.endMuteInDatabase(muteId, guild.getIdLong());
}
public CompletableFuture<Void> composeAndLogUnmute(ServerUser mutedUser, ServerUser mutingUser, Guild guild) {
CompletableFuture<Member> mutedMemberFuture = memberService.retrieveMemberInServer(mutedUser);
CompletableFuture<Member> mutingMemberFuture = memberService.retrieveMemberInServer(mutingUser);
return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture).thenCompose(unused -> {
Member mutedMember = mutedMemberFuture.join();
Member mutingMember = mutingMemberFuture.join();
MuteLogModel muteLogModel = MuteLogModel
.builder()
.muteTargetDate(null)
.oldMuteTargetDate(null)
.mutingMember(MemberDisplay.fromMember(mutingMember))
.mutedMember(MemberDisplay.fromMember(mutedMember))
.duration(null)
.reason(null)
.build();
return self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
});
}
@@ -310,6 +316,12 @@ public class MuteServiceBean implements MuteService {
}
}
@Override
public CompletableFuture<Void> sendMuteLogMessage(MuteLogModel model, Long serverId) {
MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, serverId));
}
@Override
public void completelyUnMuteUser(AUserInAServer aUserInAServer) {
log.info("Completely unmuting user {} in server {}.", aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId());

View File

@@ -32,23 +32,12 @@ abstracto.postTargets.banLog.name=banLog
abstracto.postTargets.unBanLog.name=unBanLog
abstracto.postTargets.muteLog.name=muteLog
abstracto.postTargets.decayLog.name=decayLog
abstracto.featureModes.banCommandLogging.featureName=moderation
abstracto.featureModes.banCommandLogging.mode=banCommandLogging
abstracto.featureModes.banCommandLogging.enabled=true
abstracto.featureModes.unBanCommandLogging.featureName=moderation
abstracto.featureModes.unBanCommandLogging.mode=unBanCommandLogging
abstracto.featureModes.unBanCommandLogging.enabled=true
abstracto.postTargets.infractionNotification.name=infractionNotification
abstracto.featureModes.warnDecayLogging.featureName=warnings
abstracto.featureModes.warnDecayLogging.mode=warnDecayLogging
abstracto.featureModes.warnDecayLogging.enabled=true
abstracto.featureModes.infractionReporting.featureName=infractions
abstracto.featureModes.infractionReporting.mode=infractionReporting
abstracto.featureModes.infractionReporting.enabled=true
abstracto.featureModes.anonymousReportReactions.featureName=reportReactions
abstracto.featureModes.anonymousReportReactions.mode=anonymousReportReactions
abstracto.featureModes.anonymousReportReactions.enabled=false
@@ -61,20 +50,20 @@ abstracto.featureModes.reactionReportActions.featureName=reportReactions
abstracto.featureModes.reactionReportActions.mode=reactionReportActions
abstracto.featureModes.reactionReportActions.enabled=false
abstracto.systemConfigs.infractionLvl1.name=infractionLvl1
abstracto.systemConfigs.infractionLvl1.longValue=10
abstracto.systemConfigs.infractionLevel1.name=infractionLevel1
abstracto.systemConfigs.infractionLevel1.longValue=10
abstracto.systemConfigs.infractionLvl2.name=infractionLvl2
abstracto.systemConfigs.infractionLvl2.longValue=20
abstracto.systemConfigs.infractionLevel2.name=infractionLevel2
abstracto.systemConfigs.infractionLevel2.longValue=20
abstracto.systemConfigs.infractionLvl3.name=infractionLvl3
abstracto.systemConfigs.infractionLvl3.longValue=30
abstracto.systemConfigs.infractionLevel3.name=infractionLevel3
abstracto.systemConfigs.infractionLevel3.longValue=30
abstracto.systemConfigs.infractionLvl4.name=infractionLvl4
abstracto.systemConfigs.infractionLvl4.longValue=40
abstracto.systemConfigs.infractionLevel4.name=infractionLevel4
abstracto.systemConfigs.infractionLevel4.longValue=40
abstracto.systemConfigs.infractionLvl5.name=infractionLvl5
abstracto.systemConfigs.infractionLvl5.longValue=50
abstracto.systemConfigs.infractionLevel5.name=infractionLevel5
abstracto.systemConfigs.infractionLevel5.longValue=50
abstracto.systemConfigs.infractionLevels.name=infractionLevels
abstracto.systemConfigs.infractionLevels.longValue=5

View File

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

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.moderation.model.template.command;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -13,6 +13,6 @@ import lombok.Setter;
@Setter
public class KickLogModel {
private String reason;
private MemberDisplay kickedMember;
private MemberDisplay kickingMember;
private UserDisplay kickedUser;
private UserDisplay kickingUser;
}

View File

@@ -10,15 +10,15 @@ import java.time.Instant;
@Getter
@SuperBuilder
public class MuteListenerModel {
public class MuteLogModel {
/**
* The {@link Member} being muted
*/
private MemberDisplay mutedUser;
private MemberDisplay mutedMember;
/**
* The {@link Member} executing the mute
*/
private MemberDisplay mutingUser;
private MemberDisplay mutingMember;
/**
* The persisted mute object from the database containing the information about the mute
*/

View File

@@ -1,71 +0,0 @@
package dev.sheldan.abstracto.moderation.model.template.command;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.utils.MessageUtils;
import dev.sheldan.abstracto.moderation.model.database.Mute;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.time.Duration;
import java.time.Instant;
/**
* Used when rendering the notification when a member was muted. The template is: "unmute_log_embed"
*/
@Getter
@SuperBuilder
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UnMuteLog {
/**
* The un-muted Member, is null if the member left the server
*/
private MemberDisplay unMutedUser;
/**
* The user casting the mute, is null if the member left the server
*/
private MemberDisplay mutingUser;
/**
* The persisted mute object from the database containing the information about the mute
*/
private Mute mute;
/**
* The actual duration between the date the mute started and the current time
* @return The difference between mute start and now
*/
public Duration getMuteDuration() {
return Duration.between(mute.getMuteDate(), Instant.now());
}
/**
* The duration between the date the mute started and the un-mute planned
* @return The difference between mute start and the target date
*/
public Duration getPlannedMuteDuration() {
return Duration.between(mute.getMuteDate(), mute.getMuteTargetDate());
}
/**
* The un-mute date, which is now, because this is the un-mute log message.
* @return The current time stamp
*/
public Instant getUnmuteDate() {
return Instant.now();
}
/**
* Builds the link to the original message triggering the mute
* @return A string containing an URL leading to the message where the mute was triggered
*/
public String getMessageUrl() {
if(this.mute.getMessageId() != null && this.mute.getMutingChannel() != null) {
return MessageUtils.buildMessageUrl(this.mute.getServer().getId(), this.mute.getMutingChannel().getId(), this.mute.getMessageId());
}
return null;
}
}

View File

@@ -1,15 +1,15 @@
package dev.sheldan.abstracto.moderation.model.template.listener;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.User;
@Getter
@Setter
@Builder
public class UserBannedListenerModel {
private User bannedUser;
public class UserBannedLogModel {
private UserDisplay bannedUser;
private String reason;
private User banningUser;
private UserDisplay banningUser;
}

View File

@@ -1,14 +0,0 @@
package dev.sheldan.abstracto.moderation.model.template.listener;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.User;
@Getter
@Setter
@Builder
public class UserUnBannedListenerModel {
private User unBannedUser;
private User unBanningUser;
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.moderation.model.template.listener;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class UserUnBannedLogModel {
private UserDisplay unBannedUser;
private String reason;
private UserDisplay unBanningUser;
}

View File

@@ -2,6 +2,8 @@ package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.model.BanResult;
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedLogModel;
import net.dv8tion.jda.api.entities.*;
import java.time.Duration;
@@ -12,8 +14,8 @@ public interface BanService {
String BAN_INFRACTION_TYPE = "ban";
String INFRACTION_PARAMETER_DELETION_DURATION_KEY = "DELETION_DURATION";
CompletableFuture<BanResult> banUserWithNotification(ServerUser userToBeBanned, String reason, ServerUser banningUser, Guild guild, Duration deletionDuration);
CompletableFuture<Void> unBanUserWithNotification(Long userId, ServerUser unBanningMember, Guild guild);
CompletableFuture<Void> banUser(Guild guild, ServerUser userToBeBanned, Duration deletionDuration, String reason);
CompletableFuture<Void> unbanUser(Guild guild, Long userId);
CompletableFuture<Void> softBanUser(Guild guild, ServerUser user, Duration delDays);
CompletableFuture<Void> unbanUser(Guild guild, User user, Member memberPerforming);
CompletableFuture<Void> sendBanLogMessage(UserBannedLogModel model, Long serverId);
CompletableFuture<Void> sendUnBanLogMessage(UserUnBannedLogModel model, Long serverId);
}

View File

@@ -1,8 +1,10 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import java.util.concurrent.CompletableFuture;
@@ -11,4 +13,5 @@ public interface KickService {
String KICK_INFRACTION_TYPE = "kick";
CompletableFuture<Void> kickMember(Member kickedMember, Member kickingMember, String reason);
CompletableFuture<Void> kickMember(Guild guild, ServerUser kickedUser, String reason, ServerUser kickingUser);
CompletableFuture<Message> sendKicklog(KickLogModel kickLogModel, Long serverId);
}

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.MuteResult;
import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.model.template.command.MuteLogModel;
import net.dv8tion.jda.api.entities.Guild;
import java.time.Duration;
@@ -17,11 +18,13 @@ public interface MuteService {
String INFRACTION_PARAMETER_DURATION_KEY = "DURATION";
CompletableFuture<MuteResult> muteUserInServer(Guild guild, ServerUser userBeingMuted, String reason, Duration duration);
CompletableFuture<MuteResult> muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin);
CompletableFuture<MuteResult> muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin, Instant oldTimeout);
String startUnMuteJobFor(Instant unMuteDate, Long muteId, Long serverId);
void cancelUnMuteJob(Mute mute);
CompletableFuture<Void> unMuteUser(ServerUser userToUnMute, ServerUser memberUnMuting, Guild guild);
CompletableFuture<Void> endMute(Mute mute, Guild guild);
CompletableFuture<Void> endMute(Long muteId, Long serverId);
CompletableFuture<Void> sendMuteLogMessage(MuteLogModel model, Long serverId);
void completelyUnMuteUser(AUserInAServer aUserInAServer);
void completelyUnMuteMember(ServerUser serverUser);
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.31</version>
<version>1.5.38</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.5.31</version>
<version>1.5.38</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -38,6 +38,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>metrics-int</artifactId>

View File

@@ -8,7 +8,7 @@ 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;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
@@ -40,7 +40,7 @@ public class AnonReply extends AbstractConditionableCommand {
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private MemberService memberService;
private UserService userService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
@@ -52,8 +52,8 @@ public class AnonReply extends AbstractConditionableCommand {
throw new ModMailThreadClosedException();
}
Long threadId = modMailThread.getId();
return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
modMailThreadService.loadExecutingMemberAndRelay(threadId, text, commandContext.getMessage(), true, member)
return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenCompose(user ->
modMailThreadService.loadExecutingMemberAndRelay(threadId, text, commandContext.getMessage(), true, user, commandContext.getGuild())
).thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -81,7 +81,6 @@ public class Close extends AbstractConditionableCommand {
.builder()
.closingMember(commandContext.getAuthor())
.notifyUser(true)
.channel(commandContext.getChannel())
.log(true)
.note(note)
.build();
@@ -112,19 +111,18 @@ public class Close extends AbstractConditionableCommand {
ClosingContext context = ClosingContext
.builder()
.closingMember(event.getMember())
.channel(event.getChannel())
.notifyUser(!silently)
.log(log)
.note(note)
.build();
return interactionService.replyEmbed(CLOSE_RESPONSE, event)
.thenCompose(interactionHook -> self.closeThread(context))
.thenCompose(interactionHook -> self.closeThread(context, event.getChannelIdLong()))
.thenApply(aVoid -> CommandResult.fromIgnored());
}
@Transactional
public CompletableFuture<Void> closeThread(ClosingContext closingContext) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(closingContext.getChannel().getIdLong());
public CompletableFuture<Void> closeThread(ClosingContext closingContext, Long channelId) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(channelId);
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}

View File

@@ -25,6 +25,7 @@ import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementS
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
@@ -44,7 +45,7 @@ import java.util.concurrent.CompletableFuture;
@Slf4j
public class Contact extends AbstractConditionableCommand {
private static final String CONTACT_PARAMETER = "contact";
private static final String COMMAND_NAME = "contact";
private static final String USER_PARMETER = "user";
private static final String MODMAIL_THREAD_ALREADY_EXISTS_TEMPLATE = "modmail_thread_already_exists";
private static final String CONTACT_RESPONSE = "contact_response";
@@ -87,24 +88,21 @@ public class Contact extends AbstractConditionableCommand {
List<CompletableFuture<Message>> futures = channelService.sendEmbedTemplateInTextChannelList(MODMAIL_THREAD_ALREADY_EXISTS_TEMPLATE, model, commandContext.getChannel());
return FutureUtils.toSingleFutureGeneric(futures).thenApply(aVoid -> CommandResult.fromIgnored());
} else {
return modMailThreadService.createModMailThreadForUser(targetUser, null, false, commandContext.getUndoActions())
.thenCompose(unused -> modMailThreadService.sendContactNotification(targetUser, unused, commandContext.getChannel()))
return modMailThreadService.createModMailThreadForUser(targetUser.getUser(), targetUser.getGuild(), null, false, commandContext.getUndoActions())
.thenCompose(unused -> modMailThreadService.sendContactNotification(targetUser.getUser(), unused, commandContext.getChannel()))
.thenApply(aVoid -> CommandResult.fromSuccess());
}
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Member member = slashCommandParameterService.getCommandOption(USER_PARMETER, event, Member.class);
if(!member.getGuild().equals(event.getGuild())) {
throw new EntityGuildMismatchException();
}
AUserInAServer user = userManagementService.loadOrCreateUser(member);
User user = slashCommandParameterService.getCommandOption(USER_PARMETER, event, User.class);
AUserInAServer userInAServer = userManagementService.loadOrCreateUser(event.getGuild().getIdLong(), user.getIdLong());
// if this AUserInAServer already has an open thread, we should instead post a message
// containing a link to the channel, instead of opening a new one
if(modMailThreadManagementService.hasOpenModMailThreadForUser(user)) {
log.info("Modmail thread for user {} in server {} already exists. Notifying user {}.", event.getMember().getId(), event.getGuild().getId(), user.getUserReference().getId());
ModMailThread existingThread = modMailThreadManagementService.getOpenModMailThreadForUser(user);
if(modMailThreadManagementService.hasOpenModMailThreadForUser(userInAServer)) {
log.info("Modmail thread for userInAServer {} in server {} already exists. Notifying userInAServer {}.", event.getMember().getId(), event.getGuild().getId(), userInAServer.getUserReference().getId());
ModMailThread existingThread = modMailThreadManagementService.getOpenModMailThreadForUser(userInAServer);
ModMailThreadExistsModel model = ModMailThreadExistsModel
.builder()
.existingModMailThread(existingThread)
@@ -114,9 +112,9 @@ public class Contact extends AbstractConditionableCommand {
.thenApply(interactionHook -> CommandResult.fromSuccess());
} else {
CompletableFuture<InteractionHook> response = interactionService.replyEmbed(CONTACT_RESPONSE, event);
CompletableFuture<MessageChannel> threadFuture = modMailThreadService.createModMailThreadForUser(member, null, false, new ArrayList<>());
CompletableFuture<MessageChannel> threadFuture = modMailThreadService.createModMailThreadForUser(user, event.getGuild(), null, false, new ArrayList<>());
return CompletableFuture.allOf(response, threadFuture)
.thenCompose(unused -> modMailThreadService.sendContactNotification(member, threadFuture.join(), response.join()))
.thenCompose(unused -> modMailThreadService.sendContactNotification(user, threadFuture.join(), response.join()))
.thenApply(o -> CommandResult.fromSuccess());
}
}
@@ -126,7 +124,7 @@ public class Contact extends AbstractConditionableCommand {
Parameter responseText = Parameter
.builder()
.name(USER_PARMETER)
.type(Member.class)
.type(User.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(responseText);
@@ -139,11 +137,11 @@ public class Contact extends AbstractConditionableCommand {
.builder()
.enabled(true)
.rootCommandName(ModMailSlashCommandNames.MODMAIL)
.commandName(CONTACT_PARAMETER)
.commandName(COMMAND_NAME)
.build();
return CommandConfiguration.builder()
.name(CONTACT_PARAMETER)
.name(COMMAND_NAME)
.module(ModMailModuleDefinition.MODMAIL)
.parameters(parameters)
.slashCommandConfig(slashCommandConfig)

View File

@@ -0,0 +1,121 @@
package dev.sheldan.abstracto.modmail.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
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.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailMode;
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.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class DenyModmailAppeal extends AbstractConditionableCommand {
private static final String COMMAND_NAME = "denyappeal";
private static final String FULL_COMMAND_NAME = "denyModmailAppeal";
private static final String REASON_PARAMETER = "reason";
private static final String RESPONSE_TEMPLATE = "denyModmailAppeal_response";
@Autowired
private ModMailContextCondition requiresModMailCondition;
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(event.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
String reason = slashCommandParameterService.getCommandOption(REASON_PARAMETER, event, String.class);
CompletableFuture<InteractionHook> response = interactionService.replyEmbed(RESPONSE_TEMPLATE, event);
CompletableFuture<Void> threadFuture = modMailThreadService.rejectAppeal(modMailThread, reason, event.getMember());
return CompletableFuture.allOf(response, threadFuture)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public FeatureDefinition getFeature() {
return ModMailFeatureDefinition.MOD_MAIL;
}
@Override
public CommandConfiguration getConfiguration() {
Parameter responseText = Parameter
.builder()
.name(REASON_PARAMETER)
.type(String.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(responseText);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModMailSlashCommandNames.MODMAIL)
.commandName(COMMAND_NAME)
.build();
return CommandConfiguration.builder()
.name(FULL_COMMAND_NAME)
.module(ModMailModuleDefinition.MODMAIL)
.parameters(parameters)
.slashCommandConfig(slashCommandConfig)
.help(helpInfo)
.slashCommandOnly(true)
.supportsEmbedException(true)
.templated(true)
.build();
}
@Override
public List<CommandCondition> getConditions() {
List<CommandCondition> conditions = super.getConditions();
conditions.add(requiresModMailCondition);
return conditions;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return List.of(ModMailMode.MOD_MAIL_APPEALS);
}
}

View File

@@ -9,6 +9,7 @@ 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;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
@@ -39,7 +40,7 @@ public class Reply extends AbstractConditionableCommand {
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private MemberService memberService;
private UserService userService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
@@ -50,8 +51,8 @@ public class Reply extends AbstractConditionableCommand {
throw new ModMailThreadClosedException();
}
Long threadId = modMailThread.getId();
return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
modMailThreadService.loadExecutingMemberAndRelay(threadId, text, commandContext.getMessage(), false, member)
return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenCompose(user ->
modMailThreadService.loadExecutingMemberAndRelay(threadId, text, commandContext.getMessage(), false, user, commandContext.getGuild())
).thenApply(aVoid -> CommandResult.fromSuccess());
}
@@ -65,7 +66,7 @@ public class Reply extends AbstractConditionableCommand {
.optional(true)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(responseText);
List<Parameter> parameters = List.of(responseText);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("reply")

View File

@@ -14,6 +14,7 @@ import dev.sheldan.abstracto.modmail.model.dto.ServiceChoicesPayload;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.ModMailThreadServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -25,7 +26,7 @@ import java.util.ArrayList;
public class ModMailInitialButtonListener implements ButtonClickedListener {
@Autowired
private MemberService memberService;
private GuildService guildService;
@Autowired
private ModMailThreadService modMailThreadService;
@@ -51,20 +52,19 @@ public class ModMailInitialButtonListener implements ButtonClickedListener {
Long userId = choices.getUserId();
log.debug("Executing action for creationg a modmail thread in server {} for user {}.", chosenServer.getServerId(), userId);
ArrayList<UndoActionInstance> undoActions = new ArrayList<>();
memberService.getMemberInServerAsync(chosenServer.getServerId(), userId)
.thenCompose(member -> channelService.retrieveMessageInChannel(model.getEvent().getChannel(), choices.getMessageId())
Guild guild = guildService.getGuildById(chosenServer.getServerId());
channelService.retrieveMessageInChannel(model.getEvent().getChannel(), choices.getMessageId())
.thenCompose(originalMessage -> {
try {
return modMailThreadService.createModMailThreadForUser(member, originalMessage, true, undoActions);
return modMailThreadService.createModMailThreadForUser(model.getEvent().getUser(), guild, originalMessage, true, undoActions);
} catch (Exception ex) {
log.error("Failed to setup thread correctly", ex);
undoActionService.performActions(undoActions);
return null;
}
})
.thenAccept(unused -> self.cleanup(model)))
.exceptionally(throwable -> {
log.error("Failed to setup thread correctly", throwable);
.thenAccept(unused -> self.cleanup(model)).exceptionally(throwable -> {
log.warn("Failed to setup modmail thread.");
undoActionService.performActions(undoActions);
return null;
});

View File

@@ -7,6 +7,7 @@ import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.listener.MessageDeletedModel;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.model.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
@@ -33,7 +34,7 @@ public class ModMailMessageDeletedListener implements AsyncMessageDeletedListene
private ModMailMessageDeletedListener self;
@Autowired
private MemberService memberService;
private UserService userService;
@Override
public DefaultListenerResult execute(MessageDeletedModel model) {
@@ -47,8 +48,8 @@ public class ModMailMessageDeletedListener implements AsyncMessageDeletedListene
Long channelId = thread.getChannel().getId();
Long serverId = thread.getServer().getId();
log.info("Deleting message for mod mail thread {} in channel {} in server {}.", thread.getId(), channelId, serverId);
memberService.getMemberInServerAsync(model.getServerId(), modMailMessage.getThreadReference().getUser().getUserReference().getId()).thenAccept(member -> {
CompletableFuture<Void> dmDeletePromise = messageService.deleteMessageInChannelWithUser(member.getUser(), dmMessageId);
userService.retrieveUserForId(modMailMessage.getThreadReference().getUser().getUserReference().getId()).thenAccept(user -> {
CompletableFuture<Void> dmDeletePromise = messageService.deleteMessageInChannelWithUser(user, dmMessageId);
CompletableFuture<Void> channelDeletePromise;
if(hasMessageInChannel) {
channelDeletePromise = messageService.deleteMessageInChannelInServer(serverId, channelId, channelMessage);

View File

@@ -10,9 +10,11 @@ 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;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.model.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.model.template.ModMailModeratorReplyModel;
@@ -24,6 +26,7 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -45,9 +48,6 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
@Autowired
private CommandService commandService;
@Autowired
private MemberService memberService;
@Autowired
private TemplateService templateService;
@@ -66,6 +66,12 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private MemberService memberService;
@Autowired
private UserService userService;
@Override
public DefaultListenerResult execute(MessageUpdatedModel model) {
CachedMessage messageBefore = model.getBefore();
@@ -85,7 +91,7 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
log.info("Edit did not contain the original command to retrieve the parameters for. Resulting to {}.", DEFAULT_COMMAND_FOR_MODMAIL_EDIT);
}
CompletableFuture<Parameters> parameterParseFuture = commandService.getParametersForCommand(commandName, message);
CompletableFuture<Member> loadTargetUser = memberService.getMemberInServerAsync(messageBefore.getServerId(), modMailMessage.getThreadReference().getUser().getUserReference().getId());
CompletableFuture<User> loadTargetUser = userService.retrieveUserForId(modMailMessage.getThreadReference().getUser().getUserReference().getId());
CompletableFuture<Member> loadEditingUser = memberService.getMemberInServerAsync(messageBefore.getServerId(), modMailMessage.getAuthor().getUserReference().getId());
CompletableFuture.allOf(parameterParseFuture, loadTargetUser, loadEditingUser).thenAccept(unused ->
self.updateMessageInThread(message, parameterParseFuture.join(), loadTargetUser.join(), loadEditingUser.join())
@@ -100,15 +106,10 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
}
@Transactional
public void updateMessageInThread(Message loadedMessage, Parameters parameters, Member targetMember, Member editingUser) {
public void updateMessageInThread(Message loadedMessage, Parameters parameters, User user, Member editingUser) {
String newText = (String) parameters.getParameters().get(0);
Optional<ModMailMessage> messageOptional = modMailMessageManagementService.getByMessageIdOptional(loadedMessage.getIdLong());
messageOptional.ifPresent(modMailMessage -> {
FullUserInServer fullThreadUser = FullUserInServer
.builder()
.aUserInAServer(modMailMessage.getThreadReference().getUser())
.member(targetMember)
.build();
List<String> imageUrls = loadedMessage
.getAttachments()
.stream()
@@ -128,7 +129,7 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
.attachedImageUrls(imageUrls)
.remainingAttachments(otherAttachments)
.anonymous(modMailMessage.getAnonymous())
.threadUser(fullThreadUser);
.userDisplay(UserDisplay.fromUser(user));
if(modMailMessage.getAnonymous()) {
modMailModeratorReplyModelBuilder.moderator(memberService.getBotInGuild(modMailMessage.getThreadReference().getServer()));
} else {
@@ -143,13 +144,13 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
log.debug("Editing message {} in mod mail channel {} for thread {} in server {} as well.", modMailMessage.getCreatedMessageInChannel(), channel.getId(), threadId, serverId);
channelService.editMessageInAChannel(messageToSend, channel, modMailMessage.getCreatedMessageInChannel());
}
log.debug("Editing message {} in DM channel with user {} for thread {} in server {}.", modMailMessage.getCreatedMessageInDM(), targetMember.getUser().getIdLong(), threadId, serverId);
messageService.editMessageInDMChannel(targetMember.getUser(), messageToSend, modMailMessage.getCreatedMessageInDM());
log.debug("Editing message {} in DM channel with user {} for thread {} in server {}.", modMailMessage.getCreatedMessageInDM(), user.getIdLong(), threadId, serverId);
messageService.editMessageInDMChannel(user, messageToSend, modMailMessage.getCreatedMessageInDM());
});
if(!messageOptional.isPresent()) {
log.warn("Message {} of user {} in channel {} for server {} for thread about user {} could not be found in the mod mail messages when updating the text.",
loadedMessage.getIdLong(), editingUser.getIdLong(), loadedMessage.getChannel().getIdLong(), loadedMessage.getGuild().getIdLong(), targetMember.getIdLong());
loadedMessage.getIdLong(), editingUser.getIdLong(), loadedMessage.getChannel().getIdLong(), loadedMessage.getGuild().getIdLong(), user.getIdLong());
}
}

View File

@@ -36,9 +36,6 @@ public class ModMailMessageListener implements PrivateMessageReceivedListener {
@Autowired
private UserManagementService userManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override
@Transactional
public void execute(Message message) {

View File

@@ -19,9 +19,6 @@ public class ModMailRoleServiceBean implements ModMailRoleService {
@Autowired
private CommandService commandService;
@Autowired
private FeatureManagementService featureManagementService;
@Override
public void addRoleToModMailRoles(ARole role) {
log.info("Adding role {} to modmail roles in server {}.", role.getId(), role.getServer().getId());

View File

@@ -8,17 +8,16 @@ import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
import dev.sheldan.abstracto.core.metric.service.MetricService;
import dev.sheldan.abstracto.core.metric.service.MetricTag;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.FullGuild;
import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.UndoActionInstance;
import dev.sheldan.abstracto.core.models.*;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.service.BanService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureConfig;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailMode;
@@ -51,6 +50,7 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -111,6 +111,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Autowired
private GuildService guildService;
@Autowired
private BanService banService;
@Autowired
private MessageService messageService;
@@ -187,22 +190,21 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
public static final String MODMAIL_INITIAL_ORIGIN = "modmailInitial";
@Override
public CompletableFuture<MessageChannel> createModMailThreadForUser(Member member, Message initialMessage, boolean userInitiated, List<UndoActionInstance> undoActions) {
Long serverId = member.getGuild().getIdLong();
User user = member.getUser();
AServer server = serverManagementService.loadServer(member.getGuild().getIdLong());
public CompletableFuture<MessageChannel> createModMailThreadForUser(User user, Guild guild, Message initialMessage, boolean userInitiated, List<UndoActionInstance> undoActions) {
Long serverId = guild.getIdLong();
AServer server = serverManagementService.loadServer(serverId);
metricService.incrementCounter(MODMAIL_THREAD_CREATED_COUNTER);
ModMailChannelNameModel model = ModMailChannelNameModel
.builder()
.serverId(serverId)
.userId(member.getIdLong())
.userId(user.getIdLong())
.randomText(RandomStringUtils.randomAlphanumeric(25))
.uuid(UUID.randomUUID().toString())
.currentDate(Instant.now())
.build();
String channelName = templateService.renderTemplate(TEXT_CHANNEL_NAME_TEMPLATE_KEY, model, serverId);
if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, serverId, ModMailMode.THREAD_CONTAINER)) {
MessageToSend notificationMessageToSend = getModmailNotificationMessageToSend(member, null, serverId, false);
MessageToSend notificationMessageToSend = getModmailNotificationMessageToSend(user, null, serverId, false);
Optional<GuildMessageChannel> modmailContainerOptional = postTargetService.getPostTargetChannel(ModMailPostTargets.MOD_MAIL_CONTAINER, serverId);
if(modmailContainerOptional.isEmpty()) {
throw new AbstractoRunTimeException("Modmail thread container not setup.");
@@ -218,7 +220,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.thenCompose(unused -> channelService.createThreadWithStarterMessage(textChannel, channelName, notificationMessage.get(0).join().getIdLong()))
.thenCompose(threadChannel -> {
undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, threadChannel.getIdLong()));
return self.performModMailThreadSetup(member, initialMessage, threadChannel, userInitiated, undoActions)
return self.performModMailThreadSetup(user, initialMessage, threadChannel, userInitiated, undoActions)
.thenCompose(unused -> CompletableFuture.completedFuture(threadChannel));
});
} else {
@@ -227,7 +229,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
CompletableFuture<TextChannel> textChannelFuture = channelService.createTextChannel(channelName, server, categoryId);
return textChannelFuture.thenCompose(channel -> {
undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, channel.getIdLong()));
return self.performModMailThreadSetup(member, initialMessage, channel, userInitiated, undoActions)
return self.performModMailThreadSetup(user, initialMessage, channel, userInitiated, undoActions)
.thenCompose(unused -> CompletableFuture.completedFuture(channel));
});
}
@@ -235,21 +237,21 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Transactional
@Override
public CompletableFuture<Void> sendContactNotification(Member member, MessageChannel messageChannel, MessageChannel feedBackChannel) {
public CompletableFuture<Void> sendContactNotification(User user, MessageChannel messageChannel, MessageChannel feedBackChannel) {
ContactNotificationModel model = ContactNotificationModel
.builder()
.createdChannel(messageChannel)
.targetMember(member)
.userDisplay(UserDisplay.fromUser(user))
.build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannelList(MODMAIL_THREAD_CREATED_TEMPLATE_KEY, model, feedBackChannel));
}
@Override
public CompletableFuture<Void> sendContactNotification(Member member, MessageChannel createdMessageChannel, InteractionHook interactionHook) {
public CompletableFuture<Void> sendContactNotification(User user, MessageChannel createdMessageChannel, InteractionHook interactionHook) {
ContactNotificationModel model = ContactNotificationModel
.builder()
.createdChannel(createdMessageChannel)
.targetMember(member)
.userDisplay(UserDisplay.fromUser(user))
.build();
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(MODMAIL_THREAD_CREATED_TEMPLATE_KEY, model, interactionHook));
}
@@ -257,7 +259,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* 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.
* @param member The {@link Member} for which a {@link ModMailThread} is being created
* @param user The {@link User} for which a {@link ModMailThread} is being created
* @param initialMessage The {@link Message} which was sent by the user to open a thread, this is null, if the thread was opened via a command
* @param channel The created {@link TextChannel} in which the mod mail thread is dealt with
* @param userInitiated Whether the thread was initiated by a member
@@ -265,33 +267,33 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @return A {@link CompletableFuture future} which completes when the setup is done
*/
@Transactional
public CompletableFuture<Void> performModMailThreadSetup(Member member, Message initialMessage, GuildMessageChannel channel, boolean userInitiated, List<UndoActionInstance> undoActions) {
log.info("Performing modmail thread setup for channel {} for user {} in server {}. It was initiated by a user: {}.", channel.getIdLong(), member.getId(), channel.getGuild().getId(), userInitiated);
CompletableFuture<Void> headerFuture = sendModMailHeader(channel, member);
public CompletableFuture<Void> performModMailThreadSetup(User user, Message initialMessage, GuildMessageChannel channel, boolean userInitiated, List<UndoActionInstance> undoActions) {
log.info("Performing modmail thread setup for channel {} for user {} in server {}. It was initiated by a user: {}.", channel.getIdLong(), user.getId(), channel.getGuild().getId(), userInitiated);
CompletableFuture<Void> headerFuture = sendModMailHeader(channel, user);
CompletableFuture<Message> userReplyMessage;
if(initialMessage != null){
log.info("Sending initial message {} of user {} to modmail thread {}.", initialMessage.getId(), member.getId(), channel.getId());
userReplyMessage = self.sendUserReply(channel, 0L, initialMessage, member, false);
log.info("Sending initial message {} of user {} to modmail thread {}.", initialMessage.getId(), user.getId(), channel.getId());
userReplyMessage = self.sendUserReply(channel, 0L, initialMessage, false);
} else {
log.info("No initial message to send.");
userReplyMessage = CompletableFuture.completedFuture(null);
}
CompletableFuture notificationFuture;
if (userInitiated) {
notificationFuture = self.sendModMailNotification(member, channel);
notificationFuture = self.sendModMailNotification(user, channel);
} else {
notificationFuture = CompletableFuture.completedFuture(null);
}
return CompletableFuture.allOf(headerFuture, notificationFuture, userReplyMessage).thenAccept(aVoid -> {
undoActions.clear();
self.setupModMailThreadInDB(initialMessage, channel, member, userReplyMessage.join());
self.setupModMailThreadInDB(initialMessage, channel, user, userReplyMessage.join());
});
}
@Transactional
public void setupModMailThreadInDB(Message initialMessage, GuildMessageChannel channel, Member member, Message sendMessage) {
public void setupModMailThreadInDB(Message initialMessage, GuildMessageChannel channel, User user, Message sendMessage) {
log.info("Persisting info about modmail thread {} in database.", channel.getIdLong());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(channel.getGuild().getIdLong(), user.getIdLong());
ModMailThread thread = createThreadObject(channel, aUserInAServer);
if(initialMessage != null) {
log.debug("Adding initial message {} to modmail thread in channel {}.", initialMessage.getId(), channel.getId());
@@ -301,19 +303,19 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* Sends the message containing the pings to notify the staff members to handle the opened {@link ModMailThread}
* @param member The {@link FullUserInServer} which opened the thread
* @param user The {@link FullUserInServer} which opened the thread
* @param channel The created {@link GuildMessageChannel} in which the mod mail thread is dealt with
* @return A {@link CompletableFuture future} which completes when the notification has been sent
*/
@Transactional
public CompletableFuture<Void> sendModMailNotification(Member member, GuildMessageChannel channel) {
Long serverId = member.getGuild().getIdLong();
MessageToSend messageToSend = getModmailNotificationMessageToSend(member, channel, serverId, true);
public CompletableFuture<Void> sendModMailNotification(User user, GuildMessageChannel channel) {
Long serverId = channel.getGuild().getIdLong();
MessageToSend messageToSend = getModmailNotificationMessageToSend(user, channel, serverId, true);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId));
}
private MessageToSend getModmailNotificationMessageToSend(Member member, GuildMessageChannel channel, Long serverId, boolean pingRole) {
log.info("Sending modmail notification for new modmail thread about user {} in server {}.", member.getId(), serverId);
private MessageToSend getModmailNotificationMessageToSend(User user, GuildMessageChannel channel, Long serverId, boolean pingRole) {
log.info("Sending modmail notification for new modmail thread about user {} in server {}.", user.getId(), serverId);
AServer server = serverManagementService.loadServer(serverId);
List<ModMailRole> rolesToPing;
if(pingRole) {
@@ -321,10 +323,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
} else {
rolesToPing = new ArrayList<>();
}
log.debug("Pinging {} roles to notify about modmail thread about user {} in server {}.", rolesToPing.size(), member.getId(), serverId);
log.debug("Pinging {} roles to notify about modmail thread about user {} in server {}.", rolesToPing.size(), user.getId(), serverId);
ModMailNotificationModel modMailNotificationModel = ModMailNotificationModel
.builder()
.member(member)
.userDisplay(UserDisplay.fromUser(user))
.roles(rolesToPing)
.channel(channel)
.build();
@@ -369,24 +371,57 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
if(!servers.isEmpty()) {
log.info("There are {} shared servers between user and the bot.", servers.size());
List<ServerChoice> availableGuilds = new ArrayList<>();
Set<Long> alreadyConsideredServers = new HashSet<>();
for (AServer server : servers) {
// only take the servers in which mod mail is actually enabled, would not make much sense to make the
// other servers available
if (featureFlagService.isFeatureEnabled(modMailFeatureConfig, server)) {
FullGuild guild = FullGuild
.builder()
.guild(guildService.getGuildById(server.getId()))
.server(server)
.build();
boolean possibleForModmail = featureFlagService.isFeatureEnabled(modMailFeatureConfig, server);
if (possibleForModmail) {
Guild guild = guildService.getGuildById(server.getId());
ServerChoice serverChoice = ServerChoice
.builder()
.serverId(guild.getGuild().getIdLong())
.serverName(guild.getGuild().getName())
.serverId(guild.getIdLong())
.serverName(guild.getName())
.build();
availableGuilds.add(serverChoice);
}
alreadyConsideredServers.add(server.getId());
}
List<AServer> restOfKnownServers = serverManagementService.getAllServers()
.stream()
.filter(server -> alreadyConsideredServers.contains(server.getId()))
.toList();
for (AServer server : restOfKnownServers) {
boolean possibleForModmail = false;
Long actualServerId = 0L;
Long potentialMainServer = configService.getLongValue(ModMailFeatureConfig.MOD_MAIL_APPEAL_SERVER, server.getId()); // what _other_ server is the appeal server
if(potentialMainServer != 0) {
if(featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, potentialMainServer, ModMailMode.MOD_MAIL_APPEALS)) {
Long configuredAppealServerId = configService.getLongValue(ModMailFeatureConfig.MOD_MAIL_APPEAL_SERVER, potentialMainServer);
if(configuredAppealServerId != 0 && configuredAppealServerId.equals(server.getId())) { // if the other server has set the current server as the appeal config
Guild otherGuild = guildService.getGuildById(potentialMainServer);
if(otherGuild != null) { // check if we are part of that server
possibleForModmail = true;
actualServerId = potentialMainServer;
log.info("Server {} was available, because it is using server {} as a mod mail appeal server.", server.getId(), otherGuild.getIdLong());
}
}
} else {
log.info("Server {} has set the appeal server {}, but that server does not have mod mail appeals enabled.", server.getId(), potentialMainServer);
}
}
if(possibleForModmail) {
Guild guild = guildService.getGuildById(actualServerId);
ServerChoice serverChoice = ServerChoice
.builder()
.serverId(guild.getIdLong())
.serverName(guild.getName())
.build();
availableGuilds.add(serverChoice);
}
}
log.info("There were {} shared servers found which have modmail enabled.", availableGuilds.size());
log.info("There were {} available servers found.", availableGuilds.size());
// if more than 1 server is available, show a choice dialog
ArrayList<UndoActionInstance> undoActions = new ArrayList<>();
if(availableGuilds.size() > 1) {
@@ -414,20 +449,15 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
} else if(availableGuilds.size() == 1) {
// if exactly one server is available, open the thread directly
Long chosenServerId = availableGuilds.get(0).getServerId();
Guild guild = guildService.getGuildById(chosenServerId);
log.info("Only one server available to modmail. Directly opening modmail thread for user {} in server {}.", initialMessage.getAuthor().getId(), chosenServerId);
memberService.getMemberInServerAsync(chosenServerId, initialMessage.getAuthor().getIdLong()).thenCompose(member -> {
try {
return self.createModMailThreadForUser(member, initialMessage, true, undoActions).thenApply(messageChannel -> null);
} catch (Exception exception) {
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(exception);
return future;
}
}).exceptionally(throwable -> {
log.error("Failed to setup thread correctly", throwable);
undoActionService.performActions(undoActions);
return null;
});
createModMailThreadForUser(initialMessage.getAuthor(), guild , initialMessage, true, undoActions)
.thenAccept(messageChannel -> {
log.info("Setup modmail thread for user {} in guild {}.", initialMessage.getAuthor().getIdLong(), guild.getIdLong());
}).exceptionally(throwable -> {
log.error("Failed to setup modmail channel in guild {} for user {}.", guild.getIdLong(), initialMessage.getAuthor().getIdLong(), throwable);
return null;
});
} else {
log.info("No server available to open a modmail thread in.");
// in case there is no server available, send an error message
@@ -450,18 +480,17 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* Method used to send the header of a newly created mod mail thread. This message contains information about
* the user which the thread is about
* @param channel The {@link GuildMessageChannel} in which the mod mail thread is present in
* @param member The {@link Member} which the {@link ModMailThread} is about
* @param user The {@link User} which the {@link ModMailThread} is about
*/
private CompletableFuture<Void> sendModMailHeader(GuildMessageChannel channel, Member member) {
private CompletableFuture<Void> sendModMailHeader(GuildMessageChannel channel, User user) {
log.debug("Sending modmail thread header for tread in channel {} on server {}.", channel.getIdLong(), channel.getGuild().getId());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(channel.getGuild().getIdLong(), user.getIdLong());
ModMailThread latestThread = modMailThreadManagementService.getLatestModMailThread(aUserInAServer);
List<ModMailThread> oldThreads = modMailThreadManagementService.getModMailThreadForUser(aUserInAServer);
ModMailThreaderHeader header = ModMailThreaderHeader
.builder()
.member(member)
.userDisplay(UserDisplay.fromUser(user))
.latestModMailThread(latestThread)
.memberJoinDate(member.getTimeJoined().toInstant())
.pastModMailThreadCount((long)oldThreads.size())
.build();
List<CompletableFuture<Message>> messages = channelService.sendEmbedTemplateInTextChannelList("modmail_thread_header", header, channel);
@@ -475,18 +504,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
Long modmailThreadId = modMailThread.getId();
metricService.incrementCounter(MDOMAIL_THREAD_MESSAGE_RECEIVED);
log.debug("Relaying message {} to modmail thread {} for user {} to server {}.", messageFromUser.getId(), modMailThread.getId(), messageFromUser.getAuthor().getIdLong(), modMailThread.getServer().getId());
return memberService.getMemberInServerAsync(modMailThread.getServer().getId(), messageFromUser.getAuthor().getIdLong()).thenCompose(member ->
self.relayMessage(messageFromUser, serverId, channelId, modmailThreadId, member)
);
}
@Transactional
public CompletableFuture<Message> relayMessage(Message messageFromUser, Long serverId, Long channelId, Long modmailThreadId, Member member) {
Optional<GuildMessageChannel> textChannelFromServer = channelService.getMessageChannelFromServerOptional(serverId, channelId);
if(textChannelFromServer.isPresent()) {
GuildMessageChannel guildMessageChannel = textChannelFromServer.get();
return self.sendUserReply(guildMessageChannel, modmailThreadId, messageFromUser, member, true);
return self.sendUserReply(guildMessageChannel, modmailThreadId, messageFromUser, true);
} else {
log.warn("Closing mod mail thread {}, because it seems the channel {} in server {} got deleted.", modmailThreadId, channelId, serverId);
// in this case there was no text channel on the server associated with the mod mail thread
@@ -504,11 +525,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @param messageChannel The {@link GuildMessageChannel} in which the {@link ModMailThread} is being handled
* @param modMailThreadId The id of the modmail thread to which the received {@link Message} is a reply to, can be null, if it is null, its the initial message
* @param messageFromUser The received message from the user
* @param member The {@link Member} instance from the user the thread is about. It is used as author
* @param modMailThreadExists Whether the modmail thread already exists and is persisted.
* @return A {@link CompletableFuture} which resolves when the postprocessing of the message is completed (adding read notification, and storing messageIDs)
*/
public CompletableFuture<Message> sendUserReply(GuildMessageChannel messageChannel, Long modMailThreadId, Message messageFromUser, Member member, boolean modMailThreadExists) {
public CompletableFuture<Message> sendUserReply(GuildMessageChannel messageChannel, Long modMailThreadId, Message messageFromUser, boolean modMailThreadExists) {
List<CompletableFuture<Member>> subscriberMemberFutures = new ArrayList<>();
if(modMailThreadExists) {
ModMailThread modMailThread = modMailThreadManagementService.getById(modMailThreadId);
@@ -549,7 +569,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
ModMailUserReplyModel modMailUserReplyModel = ModMailUserReplyModel
.builder()
.postedMessage(messageFromUser)
.member(member)
.userDisplay(UserDisplay.fromUser(messageFromUser.getAuthor()))
.attachedImageUrls(imageUrls)
.remainingAttachments(otherAttachments)
.subscribers(subscribers)
@@ -603,21 +623,16 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Override
@Transactional
public CompletableFuture<Void> loadExecutingMemberAndRelay(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, Member targetMember) {
log.info("Relaying message {} to user {} in modmail thread {} on server {}.", replyCommandMessage.getId(), targetMember.getId(), modmailThreadId, targetMember.getGuild().getId());
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, targetMember, executingMember));
.thenCompose(executingMember -> self.relayMessageToDm(modmailThreadId, text, replyCommandMessage, anonymous, user, executingMember));
}
@Transactional
public CompletableFuture<Void> relayMessageToDm(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, Member targetMember, Member executingMember) {
public CompletableFuture<Void> relayMessageToDm(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, User user, Member executingMember) {
metricService.incrementCounter(MDOMAIL_THREAD_MESSAGE_SENT);
ModMailThread modMailThread = modMailThreadManagementService.getById(modmailThreadId);
FullUserInServer fullThreadUser = FullUserInServer
.builder()
.aUserInAServer(modMailThread.getUser())
.member(targetMember)
.build();
List<String> imageUrls = replyCommandMessage
.getAttachments()
.stream()
@@ -637,7 +652,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.remainingAttachments(otherAttachments)
.attachedImageUrls(imageUrls)
.anonymous(anonymous)
.threadUser(fullThreadUser);
.userDisplay(UserDisplay.fromUser(user));
if(anonymous) {
log.debug("Message is sent anonymous.");
modMailModeratorReplyModelBuilder.moderator(memberService.getBotInGuild(modMailThread.getServer()));
@@ -646,7 +661,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
ModMailModeratorReplyModel modMailUserReplyModel = modMailModeratorReplyModelBuilder.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY, modMailUserReplyModel, modMailThread.getServer().getId());
CompletableFuture<Message> future = messageService.sendMessageToSendToUser(targetMember.getUser(), messageToSend);
CompletableFuture<Message> future = messageService.sendMessageToSendToUser(user, messageToSend);
CompletableFuture<Message> sameThreadMessageFuture;
if(featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, modMailThread.getServer(), ModMailMode.SEPARATE_MESSAGE)) {
sameThreadMessageFuture = channelService.sendMessageEmbedToSendToAChannel(messageToSend, modMailThread.getChannel()).get(0);
@@ -678,24 +693,24 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
log.info("Archiving thread {} for modmail thread closing.", modMailThread.getChannel().getId());
return loadUserAndSendClosingHeader(modMailThread, closingConfig)
.thenCompose(unused -> channelService.archiveThreadChannel(threadChannel))
.thenCompose(unused -> memberService.getMemberInServerAsync(serverId, userId))
.thenAccept(member -> self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions));
.thenCompose(unused -> userService.retrieveUserForId(userId))
.thenCompose(user -> self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), user, undoActions));
} else {
if(closingConfig.getLog()) {
if(!modMailMessages.isEmpty()) {
return modMailMessageService.loadModMailMessages(modMailMessages)
.thenAccept(loadedModmailThreadMessages -> self.logMessagesToModMailLog(closingConfig, modMailThreadId, undoActions, loadedModmailThreadMessages, serverId, userId));
.thenCompose(loadedModmailThreadMessages -> self.logMessagesToModMailLog(closingConfig, modMailThreadId, undoActions, loadedModmailThreadMessages, serverId, userId));
} else {
log.info("Modmail thread {} in server {} has no messages. Only logging header.", modMailThreadId, serverId);
return loadUserAndSendClosingHeader(modMailThread, closingConfig)
.thenAccept(unused -> memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions)
.thenCompose(unused -> userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenCompose(user ->
self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), user, undoActions)
));
}
} else {
log.debug("Not logging modmail thread {}.", modMailThreadId);
return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions)
return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenCompose(user ->
self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), user, undoActions)
);
}
}
@@ -735,8 +750,8 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
undoActions.add(UndoActionInstance.getMessageDeleteAction(message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong()));
}
});
return memberService.getMemberInServerAsync(serverId, userId).thenCompose(member ->
self.afterSuccessfulLog(modMailThreadId, closingContext.getNotifyUser(), member, undoActions)
return userService.retrieveUserForId(userId).thenCompose(user ->
self.afterSuccessfulLog(modMailThreadId, closingContext.getNotifyUser(), user, undoActions)
).exceptionally(throwable -> {
log.warn("Failed to retrieve member for closing the modmail thread. Closing without member information.", throwable);
self.afterSuccessfulLog(modMailThreadId, false, null, undoActions);
@@ -755,12 +770,12 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @param modMailThreadId The ID of the {@link ModMailThread} which is being closed.
* @param notifyUser Whether the user should be notified
* @param undoActions The list of {@link UndoActionInstance} to execute in case of exceptions
* @param modMailThreaduser The {@link Member member} for which the {@link ModMailThread thread} was for
* @param modMailThreaduser The {@link User member} for which the {@link ModMailThread thread} was for
* @throws ModMailThreadNotFoundException in case the {@link ModMailThread} is not found by the ID
* @return A {@link CompletableFuture future} which completes after the messages have been logged
*/
@Transactional
public CompletableFuture<Void> afterSuccessfulLog(Long modMailThreadId, Boolean notifyUser, Member modMailThreaduser, List<UndoActionInstance> undoActions) {
public CompletableFuture<Void> afterSuccessfulLog(Long modMailThreadId, Boolean notifyUser, User modMailThreaduser, List<UndoActionInstance> undoActions) {
log.debug("Mod mail logging for thread {} has completed. Starting post logging activities.", modMailThreadId);
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByIdOptional(modMailThreadId);
if(modMailThreadOpt.isPresent()) {
@@ -770,7 +785,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
HashMap<String, String> closingMessage = new HashMap<>();
String defaultValue = templateService.renderSimpleTemplate("modmail_closing_user_message_description");
closingMessage.put("closingMessage", configService.getStringValue(MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY, modMailThread.getServer().getId(), defaultValue));
return messageService.sendEmbedToUser(modMailThreaduser.getUser(), "modmail_closing_user_message", closingMessage).thenAccept(message ->
return messageService.sendEmbedToUser(modMailThreaduser, "modmail_closing_user_message", closingMessage).thenCompose(message ->
self.deleteChannelAndClose(modMailThreadId, undoActions)
).exceptionally(throwable -> {
self.deleteChannelAndClose(modMailThreadId, undoActions);
@@ -914,7 +929,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.build();
Long modmailThreadId = modMailThread.getId();
return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenApply(user -> {
headerModel.setUser(user);
headerModel.setUser(UserDisplay.fromUser(user));
return self.sendClosingHeader(headerModel, modmailThreadId).get(0);
}).thenCompose(Function.identity());
}
@@ -1001,6 +1016,28 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
}
@Transactional
public CompletableFuture<Void> banUserFromAppealServer(Long mainServerId, Long userId, String reason) {
Long configuredAppealServerId = configService.getLongValue(ModMailFeatureConfig.MOD_MAIL_APPEAL_SERVER, mainServerId);
Guild appealGuild = guildService.getGuildById(configuredAppealServerId);
return banService.banUser(appealGuild, ServerUser.fromId(configuredAppealServerId, userId), Duration.ZERO, reason);
}
@Override
public CompletableFuture<Void> rejectAppeal(ModMailThread modMailThread, String reason, Member memberPerforming) {
ClosingContext context = ClosingContext
.builder()
.closingMember(memberPerforming)
.notifyUser(true)
.log(true)
.note(reason)
.build();
Long mainServerId = modMailThread.getServer().getId();
Long userToBanId = modMailThread.getUser().getUserReference().getId();
return closeModMailThread(modMailThread, context, new ArrayList<>())
.thenCompose((nul) -> self.banUserFromAppealServer(mainServerId, userToBanId , reason));
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(MODMAIL_THREAD_CREATED_COUNTER, "Mod mail threads created");

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="modmailModule" value="(SELECT id FROM module WHERE name = 'modmail')"/>
<property name="modmailFeature" value="(SELECT id FROM feature WHERE key = 'modmail')"/>
<changeSet author="Sheldan" id="modmail_denyModmailAppeal_command">
<insert tableName="command">
<column name="name" value="denyModmailAppeal"/>
<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

@@ -3,4 +3,5 @@
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="1.0-modmail/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.37/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -21,4 +21,11 @@ abstracto.featureModes.threadContainer.enabled=false
abstracto.featureModes.threadMessage.featureName=modmail
abstracto.featureModes.threadMessage.mode=threadMessage
abstracto.featureModes.threadMessage.enabled=true
abstracto.featureModes.threadMessage.enabled=true
abstracto.featureModes.modMailAppeals.featureName=modmail
abstracto.featureModes.modMailAppeals.mode=modMailAppeals
abstracto.featureModes.modMailAppeals.enabled=false
abstracto.systemConfigs.modMailAppealServer.name=modMailAppealServer
abstracto.systemConfigs.modMailAppealServer.longValue=0

View File

@@ -1,141 +0,0 @@
package dev.sheldan.abstracto.modmail.listener;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.MessageDeletedModel;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.modmail.model.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class ModMailMessageDeletedListenerTest {
@InjectMocks
private ModMailMessageDeletedListener testUnit;
@Mock
private ModMailMessageManagementService modMailMessageManagementService;
@Mock
private MessageService messageService;
@Mock
private ModMailMessageDeletedListener self;
@Mock
private MemberService memberService;
@Mock
private CachedMessage deletedMessage;
@Mock
private ModMailMessage modMailMessage;
@Mock
private Member targetMember;
@Mock
private User targetUser;
@Mock
private AServer server;
@Mock
private AChannel channel;
@Mock
private MessageDeletedModel model;
private static final Long DELETED_MESSAGE_ID = 4L;
private static final Long CREATED_MESSAGE_ID_1 = 3L;
private static final Long CREATED_MESSAGE_ID_2 = 5L;
private static final Long USER_ID = 5L;
private static final Long SERVER_ID = 6L;
private static final Long CHANNEL_ID = 9L;
@Test
public void testDeleteOutSideOfThread() {
when(deletedMessage.getMessageId()).thenReturn(DELETED_MESSAGE_ID);
when(modMailMessageManagementService.getByMessageIdOptional(DELETED_MESSAGE_ID)).thenReturn(Optional.empty());
when(model.getCachedMessage()).thenReturn(deletedMessage);
testUnit.execute(model);
verify(memberService, times(0)).getMemberInServerAsync(anyLong(), anyLong());
}
@Test
public void testDeleteNonDuplicatedMessage() {
when(deletedMessage.getMessageId()).thenReturn(DELETED_MESSAGE_ID);
when(modMailMessageManagementService.getByMessageIdOptional(DELETED_MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
ModMailThread thread = Mockito.mock(ModMailThread.class);
AUserInAServer targetUsInAServer = Mockito.mock(AUserInAServer.class);
when(thread.getUser()).thenReturn(targetUsInAServer);
when(thread.getChannel()).thenReturn(channel);
when(thread.getServer()).thenReturn(server);
AUser targetAUser = Mockito.mock(AUser.class);
when(targetUsInAServer.getUserReference()).thenReturn(targetAUser);
when(modMailMessage.getCreatedMessageInChannel()).thenReturn(null);
when(modMailMessage.getCreatedMessageInDM()).thenReturn(CREATED_MESSAGE_ID_2);
when(targetAUser.getId()).thenReturn(USER_ID);
when(modMailMessage.getThreadReference()).thenReturn(thread);
when(targetMember.getUser()).thenReturn(targetUser);
when(memberService.getMemberInServerAsync(SERVER_ID, USER_ID)).thenReturn(CompletableFuture.completedFuture(targetMember));
when(messageService.deleteMessageInChannelWithUser(targetUser, CREATED_MESSAGE_ID_2)).thenReturn(CompletableFuture.completedFuture(null));
when(model.getCachedMessage()).thenReturn(deletedMessage);
when(model.getServerId()).thenReturn(SERVER_ID);
testUnit.execute(model);
verify(messageService, times(0)).deleteMessageInChannelInServer(eq(SERVER_ID), anyLong(), any());
verify(self, times(1)).removeMessageFromThread(DELETED_MESSAGE_ID);
}
@Test
public void testDeleteDuplicatedMessage() {
when(deletedMessage.getMessageId()).thenReturn(DELETED_MESSAGE_ID);
when(modMailMessageManagementService.getByMessageIdOptional(DELETED_MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
ModMailThread thread = Mockito.mock(ModMailThread.class);
AUserInAServer targetUsInAServer = Mockito.mock(AUserInAServer.class);
when(thread.getUser()).thenReturn(targetUsInAServer);
when(thread.getChannel()).thenReturn(channel);
when(thread.getServer()).thenReturn(server);
when(server.getId()).thenReturn(SERVER_ID);
when(channel.getId()).thenReturn(CHANNEL_ID);
AUser targetAUser = Mockito.mock(AUser.class);
when(targetUsInAServer.getUserReference()).thenReturn(targetAUser);
when(modMailMessage.getCreatedMessageInChannel()).thenReturn(CREATED_MESSAGE_ID_1);
when(modMailMessage.getCreatedMessageInDM()).thenReturn(CREATED_MESSAGE_ID_2);
when(targetAUser.getId()).thenReturn(USER_ID);
when(modMailMessage.getThreadReference()).thenReturn(thread);
when(targetMember.getUser()).thenReturn(targetUser);
when(memberService.getMemberInServerAsync(SERVER_ID, USER_ID)).thenReturn(CompletableFuture.completedFuture(targetMember));
when(messageService.deleteMessageInChannelWithUser(targetUser, CREATED_MESSAGE_ID_2)).thenReturn(CompletableFuture.completedFuture(null));
when(messageService.deleteMessageInChannelInServer(SERVER_ID, CHANNEL_ID, CREATED_MESSAGE_ID_1)).thenReturn(CompletableFuture.completedFuture(null));
when(model.getServerId()).thenReturn(SERVER_ID);
when(model.getCachedMessage()).thenReturn(deletedMessage);
testUnit.execute(model);
verify(self, times(1)).removeMessageFromThread(DELETED_MESSAGE_ID);
}
@Test
public void removeMessageFromThread() {
when(modMailMessageManagementService.getByMessageIdOptional(DELETED_MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
testUnit.removeMessageFromThread(DELETED_MESSAGE_ID);
verify(modMailMessageManagementService, times(1)).deleteMessageFromThread(modMailMessage);
}
}

View File

@@ -1,283 +0,0 @@
package dev.sheldan.abstracto.modmail.listener;
import dev.sheldan.abstracto.core.command.config.Parameters;
import dev.sheldan.abstracto.core.command.service.CommandRegistry;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.MessageUpdatedModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.modmail.model.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.template.ModMailModeratorReplyModel;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.ModMailThreadServiceBean;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.modmail.listener.ModMailMessageEditedListener.DEFAULT_COMMAND_FOR_MODMAIL_EDIT;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class ModMailMessageEditedListenerTest {
@InjectMocks
private ModMailMessageEditedListener testUnit;
@Mock
private ModMailThreadService modMailThreadService;
@Mock
private ModMailMessageManagementService modMailMessageManagementService;
@Mock
private CommandRegistry commandRegistry;
@Mock
private CommandService commandService;
@Mock
private MemberService memberService;
@Mock
private TemplateService templateService;
@Mock
private ChannelService channelService;
@Mock
private MessageService messageService;
@Mock
private ModMailMessageEditedListener self;
@Mock
private CachedMessage messageBefore;
@Mock
private Message messageAfter;
@Mock
private Message loadedMessage;
@Mock
private ModMailMessage modMailMessage;
@Mock
private Parameters parsedParameters;
@Mock
private Member targetMember;
@Mock
private User targetUser;
@Mock
private MessageToSend messageToSend;
@Mock
private Member authorMember;
@Mock
private Guild guild;
@Captor
private ArgumentCaptor<ModMailModeratorReplyModel> replyModelArgumentCaptor;
@Mock
private MessageUpdatedModel model;
private static final Long CHANNEL_ID = 5L;
private static final Long MESSAGE_ID = 6L;
private static final Long CREATED_MESSAGE_ID = 10L;
private static final String NEW_COMMAND_PART = "editedText";
private static final String NEW_PARAM = "param";
private static final String NEW_CONTENT = NEW_COMMAND_PART + " " + NEW_PARAM;
private static final Long SERVER_ID = 4L;
private static final Long USER_ID = 3L;
private static final Long AUTHOR_USER_ID = 9L;
@Test
public void testEditOutsideModMailThread() {
when(modMailThreadService.isModMailThread(CHANNEL_ID)).thenReturn(false);
when(messageBefore.getChannelId()).thenReturn(CHANNEL_ID);
when(model.getAfter()).thenReturn(messageAfter);
when(model.getBefore()).thenReturn(messageBefore);
testUnit.execute(model);
verify(modMailMessageManagementService, times(0)).getByMessageIdOptional(anyLong());
}
@Test
public void testEditMessageWithCorrectCommand() {
when(messageBefore.getChannelId()).thenReturn(CHANNEL_ID);
when(modMailThreadService.isModMailThread(CHANNEL_ID)).thenReturn(true);
when(model.getBefore()).thenReturn(messageBefore);
when(model.getAfter()).thenReturn(messageAfter);
when(messageBefore.getMessageId()).thenReturn(MESSAGE_ID);
when(modMailMessageManagementService.getByMessageIdOptional(MESSAGE_ID)).thenReturn(Optional.empty());
when(messageBefore.getChannelId()).thenReturn(CHANNEL_ID);
when(messageBefore.getMessageId()).thenReturn(MESSAGE_ID);
when(messageBefore.getServerId()).thenReturn(SERVER_ID);
when(modMailMessageManagementService.getByMessageIdOptional(MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
ModMailThread thread = Mockito.mock(ModMailThread.class);
when(modMailMessage.getThreadReference()).thenReturn(thread);
AUserInAServer targetUsInAServer = Mockito.mock(AUserInAServer.class);
when(thread.getUser()).thenReturn(targetUsInAServer);
AUser targetUser = Mockito.mock(AUser.class);
when(targetUsInAServer.getUserReference()).thenReturn(targetUser);
when(targetUser.getId()).thenReturn(USER_ID);
AUserInAServer authorUserInAServer = Mockito.mock(AUserInAServer.class);
when(modMailMessage.getAuthor()).thenReturn(authorUserInAServer);
AUser authorUser = Mockito.mock(AUser.class);
when(authorUser.getId()).thenReturn(AUTHOR_USER_ID);
when(authorUserInAServer.getUserReference()).thenReturn(authorUser);
when(messageAfter.getContentStripped()).thenReturn(NEW_CONTENT);
when(commandRegistry.getCommandName(NEW_COMMAND_PART, SERVER_ID)).thenReturn(NEW_COMMAND_PART);
when(commandService.doesCommandExist(NEW_COMMAND_PART)).thenReturn(true);
when(commandService.getParametersForCommand(NEW_COMMAND_PART, messageAfter)).thenReturn(CompletableFuture.completedFuture(parsedParameters));
when(memberService.getMemberInServerAsync(SERVER_ID, USER_ID)).thenReturn(CompletableFuture.completedFuture(targetMember));
when(memberService.getMemberInServerAsync(SERVER_ID, AUTHOR_USER_ID)).thenReturn(CompletableFuture.completedFuture(authorMember));
when(model.getAfter()).thenReturn(messageAfter);
when(model.getBefore()).thenReturn(messageBefore);
testUnit.execute(model);
verify(self, times(1)).updateMessageInThread(messageAfter, parsedParameters, targetMember, authorMember);
}
@Test
public void testEditMessageWithInCorrectCommand() {
when(messageBefore.getChannelId()).thenReturn(CHANNEL_ID);
when(messageBefore.getMessageId()).thenReturn(MESSAGE_ID);
when(messageBefore.getServerId()).thenReturn(SERVER_ID);
when(modMailThreadService.isModMailThread(CHANNEL_ID)).thenReturn(true);
when(modMailMessageManagementService.getByMessageIdOptional(MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
ModMailThread thread = Mockito.mock(ModMailThread.class);
when(modMailMessage.getThreadReference()).thenReturn(thread);
AUserInAServer aUserInAServer = Mockito.mock(AUserInAServer.class);
when(thread.getUser()).thenReturn(aUserInAServer);
AUser user = Mockito.mock(AUser.class);
when(aUserInAServer.getUserReference()).thenReturn(user);
when(user.getId()).thenReturn(USER_ID);
AUserInAServer authorUserInAServer = Mockito.mock(AUserInAServer.class);
when(modMailMessage.getAuthor()).thenReturn(authorUserInAServer);
AUser authorUser = Mockito.mock(AUser.class);
when(authorUser.getId()).thenReturn(AUTHOR_USER_ID);
when(authorUserInAServer.getUserReference()).thenReturn(authorUser);
when(messageAfter.getContentStripped()).thenReturn(NEW_CONTENT);
when(commandRegistry.getCommandName(NEW_COMMAND_PART, SERVER_ID)).thenReturn(NEW_COMMAND_PART);
when(commandService.doesCommandExist(NEW_COMMAND_PART)).thenReturn(false);
when(commandService.getParametersForCommand(DEFAULT_COMMAND_FOR_MODMAIL_EDIT, messageAfter)).thenReturn(CompletableFuture.completedFuture(parsedParameters));
when(memberService.getMemberInServerAsync(SERVER_ID, USER_ID)).thenReturn(CompletableFuture.completedFuture(targetMember));
when(memberService.getMemberInServerAsync(SERVER_ID, AUTHOR_USER_ID)).thenReturn(CompletableFuture.completedFuture(authorMember));
when(model.getAfter()).thenReturn(messageAfter);
when(model.getBefore()).thenReturn(messageBefore);
testUnit.execute(model);
verify(self, times(1)).updateMessageInThread(messageAfter, parsedParameters, targetMember, authorMember);
}
@Test
public void testUpdateAnonymousMessageInThreadNotSentToModMailThreadChannel() {
when(loadedMessage.getIdLong()).thenReturn(MESSAGE_ID);
when(modMailMessageManagementService.getByMessageIdOptional(MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
when(modMailMessage.getAnonymous()).thenReturn(true);
when(modMailMessage.getCreatedMessageInChannel()).thenReturn(null);
when(modMailMessage.getCreatedMessageInDM()).thenReturn(CREATED_MESSAGE_ID);
ModMailThread thread = Mockito.mock(ModMailThread.class);
when(modMailMessage.getThreadReference()).thenReturn(thread);
when(targetMember.getUser()).thenReturn(targetUser);
when(authorMember.getGuild()).thenReturn(guild);
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(parsedParameters.getParameters()).thenReturn(Arrays.asList(NEW_PARAM));
when(templateService.renderEmbedTemplate(eq(ModMailThreadServiceBean.MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY), replyModelArgumentCaptor.capture(), eq(SERVER_ID))).thenReturn(messageToSend);
testUnit.updateMessageInThread(loadedMessage, parsedParameters, targetMember, authorMember);
verify(channelService, times(0)).editMessageInAChannel(eq(messageToSend), any(AChannel.class), anyLong());
verify(messageService, times(1)).editMessageInDMChannel(targetUser, messageToSend, CREATED_MESSAGE_ID);
Assert.assertTrue(replyModelArgumentCaptor.getValue().getAnonymous());
}
@Test
public void testUpdateAnonymousMessageInThreadAlsoSendToModMailThreadChannel() {
when(loadedMessage.getIdLong()).thenReturn(MESSAGE_ID);
when(modMailMessageManagementService.getByMessageIdOptional(MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
when(modMailMessage.getAnonymous()).thenReturn(true);
when(modMailMessage.getCreatedMessageInChannel()).thenReturn(CREATED_MESSAGE_ID);
when(modMailMessage.getCreatedMessageInDM()).thenReturn(CREATED_MESSAGE_ID);
ModMailThread thread = Mockito.mock(ModMailThread.class);
when(modMailMessage.getThreadReference()).thenReturn(thread);
when(targetMember.getUser()).thenReturn(targetUser);
when(authorMember.getGuild()).thenReturn(guild);
when(guild.getIdLong()).thenReturn(SERVER_ID);
AChannel channel = Mockito.mock(AChannel.class);
when(thread.getChannel()).thenReturn(channel);
when(channel.getId()).thenReturn(CHANNEL_ID);
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(parsedParameters.getParameters()).thenReturn(Arrays.asList(NEW_PARAM));
when(templateService.renderEmbedTemplate(eq(ModMailThreadServiceBean.MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY), replyModelArgumentCaptor.capture(), eq(SERVER_ID))).thenReturn(messageToSend);
testUnit.updateMessageInThread(loadedMessage, parsedParameters, targetMember, authorMember);
verify(channelService, times(1)).editMessageInAChannel(eq(messageToSend), eq(channel), eq(CREATED_MESSAGE_ID));
verify(messageService, times(1)).editMessageInDMChannel(targetUser, messageToSend, CREATED_MESSAGE_ID);
Assert.assertTrue(replyModelArgumentCaptor.getValue().getAnonymous());
}
@Test
public void testUpdateMessageInThreadNotDuplicated() {
when(loadedMessage.getIdLong()).thenReturn(MESSAGE_ID);
when(modMailMessageManagementService.getByMessageIdOptional(MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
when(modMailMessage.getAnonymous()).thenReturn(false);
when(modMailMessage.getCreatedMessageInChannel()).thenReturn(null);
when(modMailMessage.getCreatedMessageInDM()).thenReturn(CREATED_MESSAGE_ID);
ModMailThread thread = Mockito.mock(ModMailThread.class);
when(modMailMessage.getThreadReference()).thenReturn(thread);
when(targetMember.getUser()).thenReturn(targetUser);
when(authorMember.getGuild()).thenReturn(guild);
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(parsedParameters.getParameters()).thenReturn(Arrays.asList(NEW_PARAM));
when(templateService.renderEmbedTemplate(eq(ModMailThreadServiceBean.MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY), replyModelArgumentCaptor.capture(), eq(SERVER_ID))).thenReturn(messageToSend);
testUnit.updateMessageInThread(loadedMessage, parsedParameters, targetMember, authorMember);
verify(channelService, times(0)).editMessageInAChannel(eq(messageToSend), any(AChannel.class), anyLong());
verify(messageService, times(1)).editMessageInDMChannel(targetUser, messageToSend, CREATED_MESSAGE_ID);
Assert.assertFalse(replyModelArgumentCaptor.getValue().getAnonymous());
}
@Test
public void testUpdateMessageInThreadDuplicated() {
when(loadedMessage.getIdLong()).thenReturn(MESSAGE_ID);
when(modMailMessageManagementService.getByMessageIdOptional(MESSAGE_ID)).thenReturn(Optional.of(modMailMessage));
when(modMailMessage.getAnonymous()).thenReturn(false);
when(modMailMessage.getCreatedMessageInChannel()).thenReturn(CREATED_MESSAGE_ID);
when(modMailMessage.getCreatedMessageInDM()).thenReturn(CREATED_MESSAGE_ID);
ModMailThread thread = Mockito.mock(ModMailThread.class);
when(modMailMessage.getThreadReference()).thenReturn(thread);
when(targetMember.getUser()).thenReturn(targetUser);
when(authorMember.getGuild()).thenReturn(guild);
when(guild.getIdLong()).thenReturn(SERVER_ID);
AChannel channel = Mockito.mock(AChannel.class);
when(thread.getChannel()).thenReturn(channel);
when(channel.getId()).thenReturn(CHANNEL_ID);
when(parsedParameters.getParameters()).thenReturn(Arrays.asList(NEW_PARAM));
when(templateService.renderEmbedTemplate(eq(ModMailThreadServiceBean.MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY), replyModelArgumentCaptor.capture(), eq(SERVER_ID))).thenReturn(messageToSend);
testUnit.updateMessageInThread(loadedMessage, parsedParameters, targetMember, authorMember);
verify(channelService, times(1)).editMessageInAChannel(eq(messageToSend), eq(channel), eq(CREATED_MESSAGE_ID));
verify(messageService, times(1)).editMessageInDMChannel(targetUser, messageToSend, CREATED_MESSAGE_ID);
Assert.assertFalse(replyModelArgumentCaptor.getValue().getAnonymous());
}
}

View File

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

View File

@@ -21,6 +21,7 @@ import java.util.List;
public class ModMailFeatureConfig implements FeatureConfig {
public static final String MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY = "modMailClosingText";
public static final String MOD_MAIL_APPEAL_SERVER = "modMailAppealServer";
@Autowired
private ModMailFeatureValidator modMailFeatureValidator;
@@ -34,27 +35,33 @@ public class ModMailFeatureConfig implements FeatureConfig {
@Override
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(ModMailPostTargets.MOD_MAIL_PING, ModMailPostTargets.MOD_MAIL_LOG, ModMailPostTargets.MOD_MAIL_CONTAINER);
return List.of(ModMailPostTargets.MOD_MAIL_PING,
ModMailPostTargets.MOD_MAIL_LOG,
ModMailPostTargets.MOD_MAIL_CONTAINER);
}
@Override
public List<FeatureValidator> getAdditionalFeatureValidators() {
return Arrays.asList(modMailFeatureValidator);
return List.of(modMailFeatureValidator);
}
@Override
public List<String> getRequiredEmotes() {
return Arrays.asList("readReaction");
return List.of("readReaction");
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(ModMailMode.LOGGING, ModMailMode.SEPARATE_MESSAGE, ModMailMode.THREAD_CONTAINER);
return List.of(ModMailMode.LOGGING,
ModMailMode.SEPARATE_MESSAGE,
ModMailMode.THREAD_CONTAINER,
ModMailMode.MOD_MAIL_APPEALS
);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY);
return List.of(MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY, MOD_MAIL_APPEAL_SERVER);
}
@Override

View File

@@ -9,7 +9,10 @@ import lombok.Getter;
*/
@Getter
public enum ModMailMode implements FeatureMode {
LOGGING("log"), SEPARATE_MESSAGE("threadMessage"), THREAD_CONTAINER("threadContainer");
LOGGING("log"),
SEPARATE_MESSAGE("threadMessage"),
THREAD_CONTAINER("threadContainer"),
MOD_MAIL_APPEALS("modMailAppeals");
private final String key;

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