added Arole to command received handler in order to handle it

restructured channel service calls a little bit
moved dm sending to message service
fixed log level configuration
added full user dto, to be used as a combination of a AUserInServer and Member, for operations which need both, to avoid converting and reloading the user
added mute command and mute role command
added mute table
added mute role table
added job to automatically unmute people at the given time period
restructured warn service
removed simple message log for warnings
added method to templating to support formatting instants
This commit is contained in:
Sheldan
2020-04-24 18:02:05 +02:00
parent cf37d4adef
commit b41a596acd
44 changed files with 1032 additions and 69 deletions

View File

@@ -17,6 +17,12 @@
<artifactId>moderation-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling-int</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,64 @@
package dev.sheldan.abstracto.moderation.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.moderation.Moderation;
import dev.sheldan.abstracto.moderation.config.ModerationFeatures;
import dev.sheldan.abstracto.moderation.models.template.commands.MuteLog;
import dev.sheldan.abstracto.moderation.service.MuteService;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Component
public class Mute extends AbstractConditionableCommand {
@Autowired
private MuteService muteService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Member member = (Member) parameters.get(0);
Duration duration = (Duration) parameters.get(1);
String reason = (String) parameters.get(2);
MuteLog muteLogModel = (MuteLog) ContextConverter.fromCommandContext(commandContext, MuteLog.class);
muteLogModel.setMessage(commandContext.getMessage());
muteLogModel.setMutedUser(member);
muteLogModel.setMutingUser(commandContext.getAuthor());
muteService.muteMemberWithLog(member, commandContext.getAuthor(), reason, Instant.now().plus(duration), muteLogModel, commandContext.getMessage());
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("user").type(Member.class).build());
parameters.add(Parameter.builder().name("duration").type(Duration.class).build());
parameters.add(Parameter.builder().name("reason").type(String.class).optional(true).remainder(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(false).build();
return CommandConfiguration.builder()
.name("mute")
.module(Moderation.MODERATION)
.templated(false)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public String getFeature() {
return ModerationFeatures.MUTING;
}
}

View File

@@ -0,0 +1,51 @@
package dev.sheldan.abstracto.moderation.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.moderation.Moderation;
import dev.sheldan.abstracto.moderation.config.ModerationFeatures;
import dev.sheldan.abstracto.moderation.service.management.MuteRoleManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class SetMuteRole extends AbstractConditionableCommand {
@Autowired
private MuteRoleManagementService muteRoleManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
ARole role = (ARole) commandContext.getParameters().getParameters().get(0);
muteRoleManagementService.setMuteRoleForServer(commandContext.getUserInitiatedContext().getServer(), role);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("roleId").type(ARole.class).build());
HelpInfo helpInfo = HelpInfo.builder().templated(false).build();
return CommandConfiguration.builder()
.name("setMuteRole")
.module(Moderation.MODERATION)
.templated(false)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public String getFeature() {
return ModerationFeatures.MUTING;
}
}

View File

@@ -43,7 +43,7 @@ public class Warn extends AbstractConditionableCommand {
warnLogModel.setMessage(commandContext.getMessage());
warnLogModel.setReason(reason);
warnLogModel.setWarningUser(commandContext.getAuthor());
warnService.warnUser(member, commandContext.getAuthor(), reason, warnLogModel);
warnService.warnUserWithLog(member, commandContext.getAuthor(), reason, warnLogModel, commandContext.getChannel());
return CommandResult.fromSuccess();
}

View File

@@ -4,4 +4,5 @@ public class ModerationFeatures {
public static String MODERATION = "moderation";
public static String WARNINGS = "warnings";
public static String LOGGING = "logging";
public static String MUTING = "mutes";
}

View File

