Compare commits

..

29 Commits

Author SHA1 Message Date
Sheldan
a39b6695a6 [maven-release-plugin] prepare release abstracto-application-1.3.9 2021-10-25 23:20:31 +02:00
Sheldan
362d87778e [AB-339] repurposing the softban command for a ban command with deletion days 2021-10-25 01:11:32 +02:00
Sheldan
0514d355c7 [AB-167] adding warning created events and infraction counter
adding disabling of post targets
adding some logging for message sending failure consumer
2021-10-25 00:04:08 +02:00
Sheldan
8909e8ebe5 [AB-333] providing some dates as separate instants instead of relying on the member attributes
truncating date to day when displaying the date for export emote stats
fixing wrong message when confirming a command and adding missing exception handling
2021-10-14 01:31:52 +02:00
Sheldan
36ca9b11e4 [AB-334] adding vote result to suggestion reminder 2021-10-13 18:00:54 +02:00
Sheldan
4a66b7fc67 [AB-331] showing separate notifications for the decision on a suggestion 2021-09-28 22:20:11 +02:00
Sheldan
b2a94059e2 [AB-332] showing separate message in case there are no warnings 2021-09-28 22:05:51 +02:00
Sheldan
16e6caa1f0 [AB-196] adding confirmation requirement to various commands
refactoring command received handler in order to re-use when confirmation has been given
removing reaction from showSuggestion and remind command
adding log message for message deleted message, in case the member left
all commands from now on must work without the member field from the message, as this is not available when retrieving the message
2021-09-27 00:22:37 +02:00
Sheldan
da1a71ecdc [AB-319] splitting emotes into two separate fields (animated and static) for server info
adding emote display model
2021-09-10 00:41:15 +02:00
Sheldan
0646efe33d [AB-163] fixing printing all possible feature modes in case an unknown one was provided for enableMode/disableMode 2021-09-10 00:21:30 +02:00
Sheldan
db856f2647 [AB-xxx] improving documentation 2021-09-09 02:20:45 +02:00
Sheldan
1d85eb1e7e [AB-297] adding server names to invite deletion log (if available) 2021-09-08 23:04:35 +02:00
Sheldan
5e6999cd45 [AB-328] fixing case sensitivity of allowed invite links 2021-09-07 01:13:34 +02:00
Sheldan
9659a3487a [maven-release-plugin] prepare for next development iteration 2021-09-06 01:54:00 +02:00
Sheldan
6d27c487f2 [maven-release-plugin] prepare release abstracto-application-1.3.8 2021-09-06 01:53:55 +02:00
Sheldan
8ac3b327e4 [AB-268] adding button feature mode to suggestions which allows for hidden suggestion votes
moving gateway metric to separate service in case JDA is not ready yet
2021-09-06 01:39:27 +02:00
Sheldan
448332f24f [AB-318] adding a mention of the original message to link embed
updating JDA version
2021-09-04 16:30:16 +02:00
Sheldan
d7f889971d [AB-325] adding async command conditions - as this is required for some conditions
adding user parameter to immune user condition evaluation
2021-09-04 13:47:03 +02:00
Sheldan
69abff77fb [AB-xxx] fixing test 2021-08-21 16:05:01 +02:00
Sheldan
2c31fa1c1e [AB-xxx] not always creating a user instance in the experience listener
adding logging in case the warn decay notification fails
2021-08-21 15:50:11 +02:00
Sheldan
19a4858da1 [AB-323] improving logging when using whenComplete 2021-08-14 12:15:18 +02:00
Sheldan
3ed1f0c54a [AB-322] adding possibility to define a max age in days for messages to be eligible for stars 2021-08-14 09:45:25 +02:00
Sheldan
d01e46a9a6 [AB-xxx] fixing modmail sometimes failing to log messages 2021-08-14 08:02:04 +02:00
Sheldan
222250b795 [AB-321] changing caching policy
changing logging messages
2021-08-14 08:00:40 +02:00
Sheldan
9a87b75998 [maven-release-plugin] prepare for next development iteration 2021-08-09 23:52:49 +02:00
Sheldan
b16cef0d3c [maven-release-plugin] prepare release abstracto-application-1.3.7 2021-08-09 23:52:40 +02:00
Sheldan
cc55934ff2 [AB-xxx] fixing not removing component payloads for message embed cleanup job 2021-08-09 00:40:36 +02:00
Sheldan
168b4a52c8 [AB-xxx] changing some exception logging
fixing moderator member not re-used for reply command
2021-08-09 00:11:49 +02:00
Sheldan
73b5684a7e [maven-release-plugin] prepare for next development iteration 2021-07-26 01:37:38 +02:00
281 changed files with 3434 additions and 835 deletions

View File

