mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-03-27 14:23:56 +00:00
[AB-xxx] refactoring handling of bans and mutes: commands actively log, the reason for this is that the command is the only place who actually knows how executed the command. the event itself only sees the bot performing the action
adding event based logging of kicks
This commit is contained in:
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -73,11 +74,15 @@ public class Mute extends AbstractConditionableCommand {
|
||||
Duration duration = (Duration) parameters.get(1);
|
||||
String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
|
||||
String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason;
|
||||
Instant oldTimeoutDate = null;
|
||||
if(member.getTimeOutEnd() != null && member.isTimedOut()) {
|
||||
oldTimeoutDate = member.getTimeOutEnd().toInstant();
|
||||
}
|
||||
ServerUser userToMute = ServerUser.fromMember(member);
|
||||
ServerUser mutingUser = ServerUser.fromMember(commandContext.getAuthor());
|
||||
Long serverId = commandContext.getGuild().getIdLong();
|
||||
ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(commandContext.getMessage());
|
||||
return muteService.muteMemberWithLog(userToMute, mutingUser, reason, duration, commandContext.getGuild(), serverChannelMessage)
|
||||
return muteService.muteMemberWithLog(userToMute, mutingUser, reason, duration, commandContext.getGuild(), serverChannelMessage, oldTimeoutDate)
|
||||
.thenCompose(muteResult -> {
|
||||
if(muteResult == NOTIFICATION_FAILED) {
|
||||
MessageToSend errorNotification = templateService.renderEmbedTemplate(MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY, new Object(), serverId);
|
||||
|
||||
@@ -50,7 +50,7 @@ public class UnBan extends AbstractConditionableCommand {
|
||||
String userIdStr = (String) parameters.get(0);
|
||||
Long userId = Long.parseLong(userIdStr);
|
||||
return userService.retrieveUserForId(userId)
|
||||
.thenCompose(user -> banService.unbanUser(commandContext.getGuild(), userId))
|
||||
.thenCompose(user -> banService.unbanUser(commandContext.getGuild(), user, commandContext.getAuthor()))
|
||||
.thenApply(aVoid -> CommandResult.fromSuccess());
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class UnBan extends AbstractConditionableCommand {
|
||||
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, String.class);
|
||||
Long userId = Long.parseLong(userIdStr);
|
||||
return userService.retrieveUserForId(userId)
|
||||
.thenCompose(user -> banService.unbanUser(event.getGuild(), userId))
|
||||
.thenCompose(user -> banService.unbanUser(event.getGuild(), user, event.getMember()))
|
||||
.thenCompose(unused -> interactionService.replyEmbed(UN_BAN_RESPONSE, event))
|
||||
.thenApply(interactionHook -> CommandResult.fromSuccess());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
|
||||
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
|
||||
import dev.sheldan.abstracto.core.interaction.InteractionService;
|
||||
import dev.sheldan.abstracto.core.models.ServerUser;
|
||||
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
|
||||
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
|
||||
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
@@ -42,9 +41,6 @@ public class UnMute extends AbstractConditionableCommand {
|
||||
@Autowired
|
||||
private InteractionService interactionService;
|
||||
|
||||
@Autowired
|
||||
private UserInServerManagementService userInServerManagementService;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
|
||||
List<Object> parameters = commandContext.getParameters().getParameters();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package dev.sheldan.abstracto.moderation.listener;
|
||||
|
||||
import dev.sheldan.abstracto.core.config.FeatureDefinition;
|
||||
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberKickedListener;
|
||||
import dev.sheldan.abstracto.core.models.listener.MemberKickedModel;
|
||||
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
|
||||
import dev.sheldan.abstracto.moderation.service.KickService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MemberKickedListener implements AsyncMemberKickedListener {
|
||||
|
||||
@Autowired
|
||||
private KickService kickService;
|
||||
|
||||
@Override
|
||||
public FeatureDefinition getFeature() {
|
||||
return ModerationFeatureDefinition.MODERATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultListenerResult execute(MemberKickedModel eventModel) {
|
||||
log.info("Notifying about kicked of user {} in guild {}.", eventModel.getKickedServerUser().getUserId(), eventModel.getServerId());
|
||||
if(eventModel.getKickingServerUser().getUserId() == eventModel.getGuild().getJDA().getSelfUser().getIdLong()) {
|
||||
log.info("Skipping logging kicked event about user {} in guild {}, because it was done by us.", eventModel.getKickedServerUser().getUserId(), eventModel.getGuild().getIdLong());
|
||||
return DefaultListenerResult.IGNORED;
|
||||
}
|
||||
KickLogModel model = KickLogModel
|
||||
.builder()
|
||||
.kickedUser(eventModel.getKickedUser() != null ? UserDisplay.fromUser(eventModel.getKickedUser()) : UserDisplay.fromId(eventModel.getKickedServerUser().getUserId()))
|
||||
.kickingUser(eventModel.getKickingUser() != null ? UserDisplay.fromUser(eventModel.getKickingUser()) : UserDisplay.fromServerUser(eventModel.getKickingServerUser()))
|
||||
.reason(eventModel.getReason())
|
||||
.build();
|
||||
kickService.sendKicklog(model, eventModel.getServerId());
|
||||
return DefaultListenerResult.PROCESSED;
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,10 @@ import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberTimeoutUpdatedListener;
|
||||
import dev.sheldan.abstracto.core.models.listener.MemberTimeoutUpdatedModel;
|
||||
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
|
||||
import dev.sheldan.abstracto.core.service.PostTargetService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
|
||||
import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel;
|
||||
import dev.sheldan.abstracto.moderation.service.MuteServiceBean;
|
||||
import dev.sheldan.abstracto.moderation.model.template.command.MuteLogModel;
|
||||
import dev.sheldan.abstracto.moderation.service.MuteService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -26,30 +20,31 @@ import java.time.Instant;
|
||||
public class MemberTimeoutLoggerListener implements AsyncMemberTimeoutUpdatedListener {
|
||||
|
||||
@Autowired
|
||||
private TemplateService templateService;
|
||||
|
||||
@Autowired
|
||||
private PostTargetService postTargetService;
|
||||
private MuteService muteService;
|
||||
|
||||
@Override
|
||||
public DefaultListenerResult execute(MemberTimeoutUpdatedModel model) {
|
||||
Guild guild = model.getGuild();
|
||||
MemberDisplay memberDisplay = model.getMember() != null ? MemberDisplay.fromMember(model.getMember()) : MemberDisplay.fromServerUser(model.getTimeoutUser());
|
||||
log.info("Notifying about timeout of user {} in guild {}.", model.getMutedUser().getUserId(), model.getServerId());
|
||||
if(model.getMutingUser().getUserId() == model.getGuild().getSelfMember().getIdLong()) {
|
||||
log.info("Skipping logging timeout event about user {} in guild {}, because it was done by us.", model.getMutedUser().getUserId(), model.getGuild().getIdLong());
|
||||
return DefaultListenerResult.IGNORED;
|
||||
}
|
||||
MemberDisplay mutedMemberDisplay = model.getMutedMember() != null ? MemberDisplay.fromMember(model.getMutedMember()) : MemberDisplay.fromServerUser(model.getMutedUser());
|
||||
MemberDisplay mutingMemberDisplay = model.getMutingMember() != null ? MemberDisplay.fromMember(model.getMutingMember()) : MemberDisplay.fromServerUser(model.getMutingUser());
|
||||
Duration duration = null;
|
||||
if(model.getNewTimeout() != null) {
|
||||
duration = Duration.between(Instant.now(), model.getNewTimeout());
|
||||
}
|
||||
MuteListenerModel muteLogModel = MuteListenerModel
|
||||
MuteLogModel muteLogModel = MuteLogModel
|
||||
.builder()
|
||||
.muteTargetDate(model.getNewTimeout() != null ? model.getNewTimeout().toInstant() : null)
|
||||
.oldMuteTargetDate(model.getOldTimeout() != null ? model.getOldTimeout().toInstant() : null)
|
||||
.mutingUser(MemberDisplay.fromIds(model.getServerId(), model.getResponsibleUserId()))
|
||||
.mutedUser(memberDisplay)
|
||||
.mutingMember(mutingMemberDisplay)
|
||||
.mutedMember(mutedMemberDisplay)
|
||||
.duration(duration)
|
||||
.reason(model.getReason())
|
||||
.build();
|
||||
MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, muteLogModel, guild.getIdLong());
|
||||
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, model.getServerId()));
|
||||
muteService.sendMuteLogMessage(muteLogModel, model.getServerId());
|
||||
return DefaultListenerResult.PROCESSED;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionLis
|
||||
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult;
|
||||
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
|
||||
import dev.sheldan.abstracto.core.models.ServerUser;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||
import dev.sheldan.abstracto.core.utils.ParseUtils;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
@@ -38,9 +37,6 @@ public class MuteModerationActionModalListener implements ModalInteractionListen
|
||||
@Autowired
|
||||
private InteractionService interactionService;
|
||||
|
||||
@Autowired
|
||||
private TemplateService templateService;
|
||||
|
||||
@Autowired
|
||||
private InteractionExceptionService interactionExceptionService;
|
||||
|
||||
|
||||
@@ -3,15 +3,11 @@ package dev.sheldan.abstracto.moderation.listener;
|
||||
import dev.sheldan.abstracto.core.config.FeatureDefinition;
|
||||
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserBannedListener;
|
||||
import dev.sheldan.abstracto.core.models.listener.UserBannedModel;
|
||||
import dev.sheldan.abstracto.core.models.listener.UserBannedListenerModel;
|
||||
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
|
||||
import dev.sheldan.abstracto.core.service.PostTargetService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
|
||||
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedListenerLogModel;
|
||||
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
|
||||
import dev.sheldan.abstracto.moderation.service.BanService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -21,24 +17,22 @@ import org.springframework.stereotype.Component;
|
||||
public class UserBannedListener implements AsyncUserBannedListener {
|
||||
|
||||
@Autowired
|
||||
private TemplateService templateService;
|
||||
|
||||
@Autowired
|
||||
private PostTargetService postTargetService;
|
||||
|
||||
public static final String USER_BANNED_NOTIFICATION_TEMPLATE = "userBanned_listener_notification";
|
||||
private BanService banService;
|
||||
|
||||
@Override
|
||||
public DefaultListenerResult execute(UserBannedModel eventModel) {
|
||||
public DefaultListenerResult execute(UserBannedListenerModel eventModel) {
|
||||
log.info("Notifying about ban of user {} in guild {}.", eventModel.getBannedServerUser().getUserId(), eventModel.getServerId());
|
||||
UserBannedListenerLogModel model = UserBannedListenerLogModel
|
||||
if(eventModel.getBanningServerUser().getUserId() == eventModel.getGuild().getJDA().getSelfUser().getIdLong()) {
|
||||
log.info("Skipping logging banned event about user {} in guild {}, because it was done by us.", eventModel.getBannedServerUser().getUserId(), eventModel.getGuild().getIdLong());
|
||||
return DefaultListenerResult.IGNORED;
|
||||
}
|
||||
UserBannedLogModel model = UserBannedLogModel
|
||||
.builder()
|
||||
.bannedUser(eventModel.getBannedUser() != null ? UserDisplay.fromUser(eventModel.getBannedUser()) : UserDisplay.fromId(eventModel.getBannedServerUser().getUserId()))
|
||||
.banningUser(eventModel.getBanningUser() != null ? UserDisplay.fromUser(eventModel.getBanningUser()) : UserDisplay.fromServerUser(eventModel.getBanningServerUser()))
|
||||
.reason(eventModel.getReason())
|
||||
.build();
|
||||
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, model, eventModel.getServerId());
|
||||
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, eventModel.getServerId()));
|
||||
banService.sendBanLogMessage(model, eventModel.getServerId());
|
||||
return DefaultListenerResult.PROCESSED;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,11 @@ package dev.sheldan.abstracto.moderation.listener;
|
||||
import dev.sheldan.abstracto.core.config.FeatureDefinition;
|
||||
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserUnBannedListener;
|
||||
import dev.sheldan.abstracto.core.models.listener.UserUnBannedModel;
|
||||
import dev.sheldan.abstracto.core.models.listener.UserUnBannedListenerModel;
|
||||
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
|
||||
import dev.sheldan.abstracto.core.service.PostTargetService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
|
||||
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedListenerLogModel;
|
||||
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedLogModel;
|
||||
import dev.sheldan.abstracto.moderation.service.BanService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -21,24 +17,22 @@ import org.springframework.stereotype.Component;
|
||||
public class UserUnBannedListener implements AsyncUserUnBannedListener {
|
||||
|
||||
@Autowired
|
||||
private TemplateService templateService;
|
||||
|
||||
@Autowired
|
||||
private PostTargetService postTargetService;
|
||||
|
||||
private static final String USER_UN_BANNED_NOTIFICATION_TEMPLATE = "userUnBanned_listener_notification";
|
||||
private BanService banService;
|
||||
|
||||
@Override
|
||||
public DefaultListenerResult execute(UserUnBannedModel eventModel) {
|
||||
public DefaultListenerResult execute(UserUnBannedListenerModel eventModel) {
|
||||
log.info("Notifying about unban of user {} in guild {}.", eventModel.getUnBannedServerUser().getUserId(), eventModel.getServerId());
|
||||
UserUnBannedListenerLogModel model = UserUnBannedListenerLogModel
|
||||
if(eventModel.getUnBanningUser().getIdLong() == eventModel.getGuild().getSelfMember().getIdLong()) {
|
||||
log.info("Skipping logging banned event about user {} in guild {}, because it was done by us.", eventModel.getUnBannedServerUser().getUserId(), eventModel.getGuild().getIdLong());
|
||||
return DefaultListenerResult.IGNORED;
|
||||
}
|
||||
UserUnBannedLogModel model = UserUnBannedLogModel
|
||||
.builder()
|
||||
.unBannedUser(eventModel.getUnBannedUser() != null ? UserDisplay.fromUser(eventModel.getUnBannedUser()) : UserDisplay.fromId(eventModel.getUnBannedServerUser().getUserId()))
|
||||
.unBanningUser(eventModel.getUnBanningUser() != null ? UserDisplay.fromUser(eventModel.getUnBanningUser()) : UserDisplay.fromServerUser(eventModel.getUnBanningServerUser()))
|
||||
.reason(eventModel.getReason())
|
||||
.build();
|
||||
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_UN_BANNED_NOTIFICATION_TEMPLATE, model, eventModel.getServerId());
|
||||
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, eventModel.getServerId()));
|
||||
banService.sendUnBanLogMessage(model, eventModel.getServerId());
|
||||
return DefaultListenerResult.PROCESSED;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefiniti
|
||||
import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionListener;
|
||||
import dev.sheldan.abstracto.moderation.model.database.Infraction;
|
||||
import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel;
|
||||
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedListenerLogModel;
|
||||
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
|
||||
import dev.sheldan.abstracto.moderation.service.BanService;
|
||||
import dev.sheldan.abstracto.moderation.service.BanServiceBean;
|
||||
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
@@ -26,8 +27,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static dev.sheldan.abstracto.moderation.listener.UserBannedListener.USER_BANNED_NOTIFICATION_TEMPLATE;
|
||||
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@@ -75,14 +74,14 @@ public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionLis
|
||||
public void handleBanUpdate(InfractionDescriptionEventModel model, CompletableFuture<User> infractionUser, CompletableFuture<User> infractionCreator, CompletableFuture<DefaultListenerResult> returningFuture) {
|
||||
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
|
||||
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId());
|
||||
UserBannedListenerLogModel banLog = UserBannedListenerLogModel
|
||||
UserBannedLogModel banLog = UserBannedLogModel
|
||||
.builder()
|
||||
.bannedUser(infractionUser.isCompletedExceptionally() ? null : UserDisplay.fromUser(infractionUser.join()))
|
||||
.banningUser(infractionCreator.isCompletedExceptionally() ? null : UserDisplay.fromUser(infractionCreator.join()))
|
||||
.reason(model.getNewDescription())
|
||||
.build();
|
||||
|
||||
MessageToSend message = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, banLog, model.getServerId());
|
||||
MessageToSend message = templateService.renderEmbedTemplate(BanServiceBean.USER_BANNED_NOTIFICATION_TEMPLATE, banLog, model.getServerId());
|
||||
messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId())
|
||||
.thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED))
|
||||
.exceptionally(throwable1 -> {
|
||||
|
||||
@@ -2,27 +2,36 @@ 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.service.ConfigService;
|
||||
import dev.sheldan.abstracto.core.service.FeatureFlagService;
|
||||
import dev.sheldan.abstracto.core.service.MessageService;
|
||||
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
|
||||
import dev.sheldan.abstracto.core.service.*;
|
||||
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.core.utils.CompletableFutureMap;
|
||||
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
|
||||
import dev.sheldan.abstracto.moderation.model.BanResult;
|
||||
import dev.sheldan.abstracto.moderation.model.database.Infraction;
|
||||
import dev.sheldan.abstracto.moderation.model.template.command.BanNotificationModel;
|
||||
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
|
||||
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedLogModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.UserSnowflake;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
@@ -52,6 +61,15 @@ public class BanServiceBean implements BanService {
|
||||
@Autowired
|
||||
private InfractionService infractionService;
|
||||
|
||||
@Autowired
|
||||
private PostTargetService postTargetService;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
public static final String USER_BANNED_NOTIFICATION_TEMPLATE = "userBanned_listener_notification";
|
||||
private static final String USER_UN_BANNED_NOTIFICATION_TEMPLATE = "userUnBanned_listener_notification";
|
||||
|
||||
@Override
|
||||
public CompletableFuture<BanResult> banUserWithNotification(ServerUser userToBeBanned, String reason, ServerUser banningUser, Guild guild, Duration deletionDuration) {
|
||||
BanResult[] result = {BanResult.SUCCESSFUL};
|
||||
@@ -61,6 +79,7 @@ public class BanServiceBean implements BanService {
|
||||
return null;
|
||||
})
|
||||
.thenCompose(unused -> banUser(guild, userToBeBanned, deletionDuration, reason))
|
||||
.thenCompose(unused -> self.composeAndSendBanLogMessage(userToBeBanned, banningUser, reason))
|
||||
.thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(userToBeBanned, guild, reason, banningUser, deletionDuration))
|
||||
.thenApply(unused -> result[0]);
|
||||
}
|
||||
@@ -103,15 +122,60 @@ public class BanServiceBean implements BanService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> unbanUser(Guild guild, Long userId) {
|
||||
log.info("Unbanning user {} in guild {}.", userId, guild.getId());
|
||||
return guild.unban(UserSnowflake.fromId(userId)).submit();
|
||||
public CompletableFuture<Void> unbanUser(Guild guild, User user, Member memberPerforming) {
|
||||
log.info("Unbanning user {} in guild {}.", user.getIdLong(), guild.getId());
|
||||
return guild.unban(user).submit().thenCompose(unused -> self.composeAndSendUnBanLogMessage(guild, user, memberPerforming));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CompletionStage<Void> composeAndSendUnBanLogMessage(Guild guild, User user, Member memberPerforming) {
|
||||
UserUnBannedLogModel model = UserUnBannedLogModel
|
||||
.builder()
|
||||
.unBannedUser(UserDisplay.fromUser(user))
|
||||
.unBanningUser(UserDisplay.fromUser(memberPerforming.getUser()))
|
||||
.reason(null)
|
||||
.build();
|
||||
return sendUnBanLogMessage(model, guild.getIdLong());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CompletableFuture<Void> composeAndSendBanLogMessage(ServerUser serverUserToBeBanned, ServerUser serverUserBanning, String reason) {
|
||||
CompletableFutureMap<Long, User> userMap = userService.retrieveUsersMapped(Arrays.asList(serverUserToBeBanned.getUserId(), serverUserBanning.getUserId()));
|
||||
return userMap.getMainFuture().thenCompose(unused -> {
|
||||
User userToBeBanned = userMap.getElement(serverUserToBeBanned.getUserId());
|
||||
User banningUser = userMap.getElement(serverUserBanning.getUserId());
|
||||
UserBannedLogModel model = UserBannedLogModel
|
||||
.builder()
|
||||
.bannedUser(UserDisplay.fromUser(userToBeBanned))
|
||||
.banningUser(UserDisplay.fromUser(banningUser))
|
||||
.reason(reason)
|
||||
.build();
|
||||
return self.sendBanLogMessage(model, serverUserToBeBanned.getServerId());
|
||||
}).exceptionally(throwable -> {
|
||||
log.warn("Failed to load users ({}, {}) for ban log message.", serverUserToBeBanned.getUserId(), serverUserBanning.getUserId(), throwable);
|
||||
UserBannedLogModel model = UserBannedLogModel
|
||||
.builder()
|
||||
.bannedUser(UserDisplay.fromId(serverUserToBeBanned.getUserId()))
|
||||
.banningUser(UserDisplay.fromId(serverUserBanning.getUserId()))
|
||||
.reason(reason)
|
||||
.build();
|
||||
self.sendBanLogMessage(model, serverUserToBeBanned.getServerId());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> softBanUser(Guild guild, ServerUser user, Duration delDays) {
|
||||
return banUser(guild, user, delDays, "")
|
||||
.thenCompose(unused -> unbanUser(guild, user.getUserId()));
|
||||
@Transactional
|
||||
public CompletableFuture<Void> sendUnBanLogMessage(UserUnBannedLogModel model, Long serverId) {
|
||||
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_UN_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
|
||||
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CompletableFuture<Void> sendBanLogMessage(UserBannedLogModel model, Long serverId) {
|
||||
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
|
||||
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.moderation.service;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.ServerUser;
|
||||
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
|
||||
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
|
||||
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
|
||||
import dev.sheldan.abstracto.core.service.ConfigService;
|
||||
import dev.sheldan.abstracto.core.service.FeatureFlagService;
|
||||
import dev.sheldan.abstracto.core.service.PostTargetService;
|
||||
import dev.sheldan.abstracto.core.service.UserService;
|
||||
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
|
||||
import dev.sheldan.abstracto.core.utils.CompletableFutureMap;
|
||||
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
@@ -16,14 +18,12 @@ import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.UserSnowflake;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -51,6 +51,9 @@ public class KickServiceBean implements KickService {
|
||||
@Autowired
|
||||
private InfractionService infractionService;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private KickServiceBean self;
|
||||
|
||||
@@ -59,7 +62,7 @@ public class KickServiceBean implements KickService {
|
||||
Guild guild = kickedMember.getGuild();
|
||||
log.info("Kicking user {} from guild {}", kickedMember.getUser().getIdLong(), guild.getIdLong());
|
||||
CompletableFuture<Void> kickFuture = guild.kick(kickedMember, reason).submit();
|
||||
CompletableFuture<Message> logFuture = sendKickLog(kickedMember, kickingMember, reason, guild.getIdLong());
|
||||
CompletableFuture<Message> logFuture = sendKickLog(kickedMember.getUser(), ServerUser.fromMember(kickedMember), kickingMember.getUser(), ServerUser.fromMember(kickingMember), reason, guild.getIdLong());
|
||||
return CompletableFuture.allOf(kickFuture, logFuture)
|
||||
.thenAccept(unused -> self.storeInfraction(kickedMember, kickingMember, reason, logFuture.join(), guild.getIdLong()));
|
||||
}
|
||||
@@ -96,27 +99,30 @@ public class KickServiceBean implements KickService {
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<Message> sendKickLog(Member kickedMember, Member kickingMember, String reason, Long serverId) {
|
||||
public CompletableFuture<Message> sendKickLog(User kickedUser, ServerUser kickedServerUser, User kickingUser, ServerUser kickingServerUser, String reason, Long serverId) {
|
||||
KickLogModel kickLogModel = KickLogModel
|
||||
.builder()
|
||||
.kickedMember(MemberDisplay.fromMember(kickedMember))
|
||||
.kickingMember(MemberDisplay.fromMember(kickingMember))
|
||||
.kickedUser(kickedUser != null ? UserDisplay.fromUser(kickedUser) : UserDisplay.fromServerUser(kickedServerUser))
|
||||
.kickingUser(kickingUser != null ? UserDisplay.fromUser(kickingUser) : UserDisplay.fromServerUser(kickingServerUser))
|
||||
.reason(reason)
|
||||
.build();
|
||||
return sendKicklog(serverId, kickLogModel);
|
||||
return sendKicklog(kickLogModel, serverId);
|
||||
}
|
||||
|
||||
private CompletableFuture<Message> sendKickLog(ServerUser kickedMember, ServerUser kickingMember, String reason, Long serverId) {
|
||||
KickLogModel kickLogModel = KickLogModel
|
||||
.builder()
|
||||
.kickedMember(MemberDisplay.fromServerUser(kickedMember))
|
||||
.kickingMember(MemberDisplay.fromServerUser(kickingMember))
|
||||
.reason(reason)
|
||||
.build();
|
||||
return sendKicklog(serverId, kickLogModel);
|
||||
public CompletableFuture<Message> sendKickLog(ServerUser kickedMember, ServerUser kickingMember, String reason, Long serverId) {
|
||||
CompletableFutureMap<Long, User> userMap = userService.retrieveUsersMapped(Arrays.asList(kickedMember.getUserId(), kickingMember.getUserId()));
|
||||
return userMap.getMainFuture().thenCompose(unused -> {
|
||||
User kickedUser = userMap.getElement(kickedMember.getUserId());
|
||||
User kickingUser = userMap.getElement(kickingMember.getUserId());
|
||||
return self.sendKickLog(kickedUser, kickedMember, kickingUser, kickingMember, reason, serverId);
|
||||
}).exceptionally(throwable -> {
|
||||
log.warn("Failed to fetch users ({}, {}) for kick event logging in server {}.", kickingMember.getUserId(), kickedMember.getUserId(), serverId, throwable);
|
||||
self.sendKickLog(null, kickedMember, null, kickingMember, reason, serverId);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Message> sendKicklog(Long serverId, KickLogModel kickLogModel) {
|
||||
public CompletableFuture<Message> sendKicklog(KickLogModel kickLogModel, Long serverId) {
|
||||
MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, serverId);
|
||||
log.debug("Sending kick log message in guild {}.", serverId);
|
||||
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, serverId);
|
||||
|
||||
@@ -6,23 +6,29 @@ import dev.sheldan.abstracto.core.models.ServerUser;
|
||||
import dev.sheldan.abstracto.core.models.database.AChannel;
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
|
||||
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
|
||||
import dev.sheldan.abstracto.core.service.*;
|
||||
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
|
||||
import dev.sheldan.abstracto.moderation.config.feature.MutingFeatureConfig;
|
||||
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
|
||||
import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException;
|
||||
import dev.sheldan.abstracto.moderation.model.MuteResult;
|
||||
import dev.sheldan.abstracto.moderation.model.database.Infraction;
|
||||
import dev.sheldan.abstracto.moderation.model.database.Mute;
|
||||
import dev.sheldan.abstracto.moderation.model.template.command.MuteLogModel;
|
||||
import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification;
|
||||
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
|
||||
import dev.sheldan.abstracto.scheduling.model.JobParameters;
|
||||
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -161,18 +167,58 @@ public class MuteServiceBean implements MuteService {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<MuteResult> muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin) {
|
||||
return muteMemberWithLog(userToMute, mutingUser, reason, duration, guild, origin, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<MuteResult> muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin, Instant oldTimeout) {
|
||||
Long serverId = userToMute.getServerId();
|
||||
Instant targetDate = Instant.now().plus(duration);
|
||||
log.debug("Muting member {} in server {}.", userToMute.getUserId(), serverId);
|
||||
log.info("Muting member {} in server {}.", userToMute.getUserId(), serverId);
|
||||
AServer server = serverManagementService.loadOrCreate(serverId);
|
||||
Long muteId = counterService.getNextCounterValue(server, MUTE_COUNTER_KEY);
|
||||
CompletableFuture<MuteResult> result = muteUserInServer(guild, userToMute, reason, duration);
|
||||
return result
|
||||
.thenCompose(muteResult -> self.composeAndLogMute(userToMute, mutingUser, reason, duration, guild, oldTimeout))
|
||||
.thenCompose(logMessage -> self.evaluateAndStoreInfraction(userToMute, mutingUser, reason, targetDate))
|
||||
.thenAccept(infractionId -> self.persistMute(userToMute, mutingUser, targetDate, muteId, reason, infractionId, origin))
|
||||
.thenApply(unused -> result.join());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CompletableFuture<Void> composeAndLogMute(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, Instant oldTimeout) {
|
||||
CompletableFuture<Member> mutedMemberFuture = memberService.retrieveMemberInServer(userToMute);
|
||||
CompletableFuture<Member> mutingMemberFuture = memberService.retrieveMemberInServer(mutingUser);
|
||||
Instant targetDate = Instant.now().plus(duration);
|
||||
return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture).thenCompose(unused -> {
|
||||
Member mutedMember = mutedMemberFuture.join();
|
||||
Member mutingMember = mutingMemberFuture.join();
|
||||
MuteLogModel muteLogModel = MuteLogModel
|
||||
.builder()
|
||||
.muteTargetDate(targetDate)
|
||||
.oldMuteTargetDate(oldTimeout)
|
||||
.mutingMember(MemberDisplay.fromMember(mutingMember))
|
||||
.mutedMember(MemberDisplay.fromMember(mutedMember))
|
||||
.duration(duration)
|
||||
.reason(reason)
|
||||
.build();
|
||||
return self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
|
||||
}).exceptionally(throwable -> {
|
||||
log.warn("Failed to load users for mute log ({}, {}) in guild {}.", userToMute.getUserId(), mutingUser.getUserId(), guild.getIdLong(), throwable);
|
||||
MuteLogModel muteLogModel = MuteLogModel
|
||||
.builder()
|
||||
.muteTargetDate(targetDate)
|
||||
.oldMuteTargetDate(null)
|
||||
.mutingMember(MemberDisplay.fromServerUser(mutingUser))
|
||||
.mutedMember(MemberDisplay.fromServerUser(userToMute))
|
||||
.duration(duration)
|
||||
.reason(reason)
|
||||
.build();
|
||||
self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CompletableFuture<Long> evaluateAndStoreInfraction(ServerUser userToMute, ServerUser mutingUser, String reason, Instant targetDate) {
|
||||
Long serverId = userToMute.getServerId();
|
||||
@@ -218,8 +264,10 @@ public class MuteServiceBean implements MuteService {
|
||||
Long muteId = mute.getMuteId().getId();
|
||||
AServer mutingServer = mute.getServer();
|
||||
ServerUser mutedUser = ServerUser.fromAUserInAServer(mute.getMutedUser());
|
||||
ServerUser mutingUser = ServerUser.fromAUserInAServer(mute.getMutingUser());
|
||||
log.info("UnMuting {} in server {}", mute.getMutedUser().getUserReference().getId(), mutingServer.getId());
|
||||
return memberService.removeTimeout(guild, mutedUser, null)
|
||||
.thenCompose(unused -> self.composeAndLogUnmute(mutedUser, mutingUser, guild))
|
||||
.thenAccept(unused -> {
|
||||
if(muteId != null) {
|
||||
self.endMuteInDatabase(muteId, guild.getIdLong());
|
||||
@@ -227,6 +275,26 @@ public class MuteServiceBean implements MuteService {
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CompletableFuture<Void> composeAndLogUnmute(ServerUser mutedUser, ServerUser mutingUser, Guild guild) {
|
||||
CompletableFuture<Member> mutedMemberFuture = memberService.retrieveMemberInServer(mutedUser);
|
||||
CompletableFuture<Member> mutingMemberFuture = memberService.retrieveMemberInServer(mutingUser);
|
||||
return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture).thenCompose(unused -> {
|
||||
Member mutedMember = mutedMemberFuture.join();
|
||||
Member mutingMember = mutingMemberFuture.join();
|
||||
MuteLogModel muteLogModel = MuteLogModel
|
||||
.builder()
|
||||
.muteTargetDate(null)
|
||||
.oldMuteTargetDate(null)
|
||||
.mutingMember(MemberDisplay.fromMember(mutingMember))
|
||||
.mutedMember(MemberDisplay.fromMember(mutedMember))
|
||||
.duration(null)
|
||||
.reason(null)
|
||||
.build();
|
||||
return self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void endMuteInDatabase(Long muteId, Long serverId) {
|
||||
Optional<Mute> muteOptional = muteManagementService.findMuteOptional(muteId, serverId);
|
||||
@@ -248,6 +316,12 @@ public class MuteServiceBean implements MuteService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> sendMuteLogMessage(MuteLogModel model, Long serverId) {
|
||||
MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, model, serverId);
|
||||
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, serverId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completelyUnMuteUser(AUserInAServer aUserInAServer) {
|
||||
log.info("Completely unmuting user {} in server {}.", aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId());
|
||||
|
||||
Reference in New Issue
Block a user