@@ -0,0 +1,37 @@
package dev.sheldan.abstracto.moderation.job;
import dev.sheldan.abstracto.moderation.service.MuteService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class UnMuteJob extends QuartzJobBean {
private Long muteId;
@Autowired
private MuteService muteService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("Executing unmute job for mute {}", muteId);
muteService.endMute(muteId);
}
public Long getMuteId() {
return muteId;
}
public void setMuteId(Long muteId) {
this.muteId = muteId;
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.moderation.models.database.Mute;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MuteRepository extends JpaRepository<Mute, Long> {
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.MuteRole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MuteRoleRepository extends JpaRepository<MuteRole, Long> {
MuteRole findByRoleServer(AServer server);
List<MuteRole> findAllByRoleServer(AServer server);
}

View File

@@ -0,0 +1,217 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.FullUser;
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.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.UserManagementService;
import dev.sheldan.abstracto.moderation.exception.MuteException;
import dev.sheldan.abstracto.moderation.models.database.Mute;
import dev.sheldan.abstracto.moderation.models.database.MuteRole;
import dev.sheldan.abstracto.moderation.models.template.commands.MuteLog;
import dev.sheldan.abstracto.moderation.models.template.commands.MuteNotification;
import dev.sheldan.abstracto.moderation.models.template.commands.UnMuteLog;
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
import dev.sheldan.abstracto.moderation.service.management.MuteRoleManagementService;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.quartz.JobDataMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class MuteServiceBean implements MuteService {
@Autowired
private MuteRoleManagementService muteRoleManagementService;
@Autowired
private RoleService roleService;
@Autowired
private UserManagementService userManagementService;
@Autowired
private SchedulerService schedulerService;
@Autowired
private MuteManagementService muteManagementService;
@Autowired
private TemplateService templateService;
@Autowired
private BotService botService;
@Autowired
private MessageService messageService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private MuteService self;
@Autowired
private ChannelManagementService channelManagementService;
private static final String MUTE_LOG_TEMPLATE = "mute_log";
private static final String UNMUTE_LOG_TEMPLATE = "unmute_log";
private static final String MUTE_LOG_TARGET = "muteLog";
private static final String MUTE_NOTIFICATION_TEMPLATE = "mute_notification";
@Override
public Mute muteMember(Member memberToMute, Member mutingMember, String reason, Instant unmuteDate, Message message) {
FullUser mutedUser = FullUser
.builder()
.aUserInAServer(userManagementService.loadUser(memberToMute))
.member(memberToMute)
.build();
FullUser mutingUser = FullUser
.builder()
.aUserInAServer(userManagementService.loadUser(memberToMute))
.member(mutingMember)
.build();
return muteUser(mutedUser, mutingUser, reason, unmuteDate, message);
}
@Override
public Mute muteMember(AUserInAServer userBeingMuted, AUserInAServer userMuting, String reason, Instant unmuteDate, Message message) {
FullUser mutedUser = FullUser
.builder()
.aUserInAServer(userBeingMuted)
.member(botService.getMemberInServer(userBeingMuted))
.build();
FullUser mutingUser = FullUser
.builder()
.aUserInAServer(userMuting)
.member(botService.getMemberInServer(userMuting))
.build();
return muteUser(mutedUser, mutingUser, reason, unmuteDate, message);
}
@Override
public Mute muteUser(FullUser userBeingMuted, FullUser userMuting, String reason, Instant unmuteDate, Message message) {
log.info("User {} mutes {} until {}",
userBeingMuted.getMember().getIdLong(), userMuting.getMember().getIdLong(), unmuteDate);
if(message != null) {
log.trace("because of message {} in channel {} in server {}", message.getId(), message.getChannel().getId(), message.getGuild().getId());
} else {
log.trace("This mute was not triggered by a message.");
}
if(!muteRoleManagementService.muteRoleForServerExists(userBeingMuted.getAUserInAServer().getServerReference())) {
log.error("Mute role for server {} has not been setup.", userBeingMuted.getAUserInAServer().getServerReference().getId());
throw new MuteException("Mute role for server has not been setup");
}
AUserInAServer userInServerBeingMuted = userBeingMuted.getAUserInAServer();
MuteRole muteRole = muteRoleManagementService.retrieveMuteRoleForServer(userInServerBeingMuted.getServerReference());
roleService.addRoleToUser(userInServerBeingMuted, muteRole.getRole());
AServerAChannelMessage origin = null;
if(message != null) {
AChannel channel = channelManagementService.loadChannel(message.getChannel().getIdLong());
origin = AServerAChannelMessage
.builder()
.channel(channel)
.server(channel.getServer())
.messageId(message.getIdLong())
.build();
}
Mute mute = muteManagementService.createMute(userInServerBeingMuted, userMuting.getAUserInAServer(), reason, unmuteDate, origin);
log.trace("Notifying the user about the mute.");
MuteNotification muteNotification = MuteNotification.builder().mute(mute).serverName(userBeingMuted.getMember().getGuild().getName()).build();
String muteNotificationMessage = templateService.renderTemplate(MUTE_NOTIFICATION_TEMPLATE, muteNotification);
TextChannel textChannel = message != null ? message.getTextChannel() : null;
messageService.sendMessageToUser(userBeingMuted.getMember().getUser(), muteNotificationMessage, textChannel);
startUnmuteJobFor(unmuteDate, mute);
return mute;
}
@Override
public void startUnmuteJobFor(Instant unmuteDate, Mute mute) {
Duration muteDuration = Duration.between(Instant.now(), unmuteDate);
if(muteDuration.getSeconds() < 60) {
log.trace("Directly scheduling the unmute, because it was below the threshold.");
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> {
try {
self.unmuteUser(mute);
} catch (Exception exception) {
log.error("Failed to remind immediately.", exception);
}
}, muteDuration.toNanos(), TimeUnit.NANOSECONDS);
} else {
log.trace("Starting scheduled job to execute unmute.");
JobDataMap parameters = new JobDataMap();
parameters.putAsString("muteId", mute.getId());
schedulerService.executeJobWithParametersOnce("unMuteJob", "moderation", parameters, Date.from(unmuteDate));
}
}
@Override
public void muteMemberWithLog(Member memberToMute, Member memberMuting, String reason, Instant unmuteDate, MuteLog muteLog, Message message) {
log.trace("Muting member with sending a mute log");
Mute mute = muteMember(memberToMute, memberMuting, reason, unmuteDate, message);
muteLog.setMute(mute);
sendMuteLog(muteLog);
}
private void sendMuteLog(MuteLog muteLogModel) {
log.trace("Sending mute log to the mute posttarget");
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, muteLogModel);
postTargetService.sendEmbedInPostTarget(message, MUTE_LOG_TARGET, muteLogModel.getServer().getId());
}
private void sendUnmuteLog(UnMuteLog muteLogModel) {
log.trace("Sending unmute log to the mute posttarget");
MessageToSend message = templateService.renderEmbedTemplate(UNMUTE_LOG_TEMPLATE, muteLogModel);
postTargetService.sendEmbedInPostTarget(message, MUTE_LOG_TARGET, muteLogModel.getServer().getId());
}
@Override
@Transactional
public void unmuteUser(Mute mute) {
AServer mutingServer = mute.getMutingServer();
log.info("Unmuting {} in server {}", mutingServer.getId(), mute.getMutedUser().getUserReference().getId());
MuteRole muteRole = muteRoleManagementService.retrieveMuteRoleForServer(mutingServer);
log.trace("Using the mute role {} mapping to role {}", muteRole.getId(), muteRole.getRole().getId());
roleService.removeRoleFromUser(mute.getMutedUser(), muteRole.getRole());
UnMuteLog unMuteLog = UnMuteLog
.builder()
.mute(mute)
.mutingUser(botService.getMemberInServer(mute.getMutingUser()))
.unMutedUser(botService.getMemberInServer(mute.getMutedUser()))
.guild(botService.getGuildById(mute.getMutingServer().getId()).orElseGet(null))
.server(mute.getMutingServer())
.build();
sendUnmuteLog(unMuteLog);
}
@Override
@Transactional
public void endMute(Long muteId) {
log.info("Unmuting the mute {}", muteId);
Mute mute = muteManagementService.findMute(muteId);
unmuteUser(mute);
}
}