@@ -14,7 +14,7 @@ An example implementation of this bot can be seen [here](https://github.com/Shel
## Technologies
* [JDA](https://github.com/DV8FromTheWorld/JDA/) The Discord API Wrapper in the version 4.3.0_284
* [JDA](https://github.com/DV8FromTheWorld/JDA/) The Discord API Wrapper in the version 4.3.0_315
* [Spring boot](https://github.com/spring-projects/spring-boot) is used as a framework to create standalone application in Java with Java EE methods. (including Dependency injection and more)
* [Hibernate](https://github.com/hibernate/hibernate-orm) is used as a reference implementation of JPA.
* [Freemarker](https://github.com/apache/freemarker) is used as a templating engine. This is used to provide internationalization for user facing text and enable dynamic embed configuration.

View File

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

View File

@@ -50,6 +50,7 @@ public class DeleteAssignableRolePlace extends AbstractConditionableCommand {
.templated(true)
.causesReaction(true)
.async(true)
.requiresConfirmation(true)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)

View File

@@ -149,6 +149,9 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
self.persistAssignableUser(member, payload, false);
});
}
}).exceptionally(throwable -> {
log.error("Failed to perform role change in assignable role place.", throwable);
return null;
});
} else {
assignableRoleService.removeAssignableRoleFromUser(roleById, member)

View File

@@ -205,16 +205,15 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
if (throwable != null) {
log.warn("Not able to delete old messages of assignable role place {} in server {}.", assignablePlaceId, serverId);
}
try {
self.createAssignableRolePlacePost(serverId, assignablePlaceId)
.thenAccept(unused1 -> postingFuture.complete(null))
.exceptionally(innerThrowable -> {
postingFuture.completeExceptionally(innerThrowable);
return null;
});
} catch (Exception ex) {
postingFuture.completeExceptionally(ex);
}
self.createAssignableRolePlacePost(serverId, assignablePlaceId)
.thenAccept(unused1 -> postingFuture.complete(null))
.exceptionally(innerThrowable -> {
postingFuture.completeExceptionally(innerThrowable);
return null;
});
}).exceptionally(throwable -> {
postingFuture.completeExceptionally(throwable);
return null;
});
return postingFuture;
}
@@ -361,17 +360,16 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
if (throwable != null) {
log.warn("Not able to delete old messages of assignable role place {} in server {}.", assignablePlaceId, serverId);
}
try {
self.setupAssignableRolePlaceInChannel(serverId, assignablePlaceId, newChannel)
.thenAccept(unused1 -> self.updateAssignableRolePlaceChannel(name, newChannel))
.thenAccept(unused1 -> returnFuture.complete(null))
.exceptionally(innerThrowable -> {
returnFuture.completeExceptionally(innerThrowable);
return null;
});
} catch (Exception ex) {
returnFuture.completeExceptionally(ex);
}
self.setupAssignableRolePlaceInChannel(serverId, assignablePlaceId, newChannel)
.thenAccept(unused1 -> self.updateAssignableRolePlaceChannel(name, newChannel))
.thenAccept(unused1 -> returnFuture.complete(null))
.exceptionally(innerThrowable -> {
returnFuture.completeExceptionally(innerThrowable);
return null;
});
}).exceptionally(throwable -> {
returnFuture.completeExceptionally(throwable);
return null;
});
return returnFuture;

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -66,6 +66,7 @@ public class SetExpRole extends AbstractConditionableCommand {
.async(true)
.supportsEmbedException(true)
.causesReaction(true)
.requiresConfirmation(true)
.parameters(parameters)
.help(helpInfo)
.build();

View File

@@ -52,6 +52,7 @@ public class SyncRoles extends AbstractConditionableCommand {
.module(ExperienceModuleDefinition.EXPERIENCE)
.templated(true)
.async(true)
.requiresConfirmation(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)

View File

@@ -67,6 +67,7 @@ public class UnSetExpRole extends AbstractConditionableCommand {
.templated(true)
.async(true)
.causesReaction(true)
.requiresConfirmation(true)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)

View File

@@ -37,16 +37,18 @@ public class JoiningUserRoleListener implements AsyncJoinListener {
@Override
public DefaultListenerResult execute(MemberJoinModel model) {
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(model.getServerId(), model.getJoiningUser().getUserId());
Optional<AUserExperience> userExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(userInAServer.getUserInServerId());
if(userExperienceOptional.isPresent()) {
log.info("User {} joined {} with previous experience. Setting up experience role again (if necessary).", model.getJoiningUser().getUserId(), model.getServerId());
userExperienceService.syncForSingleUser(userExperienceOptional.get()).thenAccept(result ->
log.info("Finished re-assigning experience for re-joining user {} in server {}.", model.getJoiningUser().getUserId(), model.getServerId())
);
} else {
log.info("Joined user {} in server {} does not have any previous experience. Not setting up anything.", model.getJoiningUser().getUserId(), model.getServerId());
}
Optional<AUserInAServer> userInAServerOptional = userInServerManagementService.loadUserOptional(model.getServerId(), model.getJoiningUser().getUserId());
userInAServerOptional.ifPresent(aUserInAServer -> {
Optional<AUserExperience> userExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(aUserInAServer.getUserInServerId());
if(userExperienceOptional.isPresent()) {
log.info("User {} joined {} with previous experience. Setting up experience role again (if necessary).", model.getJoiningUser().getUserId(), model.getServerId());
userExperienceService.syncForSingleUser(userExperienceOptional.get()).thenAccept(result ->
log.info("Finished re-assigning experience for re-joining user {} in server {}.", model.getJoiningUser().getUserId(), model.getServerId())
);
} else {
log.info("Joined user {} in server {} does not have any previous experience. Not setting up anything.", model.getJoiningUser().getUserId(), model.getServerId());
}
});
return DefaultListenerResult.PROCESSED;
}

View File

@@ -166,6 +166,9 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
FutureUtils.toSingleFutureGeneric(memberFutures).whenComplete((unused, throwable) -> {
self.updateFoundMembers(memberFutures, serverExp.getServerId(), resultFutures, futures);
experienceFuture.complete(null);
}).exceptionally(throwable -> {
experienceFuture.completeExceptionally(throwable);
return null;
});
});
return experienceFuture

View File

@@ -39,7 +39,6 @@ public class HasLevelCondition implements SystemCondition {
Map<String, Object> parameters = conditionContext.getParameters();
Long userInServerId = (Long) parameters.get(USER_IN_SERVER_ID_VARIABLE_KEY);
Integer level = (Integer) parameters.get(LEVEL_VARIABLE);
log.info("Evaluating has level condition.");
Optional<AUserInAServer> userInServerOptional = userInServerManagementService.loadUserOptional(userInServerId);
if(userInServerOptional.isPresent()) {
AUserInAServer userInServer = userInServerOptional.get();
@@ -50,7 +49,7 @@ public class HasLevelCondition implements SystemCondition {
log.info("Condition evaluated to {}", conditionResult);
return conditionResult;
}
log.info("No user in server object was found. Evaluating to false.");
log.info("No user in server object was found. Evaluating has level to false.");
return false;
}

View File

@@ -57,7 +57,7 @@ public class JoiningUserRoleListenerTest {
when(model.getJoiningUser()).thenReturn(serverUser);
when(model.getServerId()).thenReturn(SERVER_ID);
when(aUserInAServer.getUserInServerId()).thenReturn(USER_IN_SERVER_ID);
when(userInServerManagementService.loadOrCreateUser(SERVER_ID, USER_ID)).thenReturn(aUserInAServer);
when(userInServerManagementService.loadUserOptional(SERVER_ID, USER_ID)).thenReturn(Optional.of(aUserInAServer));
}
@Test

View File

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

View File

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

View File

@@ -51,6 +51,7 @@ public class RemoveTrackedInviteLinks extends AbstractConditionableCommand {
.module(InviteFilterModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)
.requiresConfirmation(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)

View File

@@ -13,5 +13,5 @@ public interface AllowedInviteLinkRepository extends JpaRepository<AllowedInvite
Optional<AllowedInviteLink> findByTargetServerIdAndServer(Long targetServerId, AServer server);
Optional<AllowedInviteLink> findByTargetServerIdAndServer_Id(Long targetServerId, Long serverId);
Optional<AllowedInviteLink> findByCodeAndServer_Id(String code, Long serverId);
Optional<AllowedInviteLink> findByCodeIgnoreCaseAndServer_Id(String code, Long serverId);
}

View File

@@ -21,6 +21,9 @@ import dev.sheldan.abstracto.invitefilter.model.template.listener.DeletedInvite;
import dev.sheldan.abstracto.invitefilter.model.template.listener.DeletedInvitesNotificationModel;
import dev.sheldan.abstracto.invitefilter.service.management.AllowedInviteLinkManagement;
import dev.sheldan.abstracto.invitefilter.service.management.FilteredInviteLinkManagement;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Invite;
@@ -34,7 +37,6 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -222,11 +224,11 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
return Invite.resolve(jda, extractCode(code)).submit();
}
private void sendDeletionNotification(List<String> codes, Message message) {
private CompletableFuture<Void> sendDeletionNotification(List<InviteToDelete> codes, Message message) {
Long serverId = message.getGuild().getIdLong();
if(!postTargetService.postTargetDefinedInServer(InviteFilterPostTarget.INVITE_DELETE_LOG, serverId)) {
log.info("Post target {} not defined for server {} - not sending invite link deletion notification.", InviteFilterPostTarget.INVITE_DELETE_LOG.getKey(), serverId);
return;
return CompletableFuture.completedFuture(null);
}
DeletedInvitesNotificationModel model = DeletedInvitesNotificationModel
.builder()
@@ -240,21 +242,30 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
codes.size(), serverId, message.getAuthor().getIdLong(), message.getChannel().getIdLong(), message.getIdLong());
MessageToSend messageToSend = templateService.renderEmbedTemplate(INVITE_LINK_DELETED_NOTIFICATION_EMBED_TEMPLATE_KEY, model, message.getGuild().getIdLong());
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, InviteFilterPostTarget.INVITE_DELETE_LOG, serverId);
FutureUtils.toSingleFutureGeneric(messageFutures).thenAccept(unused ->
return FutureUtils.toSingleFutureGeneric(messageFutures).thenAccept(unused ->
log.debug("Successfully send notification about deleted invite link in message {}.", message.getIdLong())
).exceptionally(throwable -> {
log.error("Failed to send notification about deleted invite link in message {}.", message.getIdLong());
return null;
});
);
}
private List<DeletedInvite> groupInvites(List<String> codes) {
private List<DeletedInvite> groupInvites(List<InviteToDelete> codes) {
Map<String, String> codeToGuildName = new HashMap<>();
for (InviteToDelete invite: codes) {
if(!codeToGuildName.containsKey(invite.getInviteCode())) {
codeToGuildName.put(invite.getInviteCode(), invite.getGuildName());
}
}
return codes
.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.collect(Collectors.groupingBy(InviteToDelete::getInviteCode, Collectors.counting()))
.entrySet()
.stream()
.map(functionLongEntry -> new DeletedInvite(functionLongEntry.getKey(), functionLongEntry.getValue()))
.map(functionLongEntry -> DeletedInvite
.builder()
.code(functionLongEntry.getKey())
.guildName(codeToGuildName.get(functionLongEntry.getKey()))
.count(functionLongEntry.getValue())
.build())
.collect(Collectors.toList());
}
@@ -291,18 +302,23 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
ServerUser author = ServerUser.builder().userId(message.getAuthor().getIdLong()).serverId(message.getGuild().getIdLong()).build();
boolean toDelete = false;
Map<Long, String> targetServers = new HashMap<>();
List<String> deletedInvites = new ArrayList<>();
List<InviteToDelete> deletedInvites = new ArrayList<>();
for (Invite invite : invites) {
if (invite.getType().equals(Invite.InviteType.GUILD)
&& isCodeFiltered(invite.getGuild().getIdLong(), author)) {
toDelete = true;
deletedInvites.add(invite.getCode());
InviteToDelete inviteToDelete = InviteToDelete
.builder()
.inviteCode(invite.getCode())
.guildName(invite.getGuild().getName())
.build();
deletedInvites.add(inviteToDelete);
targetServers.put(invite.getGuild().getIdLong(), invite.getGuild().getName());
}
}
List<String> unResolvedInvites = new ArrayList<>();
foundInvites.forEach(possibleUnresolvedInvite -> {
if(invites.stream().noneMatch(invite -> invite.getCode().equals(possibleUnresolvedInvite))) {
if(invites.stream().noneMatch(invite -> invite.getCode().equalsIgnoreCase(possibleUnresolvedInvite))) {
unResolvedInvites.add(possibleUnresolvedInvite);
}
});
@@ -310,7 +326,11 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
for(String unresolvedInvite : unResolvedInvites) {
if(isCodeFiltered(unresolvedInvite, author)) {
toDelete = true;
deletedInvites.add(unresolvedInvite);
InviteToDelete inviteToDelete = InviteToDelete
.builder()
.inviteCode(unresolvedInvite)
.build();
deletedInvites.add(inviteToDelete);
}
}
if(toDelete) {
@@ -322,9 +342,16 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
}
boolean sendNotification = featureModeService.featureModeActive(InviteFilterFeatureDefinition.INVITE_FILTER, serverId, InviteFilterMode.FILTER_NOTIFICATIONS);
if(sendNotification) {
sendDeletionNotification(deletedInvites, message);
sendDeletionNotification(deletedInvites, message)
.thenAccept(unused1 -> log.info("Sent invite deletion notification.")).exceptionally(throwable1 -> {
log.error("Failed to send invite deletion notification.");
return null;
});
}
}
}).exceptionally(throwable -> {
log.error("Invite matching failed.", throwable);
return null;
});
}
@@ -333,4 +360,12 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
metricService.registerCounter(MESSAGE_INVITE_FILTERED, "Amount of messages containing an invite filtered");
}
@Getter
@Setter
@Builder
private static class InviteToDelete {
private String guildName;
private String inviteCode;
}
}

