Compare commits

...

38 Commits

Author SHA1 Message Date
Sheldan
6d27c487f2 [maven-release-plugin] prepare release abstracto-application-1.3.8 2021-09-06 01:53:55 +02:00
Sheldan
8ac3b327e4 [AB-268] adding button feature mode to suggestions which allows for hidden suggestion votes
moving gateway metric to separate service in case JDA is not ready yet
2021-09-06 01:39:27 +02:00
Sheldan
448332f24f [AB-318] adding a mention of the original message to link embed
updating JDA version
2021-09-04 16:30:16 +02:00
Sheldan
d7f889971d [AB-325] adding async command conditions - as this is required for some conditions
adding user parameter to immune user condition evaluation
2021-09-04 13:47:03 +02:00
Sheldan
69abff77fb [AB-xxx] fixing test 2021-08-21 16:05:01 +02:00
Sheldan
2c31fa1c1e [AB-xxx] not always creating a user instance in the experience listener
adding logging in case the warn decay notification fails
2021-08-21 15:50:11 +02:00
Sheldan
19a4858da1 [AB-323] improving logging when using whenComplete 2021-08-14 12:15:18 +02:00
Sheldan
3ed1f0c54a [AB-322] adding possibility to define a max age in days for messages to be eligible for stars 2021-08-14 09:45:25 +02:00
Sheldan
d01e46a9a6 [AB-xxx] fixing modmail sometimes failing to log messages 2021-08-14 08:02:04 +02:00
Sheldan
222250b795 [AB-321] changing caching policy
changing logging messages
2021-08-14 08:00:40 +02:00
Sheldan
9a87b75998 [maven-release-plugin] prepare for next development iteration 2021-08-09 23:52:49 +02:00
Sheldan
b16cef0d3c [maven-release-plugin] prepare release abstracto-application-1.3.7 2021-08-09 23:52:40 +02:00
Sheldan
cc55934ff2 [AB-xxx] fixing not removing component payloads for message embed cleanup job 2021-08-09 00:40:36 +02:00
Sheldan
168b4a52c8 [AB-xxx] changing some exception logging
fixing moderator member not re-used for reply command
2021-08-09 00:11:49 +02:00
Sheldan
73b5684a7e [maven-release-plugin] prepare for next development iteration 2021-07-26 01:37:38 +02:00
Sheldan
23ba9a88aa [maven-release-plugin] prepare release abstracto-application-1.3.6 2021-07-26 01:37:31 +02:00
Sheldan
a5664946e1 [AB-xxx] hotfix for component payload 2021-07-26 01:30:47 +02:00
Sheldan
ec16548cea [maven-release-plugin] prepare for next development iteration 2021-07-25 20:28:46 +02:00
Sheldan
9c9082034a [maven-release-plugin] prepare release abstracto-application-1.3.5 2021-07-25 20:28:37 +02:00
Sheldan
1486a0e9c3 [AB-314] forcing modmail messages being logged in certain order 2021-07-25 20:17:32 +02:00
Sheldan
533f1afcd5 [AB-313] removing restriction to string parameter for combined parameters 2021-07-25 17:28:42 +02:00
Sheldan
a6c3bb5aa2 [AB-313] ignoring referenced message parameter in parameter handler if not needed 2021-07-25 17:27:32 +02:00
Sheldan
5311cfcc2e [AB-xxx] fixing not showing assignable role actions in metrics 2021-07-25 15:18:54 +02:00
Sheldan
ee7f9180dc [AB-xxx] adding JDA metrics for all events
adding support to template environment variables in liquibase configuration
removing not needed column from component payload
2021-07-25 15:17:27 +02:00
Sheldan
3f67593ef4 [maven-release-plugin] prepare for next development iteration 2021-07-21 23:34:33 +02:00
Sheldan
0ccee3b211 [maven-release-plugin] prepare release abstracto-application-1.3.4 2021-07-21 23:34:25 +02:00
Sheldan
86b9ccb164 [AB-300] adding ability to determine the channel names for modmail 2021-07-21 01:38:04 +02:00
Sheldan
bc1eb0b55f [AB-305] fixing not correctly persisting removal of an assignable role from an assignable role place 2021-07-21 00:46:11 +02:00
Sheldan
92a8b5ba64 [AB-311] adding softban command 2021-07-20 02:02:42 +02:00
Sheldan
7535b2e66d [AB-xxx] make ban reason mandatory 2021-07-18 19:29:58 +02:00
Sheldan
7117ac26d3 [AB-308] adding a separate type for assignable role places to enable booster only places
adding more detailed logging to assignable roles
adding some fall through logic to the banned listener to always log at least the basic information
refactoring some command structure for showing configuration, so the command actually executes the message response
fixing potential exception case for starboard updates causing the message ID to not be persisted
2021-07-18 19:20:14 +02:00
Sheldan
32056cd6b9 [maven-release-plugin] prepare for next development iteration 2021-07-14 02:17:57 +02:00
Sheldan
efd6c23713 [maven-release-plugin] prepare release abstracto-application-1.3.3 2021-07-14 02:17:48 +02:00
Sheldan
794fc7ceac [AB-71] adding very simple anti raid feature to automatically mute member who mass mention users 2021-07-14 01:48:53 +02:00
Sheldan
6b5a255aa8 [maven-release-plugin] prepare for next development iteration 2021-07-13 01:19:43 +02:00
Sheldan
620ef0708a [maven-release-plugin] prepare release abstracto-application-1.3.2 2021-07-13 01:19:35 +02:00
Sheldan
3564075e7f [AB-xxx] fixing javadoc build 2021-07-13 01:13:58 +02:00
Sheldan
de5ac0e3f4 [maven-release-plugin] prepare for next development iteration 2021-07-13 00:56:29 +02:00
244 changed files with 4024 additions and 610 deletions

View File

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

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.3.8</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>anti-raid-impl</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-int</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>anti-raid-int</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.antiraid.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:antiRaid-config.properties")
public class AntiRaidProperties {
}

View File

@@ -0,0 +1,36 @@
package dev.sheldan.abstracto.antiraid.listener;
import dev.sheldan.abstracto.antiraid.config.AntiRaidFeatureDefinition;
import dev.sheldan.abstracto.antiraid.service.MassPingService;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.ChannelType;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MassPingMessageListener implements AsyncMessageReceivedListener {
@Autowired
private MassPingService massPingService;
@Override
public DefaultListenerResult execute(MessageReceivedModel model) {
Message message = model.getMessage();
if(message.getAuthor().isBot() || message.isWebhookMessage() || !message.isFromGuild() || !message.isFromType(ChannelType.TEXT)) {
return DefaultListenerResult.IGNORED;
}
massPingService.processMessage(message);
return DefaultListenerResult.PROCESSED;
}
@Override
public FeatureDefinition getFeature() {
return AntiRaidFeatureDefinition.ANTI_RAID;
}
}

View File

@@ -0,0 +1,109 @@
package dev.sheldan.abstracto.antiraid.service;
import dev.sheldan.abstracto.antiraid.config.AntiRaidPostTarget;
import dev.sheldan.abstracto.antiraid.model.MassPingNotificationModel;
import dev.sheldan.abstracto.core.models.ConditionContextInstance;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ConditionService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.service.MuteService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class MassPingServiceBean implements MassPingService {
private static final String MASS_PING_MUTE_NOTIFICATION_TEMPLATE_KEY = "massPing_mute_notification";
private static final String LEVEL_CONDITION_NAME = "HAS_LEVEL";
private static final String LEVEL_CONDITION_USER_ID_PARAMETER = "userId";
private static final String LEVEL_CONDITION_LEVEL_PARAMETER = "level";
@Autowired
private ConfigService configService;
@Autowired
private MuteService muteService;
@Autowired
private TemplateService templateService;
@Autowired
private MassPingServiceBean self;
@Autowired
private PostTargetService postTargetService;
@Autowired
private ConditionService conditionService;
@Value("${abstracto.massPing.maxAllowedMentions}")
private Integer maxAllowedMentions;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override
public CompletableFuture<Void> processMessage(Message message) {
if(message.getMentionedMembers().size() > maxAllowedMentions) {
Integer level = configService.getLongValueOrConfigDefault(MassPingService.MAX_AFFECTED_LEVEL_KEY, message.getGuild().getIdLong()).intValue();
boolean allowed = allowedToMassMention(message, level);
if(!allowed) {
return muteService.muteMemberWithoutContext(message.getMember())
.thenAccept(unused -> self.sendMassPingMuteNotification(message))
.thenAccept(unused -> log.info("Muted member {} in server {} because of too many member mentions. (> {}).",
message.getMember().getIdLong(), message.getGuild().getIdLong(), maxAllowedMentions));
} else {
log.info("User {} in server {} is allowed to mass mention, because of level (or lack of level configuration).",
message.getMember().getIdLong(), message.getGuild().getIdLong());
return CompletableFuture.completedFuture(null);
}
} else {
return CompletableFuture.completedFuture(null);
}
}
private boolean allowedToMassMention(Message message, Integer level) {
log.info("Checking if member {} is allowed to mention a lot of users in server {}.", message.getAuthor().getIdLong(), message.getGuild().getIdLong());
Map<String, Object> parameters = new HashMap<>();
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(message.getMember());
parameters.put(LEVEL_CONDITION_USER_ID_PARAMETER, userInAServer.getUserInServerId());
parameters.put(LEVEL_CONDITION_LEVEL_PARAMETER, level);
ConditionContextInstance contextInstance = ConditionContextInstance
.builder()
.conditionName(LEVEL_CONDITION_NAME)
.parameters(parameters)
.build();
return conditionService.checkConditions(contextInstance);
}
@Transactional
public CompletableFuture<Void> sendMassPingMuteNotification(Message message) {
Member member = message.getMember();
MassPingNotificationModel model = MassPingNotificationModel
.builder()
.messageLink(message.getJumpUrl())
.mentionCount(message.getMentionedMembers().size())
.messageContent(message.getContentRaw())
.memberDisplay(MemberDisplay.fromMember(member))
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(MASS_PING_MUTE_NOTIFICATION_TEMPLATE_KEY, model, member.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, AntiRaidPostTarget.MASS_PING_LOG, member.getGuild().getIdLong()));
}
}

View File