View File

@@ -1,9 +1,8 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.exception.UserException;
import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.core.models.context.ServerContext;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.moderation.models.template.commands.WarnLog;
import dev.sheldan.abstracto.moderation.models.template.commands.WarnNotification;
@@ -16,17 +15,10 @@ import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Slf4j
@Component
public class WarnServiceBean implements WarnService {
@@ -50,56 +42,63 @@ public class WarnServiceBean implements WarnService {
@Autowired
private BotService botService;
@Autowired
private MessageService messageService;
private static final String WARN_LOG_TEMPLATE = "warn_log";
private static final String WARN_NOTIFICATION_TEMPLATE = "warn_notification";
@Override
public void warnUser(AUserInAServer warnedAUserInAServer, AUserInAServer warningAUserInAServer, String reason, WarnLog warnLog) {
AUser warningAUser = warningAUserInAServer.getUserReference();
AUser warnedAUser = warnedAUserInAServer.getUserReference();
AServer serverOfWarning = warnedAUserInAServer.getServerReference();
log.info("User {} is warning {} in server {} because of {}", warningAUser.getId(), warnedAUser.getId(), serverOfWarning.getId(), reason);
Warning warning = warnManagementService.createWarning(warnedAUserInAServer, warningAUserInAServer, reason);
JDA instance = botService.getInstance();
User userBeingWarned = instance.getUserById(warnedAUser.getId());
Optional<Guild> guildById = botService.getGuildById(serverOfWarning.getId());
String guildName = "<defaultName>";
if(guildById.isPresent()) {
guildName = guildById.get().getName();
}
warnLog.setWarning(warning);
this.sendWarnLog(warnLog);
WarnNotification warnNotification = WarnNotification.builder().warning(warning).serverName(guildName).build();
if(userBeingWarned != null) {
String warnLogMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification);
CompletableFuture<Message> messageFuture = new CompletableFuture<>();
public Warning warnUser(AUserInAServer warnedAUserInAServer, AUserInAServer warningAUserInAServer, String reason, TextChannel feedbackChannel) {
FullUser warnedUser = FullUser
.builder()
.aUserInAServer(warnedAUserInAServer)
.member(botService.getMemberInServer(warnedAUserInAServer))
.build();
// TODO the person executing this, is unaware that the message failed
userBeingWarned.openPrivateChannel().queue(privateChannel -> {
log.info("Messaging user {} about warn {}", warnedAUser.getId(), warning.getId());
privateChannel.sendMessage(warnLogMessage).queue(messageFuture::complete, messageFuture::completeExceptionally);
});
messageFuture.exceptionally(e -> {
log.warn("Failed to send message. ", e);
return null;
});
} else {
log.warn("Unable to find user {} in guild {} to warn.", warnedAUser.getId(), serverOfWarning.getId());
throw new UserException(String.format("Unable to find user %s.", warnedAUser.getId()));
}
FullUser warningUser = FullUser
.builder()
.aUserInAServer(warningAUserInAServer)
.member(botService.getMemberInServer(warningAUserInAServer))
.build();
return warnUser(warnedUser, warningUser, reason, feedbackChannel);
}
@Override
public void warnUser(Member warnedMember, Member warningMember, String reason, WarnLog warnLog) {
AUserInAServer warnedAUser = userManagementService.loadUser(warnedMember);
AUserInAServer warningAUser = userManagementService.loadUser(warningMember);
this.warnUser(warnedAUser, warningAUser, reason, warnLog);
public Warning warnUser(Member warnedMember, Member warningMember, String reason, TextChannel feedbackChannel) {
FullUser warnedUser = FullUser
.builder()
.aUserInAServer(userManagementService.loadUser(warnedMember))
.member(warnedMember)
.build();
FullUser warningUser = FullUser
.builder()
.aUserInAServer(userManagementService.loadUser(warningMember))
.member(warningMember)
.build();
return warnUser(warnedUser, warningUser, reason, feedbackChannel);
}
@Override
public Warning warnUser(FullUser warnedMember, FullUser warningMember, String reason, TextChannel feedbackChannel) {
Guild guild = warnedMember.getMember().getGuild();
log.info("User {} is warning {} in server {} because of {}", warnedMember.getMember().getId(), warningMember.getMember().getId(), guild.getIdLong(), reason);
Warning warning = warnManagementService.createWarning(warnedMember.getAUserInAServer(), warningMember.getAUserInAServer(), reason);
WarnNotification warnNotification = WarnNotification.builder().warning(warning).serverName(guild.getName()).build();
String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification);
messageService.sendMessageToUser(warnedMember.getMember().getUser(), warnNotificationMessage, feedbackChannel);
return warning;
}
@Override
public void warnUserWithLog(Member warnedMember, Member warningMember, String reason, WarnLog warnLog, TextChannel feedbackChannel) {
Warning warning = warnUser(warnedMember, warningMember, reason, feedbackChannel);
warnLog.setWarning(warning);
this.sendWarnLog(warnLog);
}
private void sendWarnLog(ServerContext warnLogModel) {
String warnLogMessage = templateService.renderTemplate(WARN_LOG_TEMPLATE, warnLogModel);
postTargetService.sendTextInPostTarget(warnLogMessage, WARN_LOG_TARGET, warnLogModel.getServer().getId());
MessageToSend message = templateService.renderEmbedTemplate("warn_log", warnLogModel);
postTargetService.sendEmbedInPostTarget(message, WARN_LOG_TARGET, warnLogModel.getServer().getId());
}

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.models.database.Mute;
import dev.sheldan.abstracto.moderation.repository.MuteRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Component
@Slf4j
public class MuteManagementServiceBean implements MuteManagementService {
@Autowired
private MuteRepository muteRepository;
@Override
public Mute createMute(AUserInAServer aUserInAServer, AUserInAServer mutingUser, String reason, Instant unmuteDate, AServerAChannelMessage origin) {
log.trace("Creating mute for user {} executed by user {} in server {}, user will be unmuted at {}",
aUserInAServer.getUserReference().getId(), mutingUser.getUserReference().getId(), aUserInAServer.getServerReference().getId(), unmuteDate);
Mute mute = Mute
.builder()
.muteDate(Instant.now())
.mutedUser(aUserInAServer)
.mutingUser(mutingUser)
.muteTargetDate(unmuteDate)
.mutingServer(aUserInAServer.getServerReference())
.mutingChannel(origin.getChannel())
.messageId(origin.getMessageId())
.reason(reason)
.build();
muteRepository.save(mute);
return mute;
}
@Override
public Mute findMute(Long muteId) {
return muteRepository.getOne(muteId);
}
}

View File

@@ -0,0 +1,59 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.MuteRole;
import dev.sheldan.abstracto.moderation.repository.MuteRoleRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class MuteRoleManagementServiceBean implements MuteRoleManagementService {
@Autowired
private MuteRoleRepository muteRoleRepository;
@Override
public MuteRole retrieveMuteRoleForServer(AServer server) {
return muteRoleRepository.findByRoleServer(server);
}
@Override
public MuteRole createMuteRoleForServer(AServer server, ARole role) {
log.trace("Creating mute role for server {} to be role {}", server.getId(), role.getId());
MuteRole muteRole = MuteRole
.builder()
.role(role)
.roleServer(server)
.build();
muteRoleRepository.save(muteRole);
return muteRole;
}
@Override
public List<MuteRole> retrieveMuteRolesForServer(AServer server) {
return muteRoleRepository.findAllByRoleServer(server);
}
@Override
public MuteRole setMuteRoleForServer(AServer server, ARole role) {
log.info("Setting muted role for server {} to role {}", server.getId(), role.getId());
MuteRole existing = retrieveMuteRoleForServer(server);
if(existing == null) {
return createMuteRoleForServer(server, role);
} else {
log.trace("Updating mute role for server {} to be role {} instead.", server.getId(), role.getId());
existing.setRole(role);
return existing;
}
}
@Override
public boolean muteRoleForServerExists(AServer server) {
return retrieveMuteRoleForServer(server) != null;
}
}

View File

@@ -1,4 +1,12 @@
abstracto.postTargets.moderation=joinLog,leaveLog,warnLog,kickLog,banLog,editLog,deleteLog
abstracto.postTargets.moderation=joinLog,leaveLog,warnLog,kickLog,banLog,editLog,deleteLog,muteLog
abstracto.features.moderation=false
abstracto.features.warnings=false
abstracto.features.logging=true
abstracto.features.logging=true
abstracto.features.mutes=true
abstracto.scheduling.jobs.unMuteJob.name=unMuteJob
abstracto.scheduling.jobs.unMuteJob.group=moderation
abstracto.scheduling.jobs.unMuteJob.clazz=dev.sheldan.abstracto.moderation.job.UnMuteJob
abstracto.scheduling.jobs.unMuteJob.standAlone=false
abstracto.scheduling.jobs.unMuteJob.active=true
abstracto.scheduling.jobs.unMuteJob.recovery=false

View File

@@ -0,0 +1,47 @@
{
"author": {
"name": "${mutedUser.effectiveName}",
"avatar": "${mutedUser.user.effectiveAvatarUrl}"
},
"title": {
"title": "User has been muted"
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
"fields": [
{
"name": "Muted User",
"value": "${mutedUser.effectiveName} ${mutedUser.asMention} (${mutedUser.idLong?c})"
},
{
"name": "Muted by",
"value": "${mutingUser.effectiveName} ${mutingUser.asMention} (${mutingUser.idLong?c})"
},
{
"name": "Location of the mute",
"value": "[${messageChannel.name}](${message.jumpUrl})"
},
{
"name": "Reason",
"value": "${mute.reason}"
},
{
"name": "Muted from",
"value": "${formatInstant(mute.muteDate, "yyyy-MM-dd HH:mm:ss")}"
},
{
"name": "Muted for",
"value": "${fmtDuration(muteDuration)}"
},
{
"name": "Muted until",
"value": "${formatInstant(mute.muteTargetDate, "yyyy-MM-dd HH:mm:ss")}"
}
],
"footer": {
"text": "Mute #${mute.id}"
}
}

View File

@@ -0,0 +1 @@
You were muted on the server ${serverName} for the following reason: ${mute.reason}.

View File

@@ -0,0 +1,43 @@
{
"author": {
"name": "${unMutedUser.effectiveName}",
"avatar": "${unMutedUser.user.effectiveAvatarUrl}"
},
"title": {
"title": "User has been unmuted"
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
"fields": [
{
"name": "Unmuted User",
"value": "${unMutedUser.effectiveName} ${unMutedUser.asMention} (${unMutedUser.idLong?c})"
},
{
"name": "Muted by",
"value": "${mutingUser.effectiveName} ${mutingUser.asMention} (${mutingUser.idLong?c})"
},
{
"name": "Location of the mute",
"value": "[Link](${messageUrl})"
},
{
"name": "Muted since",
"value": "${formatInstant(mute.muteDate, "yyyy-MM-dd HH:mm:ss")}"
},
{
"name": "Muted for",
"value": "${fmtDuration(muteDuration)}"
},
{
"name": "Reason",
"value": "${mute.reason}"
}
],
"footer": {
"text": "Mute #${mute.id}"
}
}

View File

@@ -1 +0,0 @@
User ${warnedUser.effectiveName} (${warnedUser.asMention}) has been warned with reason ${reason}.

View File

@@ -0,0 +1,11 @@
package dev.sheldan.abstracto.moderation.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
public class MuteException extends AbstractoRunTimeException {
public MuteException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,50 @@
package dev.sheldan.abstracto.moderation.models.database;
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 lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Entity
@Table(name="mute")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Mute {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "mutedUser", nullable = false)
private AUserInAServer mutedUser;
@ManyToOne
@JoinColumn(name = "mutingUser", nullable = false)
private AUserInAServer mutingUser;
private String reason;
private Instant muteDate;
private Instant muteTargetDate;
@Column
private Long messageId;
@ManyToOne
@JoinColumn(name = "mutingServer", nullable = false)
private AServer mutingServer;
@ManyToOne
@JoinColumn(name = "mutingChannel")
private AChannel mutingChannel;
}

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.moderation.models.database;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import lombok.*;
import javax.persistence.*;
/**
* Represents a role to be used for muting users on a certain server
*/
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "mute_role")
@Getter
@Setter
public class MuteRole {
/**
* The abstracto unique id of this mute role.
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* Reference to the {@link AServer} at which this role is used as an mute role.
*/
@ManyToOne(fetch = FetchType.LAZY)
@Getter
@Setter
@JoinColumn(name = "server_id", nullable = false)
private AServer roleServer;
/**
* Reference to the actual {@link ARole} being used to mute.
*/
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false)
private ARole role;
}

View File

@@ -6,7 +6,9 @@ import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
@Getter @SuperBuilder @Setter
@Getter
@SuperBuilder
@Setter
public class BanIdLog extends UserInitiatedServerContext {
private String reason;
private Member banningUser;

View File

@@ -6,7 +6,9 @@ import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
@Getter @SuperBuilder @Setter
@Getter
@SuperBuilder
@Setter
public class BanLog extends UserInitiatedServerContext {
private String reason;

View File

@@ -6,7 +6,9 @@ import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
@Getter @SuperBuilder @Setter
@Getter
@SuperBuilder
@Setter
public class KickLogModel extends UserInitiatedServerContext {
private String reason;
private Member kickingUser;

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.moderation.models.template.commands;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import dev.sheldan.abstracto.moderation.models.database.Mute;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import java.time.Duration;
@Getter
@SuperBuilder
@Setter
public class MuteLog extends UserInitiatedServerContext {
private Member mutedUser;
private Member mutingUser;
private Message message;
private Mute mute;
public Duration getMuteDuration() {
return Duration.between(mute.getMuteDate(), mute.getMuteTargetDate());
}
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.moderation.models.template.commands;
import dev.sheldan.abstracto.moderation.models.database.Mute;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
public class MuteNotification {
private Mute mute;
private String serverName;
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.abstracto.moderation.models.template.commands;
import dev.sheldan.abstracto.core.models.context.ServerContext;
import dev.sheldan.abstracto.core.utils.MessageUtils;
import dev.sheldan.abstracto.moderation.models.database.Mute;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import java.time.Duration;
@Getter
@SuperBuilder
@Setter
@NoArgsConstructor
public class UnMuteLog extends ServerContext {
private Member unMutedUser;
private Member mutingUser;
private Mute mute;
public Duration getMuteDuration() {
return Duration.between(mute.getMuteDate(), mute.getMuteTargetDate());
}
public String getMessageUrl() {
return MessageUtils.buildMessageUrl(this.mute.getMutingServer().getId() ,this.getMute().getMutingChannel().getId(), this.mute.getMessageId());
}
}

View File

@@ -8,8 +8,9 @@ import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
@Getter @SuperBuilder @Setter
@Getter
@SuperBuilder
@Setter
public class WarnLog extends UserInitiatedServerContext {
private String reason;

View File

@@ -4,7 +4,8 @@ import dev.sheldan.abstracto.moderation.models.database.Warning;
import lombok.Builder;
import lombok.Value;
@Value @Builder
@Value
@Builder
public class WarnNotification {
private Warning warning;
private String serverName;

View File

@@ -0,0 +1,20 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.models.database.Mute;
import dev.sheldan.abstracto.moderation.models.template.commands.MuteLog;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import java.time.Instant;
public interface MuteService {
Mute muteMember(Member memberToMute, Member userMuting, String reason, Instant unmuteDate, Message message);
Mute muteMember(AUserInAServer member, AUserInAServer userMuting, String reason, Instant unmuteDate, Message message);
Mute muteUser(FullUser userToMute, FullUser userMuting, String reason, Instant unmuteDate, Message message);
void muteMemberWithLog(Member memberToMute, Member memberMuting, String reason, Instant unmuteDate, MuteLog log, Message message);
void startUnmuteJobFor(Instant unmuteDate, Mute mute);
void unmuteUser(Mute mute);
void endMute(Long muteId);
}

View File

@@ -1,11 +1,16 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.moderation.models.database.Warning;
import dev.sheldan.abstracto.moderation.models.template.commands.WarnLog;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
public interface WarnService {
void warnUser(AUserInAServer warnedAUser, AUserInAServer warningAUser, String reason, WarnLog warnLog);
void warnUser(Member warnedUser, Member warningUser, String reason, WarnLog warnLog);
Warning warnUser(AUserInAServer warnedAUser, AUserInAServer warningAUser, String reason, TextChannel feedbackChannel);
Warning warnUser(Member warnedMember, Member warningMember, String reason, TextChannel feedbackChannel);
Warning warnUser(FullUser warnedUser, FullUser warningUser, String reason, TextChannel feedbackChannel);
void warnUserWithLog(Member warnedMember, Member warningMember, String reason, WarnLog warnLog, TextChannel feedbackChannel);
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.models.database.Mute;
import java.time.Instant;
public interface MuteManagementService {
Mute createMute(AUserInAServer aUserInAServer, AUserInAServer mutingUser, String reason, Instant unmuteDate, AServerAChannelMessage creation);
Mute findMute(Long muteId);
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.MuteRole;
import java.util.List;
public interface MuteRoleManagementService {
MuteRole retrieveMuteRoleForServer(AServer server);
MuteRole createMuteRoleForServer(AServer server, ARole role);
List<MuteRole> retrieveMuteRolesForServer(AServer server);
MuteRole setMuteRoleForServer(AServer server, ARole role);
boolean muteRoleForServerExists(AServer server);
}

View File

@@ -84,8 +84,6 @@ public class RemindServiceBean implements ReminderService {
log.error("Failed to remind immediately.", exception);
}
}, remindIn.toNanos(), TimeUnit.NANOSECONDS);
} else {
log.trace("Starting scheduled job to execute reminder.");
JobDataMap parameters = new JobDataMap();