View File

@@ -16,7 +16,12 @@ public class AllowedInviteLinkManagementBean implements AllowedInviteLinkManagem
@Override
public AllowedInviteLink createAllowedInviteLink(AServer server, Long targetServerId, String code) {
AllowedInviteLink inviteLink = AllowedInviteLink.builder().targetServerId(targetServerId).code(code).server(server).build();
AllowedInviteLink inviteLink = AllowedInviteLink
.builder()
.targetServerId(targetServerId)
.code(code)
.server(server)
.build();
return repository.save(inviteLink);
}
@@ -48,6 +53,6 @@ public class AllowedInviteLinkManagementBean implements AllowedInviteLinkManagem
@Override
public boolean allowedInviteLinkExists(ServerUser serverUser, String code) {
return repository.findByCodeAndServer_Id(code, serverUser.getServerId()).isPresent();
return repository.findByCodeIgnoreCaseAndServer_Id(code, serverUser.getServerId()).isPresent();
}
}

View File

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

View File

@@ -1,14 +1,17 @@
package dev.sheldan.abstracto.invitefilter.model.template.listener;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@EqualsAndHashCode
@Builder
public class DeletedInvite {
private String code;
private String guildName;
private Long count;
}

View File

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

View File

@@ -70,6 +70,7 @@ public class MessageEmbedListener implements MessageReceivedListener {
log.debug("We found {} links to embed in message {} in channel {} in guild {}.", links.size(), message.getId(), message.getChannel().getId(), message.getGuild().getId());
Long userEmbeddingUserInServerId = userInServerManagementService.loadOrCreateUser(message.getMember()).getUserInServerId();
for (MessageEmbedLink messageEmbedLink : links) {
// potentially support foreign linked servers
if(!messageEmbedLink.getServerId().equals(message.getGuild().getIdLong())) {
log.info("Link for message {} was from a foreign server {}. Do not embed.", messageEmbedLink.getMessageId(), messageEmbedLink.getServerId());
continue;

View File

@@ -168,6 +168,12 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
.stream()
.map(EmbeddedMessage::getEmbeddingMessageId)
.collect(Collectors.toList());
List<String> componentPayloadsToDelete = embeddedMessages
.stream()
.map(EmbeddedMessage::getDeletionComponentId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
List<CompletableFuture<Message>> reactionMessageFutures = messageService.retrieveMessages(reactionChannelMessages);
List<CompletableFuture<Message>> buttonMessageFutures = messageService.retrieveMessages(buttonChannelMessages);
CompletableFutureList<Message> reactionFutureList = new CompletableFutureList<>(reactionMessageFutures);
@@ -193,7 +199,7 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
if(throwable != null) {
log.warn("Embedded message button clearing failed.", throwable);
}
self.deleteEmbeddedMessages(embeddedMessagesHandled);
self.deleteEmbeddedMessages(embeddedMessagesHandled, componentPayloadsToDelete);
})
.exceptionally(throwable -> {
log.error("Failed to clean up embedded messages.", throwable);
@@ -231,8 +237,9 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
}
@Transactional
public void deleteEmbeddedMessages(List<Long> embeddedMessagesToDelete) {
public void deleteEmbeddedMessages(List<Long> embeddedMessagesToDelete, List<String> componentPayloadsToDelete) {
messageEmbedPostManagementService.deleteEmbeddedMessagesViaId(embeddedMessagesToDelete);
componentPayloadManagementService.deletePayloads(componentPayloadsToDelete);
}
@Transactional
@@ -309,6 +316,9 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
.builder()
.buttonId(deletionButtonEnabled ? componentServiceBean.generateComponentId() : null)
.build();
Long referencedMessageId = message.getReferencedMessage() != null ? message.getReferencedMessage().getIdLong() : null;
Boolean shouldMentionReferencedAuthor = shouldMentionReferencedAuthor(message);
return MessageEmbeddedModel
.builder()
.member(message.getMember())
@@ -319,7 +329,16 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
.guild(message.getGuild())
.useButton(deletionButtonEnabled)
.embeddedMessage(embeddedMessage)
.referencedMessageId(referencedMessageId)
.mentionsReferencedMessage(shouldMentionReferencedAuthor)
.buttonConfigModel(buttonConfigModel)
.build();
}
private Boolean shouldMentionReferencedAuthor(Message message) {
if(message.getReferencedMessage() != null) {
return message.getMentionedUsers().contains(message.getReferencedMessage().getAuthor());
}
return false;
}
}

View File

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

View File

@@ -19,5 +19,7 @@ public class MessageEmbeddedModel extends UserInitiatedServerContext {
private TextChannel sourceChannel;
private Member embeddingUser;
private ButtonConfigModel buttonConfigModel;
private Long referencedMessageId;
private Boolean mentionsReferencedMessage;
private Boolean useButton;
}

View File

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

View File

@@ -8,6 +8,7 @@ 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.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MemberJoinLogModel;
@@ -43,7 +44,11 @@ public class JoinLogger implements AsyncJoinListener {
.build();
log.debug("Logging join event for user {} in server {}.", listenerModel.getMember().getIdLong(), listenerModel.getServerId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_JOIN_TEMPLATE, model, listenerModel.getServerId());
postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.JOIN_LOG, listenerModel.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.JOIN_LOG, listenerModel.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send member joining log.", throwable);
return null;
});
return DefaultListenerResult.PROCESSED;
}

View File

@@ -8,6 +8,7 @@ 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.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import lombok.extern.slf4j.Slf4j;
@@ -46,7 +47,11 @@ public class LeaveLogger implements AsyncLeaveListener {
.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());
postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.LEAVE_LOG, listenerModel.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.LEAVE_LOG, listenerModel.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send member leaving log.", throwable);
return null;
});
return DefaultListenerResult.PROCESSED;
}
}

View File

@@ -14,6 +14,7 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.core.utils.ContextUtils;
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.MessageDeletedAttachmentLog;
@@ -64,7 +65,10 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
CachedMessage message = model.getCachedMessage();
memberService.getMemberInServerAsync(model.getServerId(), message.getAuthor().getAuthorId()).thenAccept(member ->
self.executeListener(message, member)
);
).exceptionally(throwable -> {
log.warn("Could not retrieve member {} for message deleted event in guild {}.", message.getAuthor().getAuthorId(), model.getServerId(), throwable);
return null;
});
return DefaultListenerResult.PROCESSED;
}
@@ -81,11 +85,15 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.member(authorMember)
.build();
MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_DELETED_TEMPLATE, logModel, messageFromCache.getServerId());
postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message deleted log.", throwable);
return null;
});
if(messageFromCache.getAttachments() != null){
log.debug("Notifying about deletions of {} attachments.", messageFromCache.getAttachments().size());
for (int i = 0; i < messageFromCache.getAttachments().size(); i++) {
MessageDeletedAttachmentLog log = MessageDeletedAttachmentLog
MessageDeletedAttachmentLog attachmentLogModel = MessageDeletedAttachmentLog
.builder()
.imageUrl(messageFromCache.getAttachments().get(i).getProxyUrl())
.counter(i + 1)
@@ -93,8 +101,12 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.channel(textChannel)
.member(authorMember)
.build();
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_DELETED_ATTACHMENT_TEMPLATE, log, messageFromCache.getServerId());
postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId());
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_DELETED_ATTACHMENT_TEMPLATE, attachmentLogModel, messageFromCache.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message deleted log.", throwable);
return null;
});
}
}
}

