Compare commits

...

20 Commits

Author SHA1 Message Date
release-bot
826bee1f81 [maven-release-plugin] prepare release v1.5.39 2024-08-02 20:09:26 +00:00
Sheldan
99bf9a9be0 [AB-xxx] fixed send message action sending for every level change 2024-07-31 00:11:57 +02:00
Sheldan
5b5e4973a7 [AB-117] increasing length of supported reminder text 2024-07-30 21:24:44 +02:00
Sheldan
65e956827c [AB-xxx] adding generic input format exception handling
adding level_action to send a message to a channel once a level is reached
2024-07-27 19:51:49 +02:00
release-bot
b258a8bc54 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-06-13 16:33:09 +00:00
release-bot
9864b7d875 [maven-release-plugin] prepare for next development iteration 2024-06-13 16:21:34 +00:00
release-bot
27466b7333 [maven-release-plugin] prepare release v1.5.38 2024-06-13 16:21:31 +00:00
Sheldan
bfb8969d1f [AB-xxx] fixing passing wrong ID in contact slash command 2024-06-13 18:18:51 +02:00
Sheldan
0097ff801a [AB-xxx] changing hikari max live time 2024-06-12 20:07:55 +02:00
Sheldan
b6a188c04d [AB-xxx] using the suggestion message as the thread starter for suggestion threads 2024-06-03 22:24:35 +02:00
release-bot
2fa1adde02 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-05-30 23:29:17 +00:00
release-bot
cb8b64cc01 [maven-release-plugin] prepare for next development iteration 2024-05-30 23:19:23 +00:00
release-bot
3b3dd0dbb7 [maven-release-plugin] prepare release v1.5.37 2024-05-30 23:19:22 +00:00
Sheldan
388fead2a6 [AB-xxx] typo change
java doc update
removal of todo
2024-05-31 01:14:26 +02:00
Sheldan
336c3d0bd8 [AB-xxx] adding modmail support for ban appeals
refactoring modmail to use user objects instead of member in various places
2024-05-31 01:14:26 +02:00
Sheldan
4991ad8f1c [AB-xxx] making intents, cache policies, and cache flags configurable 2024-05-18 18:22:33 +02:00
Sheldan
c5136a1808 [AB-xxx] removing example implementation repository from readme 2024-05-17 17:34:33 +02:00
release-bot
446d882eec Commit from GitHub Actions (Publishes a new version of abstracto) 2024-05-05 22:24:18 +00:00
release-bot
b3e207a967 [maven-release-plugin] prepare for next development iteration 2024-05-05 22:12:03 +00:00
release-bot
5c25345cf8 [maven-release-plugin] prepare release v1.5.36 2024-05-05 22:12:01 +00:00
140 changed files with 936 additions and 747 deletions

2
.env
View File

@@ -1,2 +1,2 @@
REGISTRY_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.5.35
VERSION=1.5.38

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assignable-roles-int</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>dynamic-activity</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>dynamic-activity</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -48,7 +48,7 @@ public class AddMemberToChannelLevelAction implements LevelActionListener {
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction) {
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}

View File

@@ -48,7 +48,7 @@ public class AddRoleLevelAction implements LevelActionListener {
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction) {
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}

View File

@@ -48,7 +48,7 @@ public class RemoveMemberFromChannelLevelAction implements LevelActionListener {
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction) {
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}

View File

@@ -49,7 +49,7 @@ public class RemoveRoleLevelAction implements LevelActionListener {
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction) {
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}

View File

