[AB-96] adding ability to edit/delete modmail messages via editing/deleting the original message causing the message,

adding featuremode to modmail to define whether or not there is a separate message posted to the mod mail thread, to see it easier, renaming modmail related tables to singular, adding some necessary methods (caching) to all entities
This commit is contained in:
Sheldan
2020-10-19 23:55:51 +02:00
parent 1b98436736
commit dca98c2953
75 changed files with 952 additions and 592 deletions

View File

@@ -18,6 +18,8 @@ import java.util.List;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AssignableRole implements Serializable {

View File

@@ -18,6 +18,8 @@ import java.util.List;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AssignableRolePlace implements Serializable {

View File

@@ -17,6 +17,8 @@ import java.util.List;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AssignableRolePlacePost implements Serializable {

View File

@@ -17,6 +17,8 @@ import java.util.List;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AssignedRoleUser implements Serializable {

View File

@@ -5,6 +5,7 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
/**
* A role for which the experience gain in a particular server has been disabled.
@@ -16,9 +17,10 @@ import javax.persistence.*;
@Table(name = "disabled_experience_roles")
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ADisabledExpRole {
public class ADisabledExpRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

View File

@@ -6,7 +6,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
/**
* Represents an existing level to reach and the total necessary experience needed to reach that level.
@@ -18,6 +17,7 @@ import java.util.Objects;
@Table(name = "experience_level")
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AExperienceLevel implements Serializable {
@@ -47,17 +47,4 @@ public class AExperienceLevel implements Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AExperienceLevel that = (AExperienceLevel) o;
return Objects.equals(level, that.level) &&
Objects.equals(experienceNeeded, that.experienceNeeded);
}
@Override
public int hashCode() {
return Objects.hash(level, experienceNeeded);
}
}

View File

@@ -10,7 +10,6 @@ import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Represents a role which is given when the user reaches a certain level. These roles are configurable per server and
@@ -23,6 +22,7 @@ import java.util.Objects;
@Table(name = "experience_role")
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AExperienceRole implements Serializable {
@@ -81,20 +81,5 @@ public class AExperienceRole implements Serializable {
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<AUserExperience> users = new ArrayList<>();
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AExperienceRole that = (AExperienceRole) o;
return Objects.equals(id, that.id) &&
Objects.equals(level, that.level) &&
Objects.equals(roleServer, that.roleServer) &&
Objects.equals(role, that.role) &&
Objects.equals(users, that.users);
}
@Override
public int hashCode() {
return Objects.hash(id, level, roleServer, role, users);
}
}

View File

@@ -7,7 +7,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
/**
@@ -21,6 +20,7 @@ import java.util.Objects;
@Table(name = "user_experience")
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AUserExperience implements Serializable {
@@ -83,22 +83,4 @@ public class AUserExperience implements Serializable {
public Integer getLevelOrDefault() {
return currentLevel != null ? currentLevel.getLevel() : 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AUserExperience that = (AUserExperience) o;
return Objects.equals(id, that.id) &&
Objects.equals(user, that.user) &&
Objects.equals(experience, that.experience) &&
Objects.equals(messageCount, that.messageCount) &&
Objects.equals(currentLevel, that.currentLevel) &&
Objects.equals(currentExperienceRole, that.currentExperienceRole);
}
@Override
public int hashCode() {
return Objects.hash(id, user, experience, messageCount, currentLevel, currentExperienceRole);
}
}

View File

@@ -5,10 +5,11 @@ import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
/**
* Table used to store mutes in order to track when the mute was cast and when it ended.
@@ -20,7 +21,10 @@ import java.util.Objects;
@NoArgsConstructor
@Getter
@Setter
public class Mute {
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Mute implements Serializable {
/**
* The globally unique id of the mute.
@@ -102,26 +106,4 @@ public class Mute {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Mute mute = (Mute) o;
return Objects.equals(muteId, mute.muteId) &&
Objects.equals(mutedUser, mute.mutedUser) &&
Objects.equals(mutingUser, mute.mutingUser) &&
Objects.equals(reason, mute.reason) &&
Objects.equals(muteDate, mute.muteDate) &&
Objects.equals(muteTargetDate, mute.muteTargetDate) &&
Objects.equals(muteEnded, mute.muteEnded) &&
Objects.equals(messageId, mute.messageId) &&
Objects.equals(server, mute.server) &&
Objects.equals(mutingChannel, mute.mutingChannel) &&
Objects.equals(triggerKey, mute.triggerKey);
}
@Override
public int hashCode() {
return Objects.hash(muteId, mutedUser, mutingUser, reason, muteDate, muteTargetDate, muteEnded, messageId, server, mutingChannel, triggerKey);
}
}

View File

@@ -3,10 +3,11 @@ 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 org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
/**
* Represents a role to be used for muting users on a certain server
@@ -18,7 +19,10 @@ import java.util.Objects;
@Table(name = "mute_role")
@Getter
@Setter
public class MuteRole {
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class MuteRole implements Serializable {
/**
* The abstracto unique id of this mute role.
@@ -59,18 +63,4 @@ public class MuteRole {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MuteRole muteRole = (MuteRole) o;
return Objects.equals(id, muteRole.id) &&
Objects.equals(roleServer, muteRole.roleServer) &&
Objects.equals(role, muteRole.role);
}
@Override
public int hashCode() {
return Objects.hash(id, roleServer, role);
}
}

View File

@@ -4,8 +4,10 @@ import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
@Entity
@@ -15,7 +17,10 @@ import java.time.Instant;
@NoArgsConstructor
@Getter
@Setter
public class UserNote {
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class UserNote implements Serializable {
@EmbeddedId
private ServerSpecificId userNoteId;

View File

@@ -4,10 +4,11 @@ import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
/**
* A warning which was given a member with a special reason by a moderating member. This warning is bound to a server.
@@ -17,7 +18,10 @@ import java.util.Objects;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Warning {
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Warning implements Serializable {
/**
* The globally unique id of this warning
@@ -94,23 +98,4 @@ public class Warning {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Warning warning = (Warning) o;
return Objects.equals(warnId, warning.warnId) &&
Objects.equals(warnedUser, warning.warnedUser) &&
Objects.equals(warningUser, warning.warningUser) &&
Objects.equals(reason, warning.reason) &&
Objects.equals(warnDate, warning.warnDate) &&
Objects.equals(decayed, warning.decayed) &&
Objects.equals(decayDate, warning.decayDate);
}
@Override
public int hashCode() {
return Objects.hash(warnId, warnedUser, warningUser, reason, warnDate, decayed, decayDate);
}
}

View File

@@ -0,0 +1,77 @@
package dev.sheldan.abstracto.modmail.listener;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.listener.MessageDeletedListener;
import dev.sheldan.abstracto.core.models.AServerAChannelAUser;
import dev.sheldan.abstracto.core.models.GuildChannelMember;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
import dev.sheldan.abstracto.modmail.models.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class ModMailMessageDeletedListener implements MessageDeletedListener {
@Autowired
private ModMailMessageManagementService modMailMessageManagementService;
@Autowired
private MessageService messageService;
@Autowired
private ModMailMessageDeletedListener self;
@Autowired
private BotService botService;
@Override
public void execute(CachedMessage messageBefore, AServerAChannelAUser authorUser, GuildChannelMember authorMember) {
Optional<ModMailMessage> messageOptional = modMailMessageManagementService.getByMessageIdOptional(messageBefore.getMessageId());
messageOptional.ifPresent(modMailMessage -> {
ModMailThread thread = modMailMessage.getThreadReference();
Long dmMessageId = modMailMessage.getCreatedMessageInDM();
boolean hasMessageInChannel = modMailMessage.getCreatedMessageInChannel() != null;
Long channelMessage = modMailMessage.getCreatedMessageInChannel();
Long channelId = thread.getChannel().getId();
Long serverId = thread.getServer().getId();
log.info("Deleting message for mod mail thread {} in channel {} in server {}.", thread.getId(), channelId, serverId);
botService.getMemberInServerAsync(messageBefore.getServerId(), modMailMessage.getThreadReference().getUser().getUserReference().getId()).thenAccept(member -> {
CompletableFuture<Void> dmDeletePromise = messageService.deleteMessageInChannelWithUser(member.getUser(), dmMessageId);
CompletableFuture<Void> channelDeletePromise;
if(hasMessageInChannel) {
channelDeletePromise = messageService.deleteMessageInChannelInServer(serverId, channelId, channelMessage);
} else {
channelDeletePromise = CompletableFuture.completedFuture(null);
}
CompletableFuture.allOf(dmDeletePromise, channelDeletePromise).thenAccept(unused ->
self.removeMessageFromThread(messageBefore.getMessageId())
);
});
});
}
@Transactional
public void removeMessageFromThread(Long messageId) {
Optional<ModMailMessage> messageOptional = modMailMessageManagementService.getByMessageIdOptional(messageId);
messageOptional.ifPresent(modMailMessage ->
modMailMessageManagementService.deleteMessageFromThread(modMailMessage)
);
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MOD_MAIL;
}
}

View File

@@ -0,0 +1,132 @@
package dev.sheldan.abstracto.modmail.listener;
import dev.sheldan.abstracto.core.command.config.Parameters;
import dev.sheldan.abstracto.core.command.service.CommandRegistry;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.listener.MessageTextUpdatedListener;
import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
import dev.sheldan.abstracto.modmail.models.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.models.template.ModMailModeratorReplyModel;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.ModMailThreadServiceBean;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
import dev.sheldan.abstracto.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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class ModMailMessageEditedListener implements MessageTextUpdatedListener {
public static final String DEFAULT_COMMAND_FOR_MODMAIL_EDIT = "reply";
@Autowired
private ModMailMessageManagementService modMailMessageManagementService;
@Autowired
private CommandService commandService;
@Autowired
private BotService botService;
@Autowired
private TemplateService templateService;
@Autowired
private ModMailMessageEditedListener self;
@Autowired
private ChannelService channelService;
@Autowired
private MessageService messageService;
@Autowired
private CommandRegistry commandRegistry;
@Autowired
private ModMailThreadService modMailThreadService;
@Override
public void execute(CachedMessage messageBefore, Message messageAfter) {
if(!modMailThreadService.isModMailThread(messageBefore.getChannelId())) {
return;
}
Optional<ModMailMessage> messageOptional = modMailMessageManagementService.getByMessageIdOptional(messageBefore.getMessageId());
messageOptional.ifPresent(modMailMessage -> {
log.info("Editing send message {} in channel {} in mod mail thread {} in server {}.", messageBefore.getMessageId(), messageBefore.getChannelId(), modMailMessage.getThreadReference().getId(), messageBefore.getServerId());
String contentStripped = messageAfter.getContentRaw();
String commandName = commandRegistry.getCommandName(contentStripped.substring(0, contentStripped.indexOf(" ")), messageBefore.getServerId());
if(!commandService.doesCommandExist(commandName)) {
commandName = DEFAULT_COMMAND_FOR_MODMAIL_EDIT;
log.info("Edit did not contain the original command to retrieve the parameters for. Resulting to {}.", DEFAULT_COMMAND_FOR_MODMAIL_EDIT);
}
CompletableFuture<Parameters> parameterParseFuture = commandService.getParametersForCommand(commandName, messageAfter);
CompletableFuture<Member> loadTargetUser = botService.getMemberInServerAsync(messageBefore.getServerId(), modMailMessage.getThreadReference().getUser().getUserReference().getId());
CompletableFuture<Member> loadEditingUser = botService.getMemberInServerAsync(messageBefore.getServerId(), modMailMessage.getAuthor().getUserReference().getId());
CompletableFuture.allOf(parameterParseFuture, loadTargetUser, loadEditingUser).thenAccept(unused ->
self.updateMessageInThread(messageAfter, parameterParseFuture.join(), loadTargetUser.join(), loadEditingUser.join())
);
});
}
@Transactional
public void updateMessageInThread(Message messageAfter, Parameters parameters, Member targetMember, Member editingUser) {
String newText = (String) parameters.getParameters().get(0);
Optional<ModMailMessage> messageOptional = modMailMessageManagementService.getByMessageIdOptional(messageAfter.getIdLong());
messageOptional.ifPresent(modMailMessage -> {
FullUserInServer fullThreadUser = FullUserInServer
.builder()
.aUserInAServer(modMailMessage.getThreadReference().getUser())
.member(targetMember)
.build();
ModMailModeratorReplyModel.ModMailModeratorReplyModelBuilder modMailModeratorReplyModelBuilder = ModMailModeratorReplyModel
.builder()
.text(newText)
.modMailThread(modMailMessage.getThreadReference())
.postedMessage(messageAfter)
.anonymous(modMailMessage.getAnonymous())
.threadUser(fullThreadUser);
if(modMailMessage.getAnonymous()) {
modMailModeratorReplyModelBuilder.moderator(botService.getBotInGuild(modMailMessage.getThreadReference().getServer()));
} else {
modMailModeratorReplyModelBuilder.moderator(editingUser);
}
ModMailModeratorReplyModel modMailUserReplyModel = modMailModeratorReplyModelBuilder.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(ModMailThreadServiceBean.MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY, modMailUserReplyModel);
Long threadId = modMailMessage.getThreadReference().getId();
long serverId = editingUser.getGuild().getIdLong();
if(modMailMessage.getCreatedMessageInChannel() != null) {
AChannel channel = modMailMessage.getThreadReference().getChannel();
log.trace("Editing message {} in mod mail channel {} for thread {} in server {} as well.", modMailMessage.getCreatedMessageInChannel(), channel.getId(), threadId, serverId);
channelService.editMessageInAChannel(messageToSend, channel, modMailMessage.getCreatedMessageInChannel());
}
log.trace("Editing message {} in DM channel with user {} for thread {} in server {}.", modMailMessage.getCreatedMessageInDM(), targetMember.getUser().getIdLong(), threadId, serverId);
messageService.editMessageInDMChannel(targetMember.getUser(), messageToSend, modMailMessage.getCreatedMessageInDM());
});
if(!messageOptional.isPresent()) {
log.warn("Message {} of user {} in channel {} for server {} for thread about user {} could not be found in the mod mail messages when updating the text.",
messageAfter.getIdLong(), editingUser.getIdLong(), messageAfter.getChannel().getIdLong(), messageAfter.getGuild().getIdLong(), targetMember.getIdLong());
}
}
@Override
public FeatureEnum getFeature() {
return ModMailFeatures.MOD_MAIL;
}
}

View File

@@ -8,6 +8,7 @@ import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
import java.util.Optional;
/**
* Repository to manage the stored {@link ModMailMessage} instances
@@ -16,4 +17,7 @@ import java.util.List;
public interface ModMailMessageRepository extends JpaRepository<ModMailMessage, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<ModMailMessage> findByThreadReference(ModMailThread modMailThread);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<ModMailMessage> findByMessageId(Long messageId);
}

View File

@@ -23,7 +23,7 @@ import java.util.Optional;
public interface ModMailThreadRepository extends JpaRepository<ModMailThread, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
ModMailThread findByChannel(AChannel channel);
Optional<ModMailThread> findByChannel(AChannel channel);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<ModMailThread> findByUser(AUserInAServer aUserInAServer);

View File

@@ -38,7 +38,6 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
modMailMessages.forEach(modMailMessage -> {
ServerChannelMessageUser.ServerChannelMessageUserBuilder serverChannelMessageBuilder = ServerChannelMessageUser
.builder()
.messageId(modMailMessage.getMessageId())
.userId(modMailMessage.getAuthor().getUserReference().getId())
.serverId(thread.getServer().getId());
// if its not from a private chat, we need to set channel ID in order to fetch the data
@@ -46,6 +45,9 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
log.trace("Message {} was from DM.", modMailMessage.getMessageId());
serverChannelMessageBuilder
.channelId(modMailMessage.getThreadReference().getChannel().getId());
serverChannelMessageBuilder.messageId(modMailMessage.getCreatedMessageInChannel());
} else {
serverChannelMessageBuilder.messageId(modMailMessage.getCreatedMessageInDM());
}
messageIds.add(serverChannelMessageBuilder.build());
});

View File

@@ -17,6 +17,7 @@ import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.modmail.config.*;
import dev.sheldan.abstracto.modmail.exception.ModMailCategoryIdException;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadChannelNotFound;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadNotFoundException;
import dev.sheldan.abstracto.modmail.models.dto.LoadedModmailThreadMessageList;
import dev.sheldan.abstracto.modmail.models.database.*;
@@ -57,6 +58,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* The template key used for default mod mail exceptions
*/
public static final String MODMAIL_EXCEPTION_GENERIC_TEMPLATE = "modmail_exception_generic";
public static final String MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY = "modmail_staff_message";
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@@ -184,7 +186,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
ModMailThread thread = createThreadObject(channel, aUserInAServer);
if(initialMessage != null) {
log.trace("Adding initial message {} to modmail thread in channel {}.", initialMessage.getId(), channel.getId());
modMailMessageManagementService.addMessageToThread(thread, sendMessage, aUserInAServer, false, false);
modMailMessageManagementService.addMessageToThread(thread, null, sendMessage, initialMessage, aUserInAServer, false, false);
}
}
@@ -327,22 +329,22 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
@Override
public CompletableFuture<Message> relayMessageToModMailThread(ModMailThread modMailThread, Message message, List<UndoActionInstance> undoActions) {
public CompletableFuture<Message> relayMessageToModMailThread(ModMailThread modMailThread, Message messageFromUser, List<UndoActionInstance> undoActions) {
Long serverId = modMailThread.getServer().getId();
Long channelId = modMailThread.getChannel().getId();
Long modmailThreadId = modMailThread.getId();
log.trace("Relaying message {} to modmail thread {} for user {} to server {}.", message.getId(), modMailThread.getId(), message.getAuthor().getIdLong(), modMailThread.getServer().getId());
return botService.getMemberInServerAsync(modMailThread.getServer().getId(), message.getAuthor().getIdLong()).thenCompose(member -> {
log.trace("Relaying message {} to modmail thread {} for user {} to server {}.", messageFromUser.getId(), modMailThread.getId(), messageFromUser.getAuthor().getIdLong(), modMailThread.getServer().getId());
return botService.getMemberInServerAsync(modMailThread.getServer().getId(), messageFromUser.getAuthor().getIdLong()).thenCompose(member -> {
Optional<TextChannel> textChannelFromServer = botService.getTextChannelFromServerOptional(serverId, channelId);
if(textChannelFromServer.isPresent()) {
TextChannel textChannel = textChannelFromServer.get();
return self.sendUserReply(textChannel, modmailThreadId, message, member, true);
return self.sendUserReply(textChannel, modmailThreadId, messageFromUser, member, true);
} else {
log.warn("Closing modmail thread {}, because it seems the channel {} in server {} got deleted.", modmailThreadId, channelId, serverId);
log.warn("Closing mod mail thread {}, because it seems the channel {} in server {} got deleted.", modmailThreadId, channelId, serverId);
// in this case there was no text channel on the server associated with the mod mail thread
// close the existing one, so the user can start a new one
self.closeModMailThreadInDb(modmailThreadId);
return message.getChannel().sendMessage(templateService.renderTemplate("modmail_failed_to_forward_message", new Object())).submit();
return messageFromUser.getChannel().sendMessage(templateService.renderTemplate("modmail_failed_to_forward_message", new Object())).submit();
}
});
@@ -354,12 +356,12 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* side.
* @param textChannel The {@link TextChannel} in which the {@link ModMailThread} is being handled
* @param modMailThreadId The id of the modmail thread to which the received {@link Message} is a reply to, can be null, if it is null, its the initial message
* @param message The received message from the user
* @param messageFromUser The received message from the user
* @param member The {@link Member} instance from the user the thread is about. It is used as author
* @param modMailThreadExists Whether or not the modmail thread already exists and is persisted.
* @return A {@link CompletableFuture} which resolves when the post processing of the message is completed (adding read notification, and storing messageIDs)
*/
public CompletableFuture<Message> sendUserReply(TextChannel textChannel, Long modMailThreadId, Message message, Member member, boolean modMailThreadExists) {
public CompletableFuture<Message> sendUserReply(TextChannel textChannel, Long modMailThreadId, Message messageFromUser, Member member, boolean modMailThreadExists) {
List<CompletableFuture<Member>> subscriberMemberFutures = new ArrayList<>();
if(modMailThreadExists) {
ModMailThread modMailThread = modMailThreadManagementService.getById(modMailThreadId);
@@ -378,7 +380,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
List<FullUserInServer> subscribers = new ArrayList<>();
ModMailUserReplyModel modMailUserReplyModel = ModMailUserReplyModel
.builder()
.postedMessage(message)
.postedMessage(messageFromUser)
.member(member)
.subscribers(subscribers)
.build();
@@ -387,11 +389,11 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]))
.thenCompose(aVoid -> {
log.trace("Adding read reaction to initial message for mod mail thread in channel {}.", textChannel.getGuild().getId());
return messageService.addReactionToMessageWithFuture("readReaction", textChannel.getGuild().getIdLong(), message);
return messageService.addReactionToMessageWithFuture("readReaction", textChannel.getGuild().getIdLong(), messageFromUser);
})
.thenApply(aVoid -> {
if(modMailThreadExists) {
self.postProcessSendMessages(textChannel, completableFutures.get(0).join());
self.postProcessSendMessages(textChannel, completableFutures.get(0).join(), messageFromUser);
}
return completableFutures.get(0).join();
});
@@ -403,26 +405,28 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* This message handles the post processing of the messages received by the user. This includes: saving the messageIDs
* in the database, updating the state of the {@link ModMailThread} and adding the read reaction to the user message
* @param message The actual {@link Message} instance received from the user.
* @param textChannel The channel in which the message
* @param messageInModMailThread The actual {@link Message} instance which was sent to the mod mail thread
* @param messageFromUser The {@link Message} object which was sent from the user
*/
@Transactional
public void postProcessSendMessages(TextChannel textChannel, Message message) {
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByIdOptional(textChannel.getIdLong());
public void postProcessSendMessages(TextChannel textChannel, Message messageInModMailThread, Message messageFromUser) {
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByChannelIdOptional(textChannel.getIdLong());
if(modMailThreadOpt.isPresent()) {
ModMailThread modMailThread = modMailThreadOpt.get();
log.trace("Adding message {} sent from user to modmail thread {} and setting status to {}.", message.getId(), modMailThread.getId(), ModMailThreadState.USER_REPLIED);
modMailMessageManagementService.addMessageToThread(modMailThread, message, modMailThread.getUser(), false, false);
log.trace("Adding created message {} based on messeage {} sent from user to modmail thread {} and setting status to {}.", messageInModMailThread.getId(), messageFromUser.getId(), modMailThread.getId(), ModMailThreadState.USER_REPLIED);
modMailMessageManagementService.addMessageToThread(modMailThread, null, messageInModMailThread, messageFromUser, modMailThread.getUser(), false, false);
// update the state of the thread
modMailThreadManagementService.setModMailThreadState(modMailThread, ModMailThreadState.USER_REPLIED);
} else {
throw new ModMailThreadNotFoundException(textChannel.getIdLong());
throw new ModMailThreadChannelNotFound();
}
}
@Override
public CompletableFuture<Void> relayMessageToDm(Long modmailThreadId, String text, Message message, boolean anonymous, MessageChannel feedBack, List<UndoActionInstance> undoActions, Member targetMember) {
log.info("Relaying message {} to user {} in modmail thread {} on server {}.", message.getId(), targetMember.getId(), modmailThreadId, targetMember.getGuild().getId());
AUserInAServer moderator = userInServerManagementService.loadUser(message.getMember());
public CompletableFuture<Void> relayMessageToDm(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, MessageChannel feedBack, List<UndoActionInstance> undoActions, Member targetMember) {
log.info("Relaying message {} to user {} in modmail thread {} on server {}.", replyCommandMessage.getId(), targetMember.getId(), modmailThreadId, targetMember.getGuild().getId());
AUserInAServer moderator = userInServerManagementService.loadUser(replyCommandMessage.getMember());
ModMailThread modMailThread = modMailThreadManagementService.getById(modmailThreadId);
FullUserInServer fullThreadUser = FullUserInServer
.builder()
@@ -433,7 +437,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.builder()
.text(text)
.modMailThread(modMailThread)
.postedMessage(message)
.postedMessage(replyCommandMessage)
.anonymous(anonymous)
.threadUser(fullThreadUser);
if(anonymous) {
@@ -445,9 +449,16 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
modMailModeratorReplyModelBuilder.moderator(moderatorMember);
}
ModMailModeratorReplyModel modMailUserReplyModel = modMailModeratorReplyModelBuilder.build();
CompletableFuture<Message> future = messageService.sendEmbedToUserWithMessage(targetMember.getUser(), "modmail_staff_message", modMailUserReplyModel);
return future.thenAccept(sendMessage ->
self.saveSendMessagesAndUpdateState(modmailThreadId, anonymous, moderator, sendMessage)
MessageToSend messageToSend = templateService.renderEmbedTemplate(MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY, modMailUserReplyModel);
CompletableFuture<Message> future = messageService.sendMessageToSendToUser(targetMember.getUser(), messageToSend);
CompletableFuture<Message> sameThreadMessageFuture;
if(featureModeService.featureModeActive(ModMailFeatures.MOD_MAIL, modMailThread.getServer(), ModMailMode.SEPARATE_MESSAGE)) {
sameThreadMessageFuture = channelService.sendMessageToSendToAChannel(messageToSend, modMailThread.getChannel()).get(0);
} else {
sameThreadMessageFuture = CompletableFuture.completedFuture(null);
}
return CompletableFuture.allOf(future, sameThreadMessageFuture).thenAccept(avoid ->
self.saveSendMessagesAndUpdateState(modmailThreadId, anonymous, moderator, future.join(), replyCommandMessage, sameThreadMessageFuture.join())
);
}
@@ -481,6 +492,17 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
}
@Override
public boolean isModMailThread(AChannel channel) {
return modMailThreadManagementService.getByChannelOptional(channel).isPresent();
}
@Override
public boolean isModMailThread(Long channelId) {
AChannel channel = channelManagementService.loadChannel(channelId);
return modMailThreadManagementService.getByChannelOptional(channel).isPresent();
}
/**
* This method takes the actively loaded futures, calls the method responsible for logging the messages, and calls the method
* after the logging has been done.
@@ -603,7 +625,13 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
log.info("Logging message {} in modmail thread {}.", loadedMessage.getId(), modMailThreadId);
ModMailMessage modmailMessage = modMailThread.getMessages()
.stream()
.filter(modMailMessage -> modMailMessage.getMessageId().equals(loadedMessage.getIdLong()))
.filter(modMailMessage -> {
if(modMailMessage.getDmChannel()) {
return modMailMessage.getCreatedMessageInDM().equals(loadedMessage.getIdLong());
} else {
return modMailMessage.getCreatedMessageInChannel().equals(loadedMessage.getIdLong());
}
})
.findFirst().orElseThrow(() -> new AbstractoRunTimeException("Could not find desired message in list of messages in thread. This should not happen, as we just retrieved them from the same place."));
Member author = futures.getMemberFuture().join();
ModMailLoggedMessageModel modMailLoggedMessageModel =
@@ -688,12 +716,12 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @throws ModMailThreadNotFoundException in case the {@link ModMailThread} is not found by the ID
*/
@Transactional
public void saveSendMessagesAndUpdateState(Long modMailThreadId, Boolean anonymous, AUserInAServer moderator, Message message) {
public void saveSendMessagesAndUpdateState(Long modMailThreadId, Boolean anonymous, AUserInAServer moderator, Message createdMessageInDM, Message replyCommandMessage, Message modMailThreadMessage) {
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByIdOptional(modMailThreadId);
if(modMailThreadOpt.isPresent()) {
ModMailThread modMailThread = modMailThreadOpt.get();
log.trace("Adding (anonymous: {}) message {} of moderator to modmail thread {} and setting state to {}.", anonymous, message.getId(), modMailThreadId, ModMailThreadState.MOD_REPLIED);
modMailMessageManagementService.addMessageToThread(modMailThread, message, moderator, anonymous, true);
log.trace("Adding (anonymous: {}) message {} of moderator to modmail thread {} and setting state to {}.", anonymous, createdMessageInDM.getId(), modMailThreadId, ModMailThreadState.MOD_REPLIED);
modMailMessageManagementService.addMessageToThread(modMailThread, createdMessageInDM, modMailThreadMessage, replyCommandMessage, moderator, anonymous, true);
modMailThreadManagementService.setModMailThreadState(modMailThread, ModMailThreadState.MOD_REPLIED);
} else {
throw new ModMailThreadNotFoundException(modMailThreadId);

View File

@@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
@Slf4j
@@ -19,17 +20,21 @@ public class ModMailMessageManagementServiceBean implements ModMailMessageManage
private ModMailMessageRepository modMailMessageRepository;
@Override
public ModMailMessage addMessageToThread(ModMailThread modMailThread, Message message, AUserInAServer author, Boolean anonymous, Boolean dmChannel) {
public ModMailMessage addMessageToThread(ModMailThread modMailThread, Message createdMessageInDM, Message createdMessageInChannel, Message userPostedMessage, AUserInAServer author, Boolean anonymous, Boolean dmChannel) {
Long dmId = createdMessageInDM != null ? createdMessageInDM.getIdLong() : null;
Long channelMessageId = createdMessageInChannel != null ? createdMessageInChannel.getIdLong() : null;
ModMailMessage modMailMessage = ModMailMessage
.builder()
.author(author)
.messageId(message.getIdLong())
.messageId(userPostedMessage.getIdLong())
.createdMessageInDM(dmId)
.createdMessageInChannel(channelMessageId)
.dmChannel(dmChannel)
.threadReference(modMailThread)
.anonymous(anonymous)
.build();
log.info("Storing modmail thread message {} to modmail thread {} of user {} in server {}.",
message.getId(), modMailThread.getId(), author.getUserReference().getId(), author.getServerReference().getId());
log.info("Storing created message in DM {} with created message in channel {} caused by message {} to modmail thread {} of user {} in server {}.",
dmId, channelMessageId, userPostedMessage.getId(), modMailThread.getId(), author.getUserReference().getId(), author.getServerReference().getId());
modMailMessageRepository.save(modMailMessage);
return modMailMessage;
@@ -39,4 +44,14 @@ public class ModMailMessageManagementServiceBean implements ModMailMessageManage
public List<ModMailMessage> getMessagesOfThread(ModMailThread modMailThread) {
return modMailMessageRepository.findByThreadReference(modMailThread);
}
@Override
public Optional<ModMailMessage> getByMessageIdOptional(Long messageId) {
return modMailMessageRepository.findByMessageId(messageId);
}
@Override
public void deleteMessageFromThread(ModMailMessage modMailMessage) {
modMailMessageRepository.delete(modMailMessage);
}
}

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadChannelNotFound;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadNotFoundException;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import dev.sheldan.abstracto.modmail.models.database.ModMailThreadState;
@@ -44,9 +45,20 @@ public class ModMailThreadManagementServiceBean implements ModMailThreadManageme
@Override
public ModMailThread getByChannel(AChannel channel) {
return modMailThreadRepository.findByChannel(channel).orElseThrow(ModMailThreadChannelNotFound::new);
}
@Override
public Optional<ModMailThread> getByChannelOptional(AChannel channel) {
return modMailThreadRepository.findByChannel(channel);
}
@Override
public Optional<ModMailThread> getByChannelIdOptional(Long channelId) {
AChannel channel = channelManagementService.loadChannel(channelId);
return getByChannelOptional(channel);
}
@Override
public List<ModMailThread> getThreadByUserAndState(AUserInAServer userInAServer, ModMailThreadState state) {
return modMailThreadRepository.findByUserAndState(userInAServer, state);

View File

@@ -10,10 +10,16 @@
<property name="today" value="(SELECT NOW())"/>
<changeSet author="Sheldan" id="modmail_default_feature_mode-insertion">
<insert tableName="default_feature_mode">
<column name="enabled" value="false"/>
<column name="enabled" value="true"/>
<column name="mode" value="log"/>
<column name="feature_id" valueComputed="${modmailFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="default_feature_mode">
<column name="enabled" value="true"/>
<column name="mode" value="threadMessage"/>
<column name="feature_id" valueComputed="${modmailFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -6,10 +6,10 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="modmail_messages-table">
<createTable tableName="modmail_messages">
<changeSet author="Sheldan" id="modmail_message-table">
<createTable tableName="modmail_message">
<column name="message_id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="modmail_messages_pkey"/>
<constraints nullable="false" primaryKey="true" primaryKeyName="modmail_message_pkey"/>
</column>
<column name="anonymous" type="BOOLEAN"/>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
@@ -22,10 +22,10 @@
</column>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="modmail_messages-fk_modmail_message_thread">
<addForeignKeyConstraint baseColumnNames="thread_reference" baseTableName="modmail_messages" constraintName="fk_modmail_message_thread" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="modmail_thread" validate="true"/>
<changeSet author="Sheldan" id="modmail_message-fk_modmail_message_thread">
<addForeignKeyConstraint baseColumnNames="thread_reference" baseTableName="modmail_message" constraintName="fk_modmail_message_thread" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="modmail_thread" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="modmail_messages-fk_modmail_message_author">
<addForeignKeyConstraint baseColumnNames="modmail_message_author" baseTableName="modmail_messages" constraintName="fk_modmail_message_author" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<changeSet author="Sheldan" id="modmail_message-fk_modmail_message_author">
<addForeignKeyConstraint baseColumnNames="modmail_message_author" baseTableName="modmail_message" constraintName="fk_modmail_message_author" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -6,10 +6,10 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="modmail_roles-table">
<createTable tableName="modmail_roles">
<changeSet author="Sheldan" id="modmail_role-table">
<createTable tableName="modmail_role">
<column autoIncrement="true" name="mod_mail_role_id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="modmail_roles_pkey"/>
<constraints nullable="false" primaryKey="true" primaryKeyName="modmail_role_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
@@ -21,10 +21,10 @@
</column>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="modmail_roles-fk_modmail_role_role">
<addForeignKeyConstraint baseColumnNames="modmail_role" baseTableName="modmail_roles" constraintName="fk_modmail_role_role" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="role" validate="true"/>
<changeSet author="Sheldan" id="modmail_role-fk_modmail_role_role">
<addForeignKeyConstraint baseColumnNames="modmail_role" baseTableName="modmail_role" constraintName="fk_modmail_role_role" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="role" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="modmail_roles-fk_modmail_role_server">
<addForeignKeyConstraint baseColumnNames="modmail_role_server" baseTableName="modmail_roles" constraintName="fk_modmail_role_server" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="server" validate="true"/>
<changeSet author="Sheldan" id="modmail_role-fk_modmail_role_server">
<addForeignKeyConstraint baseColumnNames="modmail_role_server" baseTableName="modmail_role" constraintName="fk_modmail_role_server" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="server" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -6,8 +6,8 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<include file="modmail_threads.xml" relativeToChangelogFile="true"/>
<include file="modmail_messages.xml" relativeToChangelogFile="true"/>
<include file="modmail_thread.xml" relativeToChangelogFile="true"/>
<include file="modmail_message.xml" relativeToChangelogFile="true"/>
<include file="modmail_subscriber.xml" relativeToChangelogFile="true"/>
<include file="modmail_roles.xml" relativeToChangelogFile="true"/>
<include file="modmail_role.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

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

View File

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

View File

@@ -48,7 +48,7 @@ public class ModMailFeature implements FeatureConfig {
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(ModMailMode.LOGGING);
return Arrays.asList(ModMailMode.LOGGING, ModMailMode.SEPARATE_MESSAGE);
}
@Override

View File

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

View File

@@ -0,0 +1,6 @@
package dev.sheldan.abstracto.modmail.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
public class ModMailThreadChannelNotFound extends AbstractoRunTimeException {
}

View File

@@ -5,6 +5,7 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
/**
@@ -16,19 +17,30 @@ import java.time.Instant;
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "modmail_messages")
@Cacheable
@Table(name = "modmail_message")
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ModMailMessage {
public class ModMailMessage implements Serializable {
/**
* The globally unique message ID which was send in the mod mail thread, either by a user of by the staff handling the thread
* The ID of the message which caused this message to be created, either the message containing the command or the message received from the user
*/
@Id
private Long messageId;
/**
* The message which got created:
* for a message from the user, the messageId of the message in the thread
* for a message from staff, the messageId of the message in the DM channel
*/
@Column(name = "created_message_in_dm")
private Long createdMessageInDM;
@Column
private Long createdMessageInChannel;
/**
* The {@link AUserInAServer} which authored this message
*/
@@ -39,18 +51,21 @@ public class ModMailMessage {
/**
* The {@link ModMailThread} in whose context this message was sent and this message is related to
*/
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "threadReference", nullable = false)
private ModMailThread threadReference;
/**
* Whether or not this message was from the user or a staff member, for convenience
* true: message was send via command, false: message was send from the user
* This is used to decide where to get the message from in case of logging, because the user might delete the message and we do not want to re-parse the command message
*/
@Column
private Boolean dmChannel;
/**
* Staff only: Whether or not this message meant to be sent anonymous
*/
@Column
private Boolean anonymous;
@Column(name = "created")

View File

@@ -6,6 +6,7 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
/**
@@ -16,12 +17,13 @@ import java.time.Instant;
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "modmail_roles")
@Cacheable
@Table(name = "modmail_role")
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ModMailRole {
public class ModMailRole implements Serializable {
/**
* Unique ID of the mod mail role

View File

@@ -5,6 +5,7 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@@ -18,11 +19,12 @@ import java.util.List;
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "modmail_thread")
@Cacheable
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ModMailThread {
public class ModMailThread implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@@ -16,9 +16,10 @@ import java.time.Instant;
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "modmail_subscriber")
@Cacheable
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ModMailThreadSubscriber {

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.modmail.service;
import dev.sheldan.abstracto.core.models.UndoActionInstance;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
@@ -50,9 +51,9 @@ public interface ModMailThreadService {
* In case there was no channel found, this will cause a message to be shown to the user and the existing mod mail thread will be closed.
* This is the case, if the mod mail thread was still open in the database, but no text channel was found anymore.
* @param modMailThread The {@link ModMailThread} on which the user answered
* @param message The {@link Message} object which was sent by the user to answer with
* @param messageFromUser The {@link Message} object which was sent by the user as an answer
*/
CompletableFuture<Message> relayMessageToModMailThread(ModMailThread modMailThread, Message message, List<UndoActionInstance> undoActions);
CompletableFuture<Message> relayMessageToModMailThread(ModMailThread modMailThread, Message messageFromUser, List<UndoActionInstance> undoActions);
/**
* Forwards a message send by a moderator to the direct message channel opened with the user. If the message is
@@ -91,4 +92,7 @@ public interface ModMailThreadService {
* @param logThread Whether or not the thread should be logged to the appropriate post target
*/
CompletableFuture<Void> closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, boolean logThread, List<UndoActionInstance> undoActions);
boolean isModMailThread(AChannel channel);
boolean isModMailThread(Long channelId);
}

View File

@@ -6,6 +6,7 @@ import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
import net.dv8tion.jda.api.entities.Message;
import java.util.List;
import java.util.Optional;
/**
* Management service to handle the creation and retrieval of {@link ModMailMessage} instances from the database
@@ -14,13 +15,15 @@ public interface ModMailMessageManagementService {
/**
* Creates an instance of {@link ModMailMessage}, attaches it to the given {@link ModMailThread} and returns the created instance
* @param modMailThread The {@link ModMailThread} the message should be attached to
* @param message The {@link Message} which should be attached to the {@link ModMailThread}
* @param createdMessageInDM The {@link Message} which should be attached to the {@link ModMailThread} and was posted to the DM channel (might be null)
* @param createdMessageInChannel The {@link Message} which should be attached to the {@link ModMailThread} and was posted to the modmail thread (might be null)
* @param userPostedMessage The {@link Message} which caused this message to be created, the command or the message by the user
* @param author The {@link AUserInAServer} who authored the {@link Message} originally
* @param anonymous Whether or not the message was sent anonymous (only possible by staff members)
* @param dmChannel Whether or not the message originated from the user, and therefore in an direct message channel
* @return
*/
ModMailMessage addMessageToThread(ModMailThread modMailThread, Message message, AUserInAServer author, Boolean anonymous, Boolean dmChannel);
ModMailMessage addMessageToThread(ModMailThread modMailThread, Message createdMessageInDM, Message createdMessageInChannel, Message userPostedMessage, AUserInAServer author, Boolean anonymous, Boolean dmChannel);
/**
* Retrieves all messages which were sent in a {@link ModMailThread}
@@ -28,4 +31,8 @@ public interface ModMailMessageManagementService {
* @return A list of {@link ModMailMessage} which were sent in the given thread
*/
List<ModMailMessage> getMessagesOfThread(ModMailThread modMailThread);
Optional<ModMailMessage> getByMessageIdOptional(Long messageId);
void deleteMessageFromThread(ModMailMessage modMailMessage);
}

View File

@@ -45,6 +45,8 @@ public interface ModMailThreadManagementService {
* @return The found mod mail thread, or null if none was found
*/
ModMailThread getByChannel(AChannel channel);
Optional<ModMailThread> getByChannelOptional(AChannel channel);
Optional<ModMailThread> getByChannelIdOptional(Long channelId);
/**
* Searches for mod mail threads with the appropriate staten which concern the given {@link AUserInAServer}

View File

@@ -7,8 +7,8 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name="embedded_message")
@@ -17,9 +17,10 @@ import java.util.Objects;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EmbeddedMessage {
public class EmbeddedMessage implements Serializable {
@Getter
@ManyToOne
@@ -66,23 +67,5 @@ public class EmbeddedMessage {
this.created = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmbeddedMessage that = (EmbeddedMessage) o;
return Objects.equals(embeddedUser, that.embeddedUser) &&
Objects.equals(embeddingUser, that.embeddingUser) &&
Objects.equals(embeddedServer, that.embeddedServer) &&
Objects.equals(embeddedChannel, that.embeddedChannel) &&
Objects.equals(embeddedMessageId, that.embeddedMessageId) &&
Objects.equals(embeddingServer, that.embeddingServer) &&
Objects.equals(embeddingChannel, that.embeddingChannel) &&
Objects.equals(embeddingMessageId, that.embeddingMessageId);
}
@Override
public int hashCode() {
return Objects.hash(embeddedUser, embeddingUser, embeddedServer, embeddedChannel, embeddedMessageId, embeddingServer, embeddingChannel, embeddingMessageId);
}
}

View File

@@ -7,8 +7,8 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name="reminder")
@@ -17,9 +17,10 @@ import java.util.Objects;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Reminder {
public class Reminder implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -73,24 +74,4 @@ public class Reminder {
@Setter
private String jobTriggerKey;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Reminder reminder = (Reminder) o;
return reminded == reminder.reminded &&
Objects.equals(id, reminder.id) &&
Objects.equals(remindedUser, reminder.remindedUser) &&
Objects.equals(messageId, reminder.messageId) &&
Objects.equals(channel, reminder.channel) &&
Objects.equals(server, reminder.server) &&
Objects.equals(reminderDate, reminder.reminderDate) &&
Objects.equals(targetDate, reminder.targetDate) &&
Objects.equals(text, reminder.text);
}
@Override
public int hashCode() {
return Objects.hash(id, remindedUser, messageId, channel, server, reminderDate, targetDate, text, reminded);
}
}

View File

@@ -6,9 +6,9 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name="starboard_post")
@@ -17,9 +17,10 @@ import java.util.Objects;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class StarboardPost {
public class StarboardPost implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -88,25 +89,4 @@ public class StarboardPost {
return this.reactions.size();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StarboardPost post = (StarboardPost) o;
return ignored == post.ignored &&
Objects.equals(id, post.id) &&
Objects.equals(author, post.author) &&
Objects.equals(starboardMessageId, post.starboardMessageId) &&
Objects.equals(postMessageId, post.postMessageId) &&
Objects.equals(starboardChannel, post.starboardChannel) &&
Objects.equals(sourceChanel, post.sourceChanel) &&
Objects.equals(reactionCount, post.reactionCount) &&
Objects.equals(reactions, post.reactions) &&
Objects.equals(starredDate, post.starredDate);
}
@Override
public int hashCode() {
return Objects.hash(id, author, starboardMessageId, postMessageId, starboardChannel, sourceChanel, reactionCount, reactions, starredDate, ignored);
}
}

View File

@@ -5,8 +5,8 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name="starboard_post_reaction")
@@ -15,9 +15,10 @@ import java.util.Objects;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class StarboardPostReaction {
public class StarboardPostReaction implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -40,18 +41,4 @@ public class StarboardPostReaction {
this.created = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StarboardPostReaction that = (StarboardPostReaction) o;
return Objects.equals(id, that.id) &&
Objects.equals(reactor, that.reactor) &&
Objects.equals(starboardPost, that.starboardPost);
}
@Override
public int hashCode() {
return Objects.hash(id, reactor, starboardPost);
}
}

View File

@@ -5,8 +5,10 @@ import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.utility.models.SuggestionState;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@@ -17,7 +19,10 @@ import java.util.Objects;
@NoArgsConstructor
@Getter
@Setter
public class Suggestion {
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Suggestion implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@@ -2,11 +2,14 @@ package dev.sheldan.abstracto.core.command.service;
import com.google.common.collect.Iterables;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.CommandReceivedHandler;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.condition.ConditionResult;
import dev.sheldan.abstracto.core.command.condition.ConditionalCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.Parameters;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.UnParsedCommandParameter;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
import dev.sheldan.abstracto.core.command.models.database.ACommandInAServer;
import dev.sheldan.abstracto.core.command.models.database.AModule;
@@ -19,10 +22,12 @@ import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
@@ -44,6 +49,12 @@ public class CommandServiceBean implements CommandService {
@Autowired
private CommandInServerManagementService commandInServerManagementService;
@Autowired
private CommandRegistry commandRegistry;
@Autowired
private CommandReceivedHandler commandReceivedHandler;
@Override
public ACommand createCommand(String name, String moduleName, FeatureEnum featureEnum) {
AModule module = moduleManagementService.getOrCreate(moduleName);
@@ -151,6 +162,14 @@ public class CommandServiceBean implements CommandService {
}
}
@Override
public CompletableFuture<Parameters> getParametersForCommand(String commandName, Message messageContainingContent) {
String contentStripped = messageContainingContent.getContentRaw();
UnParsedCommandParameter unParsedParameter = new UnParsedCommandParameter(contentStripped);
Command command = commandRegistry.findCommandByParameters(commandName, unParsedParameter);
return commandReceivedHandler.getParsedParameters(unParsedParameter, command, messageContainingContent);
}
private ConditionResult checkConditions(CommandContext commandContext, Command command, List<CommandCondition> conditions) {
if(conditions != null) {
for (CommandCondition condition : conditions) {

View File

@@ -6,6 +6,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name = "lock")
@@ -13,7 +14,7 @@ import javax.persistence.*;
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class ALock {
public class ALock implements Serializable {
@Id
@Column(name = "id")
private Long id;

View File

@@ -130,10 +130,7 @@ public class BotServiceBean implements BotService {
Optional<TextChannel> textChannelOptional = getTextChannelFromServerOptional(serverId, channelId);
if(textChannelOptional.isPresent()) {
TextChannel textChannel = textChannelOptional.get();
return textChannel.deleteMessageById(messageId).submit().exceptionally(throwable -> {
log.warn("Deleting the message {} in channel {} in guild {} failed.", messageId, channelId, serverId, throwable);
return null;
});
return textChannel.deleteMessageById(messageId).submit();
} else {
log.warn("Could not find channel {} in guild {} to delete message {} in.", channelId, serverId, messageId);
}

View File

@@ -275,9 +275,25 @@ public class MessageServiceBean implements MessageService {
channelService.sendEmbedTemplateInChannel(template, model, privateChannel).get(0));
}
@Override
public CompletableFuture<Message> sendMessageToSendToUser(User user, MessageToSend messageToSend) {
return user.openPrivateChannel().submit().thenCompose(privateChannel -> channelService.sendMessageToSendToChannel(messageToSend, privateChannel).get(0));
}
@Override
public CompletableFuture<Message> sendMessageToUser(User user, String text) {
log.trace("Sending direct string message to user {}.", user.getIdLong());
return user.openPrivateChannel().flatMap(privateChannel -> privateChannel.sendMessage(text)).submit();
}
@Override
public CompletableFuture<Void> deleteMessageInChannelWithUser(User user, Long messageId) {
log.info("Deleting message {} in channel with user {}.", messageId, user.getIdLong());
return user.openPrivateChannel().flatMap(privateChannel -> privateChannel.deleteMessageById(messageId)).submit();
}
@Override
public CompletableFuture<Void> editMessageInDMChannel(User user, MessageToSend messageToSend, Long messageId) {
return user.openPrivateChannel().submit().thenCompose(privateChannel -> channelService.editMessageInAChannelFuture(messageToSend, privateChannel, messageId).thenApply(message -> null));
}
}

View File

@@ -7,7 +7,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name = "command")
@@ -16,6 +15,7 @@ import java.util.Objects;
@AllArgsConstructor
@Getter
@Cacheable
@EqualsAndHashCode
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ACommand implements Serializable {
@Id
@@ -53,19 +53,4 @@ public class ACommand implements Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ACommand aCommand = (ACommand) o;
return Objects.equals(id, aCommand.id) &&
Objects.equals(name, aCommand.name) &&
Objects.equals(module, aCommand.module) &&
Objects.equals(feature, aCommand.feature);
}
@Override
public int hashCode() {
return Objects.hash(id, name, module, feature);
}
}

View File

@@ -8,7 +8,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Entity(name = "command_in_server")
@Getter
@@ -16,6 +15,7 @@ import java.util.Objects;
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ACommandInAServer implements Serializable {
@@ -49,23 +49,6 @@ public class ACommandInAServer implements Serializable {
@Column
private Boolean restricted;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ACommandInAServer that = (ACommandInAServer) o;
return Objects.equals(commandInServerId, that.commandInServerId) &&
Objects.equals(commandReference, that.commandReference) &&
Objects.equals(serverReference, that.serverReference) &&
Objects.equals(allowedRoles, that.allowedRoles) &&
Objects.equals(immuneRoles, that.immuneRoles) &&
Objects.equals(restricted, that.restricted);
}
@Override
public int hashCode() {
return Objects.hash(commandInServerId, commandReference, serverReference, allowedRoles, immuneRoles, restricted);
}
}

View File

@@ -1,9 +1,6 @@
package dev.sheldan.abstracto.core.command.models.database;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
@@ -11,7 +8,6 @@ import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name = "module")
@@ -19,6 +15,7 @@ import java.util.Objects;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AModule implements Serializable {
@@ -54,18 +51,4 @@ public class AModule implements Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AModule aModule = (AModule) o;
return Objects.equals(id, aModule.id) &&
Objects.equals(name, aModule.name) &&
Objects.equals(commands, aModule.commands);
}
@Override
public int hashCode() {
return Objects.hash(id, name, commands);
}
}

View File

@@ -2,11 +2,15 @@ package dev.sheldan.abstracto.core.command.service;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.condition.ConditionResult;
import dev.sheldan.abstracto.core.command.config.Parameters;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import net.dv8tion.jda.api.entities.Message;
import java.util.concurrent.CompletableFuture;
public interface CommandService {
ACommand createCommand(String name, String moduleName, FeatureEnum featureEnum);
@@ -21,4 +25,5 @@ public interface CommandService {
void disAllowCommandForRole(ACommand aCommand, ARole role);
void disAllowFeatureForRole(FeatureEnum featureEnum, ARole role);
ConditionResult isCommandExecutable(Command command, CommandContext commandContext);
CompletableFuture<Parameters> getParametersForCommand(String commandName, Message messageContainingContent);
}

View File

@@ -8,13 +8,13 @@ import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name="channel")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AChannel implements SnowFlake, Serializable {
@@ -55,20 +55,5 @@ public class AChannel implements SnowFlake, Serializable {
@Transient
private boolean fake;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AChannel channel = (AChannel) o;
return Objects.equals(id, channel.id) &&
Objects.equals(groups, channel.groups) &&
Objects.equals(server, channel.server) &&
type == channel.type &&
Objects.equals(deleted, channel.deleted);
}
@Override
public int hashCode() {
return Objects.hash(id, groups, server, type, deleted);
}
}

View File

@@ -7,7 +7,6 @@ import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name="channelGroup")
@@ -15,6 +14,7 @@ import java.util.Objects;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AChannelGroup implements Serializable {
@@ -49,19 +49,5 @@ public class AChannelGroup implements Serializable {
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<AChannel> channels;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AChannelGroup that = (AChannelGroup) o;
return Objects.equals(id, that.id) &&
Objects.equals(groupName, that.groupName) &&
Objects.equals(server, that.server) &&
Objects.equals(channels, that.channels);
}
@Override
public int hashCode() {
return Objects.hash(id, groupName, server, channels);
}
}

View File

@@ -6,7 +6,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name = "channel_group_command")
@@ -14,6 +13,7 @@ import java.util.Objects;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AChannelGroupCommand implements Serializable {
@@ -35,19 +35,4 @@ public class AChannelGroupCommand implements Serializable {
@Setter
private Boolean enabled;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AChannelGroupCommand that = (AChannelGroupCommand) o;
return Objects.equals(commandInGroupId, that.commandInGroupId) &&
Objects.equals(command, that.command) &&
Objects.equals(group, that.group) &&
Objects.equals(enabled, that.enabled);
}
@Override
public int hashCode() {
return Objects.hash(commandInGroupId, command, group, enabled);
}
}

View File

@@ -6,7 +6,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name="systemConfig")
@@ -14,6 +13,7 @@ import java.util.Objects;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AConfig implements Serializable {
@@ -70,20 +70,5 @@ public class AConfig implements Serializable {
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AConfig config = (AConfig) o;
return Objects.equals(id, config.id) &&
Objects.equals(name, config.name) &&
Objects.equals(stringValue, config.stringValue) &&
Objects.equals(doubleValue, config.doubleValue) &&
Objects.equals(server, config.server);
}
@Override
public int hashCode() {
return Objects.hash(id, name, stringValue, doubleValue, server);
}
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.models.database;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
@@ -12,6 +13,9 @@ import java.time.Instant;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ADefaultConfig implements Serializable {
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@@ -6,7 +6,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name = "emote")
@@ -14,6 +13,7 @@ import java.util.Objects;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AEmote implements Serializable {
@@ -71,22 +71,4 @@ public class AEmote implements Serializable {
@Transient
private boolean fake;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AEmote emote = (AEmote) o;
return Objects.equals(id, emote.id) &&
Objects.equals(name, emote.name) &&
Objects.equals(emoteKey, emote.emoteKey) &&
Objects.equals(emoteId, emote.emoteId) &&
Objects.equals(animated, emote.animated) &&
Objects.equals(custom, emote.custom) &&
Objects.equals(serverRef, emote.serverRef);
}
@Override
public int hashCode() {
return Objects.hash(id, name, emoteKey, emoteId, animated, custom, serverRef);
}
}

View File

@@ -9,13 +9,13 @@ import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name="feature")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AFeature implements SnowFlake, Serializable {
@@ -52,18 +52,5 @@ public class AFeature implements SnowFlake, Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AFeature feature = (AFeature) o;
return Objects.equals(id, feature.id) &&
Objects.equals(key, feature.key) &&
Objects.equals(commands, feature.commands);
}
@Override
public int hashCode() {
return Objects.hash(id, key, commands);
}
}

View File

@@ -7,13 +7,13 @@ import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name="feature_flag")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AFeatureFlag implements Serializable {
@@ -62,19 +62,5 @@ public class AFeatureFlag implements Serializable {
this.updateTimestamp = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AFeatureFlag that = (AFeatureFlag) o;
return enabled == that.enabled &&
Objects.equals(id, that.id) &&
Objects.equals(server, that.server) &&
Objects.equals(feature, that.feature);
}
@Override
public int hashCode() {
return Objects.hash(id, server, feature, enabled);
}
}

View File

@@ -12,6 +12,7 @@ import java.time.Instant;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AFeatureMode implements Serializable {

View File

@@ -7,13 +7,13 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name="role")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ARole implements SnowFlake, Serializable {
@@ -52,21 +52,6 @@ public class ARole implements SnowFlake, Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ARole role = (ARole) o;
return Objects.equals(id, role.id) &&
Objects.equals(server, role.server) &&
Objects.equals(deleted, role.deleted);
}
@Override
public int hashCode() {
return Objects.hash(id, server, deleted);
}
public String getAsMention() {
return "<@&" + getId() + '>';
}

View File

@@ -9,7 +9,6 @@ import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name = "server")
@@ -17,6 +16,7 @@ import java.util.Objects;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AServer implements SnowFlake, Serializable {
@@ -89,16 +89,4 @@ public class AServer implements SnowFlake, Serializable {
private List<AEmote> emotes = new ArrayList<>();
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AServer aServer = (AServer) o;
return Objects.equals(id, aServer.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@@ -7,7 +7,6 @@ import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name="auser")
@@ -15,6 +14,7 @@ import java.util.Objects;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AUser implements Serializable {
@@ -45,17 +45,4 @@ public class AUser implements Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AUser user = (AUser) o;
return Objects.equals(id, user.id) &&
Objects.equals(servers, user.servers);
}
@Override
public int hashCode() {
return Objects.hash(id, servers);
}
}

View File

@@ -6,7 +6,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name = "user_in_server")
@@ -15,6 +14,7 @@ import java.util.Objects;
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AUserInAServer implements Serializable {
@@ -47,18 +47,4 @@ public class AUserInAServer implements Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AUserInAServer that = (AUserInAServer) o;
return Objects.equals(userInServerId, that.userInServerId) &&
Objects.equals(userReference, that.userReference) &&
Objects.equals(serverReference, that.serverReference);
}
@Override
public int hashCode() {
return Objects.hash(userInServerId, userReference, serverReference);
}
}

View File

@@ -5,6 +5,7 @@ import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name = "counter")
@@ -13,9 +14,10 @@ import javax.persistence.*;
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Counter {
public class Counter implements Serializable {
@EmbeddedId
private CounterId counterId;

View File

@@ -6,7 +6,6 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name = "default_emote")
@@ -14,6 +13,7 @@ import java.util.Objects;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class DefaultEmote implements Serializable {
@@ -45,20 +45,5 @@ public class DefaultEmote implements Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DefaultEmote that = (DefaultEmote) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(emoteKey, that.emoteKey) &&
Objects.equals(created, that.created) &&
Objects.equals(updated, that.updated);
}
@Override
public int hashCode() {
return Objects.hash(id, name, emoteKey, created, updated);
}
}

View File

@@ -6,13 +6,13 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name="default_feature_flag")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class DefaultFeatureFlag implements Serializable {
@@ -53,21 +53,5 @@ public class DefaultFeatureFlag implements Serializable {
this.updateTimestamp = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DefaultFeatureFlag that = (DefaultFeatureFlag) o;
return enabled == that.enabled &&
Objects.equals(id, that.id) &&
Objects.equals(feature, that.feature) &&
Objects.equals(mode, that.mode) &&
Objects.equals(created, that.created) &&
Objects.equals(updateTimestamp, that.updateTimestamp);
}
@Override
public int hashCode() {
return Objects.hash(id, feature, enabled, mode, created, updateTimestamp);
}
}

View File

@@ -13,8 +13,8 @@ import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Cacheable
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class DefaultFeatureMode implements Serializable {

View File

@@ -6,13 +6,13 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name="default_posttarget")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class DefaultPostTarget implements Serializable {

View File

@@ -6,13 +6,13 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
@Entity
@Table(name="posttarget")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class PostTarget implements Serializable {
@@ -51,19 +51,5 @@ public class PostTarget implements Serializable {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PostTarget that = (PostTarget) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(channelReference, that.channelReference) &&
Objects.equals(serverReference, that.serverReference);
}
@Override
public int hashCode() {
return Objects.hash(id, name, channelReference, serverReference);
}
}

View File

@@ -3,10 +3,7 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.models.database.AChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.*;
import java.util.List;
import java.util.Optional;

View File

@@ -39,5 +39,8 @@ public interface MessageService {
CompletableFuture<Message> sendTemplateToUser(User user, String template, Object model);
CompletableFuture<Void> sendEmbedToUser(User user, String template, Object model);
CompletableFuture<Message> sendEmbedToUserWithMessage(User user, String template, Object model);
CompletableFuture<Message> sendMessageToSendToUser(User user, MessageToSend messageToSend);
CompletableFuture<Message> sendMessageToUser(User user, String text);
CompletableFuture<Void> deleteMessageInChannelWithUser(User user, Long messageId);
CompletableFuture<Void> editMessageInDMChannel(User user, MessageToSend messageToSend, Long messageId);
}

View File

@@ -1,6 +1,9 @@
=== Mod mail
This feature enables users to contact the moderation of the server in a private manner. This can be initiated by messaging the Abstracto bot.
The messages, in the channel which is created to contain the mod mail thread, are not automatically sent to the user, but only when using the commands
`reply` or `anonReply`. Any other message is ignored with the intention of enabling discussions within the channel. In case the message of a message sent to the user
needs to be updated or deleted, you can do simply by editing/deleting the message containing the command.
Feature key: `modmail`
@@ -27,6 +30,8 @@ Feature key: `modmail`
==== Feature modes
`log`:: If this is enabled, the messages should be logged into the `modmailLog` post target when the thread is closed (by the respective commands). This is required for the command `closeNoLog` to be available. Enabled by default.
`threadMessage`:: if this is enabled, every message which is send via the commands `reply` and `anonReply` will also be sent to the thread in order to have a visualizer how the message looks
and to have a clear indication which messages were sent. Enabled by default.
==== Emotes

View File

@@ -2,9 +2,10 @@ package dev.sheldan.abstracto.scheduling.model.database;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.util.Objects;
import java.io.Serializable;
/**
* The scheduler job instance according to the properties stored in the database. This is needed in order to have a
@@ -17,7 +18,10 @@ import java.util.Objects;
@Table(name = "scheduler_job")
@NoArgsConstructor
@AllArgsConstructor
public class SchedulerJob {
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class SchedulerJob implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -53,22 +57,4 @@ public class SchedulerJob {
*/
private boolean recovery;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SchedulerJob that = (SchedulerJob) o;
return active == that.active &&
recovery == that.recovery &&
Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(groupName, that.groupName) &&
Objects.equals(clazz, that.clazz) &&
Objects.equals(cronExpression, that.cronExpression);
}
@Override
public int hashCode() {
return Objects.hash(id, name, groupName, clazz, cronExpression, active, recovery);
}
}

View File

@@ -1,9 +1,7 @@
package dev.sheldan.abstracto.templating.model.database;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
@@ -13,6 +11,9 @@ import javax.persistence.*;
@AllArgsConstructor
@Table(name = "auto_load_macro")
@Getter
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AutoLoadMacro {
@Id

View File

@@ -1,14 +1,11 @@
package dev.sheldan.abstracto.templating.model.database;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
/**
* Represents the template stored in the database.
@@ -18,9 +15,10 @@ import java.util.Objects;
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "template")
@EqualsAndHashCode
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Template {
public class Template implements Serializable {
/**
* The globally unique key of the template
@@ -64,19 +62,4 @@ public class Template {
this.updated = Instant.now();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Template template = (Template) o;
return Objects.equals(key, template.key) &&
Objects.equals(content, template.content) &&
Objects.equals(section, template.section) &&
Objects.equals(lastModified, template.lastModified);
}
@Override
public int hashCode() {
return Objects.hash(key, content, section, lastModified);
}
}