View File

@@ -11,6 +11,7 @@ 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.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MessageDeletedAttachmentLog;
@@ -65,7 +66,11 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
.member(messageAfter.getMember())
.build();
MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_EDITED_TEMPLATE, lodModel, model.getServerId());
postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.EDIT_LOG, model.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.EDIT_LOG, model.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message edited log.", throwable);
return null;
});
if(attachmentWasRemoved) {
log.info("Attachment count changed. Old {}, new {}.", attachmentCountBefore, attachmentCountAfter);
@@ -75,7 +80,7 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
).collect(Collectors.toList());
log.debug("Logging deletion of {} attachments.", removedAttachments.size());
for (int i = 0; i < removedAttachments.size(); i++) {
MessageDeletedAttachmentLog log = MessageDeletedAttachmentLog
MessageDeletedAttachmentLog attachmentModel = MessageDeletedAttachmentLog
.builder()
.imageUrl(removedAttachments.get(i).getProxyUrl())
.counter(i + 1)
@@ -84,8 +89,12 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
.member(messageAfter.getMember())
.build();
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_EDITED_ATTACHMENT_REMOVED_TEMPLATE,
log, messageBefore.getServerId());
postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageBefore.getServerId());
attachmentModel, messageBefore.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageBefore.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message edited attachment log.", throwable);
return null;
});
}
}
return DefaultListenerResult.PROCESSED;

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</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.3.6</version>
<version>1.3.9</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -42,7 +42,7 @@ public class Ban extends AbstractConditionableCommand {
User user = (User) parameters.get(0);
String reason = (String) parameters.get(1);
return banService.banUser(user, reason, commandContext.getAuthor(), commandContext.getMessage())
return banService.banUserWithNotification(user, reason, commandContext.getAuthor(), 0, commandContext.getMessage())
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -17,7 +17,6 @@ import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -27,7 +26,7 @@ import static dev.sheldan.abstracto.moderation.service.BanService.BAN_EFFECT_KEY
@Component
@Slf4j
public class SoftBan extends AbstractConditionableCommand {
public class BanDelete extends AbstractConditionableCommand {
@Autowired
private BanService banService;
@@ -36,11 +35,9 @@ public class SoftBan extends AbstractConditionableCommand {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
User user = (User) parameters.get(0);
Duration delDays = Duration.ofDays(7);
if(parameters.size() > 1) {
delDays = (Duration) parameters.get(1);
}
return banService.softBanUser(commandContext.getGuild(), user, delDays)
Integer delDays = (Integer) parameters.get(1);
String reason = (String) parameters.get(2);
return banService.banUserWithNotification(user, reason, commandContext.getAuthor(), delDays, commandContext.getMessage())
.thenApply(unused -> CommandResult.fromSuccess());
}
@@ -48,11 +45,12 @@ public class SoftBan extends AbstractConditionableCommand {
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("user").templated(true).type(User.class).build());
parameters.add(Parameter.builder().name("delDays").templated(true).type(Duration.class).optional(true).build());
parameters.add(Parameter.builder().name("delDays").templated(true).type(Integer.class).build());
parameters.add(Parameter.builder().name("reason").templated(true).type(String.class).remainder(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
List<EffectConfig> effectConfig = Arrays.asList(EffectConfig.builder().position(0).effectKey(BAN_EFFECT_KEY).build());
return CommandConfiguration.builder()
.name("softBan")
.name("banDelete")
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)

View File

@@ -44,6 +44,7 @@ public class DecayAllWarnings extends AbstractConditionableCommand {
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)
.requiresConfirmation(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)

View File

@@ -43,6 +43,7 @@ public class DecayWarnings extends AbstractConditionableCommand {
.name("decayWarnings")
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.requiresConfirmation(true)
.async(true)
.supportsEmbedException(true)
.causesReaction(true)

View File

@@ -39,7 +39,7 @@ public class MyWarnings extends AbstractConditionableCommand {
public CommandResult execute(CommandContext commandContext) {
MyWarningsModel model = (MyWarningsModel) ContextConverter.fromCommandContext(commandContext, MyWarningsModel.class);
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor());
Long currentWarnCount = warnManagementService.getActiveWarnsForUser(userInAServer);
Long currentWarnCount = warnManagementService.getActiveWarnCountForUser(userInAServer);
model.setCurrentWarnCount(currentWarnCount);
Long totalWarnCount = warnManagementService.getTotalWarnsForUser(userInAServer);
model.setTotalWarnCount(totalWarnCount);

View File

@@ -34,7 +34,7 @@ public class UnBan extends AbstractConditionableCommand {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
User user = (User) parameters.get(0);
return banService.unBanUser(user, commandContext.getAuthor())
return banService.unBanUserWithNotification(user, commandContext.getAuthor())
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -12,9 +12,13 @@ import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.PaginatorService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
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.FutureUtils;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.converter.WarnEntryConverter;
@@ -35,6 +39,7 @@ import java.util.concurrent.CompletableFuture;
public class Warnings extends AbstractConditionableCommand {
public static final String WARNINGS_RESPONSE_TEMPLATE = "warnings_response";
public static final String NO_WARNINGS_TEMPLATE_KEY = "warnings_no_warnings_found";
@Autowired
private WarnManagementService warnManagementService;
@@ -56,6 +61,12 @@ public class Warnings extends AbstractConditionableCommand {
@Autowired
private Warnings self;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Warning> warnsToDisplay;
@@ -69,10 +80,17 @@ public class Warnings extends AbstractConditionableCommand {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
warnsToDisplay = warnManagementService.getAllWarningsOfServer(server);
}
return warnEntryConverter.fromWarnings(warnsToDisplay).thenApply(warnEntries -> {
self.renderWarnings(commandContext, warnEntries);
return CommandResult.fromIgnored();
});
if(warnsToDisplay.isEmpty()) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(NO_WARNINGS_TEMPLATE_KEY, new Object(), commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
} else {
return warnEntryConverter.fromWarnings(warnsToDisplay).thenApply(warnEntries -> {
self.renderWarnings(commandContext, warnEntries);
return CommandResult.fromSuccess();
});
}
}
@@ -96,7 +114,7 @@ public class Warnings extends AbstractConditionableCommand {
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)
.causesReaction(true)
.causesReaction(false)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)

View File

@@ -0,0 +1,24 @@
package dev.sheldan.abstracto.moderation.config;
import dev.sheldan.abstracto.core.service.ExecutorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
@Configuration
public class ModerationListenerConfig {
@Autowired
private ExecutorService executorService;
@Bean(name = "infractionLevelChangedExecutor")
public TaskExecutor infractionLevelChangedExecutor() {
return executorService.setupExecutorFor("infractionLevelChangedListener");
}
@Bean(name = "warningCreatedExecutor")
public TaskExecutor warningCreatedExecutor() {
return executorService.setupExecutorFor("warningCreatedListener");
}
}

View File

@@ -48,12 +48,11 @@ public class WarnEntryConverter {
allFutures.add(warnedMemberFuture);
});
CompletableFuture<List<WarnEntry>> future = new CompletableFuture<>();
FutureUtils.toSingleFutureGeneric(allFutures).whenComplete((unused, throwable) -> {
try {
future.complete(self.loadFullWarnEntries(loadedWarnings));
} catch (Exception exception) {
future.completeExceptionally(exception);
}
FutureUtils.toSingleFutureGeneric(allFutures)
.whenComplete((unused, throwable) -> future.complete(self.loadFullWarnEntries(loadedWarnings)))
.exceptionally(throwable -> {
future.completeExceptionally(throwable);
return null;
});
return future;
}

View File

@@ -0,0 +1,48 @@
package dev.sheldan.abstracto.moderation.listener.manager;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.listener.InfractionLevelChangedListener;
import dev.sheldan.abstracto.moderation.model.listener.InfractionLevelChangedEventModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class InfractionLevelChangedListenerManager {
@Autowired(required = false)
private List<InfractionLevelChangedListener> listeners;
@Autowired
private ListenerService listenerService;
@Autowired
@Qualifier("infractionLevelChangedExecutor")
private TaskExecutor infractionLevelChangedExecutor;
public void sendInfractionLevelChangedEvent(Integer newLevel, Integer oldLevel, Long newPoints, Long oldPoints, ServerUser serverUser) {
if(listeners == null || listeners.isEmpty()) {
return;
}
InfractionLevelChangedEventModel model = createInfractionChangedModel(newLevel, oldLevel, oldPoints, newPoints, serverUser);
listeners.forEach(listener -> listenerService.executeFeatureAwareListener(listener, model, infractionLevelChangedExecutor));
}
private InfractionLevelChangedEventModel createInfractionChangedModel(Integer newLevel, Integer oldLevel, Long oldPoints, Long newPoints, ServerUser serverUser) {
return InfractionLevelChangedEventModel
.builder()
.newLevel(newLevel)
.oldLevel(oldLevel)
.oldPoints(oldPoints)
.newPoints(newPoints)
.userId(serverUser.getUserId())
.serverId(serverUser.getServerId())
.build();
}
}

View File

@@ -0,0 +1,53 @@
package dev.sheldan.abstracto.moderation.listener.manager;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.listener.WarningCreatedListener;
import dev.sheldan.abstracto.moderation.model.listener.WarningCreatedEventModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class WarningCreatedListenerManager {
@Autowired(required = false)
private List<WarningCreatedListener> listeners;
@Autowired
private ListenerService listenerService;
@Autowired
@Qualifier("warningCreatedExecutor")
private TaskExecutor warningCreatedExecutor;
public void sendWarningCreatedEvent(ServerSpecificId warnId, ServerUser warnedUser, ServerUser warningUser,
String reason, ServerChannelMessage warnCommand) {
if(listeners == null || listeners.isEmpty()) {
return;
}
WarningCreatedEventModel model = createWarningCreatedEventModel(warnId, warnedUser, warningUser, reason, warnCommand);
listeners.forEach(listener -> listenerService.executeFeatureAwareListener(listener, model, warningCreatedExecutor));
}
private WarningCreatedEventModel createWarningCreatedEventModel(ServerSpecificId warnId, ServerUser warnedUser,
ServerUser warningUser, String reason, ServerChannelMessage warnCommandMessage) {
return WarningCreatedEventModel
.builder()
.warningId(warnId.getId())
.serverId(warnId.getServerId())
.warningUserId(warningUser.getUserId())
.warnedUserId(warnedUser.getUserId())
.reason(reason)
.warningChannelId(warnCommandMessage.getChannelId())
.warningMessageId(warnCommandMessage.getMessageId())
.build();
}
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface InfractionRepository extends JpaRepository<Infraction, Long> {
List<Infraction> findByUserAndDecayedFalse(AUserInAServer aUserInAServer);
}

View File

@@ -23,6 +23,7 @@ public interface WarnRepository extends JpaRepository<Warning, ServerSpecificId>
Long countByWarnedUser(AUserInAServer aUserInAServer);
Long countByWarnedUserAndDecayedFalse(AUserInAServer aUserInAServer);
List<Warning> findByWarnedUserAndDecayedFalse(AUserInAServer aUserInAServer);
List<Warning> findByWarnedUser(AUserInAServer aUserInAServer);

View File

@@ -17,7 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@@ -51,7 +50,7 @@ public class BanServiceBean implements BanService {
private ChannelService channelService;
@Override
public CompletableFuture<Void> banMember(Member member, String reason, Member banningMember, Message message) {
public CompletableFuture<Void> banMemberWithNotification(Member member, String reason, Member banningMember, Integer deletionDays, Message message) {
BanLog banLog = BanLog
.builder()
.bannedUser(member.getUser())
@@ -59,18 +58,19 @@ public class BanServiceBean implements BanService {
.commandMessage(message)
.reason(reason)
.build();
CompletableFuture<Void> banFuture = banUser(member.getGuild(), member.getUser(), 0, reason);
CompletableFuture<Void> banFuture = banUser(member.getGuild(), member.getUser(), deletionDays, reason);
CompletableFuture<Void> messageFuture = sendBanLogMessage(banLog, member.getGuild().getIdLong(), BAN_LOG_TEMPLATE);
return CompletableFuture.allOf(banFuture, messageFuture);
}
@Override
public CompletableFuture<Void> banUser(User user, String reason, Member banningMember, Message message) {
public CompletableFuture<Void> banUserWithNotification(User user, String reason, Member banningMember, Integer deletionDays, Message message) {
BanLog banLog = BanLog
.builder()
.bannedUser(user)
.banningMember(banningMember)
.commandMessage(message)
.deletionDays(deletionDays)
.reason(reason)
.build();
Guild guild = banningMember.getGuild();
@@ -82,7 +82,7 @@ public class BanServiceBean implements BanService {
.thenAccept(message1 -> log.info("Notified about not being able to send ban notification in server {} and channel {} based on message {} from user {}."
, message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong(), message.getAuthor().getIdLong()));
}
CompletableFuture<Void> banFuture = banUser(guild, user, 0, reason);
CompletableFuture<Void> banFuture = banUser(guild, user, deletionDays, reason);
CompletableFuture<Void> messageFuture = sendBanLogMessage(banLog, guild.getIdLong(), BAN_LOG_TEMPLATE);
CompletableFuture.allOf(banFuture, messageFuture)
.thenAccept(unused1 -> returningFuture.complete(null))
@@ -90,6 +90,9 @@ public class BanServiceBean implements BanService {
returningFuture.completeExceptionally(throwable1);
return null;
});
}).exceptionally(throwable -> {
returningFuture.completeExceptionally(throwable);
return null;
});
return returningFuture;
}
@@ -105,7 +108,7 @@ public class BanServiceBean implements BanService {
}
@Override
public CompletableFuture<Void> unBanUser(User user, Member unBanningMember) {
public CompletableFuture<Void> unBanUserWithNotification(User user, Member unBanningMember) {
Guild guild = unBanningMember.getGuild();
UnBanLog banLog = UnBanLog
.builder()
@@ -136,20 +139,14 @@ public class BanServiceBean implements BanService {
}
public CompletableFuture<Void> sendBanLogMessage(BanLog banLog, Long guildId, String template) {
CompletableFuture<Void> completableFuture;
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
log.debug("Sending ban log message in guild {}.", guildId);
List<CompletableFuture<Message>> notificationFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId);
completableFuture = FutureUtils.toSingleFutureGeneric(notificationFutures);
return completableFuture;
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId));
}
public CompletableFuture<Void> sendUnBanLogMessage(UnBanLog banLog, Long guildId, String template) {
CompletableFuture<Void> completableFuture;
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
log.debug("Sending unban log message in guild {}.", guildId);
List<CompletableFuture<Message>> notificationFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.UN_BAN_LOG, guildId);
completableFuture = FutureUtils.toSingleFutureGeneric(notificationFutures);
return completableFuture;
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.UN_BAN_LOG, guildId));
}
}

View File

@@ -0,0 +1,144 @@
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.service.ConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
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.InfractionFeatureConfig;
import dev.sheldan.abstracto.moderation.config.posttarget.InfractionPostTarget;
import dev.sheldan.abstracto.moderation.listener.manager.InfractionLevelChangedListenerManager;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.template.InfractionLevelChangeModel;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
public class InfractionServiceBean implements InfractionService {
@Autowired
private InfractionManagementService infractionManagementService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ConfigService configService;
@Autowired
private ConfigManagementService configManagementService;
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private InfractionServiceBean self;
@Autowired
private InfractionLevelChangedListenerManager infractionLevelChangedListenerManager;
private static final String INFRACTION_NOTIFICATION_TEMPLATE_KEY = "infraction_level_notification";
@Override
public void decayInfraction(Infraction infraction) {
log.info("Decaying infraction {}", infraction.getId());
infraction.setDecayed(true);
infraction.setDecayedDate(Instant.now());
}
@Override
public Long getActiveInfractionPointsForUser(AUserInAServer aUserInAServer) {
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();
}
@Override
public CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer aUserInAServer, Long points) {
Infraction createdInfraction = infractionManagementService.createInfraction(aUserInAServer, points);
Long infractionId = createdInfraction.getId();
return createInfractionNotification(aUserInAServer, points)
.thenApply(aBoolean -> self.reloadInfraction(infractionId));
}
@Override
public CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points) {
Long serverId = aUserInAServer.getServerReference().getId();
Long currentPoints = getActiveInfractionPointsForUser(aUserInAServer);
Long newPoints = currentPoints + points;
Pair<Integer, Integer> levelChange = infractionLevelChanged(serverId, newPoints, currentPoints);
Integer oldLevel = levelChange.getFirst();
Integer newLevel = levelChange.getSecond();
if(!oldLevel.equals(newLevel)) {
InfractionLevelChangeModel model = InfractionLevelChangeModel
.builder()
.member(MemberDisplay.fromAUserInAServer(aUserInAServer))
.newLevel(newLevel)
.oldLevel(oldLevel)
.oldPoints(currentPoints)
.newPoints(newPoints)
.build();
infractionLevelChangedListenerManager.sendInfractionLevelChangedEvent(newLevel, oldLevel, newPoints, currentPoints, ServerUser.fromAUserInAServer(aUserInAServer));
MessageToSend messageToSend = templateService.renderEmbedTemplate(INFRACTION_NOTIFICATION_TEMPLATE_KEY, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, InfractionPostTarget.INFRACTION_NOTIFICATION, serverId));
} else {
return CompletableFuture.completedFuture(null);
}
}
@Transactional
public Infraction reloadInfraction(Long infractionId) {
return infractionManagementService.loadInfraction(infractionId);
}
private Pair<Integer, Integer> infractionLevelChanged(Long serverId, Long newPoints, Long oldPoints) {
List<Long> levelConfig = loadInfractionConfig(serverId);
Integer newLevel = getInfractionLevel(newPoints, levelConfig);
Integer oldLevel = getInfractionLevel(oldPoints, levelConfig);
return Pair.of(oldLevel, newLevel);
}
private List<Long> loadInfractionConfig(Long serverId) {
Long levelAmount = configService.getLongValueOrConfigDefault(InfractionFeatureConfig.INFRACTION_LEVELS, serverId);
List<Long> levelConfig = new ArrayList<>();
for (long i = 1; i <= levelAmount; i++) {
String levelKey = InfractionFeatureConfig.INFRACTION_LEVEL_PREFIX + i;
if(configManagementService.configExists(serverId, levelKey)) {
levelConfig.add(configService.getLongValue(levelKey, serverId));
}
}
return levelConfig;
}
private Integer getInfractionLevel(Long points, List<Long> levelConfig) {
for (int i = 0; i < levelConfig.size(); i++) {
if(points >= levelConfig.get(i)) {
return i;
}
}
return 0;
}
}

View File

@@ -40,10 +40,8 @@ public class KickServiceBean implements KickService {
}
private CompletableFuture<Void> sendKickLog(KickLogModel kickLogModel) {
CompletableFuture<Void> completableFuture;
MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, kickLogModel.getGuild().getIdLong());
log.debug("Sending kick log message in guild {}.", kickLogModel.getGuild().getIdLong());
completableFuture = FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong()));
return completableFuture;
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong()));
}
}