@@ -0,0 +1,106 @@
package dev.sheldan.abstracto.experience.listener;
import com.google.gson.Gson;
import dev.sheldan.abstracto.core.exception.InputFormatException;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.experience.model.LevelActionPayload;
import dev.sheldan.abstracto.experience.model.SendMessageToChannelLevelActionMessageModel;
import dev.sheldan.abstracto.experience.model.SendMessageToChannelLevelActionPayload;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LevelAction;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class SendMessageToChannelLevelAction implements LevelActionListener {
public static final String ACTION_NAME = "send_message_to_channel_above_level";
private static final String LEVEL_ACTION_SEND_MESSAGE_TEMPLATE_KEY = "levelAction_sendMessageToChannel_template";
@Autowired
private Gson gson;
@Autowired
private ChannelService channelService;
@Autowired
private TemplateService templateService;
@Autowired
private MemberService memberService;
@Override
public String getName() {
return ACTION_NAME;
}
@Override
public void apply(AUserExperience userExperience, LevelAction levelAction, MemberActionModification container) {
SendMessageToChannelLevelActionPayload payload = (SendMessageToChannelLevelActionPayload) levelAction.getLoadedPayload();
SendMessageToChannelLevelActionMessageModel.SendMessageToChannelLevelActionMessageModelBuilder messageModelBuilder = SendMessageToChannelLevelActionMessageModel
.builder()
.level(userExperience.getLevelOrDefault())
.templateKey(payload.getTemplateKey())
.experience(userExperience.getExperience());
ServerUser serverUser = ServerUser.fromAUserInAServer(userExperience.getUser());
memberService.getMemberInServerAsync(serverUser).thenAccept(member -> {
messageModelBuilder.memberDisplay(MemberDisplay.fromMember(member));
SendMessageToChannelLevelActionMessageModel model = messageModelBuilder.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(LEVEL_ACTION_SEND_MESSAGE_TEMPLATE_KEY, model, serverUser.getServerId());
GuildMessageChannel targetChannel = channelService.getMessageChannelFromServer(serverUser.getServerId(), payload.getChannelId());
FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, targetChannel)).thenAccept(unused -> {
log.info("Send message to channel action sent a message to channel {} for user {} in server {}.", payload.getChannelId(), serverUser.getUserId(), serverUser.getServerId());
}).exceptionally(throwable -> {
log.warn("Send message to channel action failed to send a message to channel {} for user {} in server {}.", payload.getChannelId(), serverUser.getUserId(), serverUser.getServerId(), throwable);
return null;
});
}).exceptionally(throwable -> {
log.warn("Failed to load member {} in server {} for send message level action towards channel {}.", serverUser.getUserId(), serverUser.getServerId(), payload.getChannelId());
return null;
});
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
if(!oldLevel.equals(aUserExperience.getLevelOrDefault())) { // this means the user changed level now, this is the path from gaining a lot of experience
boolean jumpedLevelToMatch = oldLevel < levelAction.getLevel().getLevel() && aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
// this boolean means that the user did NOT have the action earlier, but does now (and more than that)
return jumpedLevelToMatch || aUserExperience.getLevelOrDefault().equals(levelAction.getLevel().getLevel()); // or the user matches the level _exactly_, this is the path from normally gaining experience
} else {
// This case is useful for re-joining, because this means, that the user did _not_ change level, and already is somewhere way above
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}
}
@Override
public void prepareAction(LevelAction levelAction) {
levelAction.setLoadedPayload(gson.fromJson(levelAction.getPayload(), SendMessageToChannelLevelActionPayload.class));
}
@Override
public LevelActionPayload createPayload(Guild guild, String input) {
if(!input.contains(";")) {
throw new InputFormatException(input, "<#channel>;template_key");
}
String channelPart = input.substring(0, input.indexOf(";"));
GuildChannel channel = ParseUtils.parseGuildChannelFromText(channelPart, guild);
String templateKey = input.substring(input.indexOf(";") + 1);
return SendMessageToChannelLevelActionPayload
.builder()
.channelId(channel.getIdLong())
.templateKey(templateKey)
.build();
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.experience.model;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class SendMessageToChannelLevelActionMessageModel implements LevelActionPayload {
private MemberDisplay memberDisplay;
private Integer level;
private Long experience;
private String templateKey;
}

View File

@@ -0,0 +1,11 @@
package dev.sheldan.abstracto.experience.model;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class SendMessageToChannelLevelActionPayload implements LevelActionPayload {
private Long channelId;
private String templateKey;
}

View File

@@ -337,8 +337,8 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
.builder()
.build();
boolean userChangesLevel = !Objects.equals(newLevel.getLevel(), aUserExperience.getCurrentLevel().getLevel());
Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0;
if(userChangesLevel) {
Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0;
log.info("User {} in server {} changed level. New {}, Old {}.", member.getIdLong(),
member.getGuild().getIdLong(), newLevel.getLevel(),
oldLevel);
@@ -373,7 +373,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
}
aUserExperience.setMessageCount(aUserExperience.getMessageCount() + 1L);
if(userChangesLevel && featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, server, ExperienceFeatureMode.LEVEL_ACTION)) {
levelActionService.applyLevelActionsToUser(aUserExperience)
levelActionService.applyLevelActionsToUser(aUserExperience, oldLevel)
.thenAccept(unused -> {
log.info("Executed level actions for user {}.", userInServerId);
})

View File

@@ -49,6 +49,11 @@ public class LevelActionServiceBean implements LevelActionService {
@Override
public CompletableFuture<Void> applyLevelActionsToUser(AUserExperience user) {
return applyLevelActionsToUser(user, user.getLevelOrDefault());
}
@Override
public CompletableFuture<Void> applyLevelActionsToUser(AUserExperience user, Integer oldLevel) {
if(levelActions == null || levelActions.isEmpty()) {
return CompletableFuture.completedFuture(null);
}
@@ -60,8 +65,16 @@ public class LevelActionServiceBean implements LevelActionService {
Map<Integer, List<LevelAction>> actionConfigMap = new HashMap<>();
Map<String, LevelActionListener> actionStringListenerMap = levelActions
.stream()
.collect(Collectors.toMap(a -> a.getName().toLowerCase(), Function.identity()));
levelActionsOfUserInServer.forEach(levelAction -> {
if(levelAction.getLevel().getLevel() > user.getLevelOrDefault()) {
LevelActionListener listener = actionStringListenerMap.get(levelAction.getAction());
if(listener == null) { // if for some reason the config is still in the database, but we don't have code for it anymore
return;
}
if(!listener.shouldExecute(user, oldLevel, levelAction)) {
return;
}
if(actionConfigMap.containsKey(levelAction.getLevel().getLevel())) {
@@ -73,9 +86,6 @@ public class LevelActionServiceBean implements LevelActionService {
}
});
Map<String, LevelActionListener> actionStringListenerMap = levelActions
.stream()
.collect(Collectors.toMap(a -> a.getName().toLowerCase(), Function.identity()));
List<Integer> levels = actionConfigMap
.keySet()

View File

@@ -0,0 +1,84 @@
package dev.sheldan.abstracto.experience.listener;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LevelAction;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class AddRoleLevelActionTest {
@InjectMocks
private AddRoleLevelAction action;
@Mock
private AUserExperience exp;
@Mock
private LevelAction levelAction;
@Mock
private AExperienceLevel level;
private final Integer LOW_LEVEL = 1;
private final Integer MID_LEVEL = 2;
private final Integer HIGH_LEVEL = 3;
@Before
public void setup() {
when(levelAction.getLevel()).thenReturn(level);
}
@Test // rejoin too low case
public void noLevelChangeActionNotReached() {
executeTest(LOW_LEVEL, LOW_LEVEL, HIGH_LEVEL, false);
}
@Test // re-join exact case
public void noLevelChangeActionReached() {
executeTest(LOW_LEVEL, LOW_LEVEL, LOW_LEVEL, true);
}
@Test // normal leveling, higher action
public void levelChangeActionNotReached() {
executeTest(LOW_LEVEL, MID_LEVEL, HIGH_LEVEL, false);
}
@Test // normal leveling
public void levelChangeActionReached() {
executeTest(LOW_LEVEL, MID_LEVEL, MID_LEVEL, true);
}
@Test // a case for this is a large experience jump
public void levelChangeActionOverJumped() {
executeTest(LOW_LEVEL, HIGH_LEVEL, MID_LEVEL, true);
}
@Test // a case for this is a re-join
public void noLevelChangeActionOverJumped() {
executeTest(HIGH_LEVEL, HIGH_LEVEL, LOW_LEVEL, true);
}
@Test // we dont want to re-execute previous actions (previous = lower level)
public void levelChangeActionEqualsPrevious() {
executeTest(LOW_LEVEL, MID_LEVEL, LOW_LEVEL, true);
}
@Test // we dont want to re-execute previous actions (previous = way lower level)
public void levelChangeActionBelow() {
executeTest(MID_LEVEL, HIGH_LEVEL, LOW_LEVEL, true);
}
private void executeTest(Integer oldLevel, Integer currentLevel, Integer actionLevel, boolean expected) {
when(exp.getLevelOrDefault()).thenReturn(currentLevel);
when(level.getLevel()).thenReturn(actionLevel);
Assertions.assertThat(action.shouldExecute(exp, oldLevel, levelAction)).isEqualTo(expected);
}
}

View File

@@ -0,0 +1,86 @@
package dev.sheldan.abstracto.experience.listener;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LevelAction;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class RemoveMemberFromChannelLevelActionTest {
@InjectMocks
private SendMessageToChannelLevelAction action;
@Mock
private AUserExperience exp;
@Mock
private LevelAction levelAction;
@Mock
private AExperienceLevel level;
private final Integer LOW_LEVEL = 1;
private final Integer MID_LEVEL = 2;
private final Integer HIGH_LEVEL = 3;
@Before
public void setup() {
when(levelAction.getLevel()).thenReturn(level);
}
@Test // rejoin too low case
public void noLevelChangeActionNotReached() {
executeTest(LOW_LEVEL, LOW_LEVEL, HIGH_LEVEL, false);
}
@Test // re-join exact case
public void noLevelChangeActionReached() {
executeTest(LOW_LEVEL, LOW_LEVEL, LOW_LEVEL, true);
}
@Test // normal leveling, higher action
public void levelChangeActionNotReached() {
executeTest(LOW_LEVEL, MID_LEVEL, HIGH_LEVEL, false);
}
@Test // normal leveling
public void levelChangeActionReached() {
executeTest(LOW_LEVEL, MID_LEVEL, MID_LEVEL, true);
}
@Test // a case for this is a large experience jump
public void levelChangeActionOverJumped() {
executeTest(LOW_LEVEL, HIGH_LEVEL, MID_LEVEL, true);
}
@Test // a case for this is a re-join
public void noLevelChangeActionOverJumped() {
executeTest(HIGH_LEVEL, HIGH_LEVEL, LOW_LEVEL, true);
}
@Test // we dont want to re-execute previous actions (previous = lower level)
public void levelChangeActionEqualsPrevious() {
executeTest(LOW_LEVEL, MID_LEVEL, LOW_LEVEL, false);
}
@Test // we dont want to re-execute previous actions (previous = way lower level)
public void levelChangeActionBelow() {
executeTest(MID_LEVEL, HIGH_LEVEL, LOW_LEVEL, false);
}
private void executeTest(Integer oldLevel, Integer currentLevel, Integer actionLevel, boolean expected) {
when(exp.getLevelOrDefault()).thenReturn(currentLevel);
when(level.getLevel()).thenReturn(actionLevel);
Assertions.assertThat(action.shouldExecute(exp, oldLevel, levelAction)).isEqualTo(expected);
}
}

View File

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

View File

@@ -4,15 +4,13 @@ import dev.sheldan.abstracto.experience.model.LevelActionPayload;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LevelAction;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.stereotype.Component;
@Component
public interface LevelActionListener {
String getName();
void apply(AUserExperience userExperience, LevelAction levelAction, MemberActionModification container);
boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction);
boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction);
void prepareAction(LevelAction levelAction);

View File

@@ -12,6 +12,7 @@ import java.util.concurrent.CompletableFuture;
public interface LevelActionService {
CompletableFuture<Void> applyLevelActionsToUser(AUserExperience user);
CompletableFuture<Void> applyLevelActionsToUser(AUserExperience user, Integer oldLevel);
List<String> getAvailableLevelActions();
Optional<LevelActionListener> getLevelActionListenerForName(String name);

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<artifactId>giveaway-impl</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<artifactId>giveaway-int</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<artifactId>giveaway</artifactId>

View File

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

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<artifactId>image-generation-int</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<artifactId>image-generation</artifactId>

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,7 +18,7 @@
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -38,6 +38,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>metrics-int</artifactId>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<property name="modmailModule" value="(SELECT id FROM module WHERE name = 'modmail')"/>
<property name="modmailFeature" value="(SELECT id FROM feature WHERE key = 'modmail')"/>
<changeSet author="Sheldan" id="modmail_denyModmailAppeal_command">
<insert tableName="command">
<column name="name" value="denyModmailAppeal"/>
<column name="module_id" valueComputed="${modmailModule}"/>
<column name="feature_id" valueComputed="${modmailFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -3,4 +3,5 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="1.0-modmail/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.37/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,6 +13,5 @@ public class ClosingContext {
private Boolean notifyUser;
private Boolean log;
private Member closingMember;
private Channel channel;
private String note;
}

View File

@@ -1,15 +1,15 @@
package dev.sheldan.abstracto.modmail.model.template;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@Getter
@Setter
@Builder
public class ContactNotificationModel {
private Member targetMember;
private UserDisplay userDisplay;
private MessageChannel createdChannel;
}

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.modmail.model.template;
import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import lombok.Builder;
import lombok.Getter;
@@ -18,10 +18,7 @@ import java.util.Map;
@Setter
@Builder
public class ModMailModeratorReplyModel {
/**
* A {@link FullUserInServer} reference representing the user the thread is about. The member attribute is null, if the user left the guild
*/
private FullUserInServer threadUser;
private UserDisplay userDisplay;
/**
* The staff {@link Member} which replied to the thread, be it anonymously or normal.
*/

View File

@@ -1,13 +1,12 @@
package dev.sheldan.abstracto.modmail.model.template;
import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.context.ServerContext;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.modmail.model.database.ModMailRole;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import java.util.List;
@@ -24,10 +23,7 @@ public class ModMailNotificationModel extends ServerContext {
* The created {@link ModMailThread} which was just created
*/
private ModMailThread modMailThread;
/**
* The {@link FullUserInServer} for which this thread is about
*/
private Member member;
private UserDisplay userDisplay;
/**
* A list of roles which will be notified upon creation of the mod mail thread.
*/

View File

@@ -1,12 +1,10 @@
package dev.sheldan.abstracto.modmail.model.template;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import java.time.Instant;
/**
* This is the model used when a new mod mail thread is opened and a message containing some information about the user
@@ -16,16 +14,12 @@ import java.time.Instant;
@Setter
@Builder
public class ModMailThreaderHeader {
/**
* A {@link Member} instance to retrieve information from
*/
private Member member;
private UserDisplay userDisplay;
/**
* The latest {@link ModMailThread}, before the current opened one. This is null if there is no closed mod mail thread
* for the user
*/
private ModMailThread latestModMailThread;
private Instant memberJoinDate;
/**
* The amount of previous mod mail thread the user has.
*/

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.modmail.model.template;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -16,10 +17,7 @@ import java.util.Map;
@Setter
@Builder
public class ModMailUserReplyModel {
/**
* The {@link Member} from which the message is and whose mod mail thread it is
*/
private Member member;
private UserDisplay userDisplay;
/**
* The {@link Message} which was posted, which contains all the possible information
*/

View File

@@ -22,16 +22,17 @@ public interface ModMailThreadService {
* Creates a new mod mail thread for the given user. including: the {@link net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel}
* in the appropriate {@link net.dv8tion.jda.api.entities.channel.concrete.Category} and calls the methods responsible for storing
* the necessary data in the database, notifying the users and sending messages related to the creation of the {@link ModMailThread}
* @param member The {@link AUserInAServer} to create the mod mail thread for
* @param user The {@link User} to create the mod mail thread for
* @param guild The {@link Guild} in which the mod mail thread should be created in
* @param initialMessage The initial message sparking this mod mail thread, null in case it was created by a command
* @param userInitiated Whether or not the mod mail thread was initiated by a user
* @param undoActions A list of {@link dev.sheldan.abstracto.core.models.UndoAction actions} to be undone in case the operation fails. This list will be filled in the method.
* @return A {@link CompletableFuture future} which completes when the modmail thread is set up
*/
CompletableFuture<MessageChannel> createModMailThreadForUser(Member member, Message initialMessage, boolean userInitiated, List<UndoActionInstance> undoActions);
CompletableFuture<MessageChannel> createModMailThreadForUser(User user, Guild guild, Message initialMessage, boolean userInitiated, List<UndoActionInstance> undoActions);
CompletableFuture<Void> sendContactNotification(Member member, MessageChannel createdMessageChannel, MessageChannel feedBackChannel);
CompletableFuture<Void> sendContactNotification(Member member, MessageChannel createdMessageChannel, InteractionHook interactionHook);
CompletableFuture<Void> sendContactNotification(User user, MessageChannel createdMessageChannel, MessageChannel feedBackChannel);
CompletableFuture<Void> sendContactNotification(User user, MessageChannel createdMessageChannel, InteractionHook interactionHook);
/**
* Changes the configuration value of the category used to create mod mail threads to the given ID.
@@ -68,10 +69,11 @@ public interface ModMailThreadService {
* @param text The parsed text of the reply
* @param message The pure {@link Message} containing the command which caused the reply
* @param anonymous Whether or nor the message should be send anonymous
* @param targetMember The {@link Member} the {@link ModMailThread} is about.
* @param targetUser The {@link User} the {@link ModMailThread} is about.
* @param guild The guild the reply is created in
* @return A {@link CompletableFuture future} which completes when the message has been relayed to the DM
*/
CompletableFuture<Void> loadExecutingMemberAndRelay(Long threadId, String text, Message message, boolean anonymous, Member targetMember);
CompletableFuture<Void> loadExecutingMemberAndRelay(Long threadId, String text, Message message, boolean anonymous, User targetUser, Guild guild);
/**
* Closes the mod mail thread which means: deletes the {@link net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel} associated with the mod mail thread,
@@ -98,4 +100,6 @@ public interface ModMailThreadService {
boolean isModMailThread(AChannel channel);
boolean isModMailThread(Long channelId);
CompletableFuture<Void> rejectAppeal(ModMailThread modMailThread, String reason, Member memberPerforming);
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>profanity-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>profanity-filter</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="reminder-resize_text">
<modifyDataType columnName="text"
newDataType="VARCHAR(4000)"
tableName="reminder"/>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -6,4 +6,5 @@
<include file="1.2.10-remind/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.12/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.3/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.39/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.5.36-SNAPSHOT</version>
<version>1.5.39</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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