@@ -0,0 +1,9 @@
abstracto.featureFlags.antiRaid.featureName=antiRaid
abstracto.featureFlags.antiRaid.enabled=false
abstracto.postTargets.massPingLog.name=massPingLog
abstracto.massPing.maxAllowedMentions=5
abstracto.systemConfigs.massPingMinLevel.name=massPingMinLevel
abstracto.systemConfigs.massPingMinLevel.longValue=15

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.3.8</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>anti-raid-int</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-int</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,39 @@
package dev.sheldan.abstracto.antiraid.config;
import dev.sheldan.abstracto.antiraid.service.MassPingService;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.moderation.config.feature.MutingFeatureConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class AntiRaidFeatureConfig implements FeatureConfig {
@Autowired
private MutingFeatureConfig mutingFeatureConfig;
@Override
public FeatureDefinition getFeature() {
return AntiRaidFeatureDefinition.ANTI_RAID;
}
@Override
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(AntiRaidPostTarget.MASS_PING_LOG);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(MassPingService.MAX_AFFECTED_LEVEL_KEY);
}
@Override
public List<FeatureConfig> getRequiredFeatures() {
return Arrays.asList(mutingFeatureConfig);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.antiraid.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum AntiRaidFeatureDefinition implements FeatureDefinition {
ANTI_RAID("antiRaid");
private String key;
AntiRaidFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.antiraid.config;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import lombok.Getter;
@Getter
public enum AntiRaidPostTarget implements PostTargetEnum {
MASS_PING_LOG("massPingLog");
private String key;
AntiRaidPostTarget(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.abstracto.antiraid.model;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class MassPingNotificationModel {
private MemberDisplay memberDisplay;
private String messageContent;
private String messageLink;
private Integer mentionCount;
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.antiraid.service;
import net.dv8tion.jda.api.entities.Message;
import java.util.concurrent.CompletableFuture;
public interface MassPingService {
String MAX_AFFECTED_LEVEL_KEY = "massPingMinLevel";
CompletableFuture<Void> processMessage(Message message);
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.3.8</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>anti-raid</artifactId>
<packaging>pom</packaging>
<modules>
<module>anti-raid-int</module>
<module>anti-raid-impl</module>
</modules>
</project>

View File

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

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.assignableroles.command;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
@@ -41,11 +42,15 @@ public class CreateAssignableRolePost extends AbstractConditionableCommand {
String name = (String) parameters.get(0);
TextChannel channel = (TextChannel) parameters.get(1);
String text = (String) parameters.get(2);
AssignableRolePlaceType type = AssignableRolePlaceType.DEFAULT;
if(parameters.size() > 3) {
type = (AssignableRolePlaceType) parameters.get(3);
}
if(!channel.getGuild().equals(commandContext.getGuild())) {
throw new EntityGuildMismatchException();
}
AChannel chosenChannel = channelManagementService.loadChannel(channel.getIdLong());
service.createAssignableRolePlace(name, chosenChannel, text);
service.createAssignableRolePlace(name, chosenChannel, text, type);
return CommandResult.fromSuccess();
}
@@ -54,10 +59,11 @@ public class CreateAssignableRolePost extends AbstractConditionableCommand {
List<ParameterValidator> rolePlaceNameValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT));
Parameter rolePostName = Parameter.builder().name("name").validators(rolePlaceNameValidator).type(String.class).templated(true).build();
Parameter channel = Parameter.builder().name("channel").type(TextChannel.class).templated(true).build();
Parameter type = Parameter.builder().name("type").type(AssignableRolePlaceType.class).templated(true).optional(true).build();
List<ParameterValidator> rolePlaceDescriptionValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT));
Parameter text = Parameter.builder().name("text").validators(rolePlaceDescriptionValidator).type(String.class).remainder(true).optional(true).templated(true).build();
Parameter text = Parameter.builder().name("text").validators(rolePlaceDescriptionValidator).type(String.class).templated(true).build();
List<String> aliases = Arrays.asList("crRPl", "crAssRoPl");
List<Parameter> parameters = Arrays.asList(rolePostName, channel, text);
List<Parameter> parameters = Arrays.asList(rolePostName, channel, text, type);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("createAssignableRolePlace")

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.assignableroles.command;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.model.template.AssignableRolePlaceConfig;
import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
@@ -9,8 +10,9 @@ 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.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -24,21 +26,24 @@ import java.util.concurrent.CompletableFuture;
@Component
public class ShowAssignableRolePlaceConfig extends AbstractConditionableCommand {
@Autowired
private AssignableRolePlaceService service;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
public static final String ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY = "assignable_roles_config_post";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
String name = (String) parameters.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild());
// TODO refactor to return something to be posted in this command here instead of relying it to be posted somewhere else
return service.showAssignablePlaceConfig(server, name, commandContext.getChannel())
.thenApply(unused -> CommandResult.fromIgnored());
AssignableRolePlaceConfig config = service.getAssignableRolePlaceConfig(commandContext.getGuild(), name);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY, config, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
@@ -51,7 +56,7 @@ public class ShowAssignableRolePlaceConfig extends AbstractConditionableCommand
.module(AssignableRoleModuleDefinition.ASSIGNABLE_ROLES)
.templated(true)
.async(true)
.causesReaction(true)
.causesReaction(false)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.assignableroles.command;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.model.template.AssignablePlaceOverview;
import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
@@ -9,7 +10,9 @@ 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.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -28,11 +31,16 @@ public class ShowAssignableRolePlaces extends AbstractConditionableCommand {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
public static final String ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY = "assignable_role_places_overview";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
return service.showAllAssignableRolePlaces(server, commandContext.getChannel())
.thenApply(aVoid -> CommandResult.fromIgnored());
AssignablePlaceOverview model = service.getAssignableRolePlaceOverview(commandContext.getGuild());
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
@@ -42,7 +50,6 @@ public class ShowAssignableRolePlaces extends AbstractConditionableCommand {
.name("showAssignableRolePlaces")
.module(AssignableRoleModuleDefinition.ASSIGNABLE_ROLES)
.templated(true)
.causesReaction(true)
.async(true)
.supportsEmbedException(true)
.help(helpInfo)

View File

@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.assignableroles.listener;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.exception.AssignableRoleNotFoundException;
import dev.sheldan.abstracto.assignableroles.exception.BoosterAssignableRolePlaceMemberNotBoostingException;
import dev.sheldan.abstracto.assignableroles.model.condition.AssignableRoleConditionResult;
import dev.sheldan.abstracto.assignableroles.model.AssignableRolePlacePayload;
import dev.sheldan.abstracto.assignableroles.model.condition.AssignableRolePlaceConditionModel;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.assignableroles.model.template.AssignableRoleSuccessNotificationModel;
import dev.sheldan.abstracto.assignableroles.service.AssignableRoleConditionServiceBean;
@@ -73,9 +75,10 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
ButtonClickEvent event = model.getEvent();
AssignableRolePlacePayload payload = (AssignableRolePlacePayload) model.getDeserializedPayload();
AssignableRolePlace place = assignableRolePlaceManagementService.findByPlaceId(payload.getPlaceId());
if(event.getGuild() != null && event.getMember() != null) {
Member member = event.getMember();
if(event.getGuild() != null && member != null) {
AssignableRolePlacePayload payload = (AssignableRolePlacePayload) model.getDeserializedPayload();
AssignableRolePlace place = assignableRolePlaceManagementService.findByPlaceId(payload.getPlaceId());
Guild guild = event.getGuild();
List<Role> removedRoles = new ArrayList<>();
Role roleById = guild.getRoleById(payload.getRoleId());
@@ -88,19 +91,25 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
throw new AssignableRoleNotFoundException(payload.getRoleId());
}
if(roleById != null) {
boolean memberHasRole = event
.getMember()
boolean memberHasRole = member
.getRoles()
.stream()
.anyMatch(memberRole -> memberRole.getIdLong() == payload.getRoleId());
if(!memberHasRole) {
if(place.getType().equals(AssignableRolePlaceType.BOOSTER) && member.getTimeBoosted() == null) {
assignableRoleService.assignableRoleConditionFailure();
throw new BoosterAssignableRolePlaceMemberNotBoostingException();
}
AssignableRole assignableRole = assignableRoleOptional.get();
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(event.getMember());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
if(!assignableRole.getConditions().isEmpty()) {
log.debug("Evaluating {} conditions for assignable role {}.", assignableRole.getConditions().size(), assignableRole.getId());
AssignableRoleConditionResult conditionResult =
assignableRoleConditionServiceBean.evaluateConditions(assignableRole.getConditions(), aUserInAServer, roleById);
if(!conditionResult.getFulfilled()) {
log.info("One condition failed to be fulfilled - notifying user.");
self.notifyUserAboutConditionFail(model, event.getInteraction(), conditionResult.getModel());
assignableRoleService.assignableRoleConditionFailure();
return ButtonClickedListenerResult.ACKNOWLEDGED;
}
}
@@ -116,9 +125,10 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
.map(roleOfUser -> guild.getRoleById(roleOfUser.getRole().getId()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
log.info("Removing {} because of unique role configuration in place {}.", rolesToRemove.size(), place.getId());
removedRoles.addAll(rolesToRemove);
List<CompletableFuture<Void>> removalFutures = new ArrayList<>();
rolesToRemove.forEach(roleToRemove -> removalFutures.add(roleService.removeRoleFromUserAsync(event.getMember(), roleToRemove)));
rolesToRemove.forEach(roleToRemove -> removalFutures.add(roleService.removeRoleFromUserAsync(member, roleToRemove)));
removalFuture = new CompletableFutureList<>(removalFutures).getMainFuture();
} else {
removalFuture = CompletableFuture.completedFuture(null);
@@ -126,29 +136,32 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
} else {
removalFuture = CompletableFuture.completedFuture(null);
}
CompletableFuture<Void> roleAdditionFuture = roleService.addRoleToMemberAsync(event.getMember(), roleById);
CompletableFuture<Void> roleAdditionFuture = assignableRoleService.assignAssignableRoleToUser(roleById, member);
CompletableFuture.allOf(removalFuture, roleAdditionFuture).whenComplete((unused, throwable) -> {
if(throwable != null) {
log.error("Failed to either add or remove roles for assignable role place {} in server {}.", payload.getPlaceId(), guild.getIdLong());
}
if(!roleAdditionFuture.isCompletedExceptionally()) {
log.info("Added role {} to member {} in server {} for assignable role interaction {} on component {}.",
roleById.getId(), event.getMember().getId(), guild.getIdLong(), event.getInteraction().getId(), event.getComponentId());
roleById.getId(), member.getId(), guild.getIdLong(), event.getInteraction().getId(), event.getComponentId());
self.notifyUser(model, true, roleById, event.getInteraction(), removedRoles).thenAccept(unused1 -> {
log.info("Persisting adding assignable role update for user {} in server {} of role {}.", event.getMember().getIdLong(), guild.getIdLong(), roleById.getId());
self.persistAssignableUser(event.getMember(), payload, false);
log.info("Persisting adding assignable role update for user {} in server {} of role {}.", member.getIdLong(), guild.getIdLong(), roleById.getId());
self.persistAssignableUser(member, payload, false);
});
}
}).exceptionally(throwable -> {
log.error("Failed to perform role change in assignable role place.", throwable);
return null;
});
} else {
roleService.removeRoleFromUserAsync(event.getMember(), roleById)
assignableRoleService.removeAssignableRoleFromUser(roleById, member)
.thenAccept(unused -> {
self.notifyUser(model, false, roleById, event.getInteraction(), new ArrayList<>());
log.info("Removed role {} from member {} in server {} for assignable role interaction {} on component {}.",
roleById.getId(), event.getMember().getId(), guild.getIdLong(), event.getInteraction().getId(), event.getComponentId());
roleById.getId(), member.getId(), guild.getIdLong(), event.getInteraction().getId(), event.getComponentId());
}).thenAccept(unused -> {
log.info("Persisting remove assignable role update for user {} in server {} of role {}.", event.getMember().getIdLong(), guild.getIdLong(), roleById.getId());
self.persistAssignableUser(event.getMember(), payload, true);
log.info("Persisting remove assignable role update for user {} in server {} of role {}.", member.getIdLong(), guild.getIdLong(), roleById.getId());
self.persistAssignableUser(member, payload, true);
});
}
} else {
@@ -183,6 +196,8 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
@Transactional
public CompletableFuture<Void> notifyUser(ButtonClickedListenerModel model, boolean roleAdded, Role role, ButtonInteraction buttonInteraction, List<Role> removedRoles) {
log.info("Notifying user {} in server {} in channel {} about role change with role {}.",
buttonInteraction.getUser().getIdLong(), buttonInteraction.getGuild().getIdLong(), buttonInteraction.getChannel().getIdLong(), role.getId());
AssignableRoleSuccessNotificationModel notificationModel = AssignableRoleSuccessNotificationModel
.builder()
.added(roleAdded)
@@ -196,6 +211,8 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
@Transactional
public CompletableFuture<Void> notifyUserAboutConditionFail(ButtonClickedListenerModel model, ButtonInteraction buttonInteraction,
AssignableRolePlaceConditionModel conditionModel) {
log.info("Notifying user {} in server {} in channel {} about failed condition.", buttonInteraction.getUser().getIdLong(),
buttonInteraction.getGuild().getIdLong(), buttonInteraction.getChannel().getIdLong());
return FutureUtils.toSingleFutureGeneric(
interactionService.sendMessageToInteraction("assignable_role_condition_notification", conditionModel, buttonInteraction.getHook())) ;
}

View File

@@ -0,0 +1,112 @@
package dev.sheldan.abstracto.assignableroles.listener;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.assignableroles.service.AssignableRoleService;
import dev.sheldan.abstracto.assignableroles.service.management.AssignableRoleManagementService;
import dev.sheldan.abstracto.assignableroles.service.management.AssignedRoleUserManagementService;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberBoostTimeUpdateListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.BoostTimeUpdatedModel;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
public class AssignableRolePlaceBoostTimeUpdateListener implements AsyncMemberBoostTimeUpdateListener {
@Autowired
private AssignedRoleUserManagementService assignedRoleUserManagementService;
@Autowired
private AssignableRoleManagementService assignableRoleManagementService;
@Autowired
private RoleService roleService;
@Autowired
private AssignableRoleService assignableRoleService;
@Autowired
private AssignableRolePlaceBoostTimeUpdateListener self;
@Override
public DefaultListenerResult execute(BoostTimeUpdatedModel model) {
Member member = model.getMember();
if(member.getTimeBoosted() == null) {
removeAssignedBoosterRoles(member);
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
private void removeAssignedBoosterRoles(Member member) {
log.info("Member {} in server {} stopped boosting.", member.getIdLong(), member.getGuild().getIdLong());
ServerUser serverUser = ServerUser.fromMember(member);
Optional<AssignedRoleUser> assignedRoleUserOptional = assignedRoleUserManagementService.findByUserInServerOptional(serverUser);
if(assignedRoleUserOptional.isPresent()) {
AssignedRoleUser assignedRoleUser = assignedRoleUserOptional.get();
List<AssignableRole> boosterRoles = assignableRoleManagementService.getAssignableRolesFromAssignableUserWithPlaceType(assignedRoleUser, AssignableRolePlaceType.BOOSTER);
if(!boosterRoles.isEmpty()) {
log.info("Removing {} assignable role mappings.", boosterRoles.size());
Guild guild = member.getGuild();
List<Role> actualRolesToDelete = boosterRoles
.stream()
.map(assignableRole -> guild.getRoleById(assignableRole.getRole().getId()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
log.debug("Which translated to {} roles in reality.", actualRolesToDelete.size());
List<CompletableFuture<Void>> list = new ArrayList<>();
actualRolesToDelete.forEach(role -> list.add(roleService.removeRoleFromUserAsync(member, role)));
FutureUtils.toSingleFutureGeneric(list)
.thenAccept(unused -> self.clearPersistedBoosterAssignableRoles(member))
.exceptionally(throwable -> {
log.warn("One or more roles might have failed to remove. ", throwable);
self.clearPersistedBoosterAssignableRoles(member);
return null;
});
} else {
log.info("Member {} in server {} did not have boost roles - doing nothing.", member.getIdLong(), member.getGuild().getIdLong());
}
} else {
log.info("Member (ID {}) in server (ID: {}), who was not tracked via assignable roles, stopped boosting - doing nothing.",
member.getIdLong(), member.getGuild().getIdLong());
}
}
@Transactional
public void clearPersistedBoosterAssignableRoles(Member member) {
ServerUser serverUser = ServerUser.fromMember(member);
Optional<AssignedRoleUser> assignedRoleUserOptional = assignedRoleUserManagementService.findByUserInServerOptional(serverUser);
if(assignedRoleUserOptional.isPresent()) {
AssignedRoleUser assignedRoleUser = assignedRoleUserOptional.get();
List<AssignableRole> boosterRoles = assignableRoleManagementService.getAssignableRolesFromAssignableUserWithPlaceType(assignedRoleUser, AssignableRolePlaceType.BOOSTER);
assignableRoleService.removeAssignableRolesFromAssignableRoleUser(boosterRoles, assignedRoleUser);
} else {
log.warn("No assigned role user found for member {} in server {}.", member.getIdLong(), member.getGuild().getIdLong());
}
}
@Override
public FeatureDefinition getFeature() {
return AssignableRoleFeatureDefinition.ASSIGNABLE_ROLES;
}
}

View File

@@ -1,12 +1,17 @@
package dev.sheldan.abstracto.assignableroles.repository;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Repository to manage the access to the table managed by {@link AssignableRole assignableRole}
*/
@Repository
public interface AssignableRoleRepository extends JpaRepository<AssignableRole, Long> {
List<AssignableRole> findByAssignedUsersContainingAndAssignablePlace_Type(AssignedRoleUser roleUser, AssignableRolePlaceType type);
}

View File

@@ -16,6 +16,7 @@ import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +28,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
@Component
@Slf4j
public class AssignableRoleConditionServiceBean implements AssignableRoleConditionService {
@Autowired
@@ -55,12 +57,15 @@ public class AssignableRoleConditionServiceBean implements AssignableRoleConditi
@Override
public AssignableRoleConditionResult evaluateConditions(List<AssignableRoleCondition> conditions, AUserInAServer aUserInAServer, Role role) {
log.debug("Evaluating {} conditions for role {}.", conditions.size(), role.getId());
for (AssignableRoleCondition condition : conditions) {
if(assignableRoleConditionEvaluators != null) {
Optional<AssignableRoleConditionEvaluator> evaluatorOptional = findEvaluatorForCondition(condition.getType());
if(evaluatorOptional.isPresent()) {
AssignableRoleConditionEvaluator evaluator = evaluatorOptional.get();
log.debug("Evaluating condition {} with evaluator {}.", condition.getType(), evaluator.getClass());
if(!evaluator.fulfillsCondition(condition, aUserInAServer)) {
log.info("Condition {} failed for role {} in server {}.", condition.getType(), role.getId(), aUserInAServer.getServerReference().getId());
return AssignableRoleConditionResult.fromFail(condition.getType(), evaluator.createNotificationModel(condition, role));
}
}
@@ -94,6 +99,7 @@ public class AssignableRoleConditionServiceBean implements AssignableRoleConditi
if(assignableRoleConditionManagementService.findAssignableRoleCondition(assignableRole, type).isPresent()) {
throw new AssignableRoleConditionAlreadyExistsException();
}
log.info("Creating new condition for role {} in place {} in server {}.", place.getId(), role.getId(), role.getGuild().getIdLong());
return assignableRoleConditionManagementService.createAssignableRoleCondition(assignableRole, type, value);
}
@@ -106,6 +112,7 @@ public class AssignableRoleConditionServiceBean implements AssignableRoleConditi
if(!existingCondition.isPresent()) {
throw new AssignableRoleConditionDoesNotExistException();
}
log.info("Deleting assignable role condition on place {} for role {} in server {}.", place.getId(), role.getId(), role.getGuild().getIdLong());
existingCondition.ifPresent(condition -> assignableRoleConditionManagementService.deleteAssignableRoleCondition(condition));
}

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.assignableroles.exception.*;
import dev.sheldan.abstracto.assignableroles.model.AssignableRolePlacePayload;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.template.*;
import dev.sheldan.abstracto.assignableroles.service.management.*;
import dev.sheldan.abstracto.core.command.exception.CommandParameterKeyValueWrongTypeException;
@@ -21,7 +22,6 @@ import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.*;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.interactions.components.ButtonStyle;
@@ -38,9 +38,7 @@ import java.util.stream.Collectors;
@Slf4j
public class AssignableRolePlaceServiceBean implements AssignableRolePlaceService {
public static final String ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY = "assignable_roles_config_post";
public static final String ASSIGNABLE_ROLES_POST_TEMPLATE_KEY = "assignable_roles_post";
public static final String ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY = "assignable_role_places_overview";
public static final int MAX_ASSIGNABLE_ROLES_PER_POST = ComponentService.MAX_BUTTONS_PER_ROW * 5;
public static final String ASSIGNABLE_ROLE_COMPONENT_ORIGIN = "assignableRoleButton";
@Autowired
@@ -85,12 +83,18 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Autowired
private AssignableRoleConditionService assignableRoleConditionService;
@Autowired
private AssignedRoleUserManagementServiceBean assignedRoleUserManagementServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Override
public void createAssignableRolePlace(String name, AChannel channel, String text) {
public void createAssignableRolePlace(String name, AChannel channel, String text, AssignableRolePlaceType type) {
if (rolePlaceManagementService.doesPlaceExist(channel.getServer(), name)) {
throw new AssignableRolePlaceAlreadyExistsException(name);
}
rolePlaceManagementService.createPlace(name, channel, text);
rolePlaceManagementService.createPlace(name, channel, text, type);
}
@Override
@@ -98,6 +102,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
public CompletableFuture<Void> addRoleToAssignableRolePlace(AServer server, String placeName, Role role, FullEmote fakeEmote, String description) {
AssignableRolePlace assignableRolePlace = rolePlaceManagementService.findByServerAndKey(server, placeName);
if (assignableRolePlace.getAssignableRoles().size() > MAX_ASSIGNABLE_ROLES_PER_POST) {
log.info("Assignable role place {} has already {} roles. Not possible to add more.", assignableRolePlace.getId(), assignableRolePlace.getAssignableRoles().size());
throw new AssignableRolePlaceMaximumRolesException();
}
if (assignableRolePlace.getAssignableRoles().stream().anyMatch(assignableRole -> assignableRole.getRole().getId().equals(role.getIdLong()))) {
@@ -113,16 +118,17 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
throw new EmoteNotUsableException(fakeEmote.getEmote());
}
}
log.debug("There are already message posts on for the assignable role place {}.", assignableRolePlace.getId());
Optional<TextChannel> channelOptional = channelService.getTextChannelFromServerOptional(server.getId(), assignableRolePlace.getChannel().getId());
if (channelOptional.isPresent()) {
TextChannel textChannel = channelOptional.get();
String buttonId = componentService.generateComponentId();
String emoteMarkdown = fakeEmote != null ? fakeEmote.getEmoteRepr() : null;
if (assignableRolePlace.getMessageId() != null) {
return componentService.addButtonToMessage(assignableRolePlace.getMessageId(), textChannel, buttonId, description, emoteMarkdown, ButtonStyle.PRIMARY)
log.debug("Assignable role place {} has already message post with ID {} - updating.", assignableRolePlace.getId(), assignableRolePlace.getMessageId());
return componentService.addButtonToMessage(assignableRolePlace.getMessageId(), textChannel, buttonId, description, emoteMarkdown, ButtonStyle.SECONDARY)
.thenAccept(message -> self.persistAssignableRoleAddition(placeId, role, description, fakeEmote, buttonId));
} else {
log.info("Assignable role place {} is not yet setup - only adding role to the database.", assignableRolePlace.getId());
self.persistAssignableRoleAddition(placeId, role, description, fakeEmote, buttonId);
return CompletableFuture.completedFuture(null);
}
@@ -134,6 +140,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Transactional
public void persistAssignableRoleAddition(Long placeId, Role role, String description, FullEmote fakeEmote, String componentId) {
AssignableRolePlace place = assignableRolePlaceManagementServiceBean.findByPlaceId(placeId);
log.info("Adding role {} to assignable role place {} with component ID {}.", role.getId(), place.getId(), componentId);
ComponentPayload payload = persistButtonCallback(place, componentId, role.getIdLong());
assignableRoleManagementServiceBean.addRoleToPlace(fakeEmote, role, description, place, payload);
}
@@ -144,6 +151,8 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
Long assignableRolePlaceId = assignableRolePlace.getId();
for (AssignableRole assignableRole : assignableRolePlace.getAssignableRoles()) {
if (assignableRole.getRole().getId().equals(role.getId())) {
log.info("Found {} role to be removed - removing button from place.", role.getId());
// TODO we might want to actually remove all the assigned roles as well
return removeButtonFromAssignableRolePlace(assignableRole, assignableRolePlace).thenAccept(aVoid ->
self.deleteAssignableRoleFromPlace(assignableRolePlaceId, assignableRole.getId())
);
@@ -154,9 +163,12 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
private CompletableFuture<Void> removeButtonFromAssignableRolePlace(AssignableRole assignableRole, AssignableRolePlace assignableRolePlace) {
String componentId = assignableRole.getComponentPayload().getId();
log.debug("Component ID to remove {} for role {}", componentId, assignableRole.getRole().getId());
return channelService.retrieveMessageInChannel(assignableRolePlace.getServer().getId(), assignableRolePlace.getChannel().getId(), assignableRolePlace.getMessageId())
.thenCompose(message ->
componentService.removeComponentWithId(message, componentId, true)
.thenCompose(message -> {
log.debug("Updating message {} to remove component with ID {}.", message.getIdLong(), componentId);
return componentService.removeComponentWithId(message, componentId, true);
}
);
}
@@ -171,6 +183,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
.findAny();
roleToRemoveOptional.ifPresent(assignableRole -> {
ComponentPayload componentPayload = assignableRole.getComponentPayload();
assignedRoleUserManagementServiceBean.removeAssignedRoleFromUsers(assignableRole);
assignableRoleManagementServiceBean.deleteAssignableRole(assignableRole);
componentPayloadManagementService.deletePayload(componentPayload);
});
@@ -192,16 +205,15 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
if (throwable != null) {
log.warn("Not able to delete old messages of assignable role place {} in server {}.", assignablePlaceId, serverId);
}
try {
self.createAssignableRolePlacePost(serverId, assignablePlaceId)
.thenAccept(unused1 -> postingFuture.complete(null))
.exceptionally(innerThrowable -> {
postingFuture.completeExceptionally(innerThrowable);
return null;
});
} catch (Exception ex) {
postingFuture.completeExceptionally(ex);
}
self.createAssignableRolePlacePost(serverId, assignablePlaceId)
.thenAccept(unused1 -> postingFuture.complete(null))
.exceptionally(innerThrowable -> {
postingFuture.completeExceptionally(innerThrowable);
return null;
});
}).exceptionally(throwable -> {
postingFuture.completeExceptionally(throwable);
return null;
});
return postingFuture;
}
@@ -232,8 +244,10 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
private CompletableFuture<Void> deleteExistingMessagePostsForPlace(AssignableRolePlace assignableRolePlace) {
if (assignableRolePlace.getMessageId() != null) {
log.info("Deleting old message {} for assignable role place {}.", assignableRolePlace.getMessageId(), assignableRolePlace.getId());
return messageService.deleteMessageInChannelInServer(assignableRolePlace.getServer().getId(), assignableRolePlace.getChannel().getId(), assignableRolePlace.getMessageId());
} else {
log.info("Assignable role place {} was not yet set up - no message ID tracked.", assignableRolePlace.getMessageId());
return CompletableFuture.completedFuture(null);
}
}
@@ -299,12 +313,11 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
}
@Override
public CompletableFuture<Void> showAssignablePlaceConfig(AServer server, String name, TextChannel channel) {
Guild guild = guildService.getGuildById(server.getId());
public AssignableRolePlaceConfig getAssignableRolePlaceConfig(Guild guild, String name) {
AServer server = serverManagementService.loadServer(guild);
AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name);
log.info("Showing assignable role place config for place {} in channel {} on server {}.", place.getId(), channel.getId(), server.getId());
AssignableRolePlaceConfig configModel = convertPlaceToAssignableRolePlaceConfig(guild, place);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY, configModel, channel));
log.info("Generating assignable role place config for place {} on server {}.", place.getId(), guild.getIdLong());
return convertPlaceToAssignableRolePlaceConfig(guild, place);
}
private AssignableRolePlaceConfig convertPlaceToAssignableRolePlaceConfig(Guild guild, AssignableRolePlace place) {
@@ -326,6 +339,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
return AssignableRolePlaceConfig
.builder()
.roles(roles)
.type(place.getType())
.placeName(place.getKey())
.placeText(place.getText())
.uniqueRoles(place.getUniqueRoles())
@@ -336,6 +350,8 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Override
public CompletableFuture<Void> moveAssignableRolePlace(AServer server, String name, TextChannel newChannel) {
AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name);
log.info("Moving assignable role place {} from channel {} to channel {} in guild {}.",
place.getId(), place.getChannel().getId(), newChannel.getId(), newChannel.getGuild().getIdLong());
CompletableFuture<Void> oldPostDeletionFuture = deleteExistingMessagePostsForPlace(place);
Long serverId = server.getId();
Long assignablePlaceId = place.getId();
@@ -344,17 +360,16 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
if (throwable != null) {
log.warn("Not able to delete old messages of assignable role place {} in server {}.", assignablePlaceId, serverId);
}
try {
self.setupAssignableRolePlaceInChannel(serverId, assignablePlaceId, newChannel)
.thenAccept(unused1 -> self.updateAssignableRolePlaceChannel(name, newChannel))
.thenAccept(unused1 -> returnFuture.complete(null))
.exceptionally(innerThrowable -> {
returnFuture.completeExceptionally(innerThrowable);
return null;
});
} catch (Exception ex) {
returnFuture.completeExceptionally(ex);
}
self.setupAssignableRolePlaceInChannel(serverId, assignablePlaceId, newChannel)
.thenAccept(unused1 -> self.updateAssignableRolePlaceChannel(name, newChannel))
.thenAccept(unused1 -> returnFuture.complete(null))
.exceptionally(innerThrowable -> {
returnFuture.completeExceptionally(innerThrowable);
return null;
});
}).exceptionally(throwable -> {
returnFuture.completeExceptionally(throwable);
return null;
});
return returnFuture;
@@ -363,13 +378,14 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Transactional
public void updateAssignableRolePlaceChannel(String name, TextChannel textChannel) {
AChannel channel = channelManagementService.loadChannel(textChannel.getIdLong());
log.info("Setting assignable role place to channel {}.", textChannel.getIdLong());
rolePlaceManagementService.moveAssignableRolePlace(name, channel);
}
@Override
public CompletableFuture<Void> deleteAssignableRolePlace(AServer server, String name) {
AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name);
log.info("Deleting assignable role place {}.", place.getId());
Long placeId = place.getId();
CompletableFuture<Void> deleteFuture = deleteExistingMessagePostsForPlace(place);
return deleteFuture.thenAccept(unused -> self.deleteAssignableRolePlaceInDatabase(placeId));
@@ -405,25 +421,24 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
}
@Override
public CompletableFuture<Void> showAllAssignableRolePlaces(AServer server, TextChannel channel) {
public AssignablePlaceOverview getAssignableRolePlaceOverview(Guild guild) {
AServer server = serverManagementService.loadServer(guild);
List<AssignableRolePlace> assignableRolePlaces = rolePlaceManagementService.findAllByServer(server);
Guild guild = channel.getGuild();
List<AssignableRolePlaceConfig> placeConfigs = assignableRolePlaces
.stream()
.map(place -> convertPlaceToAssignableRolePlaceConfig(guild, place))
.collect(Collectors.toList());
AssignablePlaceOverview overViewModel = AssignablePlaceOverview
log.info("Showing overview over all assignable role places for server {}.", server.getId());
return AssignablePlaceOverview
.builder()
.places(placeConfigs)
.build();
log.info("Showing overview over all assignable role places for server {} in channel {}.", server.getId(), channel.getId());
List<CompletableFuture<Message>> promises = channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY, overViewModel, channel);
return CompletableFuture.allOf(promises.toArray(new CompletableFuture[0]));
}
private CompletableFuture<Void> sendAssignablePostMessage(AssignableRolePlace place, TextChannel channel) {
AssignablePostMessage model = prepareAssignablePostMessageModel(place);
MessageToSend messageToSend = templateService.renderEmbedTemplate(ASSIGNABLE_ROLES_POST_TEMPLATE_KEY, model, place.getServer().getId());
log.info("Sending message for assignable role place {}.", place.getId());
CompletableFuture<Message> postFuture = channelService.sendMessageToSendToChannel(messageToSend, channel).get(0);
Long placeId = model.getPlaceId();
return postFuture.thenCompose(unused -> {
@@ -443,6 +458,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
public void persistAssignablePlaceMessageId(Long placeId, CompletableFuture<Message> messageFuture) {
AssignableRolePlace place = assignableRolePlaceManagementServiceBean.findByPlaceId(placeId);
Message message = messageFuture.join();
log.info("Setting message ID of assignable role place {} to {}.", placeId, message.getIdLong());
place.setMessageId(message.getIdLong());
}
@@ -491,7 +507,6 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
log.info("Sending assignable role place posts for place {} in channel {} in server {}.", assignableRolePlace.getId(), channel.getId(), serverId);
return sendAssignablePostMessage(assignableRolePlace, channel);
} else {
log.warn("Channel to create assignable role post in does not exist.");
throw new AssignableRolePlaceChannelDoesNotExistException(assignableRolePlace.getChannel().getId(), assignableRolePlace.getKey());
}
}

View File

@@ -24,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -71,14 +72,36 @@ public class AssignableRoleServiceBean implements AssignableRoleService {
.tagList(Arrays.asList(MetricTag.getTag(ACTION, "removed")))
.build();
private static final CounterMetric ASSIGNABLE_ROLES_CONDITION_FAILED =
CounterMetric
.builder()
.name(ASSIGNABLE_ROLES_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(ACTION, "condition")))
.build();
@Override
public CompletableFuture<Void> assignAssignableRoleToUser(Long assignableRoleId, Member member) {
metricService.incrementCounter(ASSIGNABLE_ROLES_ASSIGNED);
AssignableRole role = assignableRoleManagementServiceBean.getByAssignableRoleId(assignableRoleId);
log.info("Assigning role {} to member {} in server {}.", assignableRoleId, member.getId(), member.getGuild().getId());
metricService.incrementCounter(ASSIGNABLE_ROLES_ASSIGNED);
return roleService.addRoleToMemberAsync(member, role.getRole());
}
@Override
public CompletableFuture<Void> assignAssignableRoleToUser(Role role, Member member) {
return assignRoleToUser(role.getIdLong(), member);
}
@Override
public void assignableRoleConditionFailure() {
metricService.incrementCounter(ASSIGNABLE_ROLES_CONDITION_FAILED);
}
private CompletableFuture<Void> assignRoleToUser(Long roleId, Member member) {
metricService.incrementCounter(ASSIGNABLE_ROLES_ASSIGNED);
return roleService.addRoleToMemberAsync(member, roleId);
}
@Override
public void clearAllRolesOfUserInPlace(AssignableRolePlace place, AUserInAServer userInAServer) {
Optional<AssignedRoleUser> userOptional = assignedRoleUserManagementServiceBean.findByUserInServerOptional(userInAServer);
@@ -102,8 +125,17 @@ public class AssignableRoleServiceBean implements AssignableRoleService {
@Override
public CompletableFuture<Void> removeAssignableRoleFromUser(AssignableRole assignableRole, Member member) {
log.info("Removing assignable role {} from user {} in server {}.", assignableRole.getId(), member.getId(), member.getGuild().getId());
return removeRoleFromUser(assignableRole.getRole().getId(), member);
}
@Override
public CompletableFuture<Void> removeAssignableRoleFromUser(Role role, Member member) {
return removeRoleFromUser(role.getIdLong(), member);
}
private CompletableFuture<Void> removeRoleFromUser(Long roleId, Member member) {
metricService.incrementCounter(ASSIGNABLE_ROLES_REMOVED);
return roleService.removeRoleFromMemberAsync(member, assignableRole.getRole());
return roleService.removeRoleFromMemberAsync(member, roleId);
}
@Override
@@ -147,9 +179,17 @@ public class AssignableRoleServiceBean implements AssignableRoleService {
throw new AssignableRoleNotFoundException(roleId);
}
@Override
public void removeAssignableRolesFromAssignableRoleUser(List<AssignableRole> roles, AssignedRoleUser roleUser) {
log.info("Removing {} assignable roles from user {} in server {}.", roles.size(), roleUser.getUser().getUserReference().getId(),
roleUser.getUser().getServerReference().getId());
roles.forEach(assignableRole -> assignedRoleUserManagementServiceBean.removeAssignedRoleFromUser(assignableRole, roleUser));
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(ASSIGNABLE_ROLES_ASSIGNED, "Assignable roles assigned.");
metricService.registerCounter(ASSIGNABLE_ROLES_REMOVED, "Assignable roles removed.");
metricService.registerCounter(ASSIGNABLE_ROLES_CONDITION_FAILED, "Assignable roles failed to assign because of condition.");
}
}

View File

@@ -4,12 +4,14 @@ import dev.sheldan.abstracto.assignableroles.model.condition.AssignableRoleCondi
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRoleCondition;
import dev.sheldan.abstracto.assignableroles.repository.AssignableRoleConditionRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class AssignableRoleConditionManagementServiceBean implements AssignableRoleConditionManagementService {
@Autowired
@@ -23,12 +25,14 @@ public class AssignableRoleConditionManagementServiceBean implements AssignableR
.type(type)
.conditionValue(value)
.build();
log.info("Creating condition of type {} for assignable role {}", assignableRole.getId(), type);
assignableRole.getConditions().add(condition);
return repository.save(condition);
}
@Override
public void deleteAssignableRoleCondition(AssignableRoleCondition condition) {
log.info("Deleting condition {}.", condition.getId());
repository.delete(condition);
}

View File

@@ -2,6 +2,8 @@ package dev.sheldan.abstracto.assignableroles.service.management;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.assignableroles.repository.AssignableRoleRepository;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.FullEmote;
@@ -15,6 +17,8 @@ import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class AssignableRoleManagementServiceBean implements AssignableRoleManagementService {
@@ -51,8 +55,6 @@ public class AssignableRoleManagementServiceBean implements AssignableRoleManage
return roleToAdd;
}
@Override
public AssignableRole getByAssignableRoleId(Long assignableRoleId) {
return repository.findById(assignableRoleId).orElseThrow(() -> new AbstractoRunTimeException("Assignable role not found"));
@@ -60,7 +62,14 @@ public class AssignableRoleManagementServiceBean implements AssignableRoleManage
@Override
public void deleteAssignableRole(AssignableRole assignableRole) {
assignableRole.getAssignablePlace().getAssignableRoles().remove(assignableRole);
assignableRole.setAssignablePlace(null);
repository.delete(assignableRole);
}
@Override
public List<AssignableRole> getAssignableRolesFromAssignableUserWithPlaceType(AssignedRoleUser user, AssignableRolePlaceType type) {
return repository.findByAssignedUsersContainingAndAssignablePlace_Type(user, type);
}
}

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.assignableroles.service.management;
import dev.sheldan.abstracto.assignableroles.exception.AssignableRolePlaceNotFoundException;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.repository.AssignableRolePlaceRepository;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
@@ -20,12 +21,18 @@ public class AssignableRolePlaceManagementServiceBean implements AssignableRoleP
private AssignableRolePlaceRepository repository;
@Override
public AssignableRolePlace createPlace(String name, AChannel channel, String text) {
public AssignableRolePlace createPlace(String name, AChannel channel, String text, AssignableRolePlaceType type) {
boolean unique = false;
if(type.equals(AssignableRolePlaceType.BOOSTER)) {
unique = true;
}
AssignableRolePlace place = AssignableRolePlace
.builder()
.channel(channel)
.server(channel.getServer())
.text(text)
.uniqueRoles(unique)
.type(type)
.key(name)
.build();
log.info("Creating assignable role place in channel {} on server {}.", channel.getId(), channel.getServer().getId());

View File

@@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
@@ -43,6 +44,18 @@ public class AssignedRoleUserManagementServiceBean implements AssignedRoleUserMa
removeAssignedRoleFromUser(assignableRole, user);
}
@Override
public void removeAssignedRoleFromUsers(AssignableRole assignableRole, List<AssignedRoleUser> users) {
log.info("Clearing all assignable role {} for {} users.", assignableRole.getId(), users.size());
assignableRole.getAssignedUsers().removeAll(users);
users.forEach(roleUser -> roleUser.getRoles().remove(assignableRole));
}
@Override
public void removeAssignedRoleFromUsers(AssignableRole assignableRole) {
removeAssignedRoleFromUsers(assignableRole, assignableRole.getAssignedUsers());
}
@Override
public void removeAssignedRoleFromUser(AssignableRole assignableRole, AssignedRoleUser user) {
assignableRole.getAssignedUsers().remove(user);

View File

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

View File

@@ -0,0 +1,15 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="assignable_role_place-add_type">
<addColumn tableName="assignable_role_place">
<column name="type" type="VARCHAR2(128)" defaultValue="DEFAULT"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -7,4 +7,5 @@
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.0-assignableRoles/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.4/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

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

View File

@@ -0,0 +1,20 @@
package dev.sheldan.abstracto.assignableroles.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class BoosterAssignableRolePlaceMemberNotBoostingException extends AbstractoTemplatableException {
public BoosterAssignableRolePlaceMemberNotBoostingException() {
super("Clicking member does not boost");
}
@Override
public String getTemplateName() {
return "assignable_role_booster_place_member_not_boosting_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -10,11 +10,6 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
/**
* The place entity holding the {@link AssignableRole roles} and {@link AssignableRolePlacePost posts} together.
* This is also the entity holding all the configuration for the place and is identified by a key as a String, which is unique
* for each server. This place holds the {@link AChannel} in which the
*/
@Entity
@Table(name = "assignable_role_place")
@Builder
@@ -90,4 +85,8 @@ public class AssignableRolePlace implements Serializable {
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
@Enumerated(EnumType.STRING)
@Column(name = "type")
private AssignableRolePlaceType type;
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.assignableroles.model.database;
import dev.sheldan.abstracto.core.command.execution.CommandParameterKey;
import lombok.Getter;
@Getter
public enum AssignableRolePlaceType implements CommandParameterKey {
DEFAULT, BOOSTER
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.assignableroles.model.template;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.core.models.template.display.ChannelDisplay;
import lombok.Builder;
import lombok.Getter;
@@ -19,6 +20,7 @@ public class AssignableRolePlaceConfig {
private String placeText;
private ChannelDisplay channelDisplay;
private Boolean uniqueRoles;
private AssignableRolePlaceType type;
/**
* The {@link AssignableRolePlaceConfig roles} which are contained in this {@link AssignableRolePlace}
*/

View File

@@ -2,17 +2,21 @@ package dev.sheldan.abstracto.assignableroles.service;
import dev.sheldan.abstracto.assignableroles.config.AssignableRolePlaceParameterKey;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.template.AssignablePlaceOverview;
import dev.sheldan.abstracto.assignableroles.model.template.AssignableRolePlaceConfig;
import dev.sheldan.abstracto.core.models.FullEmote;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.TextChannel;
import java.util.concurrent.CompletableFuture;
public interface AssignableRolePlaceService {
void createAssignableRolePlace(String name, AChannel channel, String text);
void createAssignableRolePlace(String name, AChannel channel, String text, AssignableRolePlaceType type);
CompletableFuture<Void> addRoleToAssignableRolePlace(AServer server, String placeName, Role role, FullEmote emote, String description);
@@ -42,7 +46,7 @@ public interface AssignableRolePlaceService {
void multipleAssignableRolePlace(AssignableRolePlace place);
CompletableFuture<Void> showAssignablePlaceConfig(AServer server, String name, TextChannel channel);
AssignableRolePlaceConfig getAssignableRolePlaceConfig(Guild guild, String name);
CompletableFuture<Void> moveAssignableRolePlace(AServer server, String name, TextChannel newChannel);
@@ -52,5 +56,5 @@ public interface AssignableRolePlaceService {
CompletableFuture<Void> changeConfiguration(AServer server, String name, AssignableRolePlaceParameterKey keyToChange, String newValue);
CompletableFuture<Void> showAllAssignableRolePlaces(AServer server, TextChannel channel);
AssignablePlaceOverview getAssignableRolePlaceOverview(Guild guild);
}

View File

@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.assignableroles.service;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
@@ -22,6 +24,8 @@ public interface AssignableRoleService {
*/
CompletableFuture<Void> assignAssignableRoleToUser(Long assignableRoleId, Member member);
CompletableFuture<Void> assignAssignableRoleToUser(Role role, Member member);
void assignableRoleConditionFailure();
/**
* Clears all {@link AssignableRole assignableRoles} which are currently given to the {@link AUserInAServer user} of a certain
@@ -39,6 +43,7 @@ public interface AssignableRoleService {
* has been removed from the {@link Member member}
*/
CompletableFuture<Void> removeAssignableRoleFromUser(AssignableRole assignableRole, Member member);
CompletableFuture<Void> removeAssignableRoleFromUser(Role role, Member member);
/**
* Removes the {@link AssignableRole role} from the given {@link Member member}
@@ -67,4 +72,6 @@ public interface AssignableRoleService {
AssignableRole getAssignableRoleInPlace(AssignableRolePlace place, ARole role);
AssignableRole getAssignableRoleInPlace(AssignableRolePlace place, Long roleId);
void removeAssignableRolesFromAssignableRoleUser(List<AssignableRole> roles, AssignedRoleUser roleUser);
}

View File

@@ -2,41 +2,19 @@ package dev.sheldan.abstracto.assignableroles.service.management;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.core.models.FullEmote;
import dev.sheldan.abstracto.core.models.cache.CachedEmote;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
import net.dv8tion.jda.api.entities.Role;
/**
* Management service for the table of {@link AssignableRole assignableRoles}
*/
import java.util.List;
public interface AssignableRoleManagementService {
/**
* Adds the given {@link ARole role} to the {@link AssignableRolePlace place} to be identified with the given {@link AEmote emote}
* and displayed with the given description. An optional {@link AssignableRolePlacePost post} can be provided, if the
* place has already been setup.
* @param emote The {@link AEmote emote} which is used as an reaction on the {@link AssignableRolePlacePost post}
* @param role The {@link ARole role} which should be given to the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer user} placing a reaction
* @param description The description which should be displayed in the field for the given {@link ARole role}
* @return The created instance of the {@link AssignableRole assignableRole} according to the given parameters
*/
AssignableRole addRoleToPlace(FullEmote emote, Role role, String description, AssignableRolePlace place, ComponentPayload componentPayload);
/**
* Finds the {@link AssignableRole} given by the ID and returns it if found. Throws an exception otherwise.
* @param assignableRoleId The ID Of the {@link AssignableRole assignableRole} to find
* @return An instance of {@link AssignableRole assignableRole} if it exists for the given ID
*/
AssignableRole getByAssignableRoleId(Long assignableRoleId);
void deleteAssignableRole(AssignableRole assignableRole);
List<AssignableRole> getAssignableRolesFromAssignableUserWithPlaceType(AssignedRoleUser user, AssignableRolePlaceType type);
/**
* Returns the respective {@link AssignableRole assignableRole} for the {@link CachedEmote emote} which is part of the
* {@link AssignableRolePlace place}. It will throw an exception, if the {@link CachedEmote emote} is not used.
* @param cachedEmote The {@link CachedEmote emote} which should be used to identify the {@link AssignableRole role}
* @param assignableRolePlace The {@link AssignableRolePlace place} from which the {@link AssignableRole role} should be retrieved for
* @return An instance of {@link AssignableRole role} which was in the place and identified by the emote
*/
}

View File

@@ -1,88 +1,31 @@
package dev.sheldan.abstracto.assignableroles.service.management;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import java.util.List;
import java.util.Optional;
/**
* Management service for {@link AssignableRolePlace place} table
*/
public interface AssignableRolePlaceManagementService {
/**
* Creates an {@link AssignableRolePlace place} with the given attributes
* @param name The key of the {@link AssignableRolePlace place} to identify it with
* @param channel The {@link AChannel channel} in which the {@link dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlacePost posts}
* should be created in
* @param text The text which should be shown in the description
* @return The {@link AssignableRolePlace place} which was created
*/
AssignableRolePlace createPlace(String name, AChannel channel, String text);
AssignableRolePlace createPlace(String name, AChannel channel, String text, AssignableRolePlaceType type);
/**
* Whether or not a place with the key exists in the given {@link AServer server}
* @param server The {@link AServer server} in which it should be searched
* @param name The key of an {@link AssignableRolePlace place} which should be searched
* @return Whether or not an {@link AssignableRolePlace place} with the key exists in the {@link AServer server}
*/
boolean doesPlaceExist(AServer server, String name);
/**
* Retrieves an {@link AssignableRolePlace place} identified by the given key in the {@link AServer server}
* @param server The {@link AServer server} to search in
* @param name The key of the {@link AssignableRolePlace place} to search for
* @throws dev.sheldan.abstracto.assignableroles.exception.AssignableRolePlaceNotFoundException if not found
* @return Returns an instance of {@link AssignableRolePlace place}, if it was found
*/
AssignableRolePlace findByServerAndKey(AServer server, String name);
/**
* Retrieves an {@link AssignableRolePlace place} via its ID in an {@link Optional optional}
* @param id The ID to search for
* @return Returns an {@link Optional optional} with an instance of {@link AssignableRolePlace place}, if it was found
* and empty otherwise
*/
Optional<AssignableRolePlace> findByPlaceIdOptional(Long id);
/**
* Retrieves an {@link AssignableRolePlace place} via its ID
* @param id The ID to search for
* @throws dev.sheldan.abstracto.assignableroles.exception.AssignableRolePlaceNotFoundException if not found
* @return Returns an {@link AssignableRolePlace place} if one was found with this ID
*/
AssignableRolePlace findByPlaceId(Long id);
/**
* Changes the {@link AChannel channel} in which a {@link AssignableRolePlace place} should be posted towards
* when being setup.
* @param name The key of the {@link AssignableRolePlace place} to move
* @param newChannel The new {@link AChannel channel} where the place should be moved to, used to determine in which {@link AServer server}
* the {@link AssignableRolePlace place} is
*/
void moveAssignableRolePlace(String name, AChannel newChannel);
/**
* Changes the {@link AssignableRolePlace#text description} of an assignable role place
* @param server The {@link AServer server} in which to look for the place
* @param name The key of the {@link AssignableRolePlace place} to change the descripiont for
* @param newDescription The description value which should be used from now on
*/
void changeAssignableRolePlaceDescription(AServer server, String name, String newDescription);
/**
* Deletes the {@link AssignableRolePlace place}
* @param place The {@link AssignableRolePlace place} to remove
*/
void deleteAssignablePlace(AssignableRolePlace place);
/**
* Retrieves all {@link AssignableRolePlace assignableRolePlaces} for the given {@link AServer server}
* @param server The {@link AServer server} for whom the places should be retrieved for
* @return All {@link AssignableRolePlace assignableRolePlaces} in the given {@link AServer server}
*/
List<AssignableRolePlace> findAllByServer(AServer server);
}

View File

@@ -6,6 +6,7 @@ import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import java.util.List;
import java.util.Optional;
/**
@@ -25,6 +26,8 @@ public interface AssignedRoleUserManagementService {
* @param aUserInAServer The {@link AUserInAServer user} from whom the {@link AssignableRole role} should be removed
*/
void removeAssignedRoleFromUser(AssignableRole assignableRole, AUserInAServer aUserInAServer);
void removeAssignedRoleFromUsers(AssignableRole assignableRole, List<AssignedRoleUser> users);
void removeAssignedRoleFromUsers(AssignableRole assignableRole);
/**
* Removes the given {@link AssignableRole assignableFrom} from the given {@link AssignedRoleUser user}.

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ public class ActivityServiceBean implements ActivityService {
List<CustomActivity> activities = activityManagementService.getAllActivities();
if(!activities.isEmpty()) {
CustomActivity chosen = activities.get(secureRandom.nextInt(activities.size()));
log.info("Chosen {} activity.", chosen.getId());
log.info("Chosen activity {}.", chosen.getId());
switchToActivity(chosen);
} else {
log.info("No activities configured.");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,16 +39,17 @@ public class HasLevelCondition implements SystemCondition {
Map<String, Object> parameters = conditionContext.getParameters();
Long userInServerId = (Long) parameters.get(USER_IN_SERVER_ID_VARIABLE_KEY);
Integer level = (Integer) parameters.get(LEVEL_VARIABLE);
log.info("Evaluating has level condition.");
Optional<AUserInAServer> userInServerOptional = userInServerManagementService.loadUserOptional(userInServerId);
if(userInServerOptional.isPresent()) {
AUserInAServer userInServer = userInServerOptional.get();
log.info("Evaluating has level condition for user {} in server {} with level {}.",
userInServer.getUserReference().getId(), userInServer.getServerReference().getId(), level);
AUserExperience user = userExperienceManagementService.findUserInServer(userInServer);
return user.getCurrentLevel() != null && user.getCurrentLevel().getLevel() >= level;
boolean conditionResult = user.getCurrentLevel() != null && user.getCurrentLevel().getLevel() >= level;
log.info("Condition evaluated to {}", conditionResult);
return conditionResult;
}
log.info("No user in server object was found. Evaluating to false.");
log.info("No user in server object was found. Evaluating has level to false.");
return false;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -325,6 +325,9 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
sendDeletionNotification(deletedInvites, message);
}
}
}).exceptionally(throwable -> {
log.error("Invite matching failed.", throwable);
return null;
});
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -168,18 +168,39 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
.stream()
.map(EmbeddedMessage::getEmbeddingMessageId)
.collect(Collectors.toList());
List<String> componentPayloadsToDelete = embeddedMessages
.stream()
.map(EmbeddedMessage::getDeletionComponentId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
List<CompletableFuture<Message>> reactionMessageFutures = messageService.retrieveMessages(reactionChannelMessages);
List<CompletableFuture<Message>> buttonMessageFutures = messageService.retrieveMessages(buttonChannelMessages);
CompletableFutureList<Message> reactionFutureList = new CompletableFutureList<>(reactionMessageFutures);
CompletableFutureList<Message> buttonFutureList = new CompletableFutureList<>(buttonMessageFutures);
return reactionFutureList.getMainFuture()
.handle((unused, throwable) -> self.removeReactions(reactionFutureList.getObjects()))
.handle((unused, throwable) -> {
if(throwable != null) {
log.warn("Embedded messages reaction message loading failed.", throwable);
}
return self.removeReactions(reactionFutureList.getObjects());
})
.thenCompose(Function.identity())
.thenCompose(unused -> buttonFutureList.getMainFuture())
.handle((unused, throwable) -> self.removeButtons(buttonFutureList.getObjects()))
.handle((unused, throwable) -> {
if(throwable != null) {
log.warn("Embedded messages button message loading failed.", throwable);
}
return self.removeButtons(buttonFutureList.getObjects());
})
// deleting the messages from db regardless of exceptions, at most the reaction remains
.thenCompose(Function.identity())
.whenComplete((unused, throwable) -> self.deleteEmbeddedMessages(embeddedMessagesHandled))
.whenComplete((unused, throwable) -> {
if(throwable != null) {
log.warn("Embedded message button clearing failed.", throwable);
}
self.deleteEmbeddedMessages(embeddedMessagesHandled, componentPayloadsToDelete);
})
.exceptionally(throwable -> {
log.error("Failed to clean up embedded messages.", throwable);
return null;
@@ -216,8 +237,9 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
}
@Transactional
public void deleteEmbeddedMessages(List<Long> embeddedMessagesToDelete) {
public void deleteEmbeddedMessages(List<Long> embeddedMessagesToDelete, List<String> componentPayloadsToDelete) {
messageEmbedPostManagementService.deleteEmbeddedMessagesViaId(embeddedMessagesToDelete);
componentPayloadManagementService.deletePayloads(componentPayloadsToDelete);
}
@Transactional
@@ -294,6 +316,9 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
.builder()
.buttonId(deletionButtonEnabled ? componentServiceBean.generateComponentId() : null)
.build();
Long referencedMessageId = message.getReferencedMessage() != null ? message.getReferencedMessage().getIdLong() : null;
Boolean shouldMentionReferencedAuthor = shouldMentionReferencedAuthor(message);
return MessageEmbeddedModel
.builder()
.member(message.getMember())
@@ -304,7 +329,16 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
.guild(message.getGuild())
.useButton(deletionButtonEnabled)
.embeddedMessage(embeddedMessage)
.referencedMessageId(referencedMessageId)
.mentionsReferencedMessage(shouldMentionReferencedAuthor)
.buttonConfigModel(buttonConfigModel)
.build();
}
private Boolean shouldMentionReferencedAuthor(Message message) {
if(message.getReferencedMessage() != null) {
return message.getMentionedUsers().contains(message.getReferencedMessage().getAuthor());
}
return false;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.core.utils.ContextUtils;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MessageDeletedAttachmentLog;
@@ -81,11 +82,14 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.member(authorMember)
.build();
MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_DELETED_TEMPLATE, logModel, messageFromCache.getServerId());
postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId())).exceptionally(throwable -> {
log.error("Failed to send message deleted log.", throwable);
return null;
});
if(messageFromCache.getAttachments() != null){
log.debug("Notifying about deletions of {} attachments.", messageFromCache.getAttachments().size());
for (int i = 0; i < messageFromCache.getAttachments().size(); i++) {
MessageDeletedAttachmentLog log = MessageDeletedAttachmentLog
MessageDeletedAttachmentLog attachmentLogModel = MessageDeletedAttachmentLog
.builder()
.imageUrl(messageFromCache.getAttachments().get(i).getProxyUrl())
.counter(i + 1)
@@ -93,8 +97,12 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.channel(textChannel)
.member(authorMember)
.build();
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_DELETED_ATTACHMENT_TEMPLATE, log, messageFromCache.getServerId());
postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId());
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_DELETED_ATTACHMENT_TEMPLATE, attachmentLogModel, messageFromCache.getServerId());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message deleted log.", throwable);
return null;
});
}
}
}

View File

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

View File

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

View File

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

View File

@@ -40,8 +40,7 @@ public class Ban extends AbstractConditionableCommand {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
User user = (User) parameters.get(0);
String defaultReason = templateService.renderSimpleTemplate(BAN_DEFAULT_REASON_TEMPLATE, commandContext.getGuild().getIdLong());
String reason = parameters.size() == 2 ? (String) parameters.get(1) : defaultReason;
String reason = (String) parameters.get(1);
return banService.banUser(user, reason, commandContext.getAuthor(), commandContext.getMessage())
.thenApply(aVoid -> CommandResult.fromSuccess());
@@ -51,7 +50,7 @@ public class Ban extends AbstractConditionableCommand {
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("user").templated(true).type(User.class).build());
parameters.add(Parameter.builder().name("reason").templated(true).type(String.class).optional(true).remainder(true).build());
parameters.add(Parameter.builder().name("reason").templated(true).type(String.class).remainder(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).hasExample(true).build();
List<EffectConfig> effectConfig = Arrays.asList(EffectConfig.builder().position(0).effectKey(BAN_EFFECT_KEY).build());
return CommandConfiguration.builder()

View File

@@ -0,0 +1,78 @@
package dev.sheldan.abstracto.moderation.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.EffectConfig;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.service.BanService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.moderation.service.BanService.BAN_EFFECT_KEY;
@Component
@Slf4j
public class SoftBan extends AbstractConditionableCommand {
@Autowired
private BanService banService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
User user = (User) parameters.get(0);
Duration delDays = Duration.ofDays(7);
if(parameters.size() > 1) {
delDays = (Duration) parameters.get(1);
}
return banService.softBanUser(commandContext.getGuild(), user, delDays)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("user").templated(true).type(User.class).build());
parameters.add(Parameter.builder().name("delDays").templated(true).type(Duration.class).optional(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
List<EffectConfig> effectConfig = Arrays.asList(EffectConfig.builder().position(0).effectKey(BAN_EFFECT_KEY).build());
return CommandConfiguration.builder()
.name("softBan")
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)
.effects(effectConfig)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
}
@Override
public List<CommandCondition> getConditions() {
List<CommandCondition> conditions = super.getConditions();
conditions.add(immuneUserCondition);
return conditions;
}
}

View File

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

View File

@@ -50,6 +50,7 @@ public class UserBannedListener implements AsyncUserBannedListener {
.queue(auditLogEntries -> {
if(auditLogEntries.isEmpty()) {
log.info("Did not find recent bans in guild {}.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
return;
}
Optional<AuditLogEntry> banEntryOptional = auditLogEntries
@@ -65,6 +66,9 @@ public class UserBannedListener implements AsyncUserBannedListener {
log.info("Did not find the banned user in the most recent bans for guild {}. Not adding audit log information.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
}
}, throwable -> {
log.error("Retrieving bans for guild {} failed - logging ban regardless.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
});
return DefaultListenerResult.PROCESSED;
}

View File

@@ -16,6 +16,7 @@ import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -58,7 +59,7 @@ public class BanServiceBean implements BanService {
.commandMessage(message)
.reason(reason)
.build();
CompletableFuture<Void> banFuture = banUser(member.getGuild(), member.getUser(), reason);
CompletableFuture<Void> banFuture = banUser(member.getGuild(), member.getUser(), 0, reason);
CompletableFuture<Void> messageFuture = sendBanLogMessage(banLog, member.getGuild().getIdLong(), BAN_LOG_TEMPLATE);
return CompletableFuture.allOf(banFuture, messageFuture);
}
@@ -81,7 +82,7 @@ public class BanServiceBean implements BanService {
.thenAccept(message1 -> log.info("Notified about not being able to send ban notification in server {} and channel {} based on message {} from user {}."
, message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong(), message.getAuthor().getIdLong()));
}
CompletableFuture<Void> banFuture = banUser(guild, user, reason);
CompletableFuture<Void> banFuture = banUser(guild, user, 0, reason);
CompletableFuture<Void> messageFuture = sendBanLogMessage(banLog, guild.getIdLong(), BAN_LOG_TEMPLATE);
CompletableFuture.allOf(banFuture, messageFuture)
.thenAccept(unused1 -> returningFuture.complete(null))
@@ -89,6 +90,9 @@ public class BanServiceBean implements BanService {
returningFuture.completeExceptionally(throwable1);
return null;
});
}).exceptionally(throwable -> {
returningFuture.completeExceptionally(throwable);
return null;
});
return returningFuture;
}
@@ -111,10 +115,29 @@ public class BanServiceBean implements BanService {
.bannedUser(user)
.unBanningMember(unBanningMember)
.build();
return unBanUser(guild, user)
return unbanUser(guild, user)
.thenCompose(unused -> self.sendUnBanLogMessage(banLog, guild.getIdLong(), UN_BAN_LOG_TEMPLATE));
}
@Override
public CompletableFuture<Void> banUser(Guild guild, User user, Integer deletionDays, String reason) {
log.info("Banning user {} in guild {}.", user.getIdLong(), guild.getId());
return guild.ban(user, deletionDays, reason).submit();
}
@Override
public CompletableFuture<Void> unbanUser(Guild guild, User user) {
log.info("Unbanning user {} in guild {}.", user.getIdLong(), guild.getId());
return guild.unban(user).submit();
}
@Override
public CompletableFuture<Void> softBanUser(Guild guild, User user, Duration delDays) {
Long days = delDays.toDays();
return banUser(guild, user, days.intValue(), "")
.thenCompose(unused -> unbanUser(guild, user));
}
public CompletableFuture<Void> sendBanLogMessage(BanLog banLog, Long guildId, String template) {
CompletableFuture<Void> completableFuture;
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
@@ -132,14 +155,4 @@ public class BanServiceBean implements BanService {
completableFuture = FutureUtils.toSingleFutureGeneric(notificationFutures);
return completableFuture;
}
private CompletableFuture<Void> banUser(Guild guild, User user, String reason) {
log.info("Banning user {} in guild {}.", user.getIdLong(), guild.getId());
return guild.ban(user, 0, reason).submit();
}
private CompletableFuture<Void> unBanUser(Guild guild, User user) {
log.info("Unbanning user {} in guild {}.", user.getIdLong(), guild.getId());
return guild.unban(user).submit();
}
}

View File

@@ -163,6 +163,9 @@ public class MuteServiceBean implements MuteService {
channelService.sendTextToChannel(throwable.getMessage(), feedBackChannel).whenComplete((exceptionMessage, innerThrowable) -> {
notificationFuture.complete(null);
log.info("Successfully notified user {} in server {} about mute.", memberBeingMuted.getId(), memberBeingMuted.getGuild().getId());
}).exceptionally(throwable1 -> {
notificationFuture.completeExceptionally(throwable1);
return null;
});
return null;
});
@@ -378,4 +381,10 @@ public class MuteServiceBean implements MuteService {
completelyUnMuteUser(userInServerManagementService.loadOrCreateUser(member));
}
@Override
public CompletableFuture<Void> muteMemberWithoutContext(Member member) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
return applyMuteRole(aUserInAServer);
}
}

View File

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

View File

@@ -188,16 +188,20 @@ public class WarnServiceBean implements WarnService {
MessageToSend messageToSend = templateService.renderEmbedTemplate(WARN_DECAY_NOTIFICATION_TEMPLATE_KEY, model, serverId);
log.info("Notifying user {} in server {} about decayed warning {}.", userId, serverId, warningId);
notificationFutures.add(messageService.sendMessageToSendToUser(memberToSendTo.getUser(), messageToSend).exceptionally(throwable -> {
log.error("Failed to send warn decay message to user {} in server {} to notify about decay warning {}.", userId, server, warningId);
log.error("Failed to send warn decay message to user {} in server {} to notify about decay warning {}.", userId, server.getId(), warningId, throwable);
return null;
}));
} else {
log.warn("Could not find user {} in server {}. Not notifying about decayed warning {}.", userId, serverId, warningId);
}
});
CompletableFuture<Void> future = new CompletableFuture();
CompletableFuture<Void> future = new CompletableFuture<>();
FutureUtils.toSingleFutureGeneric(notificationFutures)
.whenComplete((unused, throwable) -> future.complete(null));
.whenComplete((unused, throwable) -> future.complete(null))
.exceptionally(throwable -> {
future.completeExceptionally(throwable);
return null;
});
return future;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,36 +31,16 @@ public class BanTest {
@Mock
private BanService banService;
@Mock
private TemplateService templateService;
@Captor
private ArgumentCaptor<Member> banLogModelCaptor;
private static final String REASON = "reason";
private static final Long SERVER_ID = 1L;
@Mock
private User bannedMember;
@Test
public void testBanWithDefaultReason() {
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(bannedMember));
when(parameters.getGuild().getIdLong()).thenReturn(SERVER_ID);
when(templateService.renderSimpleTemplate(Ban.BAN_DEFAULT_REASON_TEMPLATE, SERVER_ID)).thenReturn(REASON);
when(banService.banUser(eq(bannedMember), eq(REASON), banLogModelCaptor.capture(), any(Message.class))).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
Member banningMember = banLogModelCaptor.getValue();
Assert.assertEquals(parameters.getAuthor(), banningMember);
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}
@Test
public void testBanWithReason() {
String customReason = "reason2";
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(bannedMember, customReason));
when(parameters.getGuild().getIdLong()).thenReturn(SERVER_ID);
when(templateService.renderSimpleTemplate(Ban.BAN_DEFAULT_REASON_TEMPLATE, SERVER_ID)).thenReturn(REASON);
when(banService.banUser(eq(bannedMember), eq(customReason), banLogModelCaptor.capture(), any(Message.class))).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
Member banningMember = banLogModelCaptor.getValue();

View File

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

View File

@@ -1,9 +1,11 @@
package dev.sheldan.abstracto.moderation.service;
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 java.time.Duration;
import java.util.concurrent.CompletableFuture;
public interface BanService {
@@ -11,4 +13,7 @@ public interface BanService {
CompletableFuture<Void> banMember(Member member, String reason, Member banningMember, Message message);
CompletableFuture<Void> banUser(User user, String reason, Member banningMember, Message message);
CompletableFuture<Void> unBanUser(User user, Member unBanningUser);
CompletableFuture<Void> banUser(Guild guild, User user, Integer deletionDays, String reason);
CompletableFuture<Void> unbanUser(Guild guild, User user);
CompletableFuture<Void> softBanUser(Guild guild, User user, Duration delDays);
}

View File

@@ -23,4 +23,5 @@ public interface MuteService {
CompletableFuture<Void> endMute(Long muteId, Long serverId);
void completelyUnMuteUser(AUserInAServer aUserInAServer);
void completelyUnMuteMember(Member member);
CompletableFuture<Void> muteMemberWithoutContext(Member member);
}

View File

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

View File

@@ -57,7 +57,10 @@ public class ModMailMessageDeletedListener implements AsyncMessageDeletedListene
}
CompletableFuture.allOf(dmDeletePromise, channelDeletePromise).whenComplete((unused, throwable) ->
self.removeMessageFromThread(message.getMessageId())
);
).exceptionally(throwable -> {
log.error("Failed to delete message.", throwable);
return null;
});
});
});
return DefaultListenerResult.PROCESSED;

View File

@@ -101,7 +101,7 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
}
List<Message> loadedMessages = new ArrayList<>();
CompletableFuture.allOf(threadHistoryFuture, privateHistoryFuture)
.thenCompose(unused -> loadMoreMessages(messageIdsToLoad, privateHistoryFuture.join(), modMailThread, threadHistoryFuture.join(), privateChannel, loadedMessages, 0))
.thenCompose(unused -> loadMoreMessages(messageIdsToLoad.size(), messageIdsToLoad, privateHistoryFuture.join(), modMailThread, threadHistoryFuture.join(), privateChannel, loadedMessages, 0))
.thenAccept(unused -> {
Set<Long> userIds = messageIds
.stream()
@@ -124,10 +124,11 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
return future;
}
public CompletableFuture<Void> loadMoreMessages(List<Long> messagesToLoad,
public CompletableFuture<Void> loadMoreMessages(Integer totalMessageCount, List<Long> messagesToLoad,
MessageHistory privateMessageHistory, TextChannel thread,
MessageHistory threadMessageHistory, PrivateChannel dmChannel, List<Message> loadedMessages, Integer counter) {
if(counter == messagesToLoad.size()) {
// TODO maybe find a better mechanism for this... one which does not lead to infinite loops, but also doesnt miss out on history
if(counter.equals(totalMessageCount * 2)) {
log.warn("We encountered the maximum of {} iterations when loading modmail history - aborting.", messagesToLoad.size());
return CompletableFuture.completedFuture(null);
}
@@ -171,7 +172,7 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
privateHistoryAction = CompletableFuture.completedFuture(null);
}
return CompletableFuture.allOf(threadHistoryAction, privateHistoryAction)
.thenCompose(lists -> loadMoreMessages(messagesToLoad, threadHistoryAction.join(), thread, privateHistoryAction.join(), dmChannel, loadedMessages, counter + 1));
.thenCompose(lists -> loadMoreMessages(totalMessageCount, messagesToLoad, threadHistoryAction.join(), thread, privateHistoryAction.join(), dmChannel, loadedMessages, counter + 1));
}
}

View File

@@ -39,6 +39,7 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -62,6 +63,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* The config key to use for the ID of the category to create {@link MessageChannel} in
*/
public static final String MODMAIL_CATEGORY = "modmailCategory";
public static final String TEXT_CHANNEL_NAME_TEMPLATE_KEY = "modMail_channel_name";
/**
* The template key used for default mod mail exceptions
*/
@@ -189,7 +191,16 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
metricService.incrementCounter(MODMAIL_THREAD_CREATED_COUNTER);
User user = member.getUser();
log.info("Creating modmail channel for user {} in category {} on server {}.", user.getId(), categoryId, serverId);
CompletableFuture<TextChannel> textChannelFuture = channelService.createTextChannel(user.getName() + user.getDiscriminator(), server, categoryId);
ModMailChannelNameModel model = ModMailChannelNameModel
.builder()
.serverId(serverId)
.userId(member.getIdLong())
.randomText(RandomStringUtils.randomAlphanumeric(25))
.uuid(UUID.randomUUID().toString())
.currentDate(Instant.now())
.build();
String channelName = templateService.renderTemplate(TEXT_CHANNEL_NAME_TEMPLATE_KEY, model, serverId);
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, feedBackChannel);
@@ -222,10 +233,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
CompletableFuture<Void> headerFuture = sendModMailHeader(channel, member);
CompletableFuture<Message> userReplyMessage;
if(initialMessage != null){
log.debug("Sending initial message {} of user {} to modmail thread {}.", initialMessage.getId(), member.getId(), channel.getId());
log.info("Sending initial message {} of user {} to modmail thread {}.", initialMessage.getId(), member.getId(), channel.getId());
userReplyMessage = self.sendUserReply(channel, 0L, initialMessage, member, false);
} else {
log.debug("No initial message to send.");
log.info("No initial message to send.");
userReplyMessage = CompletableFuture.completedFuture(null);
}
CompletableFuture notificationFuture;
@@ -338,6 +349,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
log.info("There were {} shared servers found which have modmail enabled.", availableGuilds.size());
// if more than 1 server is available, show a choice dialog
ArrayList<UndoActionInstance> undoActions = new ArrayList<>();
if(availableGuilds.size() > 1) {
ModMailServerChooserModel modMailServerChooserModel = ModMailServerChooserModel
.builder()
@@ -354,15 +366,16 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
log.debug("Executing action for creationg a modmail thread in server {} for user {}.", chosenServerId, userId);
memberService.getMemberInServerAsync(chosenServerId, userId).thenCompose(member -> {
try {
return self.createModMailThreadForUser(member, initialMessage, initialMessage.getChannel(), true, new ArrayList<>());
return self.createModMailThreadForUser(member, initialMessage, initialMessage.getChannel(), true, undoActions);
} catch (Exception exception) {
log.error("Setting up modmail thread for user {} in server {} failed.", userId, chosenServerId, exception);
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(exception);
return future;
}
}).exceptionally(throwable -> {
}).exceptionally(throwable -> {
log.error("Failed to load member {} for modmail in server {}.", userId, chosenServerId, throwable);
undoActionService.performActions(undoActions);
return null;
});
})
@@ -375,7 +388,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
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, initialMessage.getChannel(), true, new ArrayList<>());
return self.createModMailThreadForUser(member, initialMessage, initialMessage.getChannel(), true, undoActions);
} catch (Exception exception) {
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(exception);
@@ -383,6 +396,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
}).exceptionally(throwable -> {
log.error("Failed to setup thread correctly", throwable);
undoActionService.performActions(undoActions);
return null;
});
} else {
@@ -468,9 +482,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
if(subscriberList.isEmpty()) {
subscriberMemberFutures.add(CompletableFuture.completedFuture(null));
}
log.debug("Mentioning {} subscribers for modmail thread {}.", subscriberList.size(), modMailThreadId);
log.info("Mentioning {} subscribers for modmail thread {}.", subscriberList.size(), modMailThreadId);
} else {
subscriberMemberFutures.add(CompletableFuture.completedFuture(null));
log.info("Initial setup of modmail - not mentioning subscribers.");
}
CompletableFuture<Message> messageFuture = new CompletableFuture<>();
FutureUtils.toSingleFutureGeneric(subscriberMemberFutures).whenComplete((unused, throwable) -> {
@@ -507,6 +522,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
messageFuture.completeExceptionally(throwable1);
return null;
});
}).exceptionally(throwable -> {
messageFuture.completeExceptionally(throwable);
return null;
});
return messageFuture;
@@ -537,7 +555,6 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Transactional
public CompletableFuture<Void> relayMessageToDm(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, MessageChannel feedBack, List<UndoActionInstance> undoActions, Member targetMember) {
log.info("Relaying message {} to user {} in modmail thread {} on server {}.", replyCommandMessage.getId(), targetMember.getId(), modmailThreadId, targetMember.getGuild().getId());
AUserInAServer moderator = userInServerManagementService.loadOrCreateUser(replyCommandMessage.getMember());
metricService.incrementCounter(MDOMAIL_THREAD_MESSAGE_SENT);
ModMailThread modMailThread = modMailThreadManagementService.getById(modmailThreadId);
FullUserInServer fullThreadUser = FullUserInServer
@@ -556,9 +573,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
log.debug("Message is sent anonymous.");
modMailModeratorReplyModelBuilder.moderator(memberService.getBotInGuild(modMailThread.getServer()));
} else {
// should be loaded, because we are currently processing a command caused by the message
Member moderatorMember = memberService.getMemberInServer(moderator);
modMailModeratorReplyModelBuilder.moderator(moderatorMember);
modMailModeratorReplyModelBuilder.moderator(replyCommandMessage.getMember());
}
ModMailModeratorReplyModel modMailUserReplyModel = modMailModeratorReplyModelBuilder.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY, modMailUserReplyModel, modMailThread.getServer().getId());
@@ -570,7 +585,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
sameThreadMessageFuture = CompletableFuture.completedFuture(null);
}
return CompletableFuture.allOf(future, sameThreadMessageFuture).thenAccept(avoid ->
self.saveSendMessagesAndUpdateState(modmailThreadId, anonymous, moderator, future.join(), replyCommandMessage, sameThreadMessageFuture.join())
self.saveSendMessagesAndUpdateState(modmailThreadId, anonymous, future.join(), replyCommandMessage, sameThreadMessageFuture.join())
);
}
@@ -752,11 +767,11 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.build();
List<CompletableFuture<Message>> updateMessageFutures = channelService.sendEmbedTemplateInTextChannelList(MODMAIL_CLOSE_PROGRESS_TEMPLATE_KEY, progressModel, channel);
return FutureUtils.toSingleFutureGeneric(updateMessageFutures)
.thenApply(updateMessage -> self.logMessages(modMailThreadId, messages, context, updateMessageFutures.get(0).join()));
.thenCompose(updateMessage -> self.logMessages(modMailThreadId, messages, context, updateMessageFutures.get(0).join()));
}
@Transactional
public CompletableFutureList<Message> logMessages(Long modMailThreadId, ModmailLoggingThreadMessages messages, ClosingContext context, Message updateMessage) {
public CompletableFuture<CompletableFutureList<Message>> logMessages(Long modMailThreadId, ModmailLoggingThreadMessages messages, ClosingContext context, Message updateMessage) {
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByIdOptional(modMailThreadId);
if(modMailThreadOpt.isPresent()) {
ModMailThread modMailThread = modMailThreadOpt.get();
@@ -789,10 +804,11 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
List<CompletableFuture<Message>> completableFutures = new ArrayList<>();
log.debug("Sending close header and individual mod mail messages to mod mail log target for thread {}.", modMailThreadId);
CompletableFuture<Message> headerFuture = loadUserAndSendClosingHeader(modMailThread, context);
// TODO the header might end up later on discord servers, this look out of order
completableFutures.add(headerFuture);
completableFutures.addAll(self.sendMessagesToPostTarget(modMailThread, loggedMessages, updateMessage));
return new CompletableFutureList<>(completableFutures);
return headerFuture.thenApply(message -> {
completableFutures.addAll(self.sendMessagesToPostTarget(modMailThreadId, loggedMessages, updateMessage));
return new CompletableFutureList<>(completableFutures);
});
} else {
throw new ModMailThreadNotFoundException(modMailThreadId);
}
@@ -803,11 +819,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.builder()
.closingMember(closingContext.getClosingMember())
.note(closingContext.getNote())
.silently(closingContext.getNotifyUser())
.silently(!closingContext.getNotifyUser())
.messageCount(modMailThread.getMessages().size())
.startDate(modMailThread.getCreated())
.serverId(modMailThread.getServer().getId())
.silently(!closingContext.getNotifyUser())
.userId(modMailThread.getUser().getUserReference().getId())
.build();
return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenApply(user -> {
@@ -843,11 +858,11 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* Renders the retrieved {@link Message} which are in {@link ModMailLoggedMessageModel} into {@link MessageToSend} and
* sends this to the appropriate logging {@link PostTarget}
* @param modMailThread The {@link ModMailThread} to which the loaded messages belong to
* @param modMailThreadId The ID of {@link ModMailThread} to which the loaded messages belong to
* @param loadedMessages The list of {@link ModMailLoggedMessageModel} which can be rendered
* @return A list of {@link CompletableFuture} which represent each of the messages being send to the {@link PostTarget}
*/
public List<CompletableFuture<Message>> sendMessagesToPostTarget(ModMailThread modMailThread, List<ModMailLoggedMessageModel> loadedMessages, Message updateMessage) {
public List<CompletableFuture<Message>> sendMessagesToPostTarget(Long modMailThreadId, List<ModMailLoggedMessageModel> loadedMessages, Message updateMessage) {
List<CompletableFuture<Message>> messageFutures = new ArrayList<>();
ClosingProgressModel progressModel = ClosingProgressModel
.builder()
@@ -857,9 +872,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
loadedMessages = loadedMessages.stream().sorted(Comparator.comparing(o -> o.getMessage().getTimeCreated())).collect(Collectors.toList());
for (int i = 0; i < loadedMessages.size(); i++) {
ModMailLoggedMessageModel message = loadedMessages.get(i);
log.debug("Sending message {} of modmail thread {} to modmail log post target.", modMailThread.getId(), message.getMessage().getId());
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_close_logged_message", message, modMailThread.getServer().getId());
List<CompletableFuture<Message>> logFuture = postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, modMailThread.getServer().getId());
log.debug("Sending message {} of modmail thread {} to modmail log post target.", modMailThreadId, message.getMessage().getId());
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_close_logged_message", message, updateMessage.getGuild().getIdLong());
List<CompletableFuture<Message>> logFuture = postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, updateMessage.getGuild().getIdLong());
if(i != 0 && (i % 10) == 0) {
progressModel.setLoggedMessages(i);
messageService.editMessageWithNewTemplate(updateMessage, MODMAIL_CLOSE_PROGRESS_TEMPLATE_KEY, progressModel);
@@ -874,15 +889,15 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* and updates the state of the {@link ModMailThread}.
* @param modMailThreadId The ID of the {@link ModMailThread} for which the messages were sent for
* @param anonymous Whether or not the messages were send anonymous
* @param moderator The original {@link AUserInAServer} which authored the messages
* @param createdMessageInDM The {@link Message message} which was sent to the private channel with the {@link User user}
* @param modMailThreadMessage The {@link Message message} which was sent in the channel representing the {@link ModMailThread thread}. Might be null.
* @param replyCommandMessage The {@link Message message} which contained the command used to reply to the user
* @throws ModMailThreadNotFoundException in case the {@link ModMailThread} is not found by the ID
*/
@Transactional
public void saveSendMessagesAndUpdateState(Long modMailThreadId, Boolean anonymous, AUserInAServer moderator, Message createdMessageInDM, Message replyCommandMessage, Message modMailThreadMessage) {
public void saveSendMessagesAndUpdateState(Long modMailThreadId, Boolean anonymous, Message createdMessageInDM, Message replyCommandMessage, Message modMailThreadMessage) {
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByIdOptional(modMailThreadId);
AUserInAServer moderator = userInServerManagementService.loadOrCreateUser(replyCommandMessage.getMember());
if(modMailThreadOpt.isPresent()) {
ModMailThread modMailThread = modMailThreadOpt.get();
log.debug("Adding (anonymous: {}) message {} of moderator to modmail thread {} and setting state to {}.", anonymous, createdMessageInDM.getId(), modMailThreadId, ModMailThreadState.MOD_REPLIED);

View File

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

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