View File

@@ -163,6 +163,9 @@ public class MuteServiceBean implements MuteService {
channelService.sendTextToChannel(throwable.getMessage(), feedBackChannel).whenComplete((exceptionMessage, innerThrowable) -> {
notificationFuture.complete(null);
log.info("Successfully notified user {} in server {} about mute.", memberBeingMuted.getId(), memberBeingMuted.getGuild().getId());
}).exceptionally(throwable1 -> {
notificationFuture.completeExceptionally(throwable1);
return null;
});
return null;
});
@@ -241,21 +244,15 @@ public class MuteServiceBean implements MuteService {
}
private CompletableFuture<Void> sendMuteLog(MuteContext muteLogModel, AServer server) {
CompletableFuture<Void> completableFuture;
log.debug("Sending mute log to the mute post target.");
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, muteLogModel, server.getId());
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getContext().getServerId());
completableFuture = FutureUtils.toSingleFutureGeneric(completableFutures);
return completableFuture;
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getContext().getServerId()));
}
private CompletableFuture<Void> sendUnMuteLogMessage(UnMuteLog muteLogModel, AServer server) {
CompletableFuture<Void> completableFuture;
log.debug("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId());
MessageToSend message = templateService.renderEmbedTemplate(UN_MUTE_LOG_TEMPLATE, muteLogModel, server.getId());
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId());
completableFuture = FutureUtils.toSingleFutureGeneric(completableFutures);
return completableFuture;
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId()));
}
@Override
@@ -310,13 +307,16 @@ public class MuteServiceBean implements MuteService {
finalFuture.complete(null)
).exceptionally(throwable1 -> {
log.error("Unmute log failed to send for mute {} in server {}.", muteId, serverId, throwable1);
finalFuture.complete(null);
finalFuture.completeExceptionally(null);
return null;
});
} else {
finalFuture.complete(null);
}
return null;
}).exceptionally(throwable -> {
finalFuture.completeExceptionally(throwable);
return null;
});
return finalFuture;

View File

@@ -155,14 +155,18 @@ public class PurgeServiceBean implements PurgeService {
return aVoid -> {
if (amountToDelete >= 1) {
log.debug("Still more than 1 message to delete. Continuing.");
purgeMessages(amountToDelete, channel, earliestMessage.getIdLong(), purgedMember, totalCount, currentCount, currentStatusMessageId).whenComplete((avoid, throwable) -> {
purgeMessages(amountToDelete, channel, earliestMessage.getIdLong(), purgedMember, totalCount, currentCount, currentStatusMessageId)
.whenComplete((avoid, throwable) -> {
if (throwable != null) {
deletionFuture.completeExceptionally(throwable);
} else {
deletionFuture.complete(null);
}
}
);
).exceptionally(throwable -> {
deletionFuture.completeExceptionally(throwable);
return null;
});
} else {
log.debug("Completed purging of {} messages.", totalCount);
// Todo Move to message service

View File

@@ -100,9 +100,13 @@ public class ReactionReportServiceBean implements ReactionReportService {
@Transactional
public void createReactionReportInDb(CachedMessage cachedMessage, Message reportMessage, ServerUser reporter) {
log.info("Creation reaction report in message {} about message {} in database.", reportMessage.getIdLong(), cachedMessage.getMessageId());
reactionReportManagementService.createReactionReport(cachedMessage, reportMessage);
updateModerationUserReportCooldown(reporter);
if(reportMessage == null) {
log.info("Creation reaction report about message {} was not sent - post target might be disabled in server {}.", cachedMessage.getMessageId(), cachedMessage.getServerId());
} else {
log.info("Creation reaction report in message {} about message {} in database.", reportMessage.getIdLong(), cachedMessage.getMessageId());
reactionReportManagementService.createReactionReport(cachedMessage, reportMessage);
updateModerationUserReportCooldown(reporter);
}
}
@Transactional

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.FutureMemberPair;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
@@ -12,16 +13,20 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.WarningDecayFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.WarningFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.mode.WarnDecayMode;
import dev.sheldan.abstracto.moderation.config.feature.mode.WarningMode;
import dev.sheldan.abstracto.moderation.config.posttarget.WarnDecayPostTarget;
import dev.sheldan.abstracto.moderation.config.posttarget.WarningPostTarget;
import dev.sheldan.abstracto.moderation.listener.manager.WarningCreatedListenerManager;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.Warning;
import dev.sheldan.abstracto.moderation.model.template.command.WarnContext;
import dev.sheldan.abstracto.moderation.model.template.command.WarnNotification;
import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayLogModel;
import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayWarning;
import dev.sheldan.abstracto.moderation.model.template.listener.WarnDecayMemberNotificationModel;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import dev.sheldan.abstracto.moderation.service.management.WarnManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
@@ -85,6 +90,18 @@ public class WarnServiceBean implements WarnService {
@Autowired
private WarnServiceBean self;
@Autowired
private InfractionService infractionService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private InfractionManagementService infractionManagementService;
@Autowired
private WarningCreatedListenerManager warningCreatedListenerManager;
public static final String WARN_LOG_TEMPLATE = "warn_log";
public static final String WARN_NOTIFICATION_TEMPLATE = "warn_notification";
public static final String WARNINGS_COUNTER_KEY = "WARNINGS";
@@ -100,22 +117,34 @@ public class WarnServiceBean implements WarnService {
Member warningMember = context.getMember();
Guild guild = warnedMember.getGuild();
log.info("User {} is warning {} in server {}", warnedMember.getId(), warningMember.getId(), guild.getIdLong());
WarnNotification warnNotification = WarnNotification.builder().reason(context.getReason()).warnId(warningId).serverName(guild.getName()).build();
WarnNotification warnNotification = WarnNotification
.builder()
.reason(context.getReason())
.warnId(warningId)
.serverName(guild.getName())
.build();
Long serverId = server.getId();
String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, server.getId());
List<CompletableFuture<Message>> futures = new ArrayList<>();
List<CompletableFuture> futures = new ArrayList<>();
futures.add(messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage));
log.debug("Logging warning for server {}.", server.getId());
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) {
Long infractionPoints = configService.getLongValueOrConfigDefault(WarningFeatureConfig.WARN_INFRACTION_POINTS, serverId);
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(warnedMember);
warnedUser.setServerReference(server);
futures.add(infractionService.createInfractionWithNotification(warnedUser, infractionPoints)
.thenAccept(infraction -> context.setInfractionId(infraction.getId())));
}
MessageToSend message = templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, context, server.getId());
futures.addAll(postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, context.getGuild().getIdLong()));
return FutureUtils.toSingleFutureGeneric(futures);
return FutureUtils.toSingleFuture(futures);
}
@Override
public CompletableFuture<Void> warnUserWithLog(WarnContext context) {
return notifyAndLogFullUserWarning(context).thenAccept(aVoid ->
self.persistWarning(context)
);
return notifyAndLogFullUserWarning(context)
.thenAccept(aVoid -> self.persistWarning(context));
}
@Transactional
@@ -124,8 +153,15 @@ public class WarnServiceBean implements WarnService {
context.getWarnId(), context.getGuild().getId(), context.getWarnedMember().getId(), context.getMember().getId());
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(context.getWarnedMember());
AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(context.getMember());
warnManagementService.createWarning(warnedUser, warningUser, context.getReason(), context.getWarnId());
Warning createdWarning = warnManagementService.createWarning(warnedUser, warningUser, context.getReason(), context.getWarnId());
if(context.getInfractionId() != null) {
Infraction infraction = infractionManagementService.loadInfraction(context.getInfractionId());
createdWarning.setInfraction(infraction);
}
ServerUser warnedServerUser = ServerUser.fromAUserInAServer(warnedUser);
ServerUser warningServerUser = ServerUser.fromAUserInAServer(warnedUser);
ServerChannelMessage commandMessage = ServerChannelMessage.fromMessage(context.getMessage());
warningCreatedListenerManager.sendWarningCreatedEvent(createdWarning.getWarnId(), warnedServerUser, warningServerUser, context.getReason(), commandMessage);
}
@Override
@@ -188,16 +224,20 @@ public class WarnServiceBean implements WarnService {
MessageToSend messageToSend = templateService.renderEmbedTemplate(WARN_DECAY_NOTIFICATION_TEMPLATE_KEY, model, serverId);
log.info("Notifying user {} in server {} about decayed warning {}.", userId, serverId, warningId);
notificationFutures.add(messageService.sendMessageToSendToUser(memberToSendTo.getUser(), messageToSend).exceptionally(throwable -> {
log.error("Failed to send warn decay message to user {} in server {} to notify about decay warning {}.", userId, server, warningId);
log.error("Failed to send warn decay message to user {} in server {} to notify about decay warning {}.", userId, server.getId(), warningId, throwable);
return null;
}));
} else {
log.warn("Could not find user {} in server {}. Not notifying about decayed warning {}.", userId, serverId, warningId);
}
});
CompletableFuture<Void> future = new CompletableFuture();
CompletableFuture<Void> future = new CompletableFuture<>();
FutureUtils.toSingleFutureGeneric(notificationFutures)
.whenComplete((unused, throwable) -> future.complete(null));
.whenComplete((unused, throwable) -> future.complete(null))
.exceptionally(throwable -> {
future.completeExceptionally(throwable);
return null;
});
return future;
}
@@ -231,10 +271,13 @@ public class WarnServiceBean implements WarnService {
}
@Override
public void decayWarning(Warning warning, Instant now) {
log.debug("Decaying warning {} in server {} with date {}.", warning.getWarnId().getId(), warning.getWarnId().getServerId(), now);
warning.setDecayDate(now);
public void decayWarning(Warning warning, Instant decayDate) {
log.debug("Decaying warning {} in server {} with date {}.", warning.getWarnId().getId(), warning.getWarnId().getServerId(), decayDate);
warning.setDecayDate(decayDate);
warning.setDecayed(true);
if(warning.getInfraction() != null) {
infractionService.decayInfraction(warning.getInfraction());
}
}
private CompletableFuture<Void> logDecayedWarnings(AServer server, List<Warning> warningsToDecay) {
@@ -265,6 +308,9 @@ public class WarnServiceBean implements WarnService {
return null;
});
return null;
}).exceptionally(throwable -> {
sendingFuture.completeExceptionally(throwable);
return null;
});
return sendingFuture;
@@ -300,8 +346,7 @@ public class WarnServiceBean implements WarnService {
.warnings(warnDecayWarnings)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(WARN_DECAY_LOG_TEMPLATE_KEY, warnDecayLogModel, serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, WarnDecayPostTarget.DECAY_LOG, server.getId());
return FutureUtils.toSingleFutureGeneric(messageFutures);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, WarnDecayPostTarget.DECAY_LOG, server.getId()));
}
@Override

View File

@@ -0,0 +1,38 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.repository.InfractionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class InfractionManagementServiceBean implements InfractionManagementService{
@Autowired
private InfractionRepository infractionRepository;
@Override
public Infraction createInfraction(AUserInAServer aUserInAServer, Long points) {
Infraction infraction = Infraction
.builder()
.user(aUserInAServer)
.server(aUserInAServer.getServerReference())
.points(points)
.build();
return infractionRepository.save(infraction);
}
@Override
public List<Infraction> getActiveInfractionsForUser(AUserInAServer aUserInAServer) {
return infractionRepository.findByUserAndDecayedFalse(aUserInAServer);
}
@Override
public Infraction loadInfraction(Long infraction) {
return infractionRepository.getOne(infraction);
}
}

View File

@@ -70,10 +70,15 @@ public class WarnManagementServiceBean implements WarnManagementService {
}
@Override
public Long getActiveWarnsForUser(AUserInAServer aUserInAServer) {
public Long getActiveWarnCountForUser(AUserInAServer aUserInAServer) {
return warnRepository.countByWarnedUserAndDecayedFalse(aUserInAServer);
}
@Override
public List<Warning> getActiveWarnsForUser(AUserInAServer aUserInAServer) {
return warnRepository.findByWarnedUserAndDecayedFalse(aUserInAServer);
}
@Override
public Optional<Warning> findByIdOptional(Long id, Long serverId) {
return warnRepository.findByWarnId_IdAndWarnId_ServerId(id, serverId);

View File

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

View File

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

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="infraction_feature-insertion">
<insert tableName="feature">
<column name="key" value="infractions"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,46 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="infraction-table">
<createTable tableName="infraction">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="pk_infraction"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="points" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="decayed_date" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="decayed" type="BOOLEAN"/>
<column name="infraction_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="infraction" constraintName="fk_infraction_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="infraction_user_in_server_id" baseTableName="infraction" constraintName="fk_infraction_user_in_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS infraction_update_trigger ON infraction;
CREATE TRIGGER infraction_update_trigger BEFORE UPDATE ON infraction FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS infraction_insert_trigger ON infraction;
CREATE TRIGGER infraction_insert_trigger BEFORE INSERT ON infraction FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -0,0 +1,17 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="warning-infraction_id">
<addColumn tableName="warning">
<column name="infraction_id" type="BIGINT" />
</addColumn>
<addForeignKeyConstraint baseColumnNames="infraction_id" baseTableName="warning" constraintName="fk_warning_infraction"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="infraction" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -6,10 +6,10 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="warn-decay-job-update_schedule">
<update tableName="scheduler_job">
<column name="cron_expression" value="0 0 0 * * ?"/>
<where>name='warnDecayJob'</where>
<changeSet author="Sheldan" id="command-rename_soft_ban">
<update tableName="command">
<column name="name" value="banDelete"/>
<where>name='softBan'</where>
</update>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -11,4 +11,5 @@
<include file="1.2.15/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.16/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.4/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.9/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -7,6 +7,9 @@ abstracto.systemConfigs.reactionReportCooldownSeconds.longValue=300
abstracto.featureFlags.moderation.featureName=moderation
abstracto.featureFlags.moderation.enabled=false
abstracto.featureFlags.infractions.featureName=infractions
abstracto.featureFlags.infractions.enabled=false
abstracto.featureFlags.reportReactions.featureName=reportReactions
abstracto.featureFlags.reportReactions.enabled=false
@@ -42,6 +45,31 @@ 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.systemConfigs.infractionLvl1.name=infractionLvl1
abstracto.systemConfigs.infractionLvl1.longValue=10
abstracto.systemConfigs.infractionLvl2.name=infractionLvl2
abstracto.systemConfigs.infractionLvl2.longValue=20
abstracto.systemConfigs.infractionLvl3.name=infractionLvl3
abstracto.systemConfigs.infractionLvl3.longValue=30
abstracto.systemConfigs.infractionLvl4.name=infractionLvl4
abstracto.systemConfigs.infractionLvl4.longValue=40
abstracto.systemConfigs.infractionLvl5.name=infractionLvl5
abstracto.systemConfigs.infractionLvl5.longValue=50
abstracto.systemConfigs.infractionLevels.name=infractionLevels
abstracto.systemConfigs.infractionLevels.longValue=5
abstracto.systemConfigs.warnInfractionPoints.name=warnInfractionPoints
abstracto.systemConfigs.warnInfractionPoints.longValue=0
abstracto.featureModes.automaticWarnDecayLogging.featureName=warnDecay
abstracto.featureModes.automaticWarnDecayLogging.mode=automaticWarnDecayLogging
abstracto.featureModes.automaticWarnDecayLogging.enabled=true

View File

@@ -5,7 +5,6 @@ import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.test.command.CommandConfigValidator;
import dev.sheldan.abstracto.core.test.command.CommandTestUtilities;
import dev.sheldan.abstracto.moderation.service.BanService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
@@ -41,7 +40,7 @@ public class BanTest {
public void testBanWithReason() {
String customReason = "reason2";
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(bannedMember, customReason));
when(banService.banUser(eq(bannedMember), eq(customReason), banLogModelCaptor.capture(), any(Message.class))).thenReturn(CompletableFuture.completedFuture(null));
when(banService.banUserWithNotification(eq(bannedMember), eq(customReason), banLogModelCaptor.capture(), eq(0), any(Message.class))).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
Member banningMember = banLogModelCaptor.getValue();
Assert.assertEquals(parameters.getAuthor(), banningMember);

View File

@@ -49,7 +49,7 @@ public class MyWarningsTest {
Long activeWarnCount = 8L;
AUserInAServer aUserInAServer = Mockito.mock(AUserInAServer.class);
when(userInServerManagementService.loadOrCreateUser(noParameter.getAuthor())).thenReturn(aUserInAServer);
when(warnManagementService.getActiveWarnsForUser(aUserInAServer)).thenReturn(activeWarnCount);
when(warnManagementService.getActiveWarnCountForUser(aUserInAServer)).thenReturn(activeWarnCount);
Long totalWarnCount = 10L;
when(warnManagementService.getTotalWarnsForUser(aUserInAServer)).thenReturn(totalWarnCount);
CommandResult result = testUnit.execute(noParameter);

View File

@@ -52,7 +52,7 @@ public class BanServiceBeanTest {
when(mockedGuild.ban(user, 0, REASON)).thenReturn(mockedAction);
MessageToSend mockedMessage = Mockito.mock(MessageToSend.class);
when(templateService.renderEmbedTemplate(eq(BanServiceBean.BAN_LOG_TEMPLATE), any(), eq(SERVER_ID))).thenReturn(mockedMessage);
testUnit.banMember(memberToBan, REASON, banningMember, message);
testUnit.banMemberWithNotification(memberToBan, REASON, banningMember, 0, message);
verify(mockedGuild, times(1)).ban(user, 0, REASON);
verify(postTargetService, times(1)).sendEmbedInPostTarget(mockedMessage, ModerationPostTarget.BAN_LOG, SERVER_ID);
}

View File

@@ -29,6 +29,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.Instant;
import java.util.Arrays;
@@ -134,6 +135,9 @@ public class WarnServiceBeanTest {
@Mock
private DefaultConfigManagementService defaultConfigManagementService;
@Mock
private FeatureFlagService featureFlagService;
private static final String NOTIFICATION_TEXT = "text";
private static final String GUILD_NAME = "guild";
private static final Long SERVER_ID = 4L;
@@ -204,6 +208,7 @@ public class WarnServiceBeanTest {
public void testWarnFullUser() {
setupWarnContext();
setupMocksForWarning();
when(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, SERVER_ID)).thenReturn(false);
CompletableFuture<Void> future = testUnit.notifyAndLogFullUserWarning(context);
future.join();
Assert.assertFalse(future.isCompletedExceptionally());

View File

@@ -97,7 +97,7 @@ public class WarnManagementServiceBeanTest {
public void testActiveWarnCountOfUser() {
Long count = 5L;
when(warnRepository.countByWarnedUserAndDecayedFalse(warnedUser)).thenReturn(count);
Long activeWarnsForUserCount = testUnit.getActiveWarnsForUser(warnedUser);
Long activeWarnsForUserCount = testUnit.getActiveWarnCountForUser(warnedUser);
Assert.assertEquals(count, activeWarnsForUserCount);
}

View File

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

View File

@@ -0,0 +1,32 @@
package dev.sheldan.abstracto.moderation.config.feature;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.moderation.config.posttarget.InfractionPostTarget;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class InfractionFeatureConfig implements FeatureConfig {
public static final String INFRACTION_LEVELS = "infractionLevels";
public static final String INFRACTION_LEVEL_PREFIX = "infractionLevel";
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.INFRACTIONS;
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(INFRACTION_LEVELS);
}
@Override
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(InfractionPostTarget.INFRACTION_NOTIFICATION);
}
}

View File

@@ -11,7 +11,8 @@ public enum ModerationFeatureDefinition implements FeatureDefinition {
AUTOMATIC_WARN_DECAY("warnDecay"),
USER_NOTES("userNotes"),
INVITE_FILTER("inviteFilter"),
REPORT_REACTIONS("reportReactions")
REPORT_REACTIONS("reportReactions"),
INFRACTIONS("infractions")
;
private final String key;

View File

@@ -15,6 +15,8 @@ import java.util.List;
@Component
public class WarningFeatureConfig implements FeatureConfig {
public static final String WARN_INFRACTION_POINTS = "warnInfractionPoints";
@Autowired
private WarningDecayFeatureConfig warningDecayFeatureConfig;

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.moderation.config.posttarget;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import lombok.Getter;
@Getter
public enum InfractionPostTarget implements PostTargetEnum {
INFRACTION_NOTIFICATION("infractionNotification");
private String key;
InfractionPostTarget(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.moderation.model.listener.InfractionLevelChangedEventModel;
public interface InfractionLevelChangedListener extends FeatureAwareListener<InfractionLevelChangedEventModel, DefaultListenerResult> {
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.moderation.model.listener.WarningCreatedEventModel;
public interface WarningCreatedListener extends FeatureAwareListener<WarningCreatedEventModel, DefaultListenerResult> {
}

View File

@@ -0,0 +1,47 @@
package dev.sheldan.abstracto.moderation.model.database;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Entity
@Table(name="infraction")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class Infraction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "infraction_user_in_server_id", nullable = false)
private AUserInAServer user;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@Column(name = "points")
private Long points;
@Column(name = "decayed")
private Boolean decayed;
@Column(name = "decayed_date")
private Instant decayedDate;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
}

View File

@@ -28,7 +28,7 @@ public class Warning implements Serializable {
@Setter
private ServerSpecificId warnId;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("serverId")
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@@ -90,4 +90,10 @@ public class Warning implements Serializable {
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
@Getter
@Setter
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "infraction_id")
private Infraction infraction;
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.moderation.model.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class InfractionLevelChangedEventModel implements FeatureAwareListenerModel {
private Integer oldLevel;
private Long oldPoints;
private Integer newLevel;
private Long newPoints;
private Long userId;
private Long serverId;
@Override
public Long getServerId() {
return serverId;
}
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.moderation.model.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class WarningCreatedEventModel implements FeatureAwareListenerModel {
private Long warningId;
private Long warnedUserId;
private Long serverId;
private Long warningUserId;
private Long warningChannelId;
private Long warningMessageId;
private String reason;
@Override
public Long getServerId() {
return serverId;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.moderation.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class InfractionLevelChangeModel {
private Long newPoints;
private Long oldPoints;
private Integer newLevel;
private Integer oldLevel;
private MemberDisplay member;
}

View File

@@ -28,4 +28,5 @@ public class BanLog {
*/
private User bannedUser;
private Message commandMessage;
private Integer deletionDays;
}

View File

@@ -26,4 +26,5 @@ public class WarnContext extends SlimUserInitiatedServerContext {
* The persisted {@link Warning} object from the database containing the information about the warning
*/
private Long warnId;
private Long infractionId;
}

View File

@@ -10,9 +10,9 @@ import java.util.concurrent.CompletableFuture;
public interface BanService {
String BAN_EFFECT_KEY = "ban";
CompletableFuture<Void> banMember(Member member, String reason, Member banningMember, Message message);
CompletableFuture<Void> banUser(User user, String reason, Member banningMember, Message message);
CompletableFuture<Void> unBanUser(User user, Member unBanningUser);
CompletableFuture<Void> banMemberWithNotification(Member member, String reason, Member banningMember, Integer deletionDays, Message message);
CompletableFuture<Void> banUserWithNotification(User user, String reason, Member banningMember, Integer deletionDays, Message message);
CompletableFuture<Void> unBanUserWithNotification(User user, Member unBanningUser);
CompletableFuture<Void> banUser(Guild guild, User user, Integer deletionDays, String reason);
CompletableFuture<Void> unbanUser(Guild guild, User user);
CompletableFuture<Void> softBanUser(Guild guild, User user, Duration delDays);

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