Compare commits

..

51 Commits

Author SHA1 Message Date
Sheldan
a01a5055a0 [AB-xxx] refactoring handling of bans and mutes: commands actively log, the reason for this is that the command is the only place who actually knows how executed the command. the event itself only sees the bot performing the action
adding event based logging of kicks
2024-05-06 00:07:24 +02:00
Sheldan
234aae3783 [AB-xxx] reworking ban logging to use audit log instead of actively logging or using the banned event
partially fixing broken infraction handling
adding CompletableFutureMap to handle futures easier
updating user display object to also hold name
replaced some references to UserObjects in models with UserDisplay objects
2024-05-05 01:58:26 +02:00
Sheldan
ca45137cc6 [AB-xxx] reworking mute logging to use audit log events instead of active logging and member update events 2024-05-04 20:35:56 +02:00
Sheldan
bc3d16b40e [AB-xxx] fixing using the wrong user for unmute notifications 2024-05-03 18:24:10 +02:00
Sheldan
66e212d30c [AB-xxx] updating documentation 2024-05-03 18:07:29 +02:00
Sheldan
b69811479f [AB-xxx] adding more documentation in the moderation area
removing not used feature modes from configuration
2024-04-19 23:57:33 +02:00
Sheldan
43c5d041ef [AB-xxx] adding/updating documentation for core and experience module
updating asciidoctor plugin version
adding check to not allow duplicate level action configurations
limiting experience level up notification toggle command to be only available if the feature mode is enabled
2024-04-18 23:09:56 +02:00
Sheldan
dfe9792330 [AB-xxx] fixing combined parameters not providing the appropriate option types for slash commands 2024-04-13 00:20:02 +02:00
Sheldan
250df57bd0 [AB-xxx] adding tests for command management services
some code improvements
2024-04-07 14:59:22 +02:00
Sheldan
02b8ed2b5d [AB-xxx] adding unit test for server controller
refactoring parameter parsing tests to use assertj
2024-04-07 11:49:06 +02:00
Sheldan
71c1445439 [AB-112] adding command parameter alternatives to bonk/pat to use the message author the command was replied to 2024-04-05 17:30:06 +02:00
release-bot
d86299cdf6 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-04-05 13:22:47 +00:00
release-bot
1b86fba3e0 [maven-release-plugin] prepare for next development iteration 2024-04-05 13:08:51 +00:00
release-bot
3ee7c92cdd [maven-release-plugin] prepare release v1.5.35 2024-04-05 13:08:49 +00:00
Sheldan
6c6cd130aa [AB-xxx] changing types of ids to be string instead for javascript purposes 2024-04-05 15:06:10 +02:00
release-bot
65a1d44069 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-04-05 12:43:58 +00:00
release-bot
11312a5e27 [maven-release-plugin] prepare for next development iteration 2024-04-05 12:29:23 +00:00
release-bot
6b13958ac0 [maven-release-plugin] prepare release v1.5.34 2024-04-05 12:29:20 +00:00
Sheldan
3142daafd3 [AB-xxx] renaming leaderboard url property 2024-04-05 01:58:43 +02:00
Sheldan
4bef78f847 [AB-xxx] adding link to the leaderboard to the leaderboard command response 2024-04-05 01:40:06 +02:00
Sheldan
82be86e086 [AB-xxx] adding locking mechanism for role assignments to work around discord lack of role update locking 2024-04-04 22:54:18 +02:00
Sheldan
bff505ef25 [AB-xxx] fixing not using the ban reason for moderation actions 2024-03-27 23:17:06 +01:00
release-bot
533f5671c2 [maven-release-plugin] prepare for next development iteration 2024-03-27 21:26:56 +00:00
release-bot
8c7547b485 [maven-release-plugin] prepare release v1.5.33 2024-03-27 21:26:54 +00:00
Sheldan
741c194bb8 [AB-xxx] changing styling for smaller screens for member display to truncate the name
prepare for release
2024-03-27 22:24:38 +01:00
release-bot
d2bdfd8dac [maven-release-plugin] prepare for next development iteration 2024-03-26 22:54:50 +00:00
release-bot
36c67fbe20 [maven-release-plugin] prepare release v1.5.32 2024-03-26 22:54:47 +00:00
Sheldan
8fd1aede2a [AB-xxx] changing style of leaderboard table
preparing for release
2024-03-26 23:48:22 +01:00
release-bot
287ae1f0b1 [maven-release-plugin] prepare for next development iteration 2024-03-26 21:40:02 +00:00
release-bot
903361cb58 [maven-release-plugin] prepare release v1.5.31 2024-03-26 21:39:59 +00:00
Sheldan
c8cf412a4a [AB-xxx] changing style of leaderboard table
preparing for release
2024-03-26 22:37:47 +01:00
release-bot
affc249012 [maven-release-plugin] prepare for next development iteration 2024-03-26 21:17:03 +00:00
release-bot
653671ea79 [maven-release-plugin] prepare release v1.5.30 2024-03-26 21:17:00 +00:00
Sheldan
7185908682 [AB-xxx] prepare for release 2024-03-26 22:13:42 +01:00
Sheldan
675da8d5d8 [AB-xxx] adding rank to leaderboard page
changing design of leaderboard page
fixing role not having an id
2024-03-26 22:13:18 +01:00
release-bot
e91becee0d [maven-release-plugin] prepare for next development iteration 2024-03-26 01:10:22 +00:00
release-bot
18732efe75 [maven-release-plugin] prepare release v1.5.29 2024-03-26 01:10:20 +00:00
Sheldan
63897fd914 [AB-xxx] prepare for release 2024-03-26 02:08:06 +01:00
Sheldan
9b865af43b [AB-xxx] fixing not serving static files 2024-03-26 02:07:25 +01:00
release-bot
b78275734c [maven-release-plugin] prepare for next development iteration 2024-03-26 00:36:30 +00:00
release-bot
00a6b0d1f8 [maven-release-plugin] prepare release v1.5.28 2024-03-26 00:36:28 +00:00
Sheldan
13fe6f5e51 [AB-xxx] prepare for release 2024-03-26 01:31:53 +01:00
Sheldan
bc0c3a18d7 [AB-xxx] initial experience leaderboard version 2024-03-26 01:28:56 +01:00
release-bot
8f9b7eba16 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-03-17 12:10:52 +00:00
release-bot
48eacb2e1c [maven-release-plugin] prepare for next development iteration 2024-03-17 12:02:17 +00:00
release-bot
2168814814 [maven-release-plugin] prepare release v1.5.27 2024-03-17 12:02:16 +00:00
Sheldan
972a2829d7 [AB-xxx] updating JDA version 2024-03-17 12:59:16 +01:00
Sheldan
dbf5d99622 [AB-111] adding ability to perform moderation actions on various logging/report messages 2024-03-17 12:37:30 +01:00
Sheldan
f45721ba42 [AB-xxx] fixing schema name for scheduling not being configurable 2024-03-11 22:32:09 +01:00
Sheldan
9034968868 [AB-xxx] only executing level actions if the user changes level 2024-03-01 23:14:13 +01:00
release-bot
bcb9bacea5 [maven-release-plugin] prepare for next development iteration 2024-02-28 20:31:20 +00:00
288 changed files with 21526 additions and 1575 deletions

2
.env
View File

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

View File

@@ -25,8 +25,18 @@ jobs:
with: with:
distribution: 'corretto' distribution: 'corretto'
java-version: 17 java-version: 17
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: '21.x'
- name: Build with Maven - name: Build with Maven
run: mvn -B install --file abstracto-application/pom.xml run: mvn -B install --file abstracto-application/pom.xml
- name: Install node dependencies and build
working-directory: ./ui/experience-tracking
run: npm ci
- name: Build ui application
working-directory: ./ui/experience-tracking
run: npm run build
- uses: actions/setup-ruby@v1 - uses: actions/setup-ruby@v1
- name: Send Webhook Notification - name: Send Webhook Notification
if: always() if: always()

View File

@@ -15,6 +15,10 @@ jobs:
with: with:
distribution: 'corretto' distribution: 'corretto'
java-version: 17 java-version: 17
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: '21.x'
- name: Load current version - name: Load current version
id: version id: version
working-directory: ./abstracto-application working-directory: ./abstracto-application
@@ -37,6 +41,14 @@ jobs:
release-branch-name: master release-branch-name: master
maven-args: "-Dmaven.javadoc.skip=true -s settings.xml -DskipTests" maven-args: "-Dmaven.javadoc.skip=true -s settings.xml -DskipTests"
access-token: ${{ secrets.GITHUB_TOKEN }} access-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install node dependencies and build
working-directory: ./ui/experience-tracking
run: npm ci
- name: Build ui application
working-directory: ./ui/experience-tracking
run: npm run build
- name: Copy built UI
run: cp -R ui/experience-tracking/build/* python/components/experience-tracking/resources/templates/experience/leaderboards/
- name: Login to Harbor - name: Login to Harbor
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:

View File

@@ -12,7 +12,7 @@ An example implementation of this bot can be seen [here](https://github.com/Shel
## Technologies ## Technologies
* [JDA](https://github.com/DV8FromTheWorld/JDA/) The Discord API Wrapper in the version 5.0.0-beta.13 * [JDA](https://github.com/DV8FromTheWorld/JDA/) The Discord API Wrapper in the version 5.0.0-beta.21
* [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) * [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. * [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. * [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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.entertainment.command; package dev.sheldan.abstracto.entertainment.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CombinedParameterEntry;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
@@ -86,7 +87,7 @@ public class Mock extends AbstractConditionableCommand {
public CommandConfiguration getConfiguration() { public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>(); List<Parameter> parameters = new ArrayList<>();
Map<String, Object> parameterAlternatives = new HashMap<>(); Map<String, Object> parameterAlternatives = new HashMap<>();
parameterAlternatives.put(ADDITIONAL_TYPES_KEY, Arrays.asList(Message.class, String.class)); parameterAlternatives.put(ADDITIONAL_TYPES_KEY, Arrays.asList(CombinedParameterEntry.messageParameter(Message.class), CombinedParameterEntry.parameter(String.class)));
Parameter messageParameter = Parameter Parameter messageParameter = Parameter
.builder() .builder()

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>dev.sheldan.abstracto.modules</groupId> <groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId> <artifactId>experience-tracking</artifactId>
<version>1.5.26</version> <version>1.5.36-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -38,6 +38,16 @@
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>

View File

@@ -0,0 +1,68 @@
package dev.sheldan.abstracto.experience.api;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.frontend.RoleDisplay;
import dev.sheldan.abstracto.core.service.GuildService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.experience.model.api.ExperienceConfig;
import dev.sheldan.abstracto.experience.model.api.ExperienceRoleDisplay;
import dev.sheldan.abstracto.experience.model.template.LevelRole;
import dev.sheldan.abstracto.experience.service.ExperienceRoleService;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Comparator;
import java.util.List;
@RestController
@RequestMapping(value = "/experience/v1/")
public class ExperienceConfigController {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ExperienceRoleService experienceRoleService;
@Autowired
private GuildService guildService;
@GetMapping(value = "/leaderboards/{serverId}/config", produces = "application/json")
public ExperienceConfig getLeaderboard(@PathVariable("serverId") Long serverId) {
AServer server = serverManagementService.loadServer(serverId);
List<LevelRole> levelRoles = experienceRoleService.loadLevelRoleConfigForServer(server);
levelRoles = levelRoles.stream().sorted(Comparator.comparingInt(LevelRole::getLevel).reversed()).toList();
Guild guild = guildService.getGuildById(serverId);
List<ExperienceRoleDisplay> roles = levelRoles
.stream()
.map(levelRole -> convertRole(levelRole, guild))
.toList();
return ExperienceConfig
.builder()
.roles(roles)
.build();
}
private ExperienceRoleDisplay convertRole(LevelRole levelRole, Guild guild) {
Role guildRole = guild.getRoleById(levelRole.getRoleId());
RoleDisplay roleDisplay;
if(guildRole != null) {
roleDisplay = RoleDisplay.fromRole(guildRole);
} else {
roleDisplay = RoleDisplay
.builder()
.id(String.valueOf(levelRole.getRoleId()))
.build();
}
return ExperienceRoleDisplay
.builder()
.level(levelRole.getLevel())
.role(roleDisplay)
.build();
}
}

View File

@@ -0,0 +1,81 @@
package dev.sheldan.abstracto.experience.api;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.frontend.RoleDisplay;
import dev.sheldan.abstracto.core.models.frontend.UserDisplay;
import dev.sheldan.abstracto.core.service.GuildService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.experience.model.api.UserExperienceDisplay;
import dev.sheldan.abstracto.experience.model.database.AExperienceRole;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.UserSnowflake;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/experience/v1")
public class LeaderboardController {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private UserExperienceManagementService userExperienceManagementService;
@Autowired
private GuildService guildService;
@GetMapping(value = "/leaderboards/{serverId}", produces = "application/json")
public Page<UserExperienceDisplay> getLeaderboard(@PathVariable("serverId") Long serverId,
@PageableDefault(value = 25, page = 0)
@SortDefault(sort = "experience", direction = Sort.Direction.DESC)
Pageable pageable) {
AServer server = serverManagementService.loadServer(serverId);
Guild guild = guildService.getGuildById(serverId);
Page<AUserExperience> allElements = userExperienceManagementService.loadAllUsersPaginated(server, pageable);
return allElements
.map(userExperience -> convertFromUser(guild, userExperience, pageable, allElements));
}
private UserExperienceDisplay convertFromUser(Guild guild, AUserExperience aUserExperience, Pageable pageable, Page<AUserExperience> page) {
Long userId = aUserExperience.getUser().getUserReference().getId();
Member member = guild.getMember(UserSnowflake.fromId(userId));
AExperienceRole experienceRole = aUserExperience.getCurrentExperienceRole();
UserDisplay userDisplay = null;
RoleDisplay roleDisplay = null;
if(experienceRole != null) {
Role role = guild.getRoleById(experienceRole.getRole().getId());
if(role != null) {
roleDisplay = RoleDisplay.fromRole(role);
} else {
roleDisplay = RoleDisplay.fromARole(experienceRole.getRole());
}
}
if(member != null) {
userDisplay = UserDisplay.fromMember(member);
}
return UserExperienceDisplay
.builder()
.id(String.valueOf(userId))
.messages(aUserExperience.getMessageCount())
.level(aUserExperience.getLevelOrDefault())
.rank((int) pageable.getOffset() + page.getContent().indexOf(aUserExperience) + 1)
.experience(aUserExperience.getExperience())
.role(roleDisplay)
.member(userDisplay)
.build();
}
}

View File

@@ -18,6 +18,7 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition; import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureMode; import dev.sheldan.abstracto.experience.config.ExperienceFeatureMode;
import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames; import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames;
import dev.sheldan.abstracto.experience.exception.LevelActionAlreadyExistsException;
import dev.sheldan.abstracto.experience.exception.LevelActionNotFoundException; import dev.sheldan.abstracto.experience.exception.LevelActionNotFoundException;
import dev.sheldan.abstracto.experience.listener.LevelActionListener; import dev.sheldan.abstracto.experience.listener.LevelActionListener;
import dev.sheldan.abstracto.experience.model.LevelActionPayload; import dev.sheldan.abstracto.experience.model.LevelActionPayload;
@@ -99,6 +100,9 @@ public class AddLevelAction extends AbstractConditionableCommand {
return userExperienceManagementService.saveUser(user); return userExperienceManagementService.saveUser(user);
}); });
} }
if(levelActionManagementService.getLevelAction(actionName, level, server, userExperience).isPresent()) {
throw new LevelActionAlreadyExistsException();
}
levelActionManagementService.createLevelAction(level, server, actionName, userExperience, payload); levelActionManagementService.createLevelAction(level, server, actionName, userExperience, payload);
return interactionService.replyEmbed(RESPONSE_TEMPLATE, event) return interactionService.replyEmbed(RESPONSE_TEMPLATE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess()); .thenApply(interactionHook -> CommandResult.fromSuccess());

View File

@@ -7,12 +7,14 @@ import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition; import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureMode;
import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames; import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames;
import dev.sheldan.abstracto.experience.service.AUserExperienceService; import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
@@ -100,4 +102,10 @@ public class ExpLevelUpNotification extends AbstractConditionableCommand {
.help(helpInfo) .help(helpInfo)
.build(); .build();
} }
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(ExperienceFeatureMode.LEVEL_UP_NOTIFICATION);
}
} }

View File

@@ -28,7 +28,9 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList; import java.util.ArrayList;
@@ -70,6 +72,9 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
@Autowired @Autowired
private InteractionService interactionService; private InteractionService interactionService;
@Value("${abstracto.experience.leaderboard.externalUrl}")
private String leaderboardExternalURL;
@Override @Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) { public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters(); List<Object> parameters = commandContext.getParameters().getParameters();
@@ -91,11 +96,18 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer); LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank)); CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
futures.add(userRankFuture); futures.add(userRankFuture);
String leaderboardUrl;
if(!StringUtils.isBlank(leaderboardExternalURL)) {
leaderboardUrl = String.format("%s/experience/leaderboards/%s", leaderboardExternalURL, actorUser.getGuild().getIdLong());
} else {
leaderboardUrl = null;
}
return FutureUtils.toSingleFuture(futures).thenCompose(aVoid -> { return FutureUtils.toSingleFuture(futures).thenCompose(aVoid -> {
List<LeaderBoardEntryModel> finalModels = completableFutures.join(); List<LeaderBoardEntryModel> finalModels = completableFutures.join();
LeaderBoardModel leaderBoardModel = LeaderBoardModel LeaderBoardModel leaderBoardModel = LeaderBoardModel
.builder() .builder()
.userExperiences(finalModels) .userExperiences(finalModels)
.leaderboardUrl(leaderboardUrl)
.userExecuting(userRankFuture.join().get(0)) .userExecuting(userRankFuture.join().get(0))
.build(); .build();
return CompletableFuture.completedFuture(templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, actorUser.getGuild().getIdLong())); return CompletableFuture.completedFuture(templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, actorUser.getGuild().getIdLong()));

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.experience.model.api;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
@Builder
public class ExperienceConfig {
private List<ExperienceRoleDisplay> roles;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.experience.model.api;
import dev.sheldan.abstracto.core.models.frontend.RoleDisplay;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class ExperienceRoleDisplay {
private RoleDisplay role;
private Integer level;
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.experience.model.api;
import dev.sheldan.abstracto.core.models.frontend.RoleDisplay;
import dev.sheldan.abstracto.core.models.frontend.UserDisplay;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class UserExperienceDisplay {
private UserDisplay member;
private String id;
private Integer rank;
private Integer level;
private Long experience;
private Long messages;
private RoleDisplay role;
}

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.experience.repository;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult; import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
@@ -24,6 +25,7 @@ public interface UserExperienceRepository extends JpaRepository<AUserExperience
* @return A complete list of {@link AUserExperience} of the given {@link AServer server} * @return A complete list of {@link AUserExperience} of the given {@link AServer server}
*/ */
List<AUserExperience> findByUser_ServerReference(AServer server); List<AUserExperience> findByUser_ServerReference(AServer server);
Page<AUserExperience> findAllByServer(AServer server, Pageable pageable);
/** /**
* Retrieves the {@link AUserExperience userExperience} ordered by experience, and applies the {@link Pageable pageable} to only filter out certain pages. * Retrieves the {@link AUserExperience userExperience} ordered by experience, and applies the {@link Pageable pageable} to only filter out certain pages.

View File

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

View File

@@ -166,7 +166,10 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server); List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
List<LevelRole> levelRoles = new ArrayList<>(); List<LevelRole> levelRoles = new ArrayList<>();
roles.forEach(aExperienceRole -> { roles.forEach(aExperienceRole -> {
Role role = roleService.getRoleFromGuild(aExperienceRole.getRole()); Role role = null;
if(!aExperienceRole.getRole().getDeleted()) {
role = roleService.getRoleFromGuild(aExperienceRole.getRole());
}
LevelRole levelRole = LevelRole LevelRole levelRole = LevelRole
.builder() .builder()
.role(role) .role(role)

View File

@@ -11,7 +11,9 @@ import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.repository.UserExperienceRepository; import dev.sheldan.abstracto.experience.repository.UserExperienceRepository;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
@@ -70,6 +72,11 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
return repository.findByUser_ServerReference(server); return repository.findByUser_ServerReference(server);
} }
@Override
public Page<AUserExperience> loadAllUsersPaginated(AServer server, Pageable pageable) {
return repository.findAllByServer(server, pageable);
}
@Override @Override
public List<AUserExperience> findLeaderBoardUsersPaginated(AServer aServer, Integer page, Integer size) { public List<AUserExperience> findLeaderBoardUsersPaginated(AServer aServer, Integer page, Integer size) {
return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(page, size)); return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(page, size));

View File

@@ -17,4 +17,6 @@ abstracto.featureModes.levelUpNotification.enabled=false
abstracto.featureModes.levelAction.featureName=experience abstracto.featureModes.levelAction.featureName=experience
abstracto.featureModes.levelAction.mode=levelAction abstracto.featureModes.levelAction.mode=levelAction
abstracto.featureModes.levelAction.enabled=false abstracto.featureModes.levelAction.enabled=false
abstracto.experience.leaderboard.externalUrl=${FRONTEND_BASE:}

View File

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

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.experience.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
public class LevelActionAlreadyExistsException extends AbstractoRunTimeException implements Templatable {
public LevelActionAlreadyExistsException() {
super("Level action already exists.");
}
@Override
public String getTemplateName() {
return "level_action_already_exists_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -24,4 +24,5 @@ public class LeaderBoardModel extends SlimUserInitiatedServerContext {
* The {@link LeaderBoardEntryModel} containing the leaderboard information executing the command. * The {@link LeaderBoardEntryModel} containing the leaderboard information executing the command.
*/ */
private LeaderBoardEntryModel userExecuting; private LeaderBoardEntryModel userExecuting;
private String leaderboardUrl;
} }

View File

@@ -6,6 +6,8 @@ import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.experience.model.database.AExperienceRole; import dev.sheldan.abstracto.experience.model.database.AExperienceRole;
import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult; import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -50,6 +52,7 @@ public interface UserExperienceManagementService {
* @return A list of {@link AUserExperience} objects associated with the given {@link AServer} * @return A list of {@link AUserExperience} objects associated with the given {@link AServer}
*/ */
List<AUserExperience> loadAllUsers(AServer server); List<AUserExperience> loadAllUsers(AServer server);
Page<AUserExperience> loadAllUsersPaginated(AServer server, Pageable pageable);
/** /**
* Retrieves a list of {@link AUserExperience} ordered by {@link AUserExperience} experience and only returns the positions between {@code start} and @{code end}. * Retrieves a list of {@link AUserExperience} ordered by {@link AUserExperience} experience and only returns the positions between {@code start} and @{code end}.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.imagegeneration.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition; import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CombinedParameterEntry;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.handler.parameter.CombinedParameter;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
@@ -21,16 +23,18 @@ import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationFeatureDefini
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames; import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames;
import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService; import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.command.config.Parameter.ADDITIONAL_TYPES_KEY;
@Component @Component
public class Bonk extends AbstractConditionableCommand { public class Bonk extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member"; public static final String MEMBER_PARAMETER_KEY = "member";
@@ -65,7 +69,11 @@ public class Bonk extends AbstractConditionableCommand {
if(parameters.isEmpty()) { if(parameters.isEmpty()) {
member = commandContext.getAuthor(); member = commandContext.getAuthor();
} else { } else {
member = (Member) parameters.get(0); if(parameters.get(0) instanceof Message) {
member = ((Message) parameters.get(0)).getMember();
} else {
member = (Member) parameters.get(0);
}
} }
File bonkGifFile = imageGenerationService.getBonkGif(member.getEffectiveAvatar().getUrl(imageSize)); File bonkGifFile = imageGenerationService.getBonkGif(member.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(BONK_EMBED_TEMPLATE_KEY, new Object()); MessageToSend messageToSend = templateService.renderEmbedTemplate(BONK_EMBED_TEMPLATE_KEY, new Object());
@@ -107,10 +115,13 @@ public class Bonk extends AbstractConditionableCommand {
@Override @Override
public CommandConfiguration getConfiguration() { public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>(); List<Parameter> parameters = new ArrayList<>();
Map<String, Object> parameterAlternatives = new HashMap<>();
parameterAlternatives.put(ADDITIONAL_TYPES_KEY, Arrays.asList(CombinedParameterEntry.messageParameter(Message.class), CombinedParameterEntry.parameter(Member.class)));
Parameter memberParameter = Parameter Parameter memberParameter = Parameter
.builder() .builder()
.name(MEMBER_PARAMETER_KEY) .name(MEMBER_PARAMETER_KEY)
.type(Member.class) .type(CombinedParameter.class)
.additionalInfo(parameterAlternatives)
.templated(true) .templated(true)
.optional(true) .optional(true)
.build(); .build();

View File

@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.imagegeneration.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition; import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CombinedParameterEntry;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.handler.parameter.CombinedParameter;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
@@ -21,16 +23,18 @@ import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationFeatureDefini
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames; import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames;
import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService; import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.command.config.Parameter.ADDITIONAL_TYPES_KEY;
@Component @Component
public class Pat extends AbstractConditionableCommand { public class Pat extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member"; public static final String MEMBER_PARAMETER_KEY = "member";
@@ -65,7 +69,11 @@ public class Pat extends AbstractConditionableCommand {
if(parameters.isEmpty()) { if(parameters.isEmpty()) {
member = commandContext.getAuthor(); member = commandContext.getAuthor();
} else { } else {
member = (Member) parameters.get(0); if(parameters.get(0) instanceof Message) {
member = ((Message) parameters.get(0)).getMember();
} else {
member = (Member) parameters.get(0);
}
} }
File patGifFile = imageGenerationService.getPatGif(member.getEffectiveAvatar().getUrl(imageSize)); File patGifFile = imageGenerationService.getPatGif(member.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(PAT_EMBED_TEMPLATE_KEY, new Object()); MessageToSend messageToSend = templateService.renderEmbedTemplate(PAT_EMBED_TEMPLATE_KEY, new Object());
@@ -107,10 +115,13 @@ public class Pat extends AbstractConditionableCommand {
@Override @Override
public CommandConfiguration getConfiguration() { public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>(); List<Parameter> parameters = new ArrayList<>();
Map<String, Object> parameterAlternatives = new HashMap<>();
parameterAlternatives.put(ADDITIONAL_TYPES_KEY, Arrays.asList(CombinedParameterEntry.messageParameter(Message.class), CombinedParameterEntry.parameter(Member.class)));
Parameter memberParameter = Parameter Parameter memberParameter = Parameter
.builder() .builder()
.name(MEMBER_PARAMETER_KEY) .name(MEMBER_PARAMETER_KEY)
.type(Member.class) .type(CombinedParameter.class)
.additionalInfo(parameterAlternatives)
.templated(true) .templated(true)
.optional(true) .optional(true)
.build(); .build();

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,8 @@ import dev.sheldan.abstracto.invitefilter.model.template.listener.DeletedInvite;
import dev.sheldan.abstracto.invitefilter.model.template.listener.DeletedInvitesNotificationModel; import dev.sheldan.abstracto.invitefilter.model.template.listener.DeletedInvitesNotificationModel;
import dev.sheldan.abstracto.invitefilter.service.management.AllowedInviteLinkManagement; import dev.sheldan.abstracto.invitefilter.service.management.AllowedInviteLinkManagement;
import dev.sheldan.abstracto.invitefilter.service.management.FilteredInviteLinkManagement; import dev.sheldan.abstracto.invitefilter.service.management.FilteredInviteLinkManagement;
import dev.sheldan.abstracto.moderation.model.ModerationActionButton;
import dev.sheldan.abstracto.moderation.service.ModerationActionService;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -78,6 +80,9 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
@Autowired @Autowired
private RoleImmunityService roleImmunityService; private RoleImmunityService roleImmunityService;
@Autowired(required = false)
private ModerationActionService moderationActionService;
private static final Pattern INVITE_CODE_PATTERN = Pattern.compile("(?<code>[a-z0-9-]+)", Pattern.CASE_INSENSITIVE); private static final Pattern INVITE_CODE_PATTERN = Pattern.compile("(?<code>[a-z0-9-]+)", Pattern.CASE_INSENSITIVE);
public static final String INVITE_FILTER_METRIC = "invite.filter"; public static final String INVITE_FILTER_METRIC = "invite.filter";
@@ -230,10 +235,18 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
log.info("Post target {} not defined for server {} - not sending invite link deletion notification.", InviteFilterPostTarget.INVITE_DELETE_LOG.getKey(), serverId); log.info("Post target {} not defined for server {} - not sending invite link deletion notification.", InviteFilterPostTarget.INVITE_DELETE_LOG.getKey(), serverId);
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
boolean moderationActionsEnabled = featureModeService.featureModeActive(InviteFilterFeatureDefinition.INVITE_FILTER, serverId, InviteFilterMode.FILTER_MODERATION_ACTIONS);
List<ModerationActionButton> moderationActionComponents = new ArrayList<>();
if(moderationActionsEnabled && moderationActionService != null) {
ServerUser reportedServerUser = ServerUser.fromMember(message.getMember());
List<ModerationActionButton> moderationActions = moderationActionService.getModerationActionButtons(reportedServerUser);
moderationActionComponents.addAll(moderationActions);
}
DeletedInvitesNotificationModel model = DeletedInvitesNotificationModel DeletedInvitesNotificationModel model = DeletedInvitesNotificationModel
.builder() .builder()
.author(message.getMember()) .author(message.getMember())
.guild(message.getGuild()) .guild(message.getGuild())
.moderationActionComponents(moderationActionComponents)
.message(message) .message(message)
.channel(message.getChannel()) .channel(message.getChannel())
.invites(groupInvites(codes)) .invites(groupInvites(codes))

View File

@@ -11,3 +11,6 @@ abstracto.featureModes.filterNotifications.featureName=inviteFilter
abstracto.featureModes.filterNotifications.mode=filterNotifications abstracto.featureModes.filterNotifications.mode=filterNotifications
abstracto.featureModes.filterNotifications.enabled=true abstracto.featureModes.filterNotifications.enabled=true
abstracto.featureModes.filterModerationActions.featureName=inviteFilter
abstracto.featureModes.filterModerationActions.mode=filterModerationActions
abstracto.featureModes.filterModerationActions.enabled=false

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>invite-filter</artifactId> <artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId> <groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version> <version>1.5.36-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,12 @@
<artifactId>core-int</artifactId> <artifactId>core-int</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>1.5.36-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -23,6 +23,6 @@ public class InviteFilterFeatureConfig implements FeatureConfig {
@Override @Override
public List<FeatureMode> getAvailableModes() { public List<FeatureMode> getAvailableModes() {
return Arrays.asList(InviteFilterMode.FILTER_NOTIFICATIONS, InviteFilterMode.TRACK_USES); return Arrays.asList(InviteFilterMode.values());
} }
} }

View File

@@ -5,7 +5,7 @@ import lombok.Getter;
@Getter @Getter
public enum InviteFilterMode implements FeatureMode { public enum InviteFilterMode implements FeatureMode {
TRACK_USES("trackUses"), FILTER_NOTIFICATIONS("filterNotifications"); TRACK_USES("trackUses"), FILTER_NOTIFICATIONS("filterNotifications"), FILTER_MODERATION_ACTIONS("filterModerationActions");
private final String key; private final String key;

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.invitefilter.model.template.listener; package dev.sheldan.abstracto.invitefilter.model.template.listener;
import dev.sheldan.abstracto.moderation.model.ModerationActionButton;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -17,4 +18,5 @@ public class DeletedInvitesNotificationModel {
private Member author; private Member author;
private Message message; private Message message;
private List<DeletedInvite> invites; private List<DeletedInvite> invites;
private List<ModerationActionButton> moderationActionComponents;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,13 +5,14 @@ import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncLeaveListener; import dev.sheldan.abstracto.core.listener.async.jda.AsyncLeaveListener;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.MemberLeaveModel; import dev.sheldan.abstracto.core.models.listener.MemberLeaveModel;
import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.PostTargetService; import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition; import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget; import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MemberLeaveLogModel;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -40,10 +41,10 @@ public class LeaveLogger implements AsyncLeaveListener {
.userId(listenerModel.getUser().getIdLong()) .userId(listenerModel.getUser().getIdLong())
.serverId(listenerModel.getServerId()) .serverId(listenerModel.getServerId())
.build(); .build();
MemberLeaveModel model = MemberLeaveModel MemberLeaveLogModel model = MemberLeaveLogModel
.builder() .builder()
.leavingUser(leavingUser) .leavingUser(leavingUser)
.user(listenerModel.getUser()) .user(UserDisplay.fromUser(listenerModel.getUser()))
.build(); .build();
log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), listenerModel.getServerId()); log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), listenerModel.getServerId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_LEAVE_TEMPLATE, model, listenerModel.getServerId()); MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_LEAVE_TEMPLATE, model, listenerModel.getServerId());

View File

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

View File

@@ -1,13 +1,15 @@
package dev.sheldan.abstracto.logging.model.template; package dev.sheldan.abstracto.logging.model.template;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
@Getter @Getter
@Setter @Setter
@Builder @Builder
public class MemberLeaveLogModel { public class MemberLeaveLogModel {
private Member member; private ServerUser leavingUser;
private UserDisplay user;
} }

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.UserService; import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.ParseUtils; import dev.sheldan.abstracto.core.utils.ParseUtils;
@@ -70,7 +70,7 @@ public class Ban extends AbstractConditionableCommand {
} }
if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) { if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) {
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class); Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class);
return banService.banUserWithNotification(member.getUser(), reason, event.getMember(), duration) return banService.banUserWithNotification(ServerUser.fromMember(member), reason, ServerUser.fromMember(event.getMember()), event.getGuild(), duration)
.thenCompose(banResult -> { .thenCompose(banResult -> {
if(banResult == NOTIFICATION_FAILED) { if(banResult == NOTIFICATION_FAILED) {
String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong()); String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong());
@@ -84,7 +84,7 @@ public class Ban extends AbstractConditionableCommand {
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class); String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
Long userId = Long.parseLong(userIdStr); Long userId = Long.parseLong(userIdStr);
return userService.retrieveUserForId(userId) return userService.retrieveUserForId(userId)
.thenCompose(user -> banService.banUserWithNotification(user, reason, event.getMember(), duration)) .thenCompose(user -> banService.banUserWithNotification(ServerUser.fromId(event.getGuild().getIdLong(), userId), reason, ServerUser.fromMember(event.getMember()), event.getGuild(), duration))
.thenCompose(banResult -> { .thenCompose(banResult -> {
if(banResult == NOTIFICATION_FAILED) { if(banResult == NOTIFICATION_FAILED) {
String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong()); String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong());

View File

@@ -116,7 +116,7 @@ public class Infractions extends AbstractConditionableCommand {
} else if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.STRING)){ } else if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.STRING)){
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class); String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
Long userId = Long.parseLong(userIdStr); Long userId = Long.parseLong(userIdStr);
AUserInAServer userInServer = userInServerManagementService.createUserInServer(event.getGuild().getIdLong(), userId); AUserInAServer userInServer = userInServerManagementService.loadOrCreateUser(event.getGuild().getIdLong(), userId);
infractions = infractionManagementService.getInfractionsForUser(userInServer); infractions = infractionManagementService.getInfractionsForUser(userInServer);
} else { } else {

View File

@@ -2,20 +2,21 @@ package dev.sheldan.abstracto.moderation.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.condition.CommandCondition; import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.*; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.execution.CommandContext; 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.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
import dev.sheldan.abstracto.moderation.service.KickServiceBean; import dev.sheldan.abstracto.moderation.service.KickServiceBean;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -47,30 +48,6 @@ public class Kick extends AbstractConditionableCommand {
@Autowired @Autowired
private InteractionService interactionService; private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Member member = (Member) parameters.get(0);
if(!member.getGuild().equals(commandContext.getGuild())) {
throw new EntityGuildMismatchException();
}
String defaultReason = templateService.renderSimpleTemplate(KICK_DEFAULT_REASON_TEMPLATE, commandContext.getGuild().getIdLong());
String reason = parameters.size() == 2 ? (String) parameters.get(1) : defaultReason;
KickLogModel kickLogModel = KickLogModel
.builder()
.kickedUser(member)
.reason(reason)
.guild(commandContext.getGuild())
.channel(commandContext.getChannel())
.member(commandContext.getAuthor())
.build();
kickLogModel.setKickedUser(member);
kickLogModel.setReason(reason);
return kickService.kickMember(member, reason, kickLogModel)
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@Override @Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) { public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, Member.class); Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, Member.class);
@@ -84,17 +61,7 @@ public class Kick extends AbstractConditionableCommand {
reason = templateService.renderSimpleTemplate(KICK_DEFAULT_REASON_TEMPLATE, event.getGuild().getIdLong()); reason = templateService.renderSimpleTemplate(KICK_DEFAULT_REASON_TEMPLATE, event.getGuild().getIdLong());
} }
KickLogModel kickLogModel = KickLogModel return kickService.kickMember(member, event.getMember(), reason)
.builder()
.kickedUser(member)
.reason(reason)
.guild(event.getGuild())
.channel(event.getGuildChannel())
.member(event.getMember())
.build();
kickLogModel.setKickedUser(member);
kickLogModel.setReason(reason);
return kickService.kickMember(member, reason, kickLogModel)
.thenCompose(unused -> interactionService.replyEmbed(KICK_RESPONSE, event)) .thenCompose(unused -> interactionService.replyEmbed(KICK_RESPONSE, event))
.thenApply(aVoid -> CommandResult.fromSuccess()); .thenApply(aVoid -> CommandResult.fromSuccess());
} }
@@ -143,6 +110,7 @@ public class Kick extends AbstractConditionableCommand {
.slashCommandConfig(slashCommandConfig) .slashCommandConfig(slashCommandConfig)
.supportsEmbedException(true) .supportsEmbedException(true)
.async(true) .async(true)
.slashCommandOnly(true)
.effects(effectConfig) .effects(effectConfig)
.causesReaction(true) .causesReaction(true)
.parameters(parameters) .parameters(parameters)

View File

@@ -10,12 +10,17 @@ import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParame
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils; import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.core.utils.SnowflakeUtils;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.template.command.MuteContext;
import dev.sheldan.abstracto.moderation.service.MuteService; import dev.sheldan.abstracto.moderation.service.MuteService;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
@@ -29,12 +34,14 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.moderation.model.MuteResult.NOTIFICATION_FAILED;
import static dev.sheldan.abstracto.moderation.service.MuteService.MUTE_EFFECT_KEY; import static dev.sheldan.abstracto.moderation.service.MuteService.MUTE_EFFECT_KEY;
@Component @Component
public class Mute extends AbstractConditionableCommand { public class Mute extends AbstractConditionableCommand {
private static final String MUTE_DEFAULT_REASON_TEMPLATE = "mute_default_reason"; private static final String MUTE_DEFAULT_REASON_TEMPLATE = "mute_default_reason";
public static final String MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY = "mute_notification_not_possible";
private static final String DURATION_PARAMETER = "duration"; private static final String DURATION_PARAMETER = "duration";
private static final String MUTE_COMMAND = "mute"; private static final String MUTE_COMMAND = "mute";
private static final String USER_PARAMETER = "user"; private static final String USER_PARAMETER = "user";
@@ -53,6 +60,9 @@ public class Mute extends AbstractConditionableCommand {
@Autowired @Autowired
private InteractionService interactionService; private InteractionService interactionService;
@Autowired
private ChannelService channelService;
@Override @Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) { public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters(); List<Object> parameters = commandContext.getParameters().getParameters();
@@ -64,15 +74,23 @@ public class Mute extends AbstractConditionableCommand {
Duration duration = (Duration) parameters.get(1); Duration duration = (Duration) parameters.get(1);
String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong()); String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason; String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason;
MuteContext muteLogModel = MuteContext Instant oldTimeoutDate = null;
.builder() if(member.getTimeOutEnd() != null && member.isTimedOut()) {
.muteTargetDate(Instant.now().plus(duration)) oldTimeoutDate = member.getTimeOutEnd().toInstant();
.mutedUser(member) }
.channelId(commandContext.getChannel().getIdLong()) ServerUser userToMute = ServerUser.fromMember(member);
.reason(reason) ServerUser mutingUser = ServerUser.fromMember(commandContext.getAuthor());
.mutingUser(commandContext.getAuthor()) Long serverId = commandContext.getGuild().getIdLong();
.build(); ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(commandContext.getMessage());
return muteService.muteMemberWithLog(muteLogModel) return muteService.muteMemberWithLog(userToMute, mutingUser, reason, duration, commandContext.getGuild(), serverChannelMessage, oldTimeoutDate)
.thenCompose(muteResult -> {
if(muteResult == NOTIFICATION_FAILED) {
MessageToSend errorNotification = templateService.renderEmbedTemplate(MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY, new Object(), serverId);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(errorNotification, commandContext.getChannel()));
} else {
return CompletableFuture.completedFuture(null);
}
})
.thenApply(aVoid -> CommandResult.fromSuccess()); .thenApply(aVoid -> CommandResult.fromSuccess());
} }
@@ -88,16 +106,23 @@ public class Mute extends AbstractConditionableCommand {
} else { } else {
reason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong()); reason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
} }
MuteContext muteLogModel = MuteContext Long serverId = event.getGuild().getIdLong();
ServerChannelMessage commandMessage = ServerChannelMessage
.builder() .builder()
.muteTargetDate(Instant.now().plus(duration)) .serverId(serverId)
.mutedUser(targetMember)
.reason(reason)
.channelId(event.getChannel().getIdLong()) .channelId(event.getChannel().getIdLong())
.mutingUser(event.getMember()) .messageId(SnowflakeUtils.createSnowFlake())
.build(); .build();
return muteService.muteMemberWithLog(muteLogModel) ServerUser userToMute = ServerUser.fromMember(targetMember);
.thenCompose(unused -> interactionService.replyEmbed(MUTE_RESPONSE, event)) ServerUser mutingUser = ServerUser.fromMember(event.getMember());
return muteService.muteMemberWithLog(userToMute, mutingUser, reason, duration, event.getGuild(), commandMessage)
.thenCompose(muteResult -> {
if(muteResult == NOTIFICATION_FAILED) {
return interactionService.replyEmbed(MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY, new Object(), event);
} else {
return interactionService.replyEmbed(MUTE_RESPONSE, event);
}
})
.thenApply(aVoid -> CommandResult.fromSuccess()); .thenApply(aVoid -> CommandResult.fromSuccess());
} }

View File

@@ -50,7 +50,7 @@ public class UnBan extends AbstractConditionableCommand {
String userIdStr = (String) parameters.get(0); String userIdStr = (String) parameters.get(0);
Long userId = Long.parseLong(userIdStr); Long userId = Long.parseLong(userIdStr);
return userService.retrieveUserForId(userId) return userService.retrieveUserForId(userId)
.thenCompose(user -> banService.unBanUserWithNotification(user, commandContext.getAuthor())) .thenCompose(user -> banService.unbanUser(commandContext.getGuild(), user, commandContext.getAuthor()))
.thenApply(aVoid -> CommandResult.fromSuccess()); .thenApply(aVoid -> CommandResult.fromSuccess());
} }
@@ -59,7 +59,7 @@ public class UnBan extends AbstractConditionableCommand {
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, String.class); String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, String.class);
Long userId = Long.parseLong(userIdStr); Long userId = Long.parseLong(userIdStr);
return userService.retrieveUserForId(userId) return userService.retrieveUserForId(userId)
.thenCompose(user -> banService.unBanUserWithNotification(user, event.getMember())) .thenCompose(user -> banService.unbanUser(event.getGuild(), user, event.getMember()))
.thenCompose(unused -> interactionService.replyEmbed(UN_BAN_RESPONSE, event)) .thenCompose(unused -> interactionService.replyEmbed(UN_BAN_RESPONSE, event))
.thenApply(interactionHook -> CommandResult.fromSuccess()); .thenApply(interactionHook -> CommandResult.fromSuccess());
} }

View File

@@ -11,8 +11,7 @@ import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParame
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
@@ -42,9 +41,6 @@ public class UnMute extends AbstractConditionableCommand {
@Autowired @Autowired
private InteractionService interactionService; private InteractionService interactionService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override @Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) { public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters(); List<Object> parameters = commandContext.getParameters().getParameters();
@@ -52,8 +48,9 @@ public class UnMute extends AbstractConditionableCommand {
if(!member.getGuild().equals(commandContext.getGuild())) { if(!member.getGuild().equals(commandContext.getGuild())) {
throw new EntityGuildMismatchException(); throw new EntityGuildMismatchException();
} }
AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(member); ServerUser userToUnmute = ServerUser.fromMember(member);
return muteService.unMuteUser(userToUnMute, commandContext.getAuthor()).thenApply(aVoid -> ServerUser unMutingMember = ServerUser.fromMember(commandContext.getAuthor());
return muteService.unMuteUser(userToUnmute, unMutingMember, commandContext.getGuild()).thenApply(aVoid ->
CommandResult.fromSuccess() CommandResult.fromSuccess()
); );
} }
@@ -64,8 +61,9 @@ public class UnMute extends AbstractConditionableCommand {
if(!targetMember.getGuild().equals(event.getGuild())) { if(!targetMember.getGuild().equals(event.getGuild())) {
throw new EntityGuildMismatchException(); throw new EntityGuildMismatchException();
} }
AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(targetMember); ServerUser userToUnmute = ServerUser.fromMember(targetMember);
return muteService.unMuteUser(userToUnMute, event.getMember()) ServerUser unMutingMember = ServerUser.fromMember(event.getMember());
return muteService.unMuteUser(userToUnmute, unMutingMember, event.getGuild())
.thenCompose(unused -> interactionService.replyEmbed(UN_MUTE_RESPONSE, event)) .thenCompose(unused -> interactionService.replyEmbed(UN_MUTE_RESPONSE, event))
.thenApply(interactionHook -> CommandResult.fromSuccess()); .thenApply(interactionHook -> CommandResult.fromSuccess());
} }

View File

@@ -10,10 +10,12 @@ import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParame
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.utils.SnowflakeUtils;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.template.command.WarnContext;
import dev.sheldan.abstracto.moderation.service.WarnService; import dev.sheldan.abstracto.moderation.service.WarnService;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -59,16 +61,8 @@ public class Warn extends AbstractConditionableCommand {
} }
String defaultReason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, commandContext.getGuild().getIdLong()); String defaultReason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, commandContext.getGuild().getIdLong());
String reason = parameters.size() == 2 ? (String) parameters.get(1) : defaultReason; String reason = parameters.size() == 2 ? (String) parameters.get(1) : defaultReason;
WarnContext warnLogModel = WarnContext ServerChannelMessage commandMessage = ServerChannelMessage.fromMessage(commandContext.getMessage());
.builder() return warnService.warnUserWithLog(commandContext.getGuild(), ServerUser.fromMember(member), ServerUser.fromMember(commandContext.getAuthor()), reason, commandMessage)
.reason(reason)
.warnedMember(member)
.channel(commandContext.getChannel())
.member(commandContext.getAuthor())
.guild(commandContext.getGuild())
.message(commandContext.getMessage())
.build();
return warnService.warnUserWithLog(warnLogModel)
.thenApply(warning -> CommandResult.fromSuccess()); .thenApply(warning -> CommandResult.fromSuccess());
} }
@@ -84,15 +78,13 @@ public class Warn extends AbstractConditionableCommand {
} else { } else {
reason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, event.getGuild().getIdLong()); reason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, event.getGuild().getIdLong());
} }
WarnContext warnLogModel = WarnContext ServerChannelMessage commandMessage = ServerChannelMessage
.builder() .builder()
.reason(reason) .serverId(event.getGuild().getIdLong())
.warnedMember(member) .channelId(event.getChannel().getIdLong())
.member(event.getMember()) .messageId(SnowflakeUtils.createSnowFlake())
.channel(event.getGuildChannel())
.guild(event.getGuild())
.build(); .build();
return warnService.warnUserWithLog(warnLogModel) return warnService.warnUserWithLog(event.getGuild(), ServerUser.fromMember(member), ServerUser.fromMember(event.getMember()), reason, commandMessage)
.thenCompose(unused -> interactionService.replyEmbed(WARN_RESPONSE, event)) .thenCompose(unused -> interactionService.replyEmbed(WARN_RESPONSE, event))
.thenApply(warning -> CommandResult.fromSuccess()); .thenApply(warning -> CommandResult.fromSuccess());
} }

View File

@@ -0,0 +1,114 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload;
import dev.sheldan.abstracto.core.interaction.modal.ModalService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionBanPayload;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionBanModalModel;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel;
import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class BanModerationActionListener implements ButtonClickedListener {
@Autowired
private ModalService modalService;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private BanModerationActionListener self;
private static final String BAN_REASON_MODERATION_ACTION_MODAL = "moderationAction_ban";
public static final String BAN_MODAL_ORIGIN = "BAN_MODERATION_ACTION_ORIGIN";
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload();
if(ModerationActionServiceBean.BAN_ACTION.equals(payload.getAction())) {
log.info("Handling ban button interaction by user {} in server {} for user {}.",
payload.getUser().getUserId(), payload.getUser().getServerId(), model.getEvent().getMember().getIdLong());
String modalId = componentService.generateComponentId();
String reasonInputId = componentService.generateComponentId();
String durationInputId = componentService.generateComponentId();
ModerationActionBanModalModel modalModel = ModerationActionBanModalModel
.builder()
.modalId(modalId)
.durationComponentId(durationInputId)
.reasonComponentId(reasonInputId)
.build();
modalService.replyModal(model.getEvent(), BAN_REASON_MODERATION_ACTION_MODAL, modalModel).thenAccept(unused -> {
log.info("Returned ban reason moderation action modal for user {} towards user {} in server {}.",
payload.getUser().getUserId(), model.getEvent().getMember().getIdLong(), model.getServerId());
self.persistBanModerationActionPayload(payload.getUser(), reasonInputId, modalId);
}).exceptionally(throwable -> {
log.error("Failed to show modal for ban moderation action.", throwable);
return null;
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
} else {
return ButtonClickedListenerResult.IGNORED;
}
}
@Transactional
public void persistBanModerationActionPayload(ServerUser userToBan, String reasonInput, String modalId) {
ModerationActionBanPayload payload = ModerationActionBanPayload
.builder()
.bannedUserId(userToBan.getUserId())
.serverId(userToBan.getServerId())
.reasonInputId(reasonInput)
.modalId(modalId)
.build();
ModalConfigPayload payloadConfig = ModalConfigPayload
.builder()
.modalPayload(payload)
.origin(BAN_MODAL_ORIGIN)
.payloadType(payload.getClass())
.modalId(modalId)
.build();
componentPayloadManagementService.createModalPayload(payloadConfig, userToBan.getServerId());
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
if(ModerationActionServiceBean.MODERATION_ACTION_ORIGIN.equals(model.getOrigin())){
ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload();
return ModerationActionServiceBean.BAN_ACTION.equals(payload.getAction());
}
return false;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public Boolean autoAcknowledgeEvent() {
return false;
}
}

View File

@@ -0,0 +1,113 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionExceptionService;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionBanPayload;
import dev.sheldan.abstracto.moderation.service.BanService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
@Slf4j
public class BanModerationActionModalListener implements ModalInteractionListener {
private static final String KICK_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE = "moderationAction_ban_response";
private static final String DEFAULT_BAN_REASON_TEMPLATE_KEY = "ban_default_reason";
@Autowired
private BanService banService;
@Autowired
private InteractionService interactionService;
@Autowired
private InteractionExceptionService interactionExceptionService;
@Autowired
private TemplateService templateService;
@Override
public Boolean handlesEvent(ModalInteractionListenerModel model) {
return BanModerationActionListener.BAN_MODAL_ORIGIN.equals(model.getOrigin());
}
@Override
public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) {
ModerationActionBanPayload payload = (ModerationActionBanPayload) model.getDeserializedPayload();
ServerUser userBeingBanned = ServerUser
.builder()
.userId(payload.getBannedUserId())
.serverId(payload.getServerId())
.build();
ServerUser kickingUser = ServerUser.fromMember(model.getEvent().getMember());
String duration = model
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
Duration messageDeletionDuration;
if(duration != null) {
messageDeletionDuration = ParseUtils.parseDuration(duration.trim());
} else {
messageDeletionDuration = null;
}
String reason;
String tempReason = model
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
if(StringUtils.isBlank(tempReason)) {
reason = templateService.renderSimpleTemplate(DEFAULT_BAN_REASON_TEMPLATE_KEY);
} else {
reason = tempReason;
}
log.debug("Handling ban moderation action modal interaction by user {} in server {}.", kickingUser.getUserId(), kickingUser.getServerId());
model.getEvent().deferReply(true).queue(interactionHook -> {
banService.banUserWithNotification(userBeingBanned, reason, kickingUser, model.getEvent().getGuild(), messageDeletionDuration)
.thenCompose((future) -> FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(KICK_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE, new Object(), model.getEvent().getInteraction().getHook())))
.thenAccept(unused -> {
log.info("Kicked user {} from server {}. Performed by user {}.", userBeingBanned.getUserId(), kickingUser.getServerId(), kickingUser.getUserId());
}).exceptionally(throwable -> {
interactionExceptionService.reportExceptionToInteraction(throwable, model, this);
log.error("Failed to kick user {} from server {}. Performed by user {}.", userBeingBanned.getUserId(), kickingUser.getServerId(), kickingUser.getUserId(), throwable);
return null;
});
});
return ModalInteractionListenerResult.ACKNOWLEDGED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.sync.jda.RoleAddedListener; import dev.sheldan.abstracto.core.listener.sync.jda.RoleAddedListener;
import dev.sheldan.abstracto.core.models.ConditionContextInstance; import dev.sheldan.abstracto.core.models.ConditionContextInstance;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.RoleAddedModel; import dev.sheldan.abstracto.core.models.listener.RoleAddedModel;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
@@ -23,6 +24,7 @@ import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.HashMap; import java.util.HashMap;
@@ -77,7 +79,8 @@ public class HoneyPotRoleAddedListener implements RoleAddedListener {
.roleDisplay(RoleDisplay.fromRole(model.getRole())) .roleDisplay(RoleDisplay.fromRole(model.getRole()))
.build(); .build();
String banReason = templateService.renderTemplate(HONEYPOT_BAN_REASON_TEMPLATE, reasonModel); String banReason = templateService.renderTemplate(HONEYPOT_BAN_REASON_TEMPLATE, reasonModel);
banService.banUserWithNotification(model.getTargetMember().getUser(), banReason, model.getTargetMember().getGuild().getSelfMember(), null).thenAccept(banResult -> { banService.banUserWithNotification(model.getTargetUser(), banReason, ServerUser.fromMember(model.getTargetMember().getGuild().getSelfMember()),
model.getTargetMember().getGuild(), Duration.ofDays(7)).thenAccept(banResult -> {
log.info("Banned user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId()); log.info("Banned user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId());
}).exceptionally(throwable -> { }).exceptionally(throwable -> {
log.error("Failed to ban user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId(), throwable); log.error("Failed to ban user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId(), throwable);

View File

@@ -0,0 +1,111 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload;
import dev.sheldan.abstracto.core.interaction.modal.ModalService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionKickPayload;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionKickModalModel;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel;
import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class KickModerationActionListener implements ButtonClickedListener {
@Autowired
private ModalService modalService;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private KickModerationActionListener self;
private static final String KICK_REASON_MODERATION_ACTION_MODAL = "moderationAction_kick";
public static final String KICK_MODAL_ORIGIN = "KICK_MODERATION_ACTION_ORIGIN";
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload();
if(ModerationActionServiceBean.KICK_ACTION.equals(payload.getAction())) {
log.info("Handling kick button interaction by user {} in server {} for user {}.",
payload.getUser().getUserId(), payload.getUser().getServerId(), model.getEvent().getMember().getIdLong());
String modalId = componentService.generateComponentId();
String reasonInputId = componentService.generateComponentId();
ModerationActionKickModalModel modalModel = ModerationActionKickModalModel
.builder()
.modalId(modalId)
.reasonComponentId(reasonInputId)
.build();
modalService.replyModal(model.getEvent(), KICK_REASON_MODERATION_ACTION_MODAL, modalModel).thenAccept(unused -> {
log.info("Returned kick reason moderation action modal for user {} towards user {} in server {}.",
payload.getUser().getUserId(), model.getEvent().getMember().getIdLong(), model.getServerId());
self.persistKickModerationActionPayload(payload.getUser(), reasonInputId, modalId);
}).exceptionally(throwable -> {
log.error("Failed to show modal for kick moderation action.", throwable);
return null;
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
} else {
return ButtonClickedListenerResult.IGNORED;
}
}
@Transactional
public void persistKickModerationActionPayload(ServerUser userToKick, String reasonInput, String modalId) {
ModerationActionKickPayload payload = ModerationActionKickPayload
.builder()
.kickedUserId(userToKick.getUserId())
.serverId(userToKick.getServerId())
.reasonInputId(reasonInput)
.modalId(modalId)
.build();
ModalConfigPayload payloadConfig = ModalConfigPayload
.builder()
.modalPayload(payload)
.origin(KICK_MODAL_ORIGIN)
.payloadType(payload.getClass())
.modalId(modalId)
.build();
componentPayloadManagementService.createModalPayload(payloadConfig, userToKick.getServerId());
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
if(ModerationActionServiceBean.MODERATION_ACTION_ORIGIN.equals(model.getOrigin())){
ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload();
return ModerationActionServiceBean.KICK_ACTION.equals(payload.getAction());
}
return false;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public Boolean autoAcknowledgeEvent() {
return false;
}
}

View File

@@ -0,0 +1,83 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionExceptionService;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionKickPayload;
import dev.sheldan.abstracto.moderation.service.KickService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class KickModerationActionModalListener implements ModalInteractionListener {
private static final String KICK_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE = "moderationAction_kick_response";
@Autowired
private KickService kickService;
@Autowired
private InteractionService interactionService;
@Autowired
private InteractionExceptionService interactionExceptionService;
@Override
public Boolean handlesEvent(ModalInteractionListenerModel model) {
return KickModerationActionListener.KICK_MODAL_ORIGIN.equals(model.getOrigin());
}
@Override
public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) {
ModerationActionKickPayload payload = (ModerationActionKickPayload) model.getDeserializedPayload();
ServerUser userBeingKicked = ServerUser
.builder()
.userId(payload.getKickedUserId())
.serverId(payload.getServerId())
.build();
ServerUser kickingUser = ServerUser.fromMember(model.getEvent().getMember());
String reason = model
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
log.debug("Handling kick moderation action modal interaction by user {} in server {}.", kickingUser.getUserId(), kickingUser.getServerId());
model.getEvent().deferReply(true).queue(interactionHook -> {
kickService.kickMember(model.getEvent().getGuild(), userBeingKicked, reason, kickingUser)
.thenCompose((future) -> FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(KICK_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE, new Object(), model.getEvent().getInteraction().getHook())))
.thenAccept(unused -> {
log.info("Kicked user {} from server {}. Performed by user {}.", userBeingKicked.getUserId(), kickingUser.getServerId(), kickingUser.getUserId());
}).exceptionally(throwable -> {
interactionExceptionService.reportExceptionToInteraction(throwable, model, this);
log.error("Failed to kick user {} from server {}. Performed by user {}.", userBeingKicked.getUserId(), kickingUser.getServerId(), kickingUser.getUserId(), throwable);
return null;
});
});
return ModalInteractionListenerResult.ACKNOWLEDGED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -0,0 +1,43 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberKickedListener;
import dev.sheldan.abstracto.core.models.listener.MemberKickedModel;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
import dev.sheldan.abstracto.moderation.service.KickService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MemberKickedListener implements AsyncMemberKickedListener {
@Autowired
private KickService kickService;
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
}
@Override
public DefaultListenerResult execute(MemberKickedModel eventModel) {
log.info("Notifying about kicked of user {} in guild {}.", eventModel.getKickedServerUser().getUserId(), eventModel.getServerId());
if(eventModel.getKickingServerUser().getUserId() == eventModel.getGuild().getJDA().getSelfUser().getIdLong()) {
log.info("Skipping logging kicked event about user {} in guild {}, because it was done by us.", eventModel.getKickedServerUser().getUserId(), eventModel.getGuild().getIdLong());
return DefaultListenerResult.IGNORED;
}
KickLogModel model = KickLogModel
.builder()
.kickedUser(eventModel.getKickedUser() != null ? UserDisplay.fromUser(eventModel.getKickedUser()) : UserDisplay.fromId(eventModel.getKickedServerUser().getUserId()))
.kickingUser(eventModel.getKickingUser() != null ? UserDisplay.fromUser(eventModel.getKickingUser()) : UserDisplay.fromServerUser(eventModel.getKickingServerUser()))
.reason(eventModel.getReason())
.build();
kickService.sendKicklog(model, eventModel.getServerId());
return DefaultListenerResult.PROCESSED;
}
}

View File

@@ -1,138 +0,0 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberTimeoutUpdatedListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.MemberTimeoutUpdatedModel;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel;
import dev.sheldan.abstracto.moderation.service.MuteServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.audit.ActionType;
import net.dv8tion.jda.api.audit.AuditLogEntry;
import net.dv8tion.jda.api.audit.AuditLogKey;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class MemberTimeoutListener implements AsyncMemberTimeoutUpdatedListener {
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private MemberTimeoutListener self;
@Autowired
private MemberService memberService;
@Override
public DefaultListenerResult execute(MemberTimeoutUpdatedModel model) {
Guild guild = model.getGuild();
guild.retrieveAuditLogs()
.type(ActionType.MEMBER_UPDATE)
.limit(10)
.queue(auditLogEntries -> {
CompletableFuture<Void> notificationFuture = null;
if(auditLogEntries.isEmpty()) {
log.info("Did not find recent timeouts in guild {}.", model.getServerId());
notificationFuture = self.sendMutingUpdateNotification(model, null);
} else {
Optional<AuditLogEntry> timeoutEntryOptional = auditLogEntries
.stream()
.filter(auditLogEntry -> auditLogEntry.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT) != null
&& auditLogEntry.getTargetIdLong() == model.getTimeoutUser().getUserId())
.findFirst();
if(timeoutEntryOptional.isPresent()) {
AuditLogEntry auditLogEntry = timeoutEntryOptional.get();
User responsibleUser = auditLogEntry.getUser();
if(guild.getSelfMember().getIdLong() != responsibleUser.getIdLong()) {
notificationFuture = self.sendMutingUpdateNotification(model, auditLogEntry);
}
} else {
notificationFuture = self.sendMutingUpdateNotification(model, null);
}
}
if(notificationFuture != null) {
notificationFuture.thenAccept(unused -> {
log.info("Sent notification about timeout change {} -> {} of user {} in server {}.",
model.getOldTimeout(), model.getNewTimeout(), model.getTimeoutUser().getUserId(), model.getServerId());
}).exceptionally(throwable -> {
log.info("Sent notification about timeout change {} -> {} of user {} in server {}.",
model.getOldTimeout(), model.getNewTimeout(), model.getTimeoutUser().getUserId(), model.getServerId());
return null;
});
}
});
return DefaultListenerResult.PROCESSED;
}
@Transactional
public CompletableFuture<Void> sendMutingUpdateNotification(MemberTimeoutUpdatedModel model, AuditLogEntry logEntry) {
User responsibleUser;
Guild guild = model.getGuild();
CompletableFuture<Member> future;
String reason;
if(logEntry != null) {
responsibleUser = logEntry.getUser();
if(responsibleUser != null) {
ServerUser responsibleServerUser = ServerUser
.builder()
.serverId(guild.getIdLong())
.isBot(responsibleUser.isBot())
.userId(responsibleUser.getIdLong())
.build();
future = memberService.retrieveMemberInServer(responsibleServerUser);
} else {
future = CompletableFuture.completedFuture(null);
}
reason = logEntry.getReason();
} else {
future = CompletableFuture.completedFuture(null);
reason = null;
}
CompletableFuture<Void> returningFuture = new CompletableFuture<>();
future.whenComplete((aVoid, throwable) -> {
try {
MuteListenerModel muteLogModel = MuteListenerModel
.builder()
.muteTargetDate(model.getNewTimeout() != null ? model.getNewTimeout().toInstant() : null)
.oldMuteTargetDate(model.getOldTimeout() != null ? model.getOldTimeout().toInstant() : null)
.mutingUser(future.isCompletedExceptionally() ? null : future.join())
.mutedUser(model.getMember())
.reason(reason)
.build();
MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, muteLogModel, guild.getIdLong());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, model.getServerId()));
returningFuture.complete(null);
} catch (Exception exception) {
log.error("Failed to log timeout update event for user {} in guild {}.", model.getTimeoutUser().getUserId(), model.getServerId(), exception);
}
});
return returningFuture;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
}
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberTimeoutUpdatedListener;
import dev.sheldan.abstracto.core.models.listener.MemberTimeoutUpdatedModel;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.template.command.MuteLogModel;
import dev.sheldan.abstracto.moderation.service.MuteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
@Component
@Slf4j
public class MemberTimeoutLoggerListener implements AsyncMemberTimeoutUpdatedListener {
@Autowired
private MuteService muteService;
@Override
public DefaultListenerResult execute(MemberTimeoutUpdatedModel model) {
log.info("Notifying about timeout of user {} in guild {}.", model.getMutedUser().getUserId(), model.getServerId());
if(model.getMutingUser().getUserId() == model.getGuild().getSelfMember().getIdLong()) {
log.info("Skipping logging timeout event about user {} in guild {}, because it was done by us.", model.getMutedUser().getUserId(), model.getGuild().getIdLong());
return DefaultListenerResult.IGNORED;
}
MemberDisplay mutedMemberDisplay = model.getMutedMember() != null ? MemberDisplay.fromMember(model.getMutedMember()) : MemberDisplay.fromServerUser(model.getMutedUser());
MemberDisplay mutingMemberDisplay = model.getMutingMember() != null ? MemberDisplay.fromMember(model.getMutingMember()) : MemberDisplay.fromServerUser(model.getMutingUser());
Duration duration = null;
if(model.getNewTimeout() != null) {
duration = Duration.between(Instant.now(), model.getNewTimeout());
}
MuteLogModel muteLogModel = MuteLogModel
.builder()
.muteTargetDate(model.getNewTimeout() != null ? model.getNewTimeout().toInstant() : null)
.oldMuteTargetDate(model.getOldTimeout() != null ? model.getOldTimeout().toInstant() : null)
.mutingMember(mutingMemberDisplay)
.mutedMember(mutedMemberDisplay)
.duration(duration)
.reason(model.getReason())
.build();
muteService.sendMuteLogMessage(muteLogModel, model.getServerId());
return DefaultListenerResult.PROCESSED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
}
}

View File

@@ -0,0 +1,114 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload;
import dev.sheldan.abstracto.core.interaction.modal.ModalService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionMutePayload;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionMuteModalModel;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel;
import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MuteModerationActionListener implements ButtonClickedListener {
@Autowired
private ComponentService componentService;
@Autowired
private ModalService modalService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private MuteModerationActionListener self;
private static final String MUTE_REASON_MODERATION_ACTION_MODAL = "moderationAction_mute";
public static final String MUTE_MODAL_ORIGIN = "MUTE_MODERATION_ACTION_ORIGIN";
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload();
if(ModerationActionServiceBean.MUTE_ACTION.equals(payload.getAction())) {
log.info("Handling mute button interaction by user {} in server {} for user {}.",
payload.getUser().getUserId(), payload.getUser().getServerId(), model.getEvent().getMember().getIdLong());
String modalId = componentService.generateComponentId();
String reasonInputId = componentService.generateComponentId();
String durationInputId = componentService.generateComponentId();
ModerationActionMuteModalModel modalModel = ModerationActionMuteModalModel
.builder()
.modalId(modalId)
.durationComponentId(durationInputId)
.reasonComponentId(reasonInputId)
.build();
modalService.replyModal(model.getEvent(), MUTE_REASON_MODERATION_ACTION_MODAL, modalModel).thenAccept(unused -> {
log.info("Returned mute reason moderation action modal for user {} towards user {} in server {}.",
payload.getUser().getUserId(), model.getEvent().getMember().getIdLong(), model.getServerId());
self.persistMuteModerationActionPayload(payload.getUser(), reasonInputId, modalId, durationInputId);
}).exceptionally(throwable -> {
log.error("Failed to show modal for mute moderation action.", throwable);
return null;
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
} else {
return ButtonClickedListenerResult.IGNORED;
}
}
@Transactional
public void persistMuteModerationActionPayload(ServerUser userToMute, String reasonInput, String modalId, String durationInputId) {
ModerationActionMutePayload payload = ModerationActionMutePayload
.builder()
.mutedUserId(userToMute.getUserId())
.serverId(userToMute.getServerId())
.reasonInputId(reasonInput)
.durationInputId(durationInputId)
.modalId(modalId)
.build();
ModalConfigPayload payloadConfig = ModalConfigPayload
.builder()
.modalPayload(payload)
.origin(MUTE_MODAL_ORIGIN)
.payloadType(payload.getClass())
.modalId(modalId)
.build();
componentPayloadManagementService.createModalPayload(payloadConfig, userToMute.getServerId());
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
if(ModerationActionServiceBean.MODERATION_ACTION_ORIGIN.equals(model.getOrigin())){
ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload();
return ModerationActionServiceBean.MUTE_ACTION.equals(payload.getAction());
}
return false;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public Boolean autoAcknowledgeEvent() {
return false;
}
}

View File

@@ -0,0 +1,113 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionExceptionService;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionMutePayload;
import dev.sheldan.abstracto.moderation.service.MuteService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import static dev.sheldan.abstracto.moderation.command.Mute.MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY;
import static dev.sheldan.abstracto.moderation.model.MuteResult.NOTIFICATION_FAILED;
@Component
@Slf4j
public class MuteModerationActionModalListener implements ModalInteractionListener {
private static final String MUTE_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE = "moderationAction_mute_response";
@Autowired
private MuteService muteService;
@Autowired
private InteractionService interactionService;
@Autowired
private InteractionExceptionService interactionExceptionService;
@Override
public Boolean handlesEvent(ModalInteractionListenerModel model) {
return MuteModerationActionListener.MUTE_MODAL_ORIGIN.equals(model.getOrigin());
}
@Override
public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) {
ModerationActionMutePayload payload = (ModerationActionMutePayload) model.getDeserializedPayload();
ServerUser userBeingMuted = ServerUser
.builder()
.userId(payload.getMutedUserId())
.serverId(payload.getServerId())
.build();
ServerUser mutingUser = ServerUser.fromMember(model.getEvent().getMember());
String reason = model
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
String duration = model
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
Duration muteDuration;
if(duration != null) {
muteDuration = ParseUtils.parseDuration(duration.trim());
} else {
muteDuration = Duration.ofDays(Member.MAX_TIME_OUT_LENGTH);
}
ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(model.getEvent().getMessage());
log.debug("Handling mute moderation action modal interaction by user {} in server {}.", mutingUser.getUserId(), mutingUser.getServerId());
model.getEvent().deferReply(true).queue(interactionHook -> {
muteService.muteMemberWithLog(userBeingMuted, mutingUser, reason, muteDuration, model.getEvent().getGuild(), serverChannelMessage)
.thenCompose((future) -> {
if(future == NOTIFICATION_FAILED) {
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(MUTE_NOTIFICATION_NOT_POSSIBLE_TEMPLATE_KEY, new Object(), model.getEvent().getInteraction().getHook()));
} else {
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(MUTE_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE, new Object(), model.getEvent().getInteraction().getHook()));
}
})
.thenAccept(unused -> {
log.info("Muted user {} in server {}. Performed by user {}.", userBeingMuted.getUserId(), mutingUser.getServerId(), mutingUser.getUserId());
}).exceptionally(throwable -> {
interactionExceptionService.reportExceptionToInteraction(throwable, model, this);
log.error("Failed to mute user {} in server {}. Performed by user {}.", userBeingMuted.getUserId(), mutingUser.getServerId(), mutingUser.getUserId(), throwable);
return null;
});
});
return ModalInteractionListenerResult.ACKNOWLEDGED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -3,82 +3,37 @@ package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserBannedListener; import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserBannedListener;
import dev.sheldan.abstracto.core.models.listener.UserBannedModel; import dev.sheldan.abstracto.core.models.listener.UserBannedListenerModel;
import dev.sheldan.abstracto.core.service.PostTargetService; import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget; import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedListenerModel; import dev.sheldan.abstracto.moderation.service.BanService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.audit.ActionType;
import net.dv8tion.jda.api.audit.AuditLogEntry;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component @Component
@Slf4j @Slf4j
public class UserBannedListener implements AsyncUserBannedListener { public class UserBannedListener implements AsyncUserBannedListener {
@Autowired @Autowired
private TemplateService templateService; private BanService banService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private UserBannedListener self;
private static final String USER_BANNED_NOTIFICATION_TEMPLATE = "userBanned_listener_notification";
@Override @Override
public DefaultListenerResult execute(UserBannedModel model) { public DefaultListenerResult execute(UserBannedListenerModel eventModel) {
model.getGuild() log.info("Notifying about ban of user {} in guild {}.", eventModel.getBannedServerUser().getUserId(), eventModel.getServerId());
.retrieveAuditLogs() if(eventModel.getBanningServerUser().getUserId() == eventModel.getGuild().getJDA().getSelfUser().getIdLong()) {
.type(ActionType.BAN) log.info("Skipping logging banned event about user {} in guild {}, because it was done by us.", eventModel.getBannedServerUser().getUserId(), eventModel.getGuild().getIdLong());
.limit(5) return DefaultListenerResult.IGNORED;
.queue(auditLogEntries -> { }
if(auditLogEntries.isEmpty()) { UserBannedLogModel model = UserBannedLogModel
log.info("Did not find recent bans in guild {}.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
return;
}
Optional<AuditLogEntry> banEntryOptional = auditLogEntries
.stream()
.filter(auditLogEntry -> auditLogEntry.getTargetIdLong() == model.getBannedUser().getUserId())
.findFirst();
if(banEntryOptional.isPresent()) {
AuditLogEntry auditLogEntry = banEntryOptional.get();
if(!model.getGuild().getJDA().getSelfUser().equals(auditLogEntry.getUser())) {
self.sendBannedNotification(model.getUser(), auditLogEntry.getUser(), auditLogEntry.getReason(), model.getServerId());
}
} else {
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;
}
@Transactional
public CompletableFuture<Void> sendBannedNotification(User bannedUser, User banningUser, String reason, Long serverId) {
UserBannedListenerModel model = UserBannedListenerModel
.builder() .builder()
.bannedUser(bannedUser) .bannedUser(eventModel.getBannedUser() != null ? UserDisplay.fromUser(eventModel.getBannedUser()) : UserDisplay.fromId(eventModel.getBannedServerUser().getUserId()))
.banningUser(banningUser) .banningUser(eventModel.getBanningUser() != null ? UserDisplay.fromUser(eventModel.getBanningUser()) : UserDisplay.fromServerUser(eventModel.getBanningServerUser()))
.reason(reason) .reason(eventModel.getReason())
.build(); .build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, model, serverId); banService.sendBanLogMessage(model, eventModel.getServerId());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId)); return DefaultListenerResult.PROCESSED;
} }
@Override @Override

View File

@@ -3,88 +3,37 @@ package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserUnBannedListener; import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserUnBannedListener;
import dev.sheldan.abstracto.core.models.listener.UserUnBannedModel; import dev.sheldan.abstracto.core.models.listener.UserUnBannedListenerModel;
import dev.sheldan.abstracto.core.service.FeatureModeService; import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget; import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedLogModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedListenerModel; import dev.sheldan.abstracto.moderation.service.BanService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.audit.ActionType;
import net.dv8tion.jda.api.audit.AuditLogEntry;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component @Component
@Slf4j @Slf4j
public class UserUnBannedListener implements AsyncUserUnBannedListener { public class UserUnBannedListener implements AsyncUserUnBannedListener {
@Autowired
private FeatureModeService featureModeService;
@Autowired @Autowired
private TemplateService templateService; private BanService banService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private UserUnBannedListener self;
private static final String USER_UN_BANNED_NOTIFICATION_TEMPLATE = "userUnBanned_listener_notification";
@Override @Override
public DefaultListenerResult execute(UserUnBannedModel model) { public DefaultListenerResult execute(UserUnBannedListenerModel eventModel) {
log.info("Notifying about unban of user {} in guild {}.", model.getUnbannedUser().getUserId(), model.getServerId()); log.info("Notifying about unban of user {} in guild {}.", eventModel.getUnBannedServerUser().getUserId(), eventModel.getServerId());
model.getGuild() if(eventModel.getUnBanningUser().getIdLong() == eventModel.getGuild().getSelfMember().getIdLong()) {
.retrieveAuditLogs() log.info("Skipping logging banned event about user {} in guild {}, because it was done by us.", eventModel.getUnBannedServerUser().getUserId(), eventModel.getGuild().getIdLong());
.type(ActionType.UNBAN) return DefaultListenerResult.IGNORED;
.limit(5) }
.queue(auditLogEntries -> { UserUnBannedLogModel model = UserUnBannedLogModel
try {
if(auditLogEntries.isEmpty()) {
log.info("Did not find recent bans in guild {}.", model.getServerId());
return;
}
Optional<AuditLogEntry> banEntryOptional = auditLogEntries
.stream()
.filter(auditLogEntry -> auditLogEntry.getTargetIdLong() == model.getUnbannedUser().getUserId())
.findFirst();
if(banEntryOptional.isPresent()) {
AuditLogEntry auditLogEntry = banEntryOptional.get();
if(!model.getGuild().getJDA().getSelfUser().equals(auditLogEntry.getUser())) {
self.sendUnBannedNotification(model.getUser(), auditLogEntry.getUser(), model.getServerId());
}
} else {
log.info("Did not find the un-banned user in the most recent un-bans for guild {}. Not adding audit log information.", model.getServerId());
self.sendUnBannedNotification(model.getUser(), null, model.getServerId());
}
} catch (Exception exception) {
log.error("Failed to properly send un ban log with error.", exception);
}
}, throwable -> {
log.error("Failed to retrieve audit log entries for unban log.", throwable);
self.sendUnBannedNotification(model.getUser(), null, model.getServerId());
});
return DefaultListenerResult.PROCESSED;
}
@Transactional
public CompletableFuture<Void> sendUnBannedNotification(User unbannedUser, User unbanningUser, Long serverId) {
UserUnBannedListenerModel model = UserUnBannedListenerModel
.builder() .builder()
.unBannedUser(unbannedUser) .unBannedUser(eventModel.getUnBannedUser() != null ? UserDisplay.fromUser(eventModel.getUnBannedUser()) : UserDisplay.fromId(eventModel.getUnBannedServerUser().getUserId()))
.unBanningUser(unbanningUser) .unBanningUser(eventModel.getUnBanningUser() != null ? UserDisplay.fromUser(eventModel.getUnBanningUser()) : UserDisplay.fromServerUser(eventModel.getUnBanningServerUser()))
.reason(eventModel.getReason())
.build(); .build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_UN_BANNED_NOTIFICATION_TEMPLATE, model, serverId); banService.sendUnBanLogMessage(model, eventModel.getServerId());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId)); return DefaultListenerResult.PROCESSED;
} }
@Override @Override

View File

@@ -0,0 +1,111 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload;
import dev.sheldan.abstracto.core.interaction.modal.ModalService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionWarnPayload;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionWarnModalModel;
import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class WarnModerationActionListener implements ButtonClickedListener {
@Autowired
private ModalService modalService;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private WarnModerationActionListener self;
private static final String WARN_REASON_MODERATION_ACTION_MODAL = "moderationAction_warn";
public static final String WARN_MODAL_ORIGIN = "WARN_MODERATION_ACTION_ORIGIN";
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload();
if(ModerationActionServiceBean.WARN_ACTION.equals(payload.getAction())) {
log.info("Handling warn button interaction by user {} in server {} for user {}.",
payload.getUser().getUserId(), payload.getUser().getServerId(), model.getEvent().getMember().getIdLong());
String modalId = componentService.generateComponentId();
String reasonInputId = componentService.generateComponentId();
ModerationActionWarnModalModel modalModel = ModerationActionWarnModalModel
.builder()
.modalId(modalId)
.reasonComponentId(reasonInputId)
.build();
modalService.replyModal(model.getEvent(), WARN_REASON_MODERATION_ACTION_MODAL, modalModel).thenAccept(unused -> {
log.info("Returned warn reason moderation action modal for user {} towards user {} in server {}.",
payload.getUser().getUserId(), model.getEvent().getMember().getIdLong(), model.getServerId());
self.persistWarnModerationActionPayload(payload.getUser(), reasonInputId, modalId);
}).exceptionally(throwable -> {
log.error("Failed to show modal for warn moderation action.", throwable);
return null;
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
} else {
return ButtonClickedListenerResult.IGNORED;
}
}
@Transactional
public void persistWarnModerationActionPayload(ServerUser userToWarn, String reasonInput, String modalId) {
ModerationActionWarnPayload payload = ModerationActionWarnPayload
.builder()
.warnedUserId(userToWarn.getUserId())
.serverId(userToWarn.getServerId())
.reasonInputId(reasonInput)
.modalId(modalId)
.build();
ModalConfigPayload payloadConfig = ModalConfigPayload
.builder()
.modalPayload(payload)
.origin(WARN_MODAL_ORIGIN)
.payloadType(payload.getClass())
.modalId(modalId)
.build();
componentPayloadManagementService.createModalPayload(payloadConfig, userToWarn.getServerId());
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
if(ModerationActionServiceBean.MODERATION_ACTION_ORIGIN.equals(model.getOrigin())){
ModerationActionPayloadModel payload = (ModerationActionPayloadModel) model.getDeserializedPayload();
return ModerationActionServiceBean.WARN_ACTION.equals(payload.getAction());
}
return false;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.WARNING;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public Boolean autoAcknowledgeEvent() {
return false;
}
}

View File

@@ -0,0 +1,98 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionExceptionService;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionWarnPayload;
import dev.sheldan.abstracto.moderation.service.WarnService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static dev.sheldan.abstracto.moderation.command.Warn.WARN_DEFAULT_REASON_TEMPLATE;
@Component
@Slf4j
public class WarnModerationActionModalListener implements ModalInteractionListener {
private static final String WARN_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE = "moderationAction_warn_response";
@Autowired
private WarnService warnService;
@Autowired
private InteractionService interactionService;
@Autowired
private TemplateService templateService;
@Autowired
private InteractionExceptionService interactionExceptionService;
@Override
public Boolean handlesEvent(ModalInteractionListenerModel model) {
return WarnModerationActionListener.WARN_MODAL_ORIGIN.equals(model.getOrigin());
}
@Override
public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) {
ModerationActionWarnPayload payload = (ModerationActionWarnPayload) model.getDeserializedPayload();
ServerUser userBeingWarned = ServerUser
.builder()
.userId(payload.getWarnedUserId())
.serverId(payload.getServerId())
.build();
ServerUser warningUser = ServerUser.fromMember(model.getEvent().getMember());
String reason;
String tempReason = model
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
if(StringUtils.isBlank(tempReason)) {
reason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, model.getServerId());
} else {
reason = tempReason;
}
ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(model.getEvent().getMessage());
log.debug("Handling warn moderation action modal interaction by user {} in server {}.", warningUser.getUserId(), warningUser.getServerId());
model.getEvent().deferReply(true).queue(interactionHook -> {
warnService.warnUserWithLog(model.getEvent().getGuild(), userBeingWarned, warningUser, reason, serverChannelMessage)
.thenCompose((future) -> FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(WARN_MODERATION_ACTION_MODAL_RESPONSE_TEMPLATE, new Object(), model.getEvent().getInteraction().getHook())))
.thenAccept(unused -> {
log.info("Warned user {} in server {}. Performed by user {}.", userBeingWarned.getUserId(), warningUser.getServerId(), warningUser.getUserId());
}).exceptionally(throwable -> {
interactionExceptionService.reportExceptionToInteraction(throwable, model, this);
log.error("Failed to warn user {} from server {}. Performed by user {}.", userBeingWarned.getUserId(), warningUser.getServerId(), warningUser.getUserId(), throwable);
return null;
});
});
return ModalInteractionListenerResult.ACKNOWLEDGED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.WARNING;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -3,32 +3,31 @@ package dev.sheldan.abstracto.moderation.listener.infraction;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority; import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService; import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.UserService; import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionListener; import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionListener;
import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.InfractionParameter;
import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel; import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel;
import dev.sheldan.abstracto.moderation.model.template.command.BanLog; import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
import dev.sheldan.abstracto.moderation.service.BanService; import dev.sheldan.abstracto.moderation.service.BanService;
import dev.sheldan.abstracto.moderation.service.BanServiceBean; import dev.sheldan.abstracto.moderation.service.BanServiceBean;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService; import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@Component @Component
@Slf4j @Slf4j
public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionListener { public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionListener {
@@ -43,7 +42,7 @@ public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionLis
private UserService userService; private UserService userService;
@Autowired @Autowired
private BanServiceBean banServiceBean; private TemplateService templateService;
@Autowired @Autowired
private BanReasonUpdatedListener self; private BanReasonUpdatedListener self;
@@ -58,7 +57,7 @@ public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionLis
public CompletableFuture<DefaultListenerResult> execute(InfractionDescriptionEventModel model) { public CompletableFuture<DefaultListenerResult> execute(InfractionDescriptionEventModel model) {
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId()); Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
CompletableFuture<User> infractionUser = userService.retrieveUserForId(infraction.getUser().getUserReference().getId()); CompletableFuture<User> infractionUser = userService.retrieveUserForId(infraction.getUser().getUserReference().getId());
CompletableFuture<Member> creatorUser = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(infraction.getInfractionCreator())); CompletableFuture<User> creatorUser = userService.retrieveUserForId(infraction.getInfractionCreator().getUserReference().getId());
CompletableFuture<DefaultListenerResult> returningFuture = new CompletableFuture<>(); CompletableFuture<DefaultListenerResult> returningFuture = new CompletableFuture<>();
Long infractionId = infraction.getId(); Long infractionId = infraction.getId();
CompletableFuture.allOf(infractionUser, creatorUser) CompletableFuture.allOf(infractionUser, creatorUser)
@@ -72,26 +71,17 @@ public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionLis
} }
@Transactional @Transactional
public void handleBanUpdate(InfractionDescriptionEventModel model, CompletableFuture<User> infractionUser, CompletableFuture<Member> infractionCreator, CompletableFuture<DefaultListenerResult> returningFuture) { public void handleBanUpdate(InfractionDescriptionEventModel model, CompletableFuture<User> infractionUser, CompletableFuture<User> infractionCreator, CompletableFuture<DefaultListenerResult> returningFuture) {
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId()); Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId()); GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId());
Duration deletionDuration = infraction UserBannedLogModel banLog = UserBannedLogModel
.getParameters()
.stream()
.filter(infractionParameter -> infractionParameter.getInfractionParameterId().getName().equals(BanService.INFRACTION_PARAMETER_DELETION_DURATION_KEY))
.findAny()
.map(InfractionParameter::getValue)
.map(Duration::parse)
.orElse(Duration.ZERO);
BanLog banLog = BanLog
.builder() .builder()
.bannedUser(infractionUser.isCompletedExceptionally() ? null : infractionUser.join()) .bannedUser(infractionUser.isCompletedExceptionally() ? null : UserDisplay.fromUser(infractionUser.join()))
.banningMember(infractionCreator.isCompletedExceptionally() ? null : infractionCreator.join()) .banningUser(infractionCreator.isCompletedExceptionally() ? null : UserDisplay.fromUser(infractionCreator.join()))
.deletionDuration(deletionDuration)
.reason(model.getNewDescription()) .reason(model.getNewDescription())
.build(); .build();
MessageToSend message = banServiceBean.renderBanMessage(banLog, model.getServerId()); MessageToSend message = templateService.renderEmbedTemplate(BanServiceBean.USER_BANNED_NOTIFICATION_TEMPLATE, banLog, model.getServerId());
messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId()) messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId())
.thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED)) .thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED))
.exceptionally(throwable1 -> { .exceptionally(throwable1 -> {

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority; import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.GuildService; import dev.sheldan.abstracto.core.service.GuildService;
import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.service.MemberService;
@@ -14,7 +15,7 @@ import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionLis
import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.Warning; import dev.sheldan.abstracto.moderation.model.database.Warning;
import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel; import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel;
import dev.sheldan.abstracto.moderation.model.template.command.WarnContext; import dev.sheldan.abstracto.moderation.model.template.command.WarnLogModel;
import dev.sheldan.abstracto.moderation.service.WarnServiceBean; import dev.sheldan.abstracto.moderation.service.WarnServiceBean;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService; import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import dev.sheldan.abstracto.moderation.service.management.WarnManagementService; import dev.sheldan.abstracto.moderation.service.management.WarnManagementService;
@@ -82,15 +83,14 @@ public class WarnReasonUpdatedListener implements InfractionUpdatedDescriptionLi
Guild guild = guildService.getGuildById(model.getServerId()); Guild guild = guildService.getGuildById(model.getServerId());
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId()); Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId()); GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId());
WarnContext context = WarnContext WarnLogModel context = WarnLogModel
.builder() .builder()
.warnedMember(warnedUser.isCompletedExceptionally() ? null : warnedUser.join()) .warnedMember(warnedUser.isCompletedExceptionally() ? null : MemberDisplay.fromMember(warnedUser.join()))
.member(warningUser.isCompletedExceptionally() ? null : warningUser.join()) .warningMember(warningUser.isCompletedExceptionally() ? null : MemberDisplay.fromMember(warningUser.join()))
.reason(model.getNewDescription()) .reason(model.getNewDescription())
.warnId(warnId) .warnId(warnId)
.guild(guild)
.build(); .build();
MessageToSend message = warnService.renderMessageModel(context); MessageToSend message = warnService.renderMessageModel(context, guild.getIdLong());
messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId()) messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId())
.thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED)) .thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED))
.exceptionally(throwable1 -> { .exceptionally(throwable1 -> {

View File

@@ -1,46 +1,48 @@
package dev.sheldan.abstracto.moderation.service; package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.CompletableFutureMap;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.moderation.model.BanResult; import dev.sheldan.abstracto.moderation.model.BanResult;
import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.template.command.BanLog;
import dev.sheldan.abstracto.moderation.model.template.command.BanNotificationModel; import dev.sheldan.abstracto.moderation.model.template.command.BanNotificationModel;
import dev.sheldan.abstracto.moderation.model.template.command.UnBanLog; import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedLogModel;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.UserSnowflake;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Component @Component
@Slf4j @Slf4j
public class BanServiceBean implements BanService { public class BanServiceBean implements BanService {
public static final String BAN_LOG_TEMPLATE = "ban_log";
public static final String UN_BAN_LOG_TEMPLATE = "unBan_log";
public static final String BAN_NOTIFICATION = "ban_notification"; public static final String BAN_NOTIFICATION = "ban_notification";
@Autowired @Autowired
private TemplateService templateService; private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired @Autowired
private BanServiceBean self; private BanServiceBean self;
@@ -59,103 +61,121 @@ public class BanServiceBean implements BanService {
@Autowired @Autowired
private InfractionService infractionService; private InfractionService infractionService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private UserService userService;
public static final String USER_BANNED_NOTIFICATION_TEMPLATE = "userBanned_listener_notification";
private static final String USER_UN_BANNED_NOTIFICATION_TEMPLATE = "userUnBanned_listener_notification";
@Override @Override
public CompletableFuture<BanResult> banUserWithNotification(User user, String reason, Member banningMember, Duration deletionDuration) { public CompletableFuture<BanResult> banUserWithNotification(ServerUser userToBeBanned, String reason, ServerUser banningUser, Guild guild, Duration deletionDuration) {
BanLog banLog = BanLog
.builder()
.bannedUser(user)
.banningMember(banningMember)
.deletionDuration(deletionDuration)
.reason(reason)
.build();
Guild guild = banningMember.getGuild();
BanResult[] result = {BanResult.SUCCESSFUL}; BanResult[] result = {BanResult.SUCCESSFUL};
return sendBanNotification(user, reason, guild) return sendBanNotification(userToBeBanned, reason, guild)
.exceptionally(throwable -> { .exceptionally(throwable -> {
result[0] = BanResult.NOTIFICATION_FAILED; result[0] = BanResult.NOTIFICATION_FAILED;
return null; return null;
}) })
.thenCompose(unused -> banUser(guild, user, deletionDuration, reason)) .thenCompose(unused -> banUser(guild, userToBeBanned, deletionDuration, reason))
.thenCompose(unused -> sendBanLogMessage(banLog, guild.getIdLong())) .thenCompose(unused -> self.composeAndSendBanLogMessage(userToBeBanned, banningUser, reason))
.thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(user, guild, reason, banningMember, banLogMessage, deletionDuration)) .thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(userToBeBanned, guild, reason, banningUser, deletionDuration))
.thenApply(unused -> result[0]); .thenApply(unused -> result[0]);
} }
@Transactional @Transactional
public CompletableFuture<Long> evaluateAndStoreInfraction(User user, Guild guild, String reason, Member banningMember, Message banLogMessage, Duration deletionDuration) { public CompletableFuture<Long> evaluateAndStoreInfraction(ServerUser user, Guild guild, String reason, ServerUser banningMember, Duration deletionDuration) {
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) { if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) {
Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.BAN_INFRACTION_POINTS, guild.getIdLong()); Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.BAN_INFRACTION_POINTS, guild.getIdLong());
AUserInAServer bannedUser = userInServerManagementService.loadOrCreateUser(guild.getIdLong(), user.getIdLong()); AUserInAServer bannedUser = userInServerManagementService.loadOrCreateUser(guild.getIdLong(), user.getUserId());
AUserInAServer banningUser = userInServerManagementService.loadOrCreateUser(banningMember); AUserInAServer banningUser = userInServerManagementService.loadOrCreateUser(banningMember);
Map<String, String> parameters = new HashMap<>(); Map<String, String> parameters = new HashMap<>();
if(deletionDuration == null) { if(deletionDuration == null) {
deletionDuration = Duration.ZERO; deletionDuration = Duration.ZERO;
} }
parameters.put(INFRACTION_PARAMETER_DELETION_DURATION_KEY, deletionDuration.toString()); parameters.put(INFRACTION_PARAMETER_DELETION_DURATION_KEY, deletionDuration.toString());
return infractionService.createInfractionWithNotification(bannedUser, infractionPoints, BAN_INFRACTION_TYPE, reason, banningUser, parameters, banLogMessage) return infractionService.createInfractionWithNotification(bannedUser, infractionPoints, BAN_INFRACTION_TYPE, reason, banningUser, parameters)
.thenApply(Infraction::getId); .thenApply(Infraction::getId);
} else { } else {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
} }
private CompletableFuture<Void> sendBanNotification(User user, String reason, Guild guild) { private CompletableFuture<Void> sendBanNotification(ServerUser serverUser, String reason, Guild guild) {
BanNotificationModel model = BanNotificationModel BanNotificationModel model = BanNotificationModel
.builder() .builder()
.serverName(guild.getName()) .serverName(guild.getName())
.reason(reason) .reason(reason)
.build(); .build();
String message = templateService.renderTemplate(BAN_NOTIFICATION, model, guild.getIdLong()); String message = templateService.renderTemplate(BAN_NOTIFICATION, model, guild.getIdLong());
return messageService.sendMessageToUser(user, message).thenAccept(message1 -> {}); return messageService.sendMessageToUser(serverUser, message).thenAccept(message1 -> {});
} }
@Override @Override
public CompletableFuture<Void> unBanUserWithNotification(User user, Member unBanningMember) { public CompletableFuture<Void> banUser(Guild guild, ServerUser userToBeBanned, Duration deletionDuration, String reason) {
Guild guild = unBanningMember.getGuild(); log.info("Banning user {} in guild {}.", userToBeBanned.getUserId(), guild.getId());
UnBanLog banLog = UnBanLog
.builder()
.bannedUser(user)
.unBanningMember(unBanningMember)
.build();
return unbanUser(guild, user)
.thenCompose(unused -> self.sendUnBanLogMessage(banLog, guild.getIdLong(), UN_BAN_LOG_TEMPLATE));
}
@Override
public CompletableFuture<Void> banUser(Guild guild, User user, Duration deletionDuration, String reason) {
log.info("Banning user {} in guild {}.", user.getIdLong(), guild.getId());
if(deletionDuration == null || deletionDuration.isNegative()) { if(deletionDuration == null || deletionDuration.isNegative()) {
deletionDuration = Duration.ZERO; deletionDuration = Duration.ZERO;
} }
return guild.ban(user, (int) deletionDuration.getSeconds(), TimeUnit.SECONDS).reason(reason).submit(); return guild.ban(UserSnowflake.fromId(userToBeBanned.getUserId()), (int) deletionDuration.getSeconds(), TimeUnit.SECONDS).reason(reason).submit();
} }
@Override @Override
public CompletableFuture<Void> unbanUser(Guild guild, User user) { public CompletableFuture<Void> unbanUser(Guild guild, User user, Member memberPerforming) {
log.info("Unbanning user {} in guild {}.", user.getIdLong(), guild.getId()); log.info("Unbanning user {} in guild {}.", user.getIdLong(), guild.getId());
return guild.unban(user).submit(); return guild.unban(user).submit().thenCompose(unused -> self.composeAndSendUnBanLogMessage(guild, user, memberPerforming));
}
@Transactional
public CompletionStage<Void> composeAndSendUnBanLogMessage(Guild guild, User user, Member memberPerforming) {
UserUnBannedLogModel model = UserUnBannedLogModel
.builder()
.unBannedUser(UserDisplay.fromUser(user))
.unBanningUser(UserDisplay.fromUser(memberPerforming.getUser()))
.reason(null)
.build();
return sendUnBanLogMessage(model, guild.getIdLong());
}
@Transactional
public CompletableFuture<Void> composeAndSendBanLogMessage(ServerUser serverUserToBeBanned, ServerUser serverUserBanning, String reason) {
CompletableFutureMap<Long, User> userMap = userService.retrieveUsersMapped(Arrays.asList(serverUserToBeBanned.getUserId(), serverUserBanning.getUserId()));
return userMap.getMainFuture().thenCompose(unused -> {
User userToBeBanned = userMap.getElement(serverUserToBeBanned.getUserId());
User banningUser = userMap.getElement(serverUserBanning.getUserId());
UserBannedLogModel model = UserBannedLogModel
.builder()
.bannedUser(UserDisplay.fromUser(userToBeBanned))
.banningUser(UserDisplay.fromUser(banningUser))
.reason(reason)
.build();
return self.sendBanLogMessage(model, serverUserToBeBanned.getServerId());
}).exceptionally(throwable -> {
log.warn("Failed to load users ({}, {}) for ban log message.", serverUserToBeBanned.getUserId(), serverUserBanning.getUserId(), throwable);
UserBannedLogModel model = UserBannedLogModel
.builder()
.bannedUser(UserDisplay.fromId(serverUserToBeBanned.getUserId()))
.banningUser(UserDisplay.fromId(serverUserBanning.getUserId()))
.reason(reason)
.build();
self.sendBanLogMessage(model, serverUserToBeBanned.getServerId());
return null;
});
} }
@Override @Override
public CompletableFuture<Void> softBanUser(Guild guild, User user, Duration delDays) { @Transactional
return banUser(guild, user, delDays, "") public CompletableFuture<Void> sendUnBanLogMessage(UserUnBannedLogModel model, Long serverId) {
.thenCompose(unused -> unbanUser(guild, user)); MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_UN_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
} }
public CompletableFuture<Message> sendBanLogMessage(BanLog banLog, Long guildId) { @Override
MessageToSend banLogMessage = renderBanMessage(banLog, guildId); @Transactional
log.debug("Sending ban log message in guild {}.", guildId); public CompletableFuture<Void> sendBanLogMessage(UserBannedLogModel model, Long serverId) {
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId); MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(messageFutures).thenApply(unused -> messageFutures.get(0).join()); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
} }
public MessageToSend renderBanMessage(BanLog banLog, Long guildId) {
return templateService.renderEmbedTemplate(BAN_LOG_TEMPLATE, banLog, guildId);
}
public CompletableFuture<Void> sendUnBanLogMessage(UnBanLog banLog, Long guildId, String template) {
MessageToSend banLogMessage = templateService.renderEmbedTemplate(template, banLog, guildId);
log.debug("Sending unban log message in guild {}.", guildId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.UN_BAN_LOG, guildId));
}
} }

View File

@@ -81,7 +81,7 @@ public class InfractionServiceBean implements InfractionService {
List<Infraction> infractions = infractionManagementService.getActiveInfractionsForUser(aUserInAServer); List<Infraction> infractions = infractionManagementService.getActiveInfractionsForUser(aUserInAServer);
log.info("Calculating points for user {} in server {} with {} infractions.", log.info("Calculating points for user {} in server {} with {} infractions.",
aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId(), infractions.size()); aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId(), infractions.size());
return infractions.stream().collect(Collectors.summarizingLong(Infraction::getPoints)).getCount(); return infractions.stream().collect(Collectors.summarizingLong(Infraction::getPoints)).getSum();
} }
@Override @Override
@@ -112,8 +112,8 @@ public class InfractionServiceBean implements InfractionService {
public CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points, String type, String description) { public CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points, String type, String description) {
Long serverId = aUserInAServer.getServerReference().getId(); Long serverId = aUserInAServer.getServerReference().getId();
Long currentPoints = getActiveInfractionPointsForUser(aUserInAServer); Long currentPoints = getActiveInfractionPointsForUser(aUserInAServer);
Long newPoints = currentPoints + points; Long oldPoints = currentPoints - points;
Pair<Integer, Integer> levelChange = infractionLevelChanged(serverId, newPoints, currentPoints); Pair<Integer, Integer> levelChange = infractionLevelChanged(serverId, currentPoints, oldPoints);
Integer oldLevel = levelChange.getFirst(); Integer oldLevel = levelChange.getFirst();
Integer newLevel = levelChange.getSecond(); Integer newLevel = levelChange.getSecond();
if(!oldLevel.equals(newLevel)) { if(!oldLevel.equals(newLevel)) {
@@ -124,10 +124,10 @@ public class InfractionServiceBean implements InfractionService {
.oldLevel(oldLevel) .oldLevel(oldLevel)
.type(type) .type(type)
.description(description) .description(description)
.oldPoints(currentPoints) .oldPoints(oldPoints)
.newPoints(newPoints) .newPoints(currentPoints)
.build(); .build();
infractionLevelChangedListenerManager.sendInfractionLevelChangedEvent(newLevel, oldLevel, newPoints, currentPoints, ServerUser.fromAUserInAServer(aUserInAServer)); infractionLevelChangedListenerManager.sendInfractionLevelChangedEvent(newLevel, oldLevel, currentPoints, oldPoints, ServerUser.fromAUserInAServer(aUserInAServer));
MessageToSend messageToSend = templateService.renderEmbedTemplate(INFRACTION_NOTIFICATION_TEMPLATE_KEY, model, serverId); MessageToSend messageToSend = templateService.renderEmbedTemplate(INFRACTION_NOTIFICATION_TEMPLATE_KEY, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, InfractionPostTarget.INFRACTION_NOTIFICATION, serverId)); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, InfractionPostTarget.INFRACTION_NOTIFICATION, serverId));
} else { } else {

View File

@@ -1,10 +1,14 @@
package dev.sheldan.abstracto.moderation.service; package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.ConfigService; import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.PostTargetService; import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureMap;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
@@ -14,13 +18,12 @@ import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.*;
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.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -48,35 +51,82 @@ public class KickServiceBean implements KickService {
@Autowired @Autowired
private InfractionService infractionService; private InfractionService infractionService;
@Autowired
private UserService userService;
@Autowired @Autowired
private KickServiceBean self; private KickServiceBean self;
@Override @Override
public CompletableFuture<Void> kickMember(Member member, String reason, KickLogModel kickLogModel) { public CompletableFuture<Void> kickMember(Member kickedMember, Member kickingMember, String reason) {
Guild guild = member.getGuild(); Guild guild = kickedMember.getGuild();
log.info("Kicking user {} from guild {}", member.getUser().getIdLong(), guild.getIdLong()); log.info("Kicking user {} from guild {}", kickedMember.getUser().getIdLong(), guild.getIdLong());
CompletableFuture<Void> kickFuture = guild.kick(member, reason).submit(); CompletableFuture<Void> kickFuture = guild.kick(kickedMember, reason).submit();
CompletableFuture<Message> logFuture = this.sendKickLog(kickLogModel); CompletableFuture<Message> logFuture = sendKickLog(kickedMember.getUser(), ServerUser.fromMember(kickedMember), kickingMember.getUser(), ServerUser.fromMember(kickingMember), reason, guild.getIdLong());
return CompletableFuture.allOf(kickFuture, logFuture) return CompletableFuture.allOf(kickFuture, logFuture)
.thenAccept(unused -> self.storeInfraction(member, reason, kickLogModel, guild, logFuture.join())); .thenAccept(unused -> self.storeInfraction(kickedMember, kickingMember, reason, logFuture.join(), guild.getIdLong()));
}
@Override
public CompletableFuture<Void> kickMember(Guild guild, ServerUser kickedUser, String reason, ServerUser kickingUser) {
CompletableFuture<Void> kickFuture = guild.kick(UserSnowflake.fromId(kickedUser.getUserId())).submit();
CompletableFuture<Message> logFuture = sendKickLog(kickedUser, kickingUser, reason, guild.getIdLong());
return CompletableFuture.allOf(kickFuture, logFuture)
.thenAccept(unused -> self.storeInfraction(kickedUser, kickingUser, reason, logFuture.join(), guild.getIdLong()));
} }
@Transactional @Transactional
public CompletableFuture<Long> storeInfraction(Member member, String reason, KickLogModel kickLogModel, Guild guild, Message logMessage) { public CompletableFuture<Long> storeInfraction(Member member, Member kickingMember, String reason, Message logMessage, Long serverId) {
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) { if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) {
Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.KICK_INFRACTION_POINTS, guild.getIdLong()); Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.KICK_INFRACTION_POINTS, serverId);
AUserInAServer kickedUser = userInServerManagementService.loadOrCreateUser(member); AUserInAServer kickedUser = userInServerManagementService.loadOrCreateUser(member);
AUserInAServer kickingUser = userInServerManagementService.loadOrCreateUser(kickLogModel.getMember()); AUserInAServer kickingUser = userInServerManagementService.loadOrCreateUser(kickingMember);
return infractionService.createInfractionWithNotification(kickedUser, infractionPoints, KICK_INFRACTION_TYPE, reason, kickingUser, logMessage).thenApply(Infraction::getId); return infractionService.createInfractionWithNotification(kickedUser, infractionPoints, KICK_INFRACTION_TYPE, reason, kickingUser, logMessage).thenApply(Infraction::getId);
} else { } else {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
} }
private CompletableFuture<Message> sendKickLog(KickLogModel kickLogModel) { @Transactional
MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, kickLogModel.getGuild().getIdLong()); public CompletableFuture<Long> storeInfraction(ServerUser member, ServerUser kickingMember, String reason, Message logMessage, Long serverId) {
log.debug("Sending kick log message in guild {}.", kickLogModel.getGuild().getIdLong()); if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) {
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong()); Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.KICK_INFRACTION_POINTS, serverId);
AUserInAServer kickedUser = userInServerManagementService.loadOrCreateUser(member);
AUserInAServer kickingUser = userInServerManagementService.loadOrCreateUser(kickingMember);
return infractionService.createInfractionWithNotification(kickedUser, infractionPoints, KICK_INFRACTION_TYPE, reason, kickingUser, logMessage).thenApply(Infraction::getId);
} else {
return CompletableFuture.completedFuture(null);
}
}
public CompletableFuture<Message> sendKickLog(User kickedUser, ServerUser kickedServerUser, User kickingUser, ServerUser kickingServerUser, String reason, Long serverId) {
KickLogModel kickLogModel = KickLogModel
.builder()
.kickedUser(kickedUser != null ? UserDisplay.fromUser(kickedUser) : UserDisplay.fromServerUser(kickedServerUser))
.kickingUser(kickingUser != null ? UserDisplay.fromUser(kickingUser) : UserDisplay.fromServerUser(kickingServerUser))
.reason(reason)
.build();
return sendKicklog(kickLogModel, serverId);
}
public CompletableFuture<Message> sendKickLog(ServerUser kickedMember, ServerUser kickingMember, String reason, Long serverId) {
CompletableFutureMap<Long, User> userMap = userService.retrieveUsersMapped(Arrays.asList(kickedMember.getUserId(), kickingMember.getUserId()));
return userMap.getMainFuture().thenCompose(unused -> {
User kickedUser = userMap.getElement(kickedMember.getUserId());
User kickingUser = userMap.getElement(kickingMember.getUserId());
return self.sendKickLog(kickedUser, kickedMember, kickingUser, kickingMember, reason, serverId);
}).exceptionally(throwable -> {
log.warn("Failed to fetch users ({}, {}) for kick event logging in server {}.", kickingMember.getUserId(), kickedMember.getUserId(), serverId, throwable);
self.sendKickLog(null, kickedMember, null, kickingMember, reason, serverId);
return null;
});
}
public CompletableFuture<Message> sendKicklog(KickLogModel kickLogModel, Long serverId) {
MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, serverId);
log.debug("Sending kick log message in guild {}.", serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, serverId);
return FutureUtils.toSingleFutureGeneric(messageFutures).thenApply(unused -> messageFutures.get(0).join()); return FutureUtils.toSingleFutureGeneric(messageFutures).thenApply(unused -> messageFutures.get(0).join());
} }
} }

View File

@@ -0,0 +1,95 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.ModerationActionButton;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@Slf4j
public class ModerationActionServiceBean implements ModerationActionService {
public static final String WARN_ACTION = "warn";
public static final String MUTE_ACTION = "mute";
public static final String KICK_ACTION = "kick";
public static final String BAN_ACTION = "ban";
public static final String MODERATION_ACTION_ORIGIN = "moderationAction";
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadService componentPayloadService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public List<ModerationActionButton> getModerationActionButtons(ServerUser serverUser) {
AServer server = serverManagementService.loadServer(serverUser.getServerId());
boolean mutingEnabled = featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.MUTING, serverUser.getServerId());
boolean moderationEnabled = featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.MODERATION, serverUser.getServerId());
boolean warningsEnabled = featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.WARNING, serverUser.getServerId());
List<ModerationActionButton> buttons = new ArrayList<>();
if(warningsEnabled) {
String warnButtonId = componentService.generateComponentId();
ModerationActionPayloadModel warnPayload = ModerationActionPayloadModel.forAction(WARN_ACTION, serverUser);
componentPayloadService.createButtonPayload(warnButtonId, warnPayload, MODERATION_ACTION_ORIGIN, server);
ModerationActionButton warnAction = ModerationActionButton
.builder()
.componentId(warnButtonId)
.action(WARN_ACTION)
.build();
buttons.add(warnAction);
}
if(mutingEnabled) {
String muteButtonId = componentService.generateComponentId();
ModerationActionPayloadModel mutePayload = ModerationActionPayloadModel.forAction(MUTE_ACTION, serverUser);
componentPayloadService.createButtonPayload(muteButtonId, mutePayload, MODERATION_ACTION_ORIGIN, server);
ModerationActionButton muteAction = ModerationActionButton
.builder()
.componentId(muteButtonId)
.action(MUTE_ACTION)
.build();
buttons.add(muteAction);
}
if(moderationEnabled) {
String kickButtonId = componentService.generateComponentId();
String banButtonId = componentService.generateComponentId();
ModerationActionPayloadModel kickPayload = ModerationActionPayloadModel.forAction(KICK_ACTION, serverUser);
ModerationActionPayloadModel banPayload = ModerationActionPayloadModel.forAction(BAN_ACTION, serverUser);
componentPayloadService.createButtonPayload(kickButtonId, kickPayload, MODERATION_ACTION_ORIGIN, server);
componentPayloadService.createButtonPayload(banButtonId, banPayload, MODERATION_ACTION_ORIGIN, server);
ModerationActionButton kickAction = ModerationActionButton
.builder()
.componentId(kickButtonId)
.action(KICK_ACTION)
.build();
buttons.add(kickAction);
ModerationActionButton banAction = ModerationActionButton
.builder()
.componentId(banButtonId)
.action(BAN_ACTION)
.build();
buttons.add(banAction);
}
log.info("Attaching {} buttons to moderation action for user {} in server {}.", buttons.size(), serverUser.getUserId(), serverUser.getServerId());
return buttons;
}
}

View File

@@ -1,11 +1,12 @@
package dev.sheldan.abstracto.moderation.service; package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage; import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.FullUserInServer; import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService; import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService;
@@ -17,20 +18,17 @@ import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefiniti
import dev.sheldan.abstracto.moderation.config.feature.MutingFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.MutingFeatureConfig;
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException; import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException;
import dev.sheldan.abstracto.moderation.model.MuteResult;
import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.Mute; import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.model.template.command.MuteContext; import dev.sheldan.abstracto.moderation.model.template.command.MuteLogModel;
import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel;
import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification; import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification;
import dev.sheldan.abstracto.moderation.model.template.command.UnMuteLog;
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService; import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
import dev.sheldan.abstracto.scheduling.model.JobParameters; import dev.sheldan.abstracto.scheduling.model.JobParameters;
import dev.sheldan.abstracto.scheduling.service.SchedulerService; import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -87,9 +85,6 @@ public class MuteServiceBean implements MuteService {
@Autowired @Autowired
private ServerManagementService serverManagementService; private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Autowired @Autowired
private FeatureFlagService featureFlagService; private FeatureFlagService featureFlagService;
@@ -104,68 +99,39 @@ public class MuteServiceBean implements MuteService {
public static final String MUTE_COUNTER_KEY = "MUTES"; public static final String MUTE_COUNTER_KEY = "MUTES";
@Override @Override
public CompletableFuture<Void> muteMember(Member memberToMute, String reason, Instant unMuteDate, Long channelId) { public CompletableFuture<MuteResult> muteUserInServer(Guild guild, ServerUser userBeingMuted, String reason, Duration duration) {
FullUserInServer mutedUser = FullUserInServer Long serverId = guild.getIdLong();
Instant targetDate = Instant.now().plus(duration);
MuteNotification muteNotificationModel = MuteNotification
.builder() .builder()
.aUserInAServer(userInServerManagementService.loadOrCreateUser(memberToMute)) .muteTargetDate(targetDate)
.member(memberToMute)
.build();
return muteUserInServer(mutedUser, reason, unMuteDate, channelId);
}
@Override
public CompletableFuture<Void> muteUserInServer(FullUserInServer userBeingMuted, String reason, Instant unMuteDate, Long channelId) {
Member memberBeingMuted = userBeingMuted.getMember();
List<CompletableFuture<Void>> futures = new ArrayList<>();
futures.add(memberService.timeoutUser(userBeingMuted.getMember(), unMuteDate));
Guild guild = memberBeingMuted.getGuild();
if(memberBeingMuted.getVoiceState() != null && memberBeingMuted.getVoiceState().getChannel() != null) {
futures.add(guild.kickVoiceMember(memberBeingMuted).submit());
}
MuteNotification muteNotification = MuteNotification
.builder()
.muteTargetDate(unMuteDate)
.reason(reason) .reason(reason)
.serverName(guild.getName()) .serverName(guild.getName())
.build(); .build();
futures.add(sendMuteNotification(memberBeingMuted, muteNotification, channelId)); MuteResult[] result = {MuteResult.SUCCESSFUL};
return FutureUtils.toSingleFutureGeneric(futures);
}
private CompletableFuture<Void> sendMuteNotification(Member memberBeingMuted, MuteNotification muteNotification, Long channelId) {
log.info("Notifying the user about the mute."); log.info("Notifying the user about the mute.");
CompletableFuture<Void> notificationFuture = new CompletableFuture<>(); String muteNotificationMessage = templateService.renderTemplate(MUTE_NOTIFICATION_TEMPLATE, muteNotificationModel, serverId);
Long guildId = memberBeingMuted.getGuild().getIdLong(); return messageService.sendMessageToUser(userBeingMuted, muteNotificationMessage)
String muteNotificationMessage = templateService.renderTemplate(MUTE_NOTIFICATION_TEMPLATE, muteNotification, guildId); .exceptionally(throwable -> {
CompletableFuture<Message> messageCompletableFuture = messageService.sendMessageToUser(memberBeingMuted.getUser(), muteNotificationMessage); log.warn("Failed to notify about mute", throwable);
messageCompletableFuture.exceptionally(throwable -> { result[0] = MuteResult.NOTIFICATION_FAILED;
GuildMessageChannel feedBackChannel = channelService.getMessageChannelFromServer(guildId, channelId); return null;
channelService.sendTextToChannel(throwable.getMessage(), feedBackChannel).whenComplete((exceptionMessage, innerThrowable) -> { })
notificationFuture.complete(null); .thenCompose(unused -> memberService.timeoutMember(guild, userBeingMuted, duration, reason))
log.info("Successfully notified user {} in server {} about mute.", memberBeingMuted.getId(), memberBeingMuted.getGuild().getId()); .thenApply(message -> result[0]);
}).exceptionally(throwable1 -> {
notificationFuture.completeExceptionally(throwable1);
return null;
});
return null;
});
messageCompletableFuture.thenAccept(message1 ->
notificationFuture.complete(null)
);
return notificationFuture;
} }
private void createMuteObject(MuteContext muteContext, String triggerKey, Long infractionId) { private void createMuteObject(ServerUser userToMute, ServerUser mutingUser, String reason, Instant targetDate, Long muteId,
AChannel channel = channelManagementService.loadChannel(muteContext.getChannelId()); String triggerKey, Long infractionId, ServerChannelMessage serverChannelMessage) {
AChannel channel = channelManagementService.loadChannel(serverChannelMessage.getChannelId());
AServerAChannelMessage origin = AServerAChannelMessage AServerAChannelMessage origin = AServerAChannelMessage
.builder() .builder()
.channel(channel) .channel(channel)
.server(channel.getServer()) .server(channel.getServer())
.build(); .build();
AUserInAServer userInServerBeingMuted = userInServerManagementService.loadOrCreateUser(muteContext.getMutedUser()); AUserInAServer userInServerBeingMuted = userInServerManagementService.loadOrCreateUser(userToMute);
AUserInAServer userInServerMuting = userInServerManagementService.loadOrCreateUser(muteContext.getMutingUser()); AUserInAServer userInServerMuting = userInServerManagementService.loadOrCreateUser(mutingUser);
muteManagementService.createMute(userInServerBeingMuted, userInServerMuting, muteContext.getReason(), muteContext.getMuteTargetDate(), origin, triggerKey, muteContext.getMuteId(), infractionId); muteManagementService.createMute(userInServerBeingMuted, userInServerMuting, reason, targetDate, origin, triggerKey, muteId, infractionId);
} }
@Override @Override
@@ -200,27 +166,69 @@ public class MuteServiceBean implements MuteService {
} }
@Override @Override
public CompletableFuture<Void> muteMemberWithLog(MuteContext context) { public CompletableFuture<MuteResult> muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin) {
log.debug("Muting member {} in server {}.", context.getMutedUser().getId(), context.getMutedUser().getGuild().getId()); return muteMemberWithLog(userToMute, mutingUser, reason, duration, guild, origin, null);
AServer server = serverManagementService.loadOrCreate(context.getMutedUser().getGuild().getIdLong()); }
Long nextCounterValue = counterService.getNextCounterValue(server, MUTE_COUNTER_KEY);
context.setMuteId(nextCounterValue); @Override
return muteMember(context.getMutedUser(), context.getReason(), context.getMuteTargetDate(), context.getChannelId()) public CompletableFuture<MuteResult> muteMemberWithLog(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, ServerChannelMessage origin, Instant oldTimeout) {
.thenCompose(unused -> self.sendMuteLog(context)) Long serverId = userToMute.getServerId();
.thenCompose(logMessage -> self.evaluateAndStoreInfraction(context, logMessage)) Instant targetDate = Instant.now().plus(duration);
.thenAccept(infractionId -> self.persistMute(context, infractionId)); log.info("Muting member {} in server {}.", userToMute.getUserId(), serverId);
AServer server = serverManagementService.loadOrCreate(serverId);
Long muteId = counterService.getNextCounterValue(server, MUTE_COUNTER_KEY);
CompletableFuture<MuteResult> result = muteUserInServer(guild, userToMute, reason, duration);
return result
.thenCompose(muteResult -> self.composeAndLogMute(userToMute, mutingUser, reason, duration, guild, oldTimeout))
.thenCompose(logMessage -> self.evaluateAndStoreInfraction(userToMute, mutingUser, reason, targetDate))
.thenAccept(infractionId -> self.persistMute(userToMute, mutingUser, targetDate, muteId, reason, infractionId, origin))
.thenApply(unused -> result.join());
} }
@Transactional @Transactional
public CompletableFuture<Long> evaluateAndStoreInfraction(MuteContext context, Message logMessage) { public CompletableFuture<Void> composeAndLogMute(ServerUser userToMute, ServerUser mutingUser, String reason, Duration duration, Guild guild, Instant oldTimeout) {
Guild guild = context.getMutedUser().getGuild(); CompletableFuture<Member> mutedMemberFuture = memberService.retrieveMemberInServer(userToMute);
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) { CompletableFuture<Member> mutingMemberFuture = memberService.retrieveMemberInServer(mutingUser);
Long infractionPoints = configService.getLongValueOrConfigDefault(MutingFeatureConfig.MUTE_INFRACTION_POINTS, guild.getIdLong()); Instant targetDate = Instant.now().plus(duration);
AUserInAServer mutedUser = userInServerManagementService.loadOrCreateUser(context.getMutedUser()); return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture).thenCompose(unused -> {
AUserInAServer mutingUser = userInServerManagementService.loadOrCreateUser(context.getMutingUser()); Member mutedMember = mutedMemberFuture.join();
Member mutingMember = mutingMemberFuture.join();
MuteLogModel muteLogModel = MuteLogModel
.builder()
.muteTargetDate(targetDate)
.oldMuteTargetDate(oldTimeout)
.mutingMember(MemberDisplay.fromMember(mutingMember))
.mutedMember(MemberDisplay.fromMember(mutedMember))
.duration(duration)
.reason(reason)
.build();
return self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to load users for mute log ({}, {}) in guild {}.", userToMute.getUserId(), mutingUser.getUserId(), guild.getIdLong(), throwable);
MuteLogModel muteLogModel = MuteLogModel
.builder()
.muteTargetDate(targetDate)
.oldMuteTargetDate(null)
.mutingMember(MemberDisplay.fromServerUser(mutingUser))
.mutedMember(MemberDisplay.fromServerUser(userToMute))
.duration(duration)
.reason(reason)
.build();
self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
return null;
});
}
@Transactional
public CompletableFuture<Long> evaluateAndStoreInfraction(ServerUser userToMute, ServerUser mutingUser, String reason, Instant targetDate) {
Long serverId = userToMute.getServerId();
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) {
Long infractionPoints = configService.getLongValueOrConfigDefault(MutingFeatureConfig.MUTE_INFRACTION_POINTS, serverId);
AUserInAServer mutedUserInAServer = userInServerManagementService.loadOrCreateUser(userToMute);
AUserInAServer mutingUserInAServer = userInServerManagementService.loadOrCreateUser(mutingUser);
Map<String, String> parameters = new HashMap<>(); Map<String, String> parameters = new HashMap<>();
parameters.put(INFRACTION_PARAMETER_DURATION_KEY, templateService.renderDuration(Duration.between(Instant.now(), context.getMuteTargetDate()), guild.getIdLong())); parameters.put(INFRACTION_PARAMETER_DURATION_KEY, templateService.renderDuration(Duration.between(Instant.now(), targetDate), serverId));
return infractionService.createInfractionWithNotification(mutedUser, infractionPoints, MUTE_INFRACTION_TYPE, context.getReason(), mutingUser, parameters, logMessage) return infractionService.createInfractionWithNotification(mutedUserInAServer, infractionPoints, MUTE_INFRACTION_TYPE, reason, mutingUserInAServer, parameters)
.thenApply(Infraction::getId); .thenApply(Infraction::getId);
} else { } else {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
@@ -228,102 +236,62 @@ public class MuteServiceBean implements MuteService {
} }
@Transactional @Transactional
public void persistMute(MuteContext context, Long infractionId) { public void persistMute(ServerUser userToMute, ServerUser mutingUser, Instant targetDate, Long muteId, String reason, Long infractionId, ServerChannelMessage origin) {
completelyUnMuteMember(context.getMutedUser()); completelyUnMuteMember(userToMute);
String triggerKey = startUnMuteJobFor(context.getMuteTargetDate(), context.getMuteId(), context.getMutedUser().getGuild().getIdLong()); String triggerKey = startUnMuteJobFor(targetDate, muteId, userToMute.getServerId());
createMuteObject(context, triggerKey, infractionId); createMuteObject(userToMute, mutingUser, reason, targetDate, muteId, triggerKey, infractionId, origin);
}
@Transactional
public CompletableFuture<Message> sendMuteLog(MuteContext muteLogModel) {
MuteListenerModel model = MuteListenerModel
.builder()
.mutedUser(muteLogModel.getMutedUser())
.mutingUser(muteLogModel.getMutingUser())
.channelId(muteLogModel.getChannelId())
.oldMuteTargetDate(null)
.muteTargetDate(muteLogModel.getMuteTargetDate())
.reason(muteLogModel.getReason())
.build();
log.debug("Sending mute log to the mute post target.");
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, model, muteLogModel.getMutedUser().getIdLong());
List<CompletableFuture<Message>> futures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getMutedUser().getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(futures).thenApply(unused -> futures.get(0).join());
}
private CompletableFuture<Void> sendUnMuteLogMessage(UnMuteLog muteLogModel, AServer server) {
MuteListenerModel model = MuteListenerModel
.builder()
.mutedUser(muteLogModel.getUnMutedUser())
.mutingUser(muteLogModel.getMutingUser())
.oldMuteTargetDate(muteLogModel.getMute() != null ? muteLogModel.getMute().getMuteTargetDate() : null)
.muteTargetDate(null)
.build();
if(muteLogModel.getMute() != null) {
log.debug("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId());
}
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, model, server.getId());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId()));
} }
@Override @Override
@Transactional @Transactional
public CompletableFuture<Void> unMuteUser(AUserInAServer userToUnmute, Member unMutingMember) { public CompletableFuture<Void> unMuteUser(ServerUser userToUnmute, ServerUser unMutingUser, Guild guild) {
boolean muteActive = muteManagementService.hasActiveMute(userToUnmute); AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(userToUnmute);
boolean muteActive = muteManagementService.hasActiveMute(aUserInAServer);
if(!muteActive) { if(!muteActive) {
CompletableFuture<Member> unMutedMemberFuture = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(userToUnmute)); return memberService.removeTimeout(guild, userToUnmute, null);
return unMutedMemberFuture
.thenCompose(member -> memberService.removeTimeout(member))
.thenCompose(unused -> self.sendUnmuteLog(null, unMutingMember.getGuild(), unMutedMemberFuture.join(), unMutingMember));
} else { } else {
Mute mute = muteManagementService.getAMuteOf(userToUnmute); Mute mute = muteManagementService.getAMuteOf(aUserInAServer);
return endMute(mute); return endMute(mute, guild);
} }
} }
@Override @Override
public CompletableFuture<Void> endMute(Mute mute) { public CompletableFuture<Void> endMute(Mute mute, Guild guild) {
if(mute.getMuteEnded()) { if(mute.getMuteEnded()) {
log.info("Mute {} in server {} has already ended. Not unmuting.", mute.getMuteId().getId(), mute.getMuteId().getServerId()); log.info("Mute {} in server {} has already ended. Not unmuting.", mute.getMuteId().getId(), mute.getMuteId().getServerId());
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
Long muteId = mute.getMuteId().getId(); Long muteId = mute.getMuteId().getId();
Guild guild = guildService.getGuildById(mute.getMuteId().getServerId());
AServer mutingServer = mute.getServer(); AServer mutingServer = mute.getServer();
ServerUser mutedUser = ServerUser.fromAUserInAServer(mute.getMutedUser());
ServerUser mutingUser = ServerUser.fromAUserInAServer(mute.getMutingUser());
log.info("UnMuting {} in server {}", mute.getMutedUser().getUserReference().getId(), mutingServer.getId()); log.info("UnMuting {} in server {}", mute.getMutedUser().getUserReference().getId(), mutingServer.getId());
CompletableFuture<Member> mutedMemberFuture = memberService.getMemberInServerAsync(mute.getMutedUser()); return memberService.removeTimeout(guild, mutedUser, null)
CompletableFuture<Member> mutingMemberFuture = memberService.getMemberInServerAsync(mute.getMutingUser()); .thenCompose(unused -> self.composeAndLogUnmute(mutedUser, mutingUser, guild))
return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture) .thenAccept(unused -> {
.thenAccept(member -> memberService.removeTimeout(mutedMemberFuture.join())) if(muteId != null) {
.thenCompose(unused -> self.sendUnmuteLog(muteId, guild, mutingMemberFuture, mutedMemberFuture)); self.endMuteInDatabase(muteId, guild.getIdLong());
}
});
} }
@Transactional @Transactional
public CompletableFuture<Void> sendUnmuteLog(Long muteId, Guild guild, CompletableFuture<Member> mutingMemberFuture, CompletableFuture<Member> mutedMemberFuture) { public CompletableFuture<Void> composeAndLogUnmute(ServerUser mutedUser, ServerUser mutingUser, Guild guild) {
Member mutingMember = !mutingMemberFuture.isCompletedExceptionally() ? mutingMemberFuture.join() : null; CompletableFuture<Member> mutedMemberFuture = memberService.retrieveMemberInServer(mutedUser);
Member mutedMember = !mutedMemberFuture.isCompletedExceptionally() ? mutedMemberFuture.join() : null; CompletableFuture<Member> mutingMemberFuture = memberService.retrieveMemberInServer(mutingUser);
return sendUnmuteLog(muteId, guild, mutedMember, mutingMember); return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture).thenCompose(unused -> {
} Member mutedMember = mutedMemberFuture.join();
Member mutingMember = mutingMemberFuture.join();
@Transactional MuteLogModel muteLogModel = MuteLogModel
public CompletableFuture<Void> sendUnmuteLog(Long muteId, Guild guild, Member mutedMember, Member mutingMember) { .builder()
Mute mute = null; .muteTargetDate(null)
if(muteId != null) { .oldMuteTargetDate(null)
mute = muteManagementService.findMute(muteId, guild.getIdLong()); .mutingMember(MemberDisplay.fromMember(mutingMember))
} .mutedMember(MemberDisplay.fromMember(mutedMember))
AServer mutingServer = serverManagementService.loadServer(guild.getIdLong()); .duration(null)
UnMuteLog unMuteLog = UnMuteLog .reason(null)
.builder() .build();
.mute(mute) return self.sendMuteLogMessage(muteLogModel, guild.getIdLong());
.mutingUser(mutingMember)
.unMutedUser(mutedMember)
.guild(guild)
.build();
CompletableFuture<Void> notificationFuture = sendUnMuteLogMessage(unMuteLog, mutingServer);
return CompletableFuture.allOf(notificationFuture).thenAccept(aVoid -> {
if(muteId != null) {
self.endMuteInDatabase(muteId, guild.getIdLong());
}
}); });
} }
@@ -341,12 +309,19 @@ public class MuteServiceBean implements MuteService {
log.info("UnMuting the mute {} in server {}", muteId, serverId); log.info("UnMuting the mute {} in server {}", muteId, serverId);
Optional<Mute> muteOptional = muteManagementService.findMuteOptional(muteId, serverId); Optional<Mute> muteOptional = muteManagementService.findMuteOptional(muteId, serverId);
if(muteOptional.isPresent()) { if(muteOptional.isPresent()) {
return endMute(muteOptional.get()); Guild guild = guildService.getGuildById(serverId);
return endMute(muteOptional.get(), guild);
} else { } else {
throw new NoMuteFoundException(); throw new NoMuteFoundException();
} }
} }
@Override
public CompletableFuture<Void> sendMuteLogMessage(MuteLogModel model, Long serverId) {
MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, serverId));
}
@Override @Override
public void completelyUnMuteUser(AUserInAServer aUserInAServer) { public void completelyUnMuteUser(AUserInAServer aUserInAServer) {
log.info("Completely unmuting user {} in server {}.", aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId()); log.info("Completely unmuting user {} in server {}.", aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId());
@@ -359,8 +334,8 @@ public class MuteServiceBean implements MuteService {
} }
@Override @Override
public void completelyUnMuteMember(Member member) { public void completelyUnMuteMember(ServerUser serverUser) {
completelyUnMuteUser(userInServerManagementService.loadOrCreateUser(member)); completelyUnMuteUser(userInServerManagementService.loadOrCreateUser(serverUser));
} }
} }

View File

@@ -12,6 +12,7 @@ import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefiniti
import dev.sheldan.abstracto.moderation.config.feature.mode.ReportReactionMode; import dev.sheldan.abstracto.moderation.config.feature.mode.ReportReactionMode;
import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget;
import dev.sheldan.abstracto.moderation.listener.manager.ReportMessageCreatedListenerManager; import dev.sheldan.abstracto.moderation.listener.manager.ReportMessageCreatedListenerManager;
import dev.sheldan.abstracto.moderation.model.ModerationActionButton;
import dev.sheldan.abstracto.moderation.model.database.ModerationUser; import dev.sheldan.abstracto.moderation.model.database.ModerationUser;
import dev.sheldan.abstracto.moderation.model.database.ReactionReport; import dev.sheldan.abstracto.moderation.model.database.ReactionReport;
import dev.sheldan.abstracto.moderation.model.template.listener.ReportReactionNotificationModel; import dev.sheldan.abstracto.moderation.model.template.listener.ReportReactionNotificationModel;
@@ -27,6 +28,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -68,6 +70,9 @@ public class ReactionReportServiceBean implements ReactionReportService {
@Autowired @Autowired
private ReportMessageCreatedListenerManager reportMessageCreatedListenerManager; private ReportMessageCreatedListenerManager reportMessageCreatedListenerManager;
@Autowired
private ModerationActionService moderationActionService;
private static final String REACTION_REPORT_TEMPLATE_KEY = "reactionReport_notification"; private static final String REACTION_REPORT_TEMPLATE_KEY = "reactionReport_notification";
public static final String REACTION_REPORT_MODAL_ORIGIN = "reportMessageModal"; public static final String REACTION_REPORT_MODAL_ORIGIN = "reportMessageModal";
public static final String REACTION_REPORT_RESPONSE_TEMPLATE = "reactionReport_response"; public static final String REACTION_REPORT_RESPONSE_TEMPLATE = "reactionReport_response";
@@ -93,11 +98,19 @@ public class ReactionReportServiceBean implements ReactionReportService {
return channelService.editFieldValueInMessage(reportTextChannel, report.getReportMessageId(), 0, report.getReportCount().toString()) return channelService.editFieldValueInMessage(reportTextChannel, report.getReportMessageId(), 0, report.getReportCount().toString())
.thenAccept(message -> self.updateModerationUserReportCooldown(reporter)); .thenAccept(message -> self.updateModerationUserReportCooldown(reporter));
} else { } else {
boolean reportActionsEnabled = featureModeService.featureModeActive(ModerationFeatureDefinition.REPORT_REACTIONS, serverId, ReportReactionMode.REPORT_ACTIONS);
List<ModerationActionButton> moderationActionComponents = new ArrayList<>();
if(reportActionsEnabled) {
ServerUser reportedServerUser = ServerUser.fromAUserInAServer(reportedUser);
List<ModerationActionButton> moderationActions = moderationActionService.getModerationActionButtons(reportedServerUser);
moderationActionComponents.addAll(moderationActions);
}
ReportReactionNotificationModel model = ReportReactionNotificationModel ReportReactionNotificationModel model = ReportReactionNotificationModel
.builder() .builder()
.reportCount(1) .reportCount(1)
.context(context) .context(context)
.singularMessage(singularMessage) .singularMessage(singularMessage)
.moderationActionComponents(moderationActionComponents)
.reportedMessage(reportedMessage) .reportedMessage(reportedMessage)
.build(); .build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(REACTION_REPORT_TEMPLATE_KEY, model, serverId); MessageToSend messageToSend = templateService.renderEmbedTemplate(REACTION_REPORT_TEMPLATE_KEY, model, serverId);

View File

@@ -6,12 +6,12 @@ import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService; import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.SnowflakeUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.WarningDecayFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.WarningDecayFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.WarningFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.WarningFeatureConfig;
@@ -22,7 +22,7 @@ import dev.sheldan.abstracto.moderation.config.posttarget.WarningPostTarget;
import dev.sheldan.abstracto.moderation.listener.manager.WarningCreatedListenerManager; import dev.sheldan.abstracto.moderation.listener.manager.WarningCreatedListenerManager;
import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.Warning; import dev.sheldan.abstracto.moderation.model.database.Warning;
import dev.sheldan.abstracto.moderation.model.template.command.WarnContext; import dev.sheldan.abstracto.moderation.model.template.command.WarnLogModel;
import dev.sheldan.abstracto.moderation.model.template.command.WarnNotification; import dev.sheldan.abstracto.moderation.model.template.command.WarnNotification;
import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayLogModel; import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayLogModel;
import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayWarning; import dev.sheldan.abstracto.moderation.model.template.job.WarnDecayWarning;
@@ -109,50 +109,31 @@ public class WarnServiceBean implements WarnService {
public static final String WARN_DECAY_LOG_TEMPLATE_KEY = "warn_decay_log"; public static final String WARN_DECAY_LOG_TEMPLATE_KEY = "warn_decay_log";
public static final String WARN_DECAY_NOTIFICATION_TEMPLATE_KEY = "warn_decay_member_notification"; public static final String WARN_DECAY_NOTIFICATION_TEMPLATE_KEY = "warn_decay_member_notification";
@Override
public CompletableFuture<Void> notifyAndLogFullUserWarning(WarnContext context) {
Long serverId = context.getGuild().getIdLong();
Long warningId = counterService.getNextCounterValue(serverId, WARNINGS_COUNTER_KEY);
context.setWarnId(warningId);
Member warnedMember = context.getWarnedMember();
Member warningMember = context.getMember();
Guild guild = warnedMember.getGuild();
log.info("User {} is warning {} in server {}", warnedMember.getId(), warningMember.getId(), guild.getIdLong());
WarnNotification warnNotification = WarnNotification
.builder()
.reason(context.getReason())
.warnId(warningId)
.serverName(guild.getName())
.build();
String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, serverId);
return messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage)
.exceptionally(throwable -> {
log.warn("Failed to notify user {} of warning {} in guild {}.", warnedMember.getId(), warningId, serverId);
return null;
})
.thenCompose(message -> self.sendWarningLog(context))
.thenCompose(logMessage -> self.evaluateInfraction(context, logMessage))
.thenAccept(context::setInfractionId);
}
@Transactional @Transactional
public CompletableFuture<Message> sendWarningLog(WarnContext context) { public CompletableFuture<Message> sendWarningLog(Guild guild, ServerUser warnedUser, ServerUser warningUser, String reason, ServerChannelMessage serverChannelMessage, Long warningId) {
MessageToSend message = renderMessageModel(context); WarnLogModel warnContext = WarnLogModel
List<CompletableFuture<Message>> futures = postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, context.getGuild().getIdLong()); .builder()
.warnedMember(MemberDisplay.fromServerUser(warnedUser))
.warningMember(MemberDisplay.fromServerUser(warningUser))
.channelMessage(serverChannelMessage)
.warnId(warningId)
.reason(reason)
.build();
MessageToSend message = renderMessageModel(warnContext, guild.getIdLong());
List<CompletableFuture<Message>> futures = postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, guild.getIdLong());
return FutureUtils.toSingleFutureGeneric(futures).thenCompose(unused -> futures.get(0)); return FutureUtils.toSingleFutureGeneric(futures).thenCompose(unused -> futures.get(0));
} }
@Transactional @Transactional
public CompletableFuture<Long> evaluateInfraction(WarnContext context, Message logMessage) { public CompletableFuture<Long> evaluateInfraction(Guild guild, ServerUser warnedUser, ServerUser warningUser, String reason, Message logMessage) {
Long serverId = context.getGuild().getIdLong(); Long serverId = guild.getIdLong();
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) { if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) {
Long infractionPoints = configService.getLongValueOrConfigDefault(WarningFeatureConfig.WARN_INFRACTION_POINTS, serverId); Long infractionPoints = configService.getLongValueOrConfigDefault(WarningFeatureConfig.WARN_INFRACTION_POINTS, serverId);
AServer server = serverManagementService.loadServer(context.getGuild()); AUserInAServer warnedUserInAServer = userInServerManagementService.loadOrCreateUser(warnedUser);
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(server, context.getWarnedMember().getIdLong()); AUserInAServer warningUserInAServer = userInServerManagementService.loadOrCreateUser(warningUser);
AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(server, context.getMember().getIdLong());
// both user could create the server object, we need to make sure we have the same reference // both user could create the server object, we need to make sure we have the same reference
warnedUser.setServerReference(warningUser.getServerReference()); warnedUserInAServer.setServerReference(warningUserInAServer.getServerReference());
return infractionService.createInfractionWithNotification(warnedUser, infractionPoints, WARN_INFRACTION_TYPE, context.getReason(), warningUser, logMessage) return infractionService.createInfractionWithNotification(warnedUserInAServer, infractionPoints, WARN_INFRACTION_TYPE, reason, warningUserInAServer, logMessage)
.thenApply(Infraction::getId); .thenApply(Infraction::getId);
} else { } else {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
@@ -160,36 +141,40 @@ public class WarnServiceBean implements WarnService {
} }
@Override @Override
public CompletableFuture<Void> warnUserWithLog(WarnContext context) { public CompletableFuture<Void> warnUserWithLog(Guild guild, ServerUser warnedUser, ServerUser warningUser, String reason, ServerChannelMessage serverChannelMessage) {
return notifyAndLogFullUserWarning(context) Long serverId = guild.getIdLong();
.thenAccept(aVoid -> self.persistWarning(context)); Long warningId = counterService.getNextCounterValue(serverId, WARNINGS_COUNTER_KEY);
log.info("User {} is warning {} in server {}", warningUser.getUserId(), warnedUser.getUserId(), serverId);
WarnNotification warnNotification = WarnNotification
.builder()
.reason(reason)
.warnId(warningId)
.serverName(guild.getName())
.build();
String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, serverId);
return messageService.sendMessageToUser(warnedUser, warnNotificationMessage)
.exceptionally(throwable -> {
log.warn("Failed to notify user {} of warning {} in guild {}.", warnedUser.getUserId(), warningId, serverId, throwable);
return null;
})
.thenCompose(message -> self.sendWarningLog(guild, warnedUser, warningUser, reason, serverChannelMessage, warningId))
.thenCompose(logMessage -> self.evaluateInfraction(guild, warnedUser, warningUser, reason, logMessage))
.thenAccept(infractionId -> self.persistWarning(warnedUser, warningUser, reason, serverChannelMessage, infractionId, warningId));
} }
@Transactional @Transactional
public void persistWarning(WarnContext context) { public void persistWarning(ServerUser warnedUser, ServerUser warningUser, String reason, ServerChannelMessage serverChannelMessage, Long infractionId, Long warningId) {
Long serverId = warnedUser.getServerId();
log.info("Persisting warning {} in server {} for user {} by user {}.", log.info("Persisting warning {} in server {} for user {} by user {}.",
context.getWarnId(), context.getGuild().getId(), context.getWarnedMember().getId(), context.getMember().getId()); warningId, serverId, warnedUser.getUserId(), warningUser.getUserId());
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(context.getWarnedMember()); AUserInAServer warnedUserInAServer = userInServerManagementService.loadOrCreateUser(warnedUser);
AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(context.getMember()); AUserInAServer warningUserInAServer = userInServerManagementService.loadOrCreateUser(warningUser);
Warning createdWarning = warnManagementService.createWarning(warnedUser, warningUser, context.getReason(), context.getWarnId()); Warning createdWarning = warnManagementService.createWarning(warnedUserInAServer, warningUserInAServer, reason, warningId);
if(context.getInfractionId() != null) { if(infractionId != null) {
Infraction infraction = infractionManagementService.loadInfraction(context.getInfractionId()); Infraction infraction = infractionManagementService.loadInfraction(infractionId);
createdWarning.setInfraction(infraction); createdWarning.setInfraction(infraction);
} }
ServerUser warnedServerUser = ServerUser.fromAUserInAServer(warnedUser); warningCreatedListenerManager.sendWarningCreatedEvent(createdWarning.getWarnId(), warnedUser, warningUser, reason, serverChannelMessage);
ServerUser warningServerUser = ServerUser.fromAUserInAServer(warnedUser);
ServerChannelMessage commandMessage;
if(context.getMessage() != null) {
commandMessage = ServerChannelMessage.fromMessage(context.getMessage());
} else {
commandMessage = ServerChannelMessage
.builder()
.serverId(context.getGuild().getIdLong())
.channelId(context.getChannel().getIdLong())
.messageId(SnowflakeUtils.createSnowFlake())
.build();
}
warningCreatedListenerManager.sendWarningCreatedEvent(createdWarning.getWarnId(), warnedServerUser, warningServerUser, context.getReason(), commandMessage);
} }
@Override @Override
@@ -308,8 +293,8 @@ public class WarnServiceBean implements WarnService {
} }
} }
public MessageToSend renderMessageModel(WarnContext warnContext) { public MessageToSend renderMessageModel(WarnLogModel warnContext, Long serverId) {
return templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, warnContext, warnContext.getGuild().getIdLong()); return templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, warnContext, serverId);
} }
private CompletableFuture<Void> logDecayedWarnings(AServer server, List<Warning> warningsToDecay) { private CompletableFuture<Void> logDecayedWarnings(AServer server, List<Warning> warningsToDecay) {

View File

@@ -32,23 +32,12 @@ abstracto.postTargets.banLog.name=banLog
abstracto.postTargets.unBanLog.name=unBanLog abstracto.postTargets.unBanLog.name=unBanLog
abstracto.postTargets.muteLog.name=muteLog abstracto.postTargets.muteLog.name=muteLog
abstracto.postTargets.decayLog.name=decayLog abstracto.postTargets.decayLog.name=decayLog
abstracto.postTargets.infractionNotification.name=infractionNotification
abstracto.featureModes.banCommandLogging.featureName=moderation
abstracto.featureModes.banCommandLogging.mode=banCommandLogging
abstracto.featureModes.banCommandLogging.enabled=true
abstracto.featureModes.unBanCommandLogging.featureName=moderation
abstracto.featureModes.unBanCommandLogging.mode=unBanCommandLogging
abstracto.featureModes.unBanCommandLogging.enabled=true
abstracto.featureModes.warnDecayLogging.featureName=warnings abstracto.featureModes.warnDecayLogging.featureName=warnings
abstracto.featureModes.warnDecayLogging.mode=warnDecayLogging abstracto.featureModes.warnDecayLogging.mode=warnDecayLogging
abstracto.featureModes.warnDecayLogging.enabled=true abstracto.featureModes.warnDecayLogging.enabled=true
abstracto.featureModes.infractionReporting.featureName=infractions
abstracto.featureModes.infractionReporting.mode=infractionReporting
abstracto.featureModes.infractionReporting.enabled=true
abstracto.featureModes.anonymousReportReactions.featureName=reportReactions abstracto.featureModes.anonymousReportReactions.featureName=reportReactions
abstracto.featureModes.anonymousReportReactions.mode=anonymousReportReactions abstracto.featureModes.anonymousReportReactions.mode=anonymousReportReactions
abstracto.featureModes.anonymousReportReactions.enabled=false abstracto.featureModes.anonymousReportReactions.enabled=false
@@ -57,20 +46,24 @@ abstracto.featureModes.singularReportReactions.featureName=reportReactions
abstracto.featureModes.singularReportReactions.mode=singularReportReactions abstracto.featureModes.singularReportReactions.mode=singularReportReactions
abstracto.featureModes.singularReportReactions.enabled=false abstracto.featureModes.singularReportReactions.enabled=false
abstracto.systemConfigs.infractionLvl1.name=infractionLvl1 abstracto.featureModes.reactionReportActions.featureName=reportReactions
abstracto.systemConfigs.infractionLvl1.longValue=10 abstracto.featureModes.reactionReportActions.mode=reactionReportActions
abstracto.featureModes.reactionReportActions.enabled=false
abstracto.systemConfigs.infractionLvl2.name=infractionLvl2 abstracto.systemConfigs.infractionLevel1.name=infractionLevel1
abstracto.systemConfigs.infractionLvl2.longValue=20 abstracto.systemConfigs.infractionLevel1.longValue=10
abstracto.systemConfigs.infractionLvl3.name=infractionLvl3 abstracto.systemConfigs.infractionLevel2.name=infractionLevel2
abstracto.systemConfigs.infractionLvl3.longValue=30 abstracto.systemConfigs.infractionLevel2.longValue=20
abstracto.systemConfigs.infractionLvl4.name=infractionLvl4 abstracto.systemConfigs.infractionLevel3.name=infractionLevel3
abstracto.systemConfigs.infractionLvl4.longValue=40 abstracto.systemConfigs.infractionLevel3.longValue=30
abstracto.systemConfigs.infractionLvl5.name=infractionLvl5 abstracto.systemConfigs.infractionLevel4.name=infractionLevel4
abstracto.systemConfigs.infractionLvl5.longValue=50 abstracto.systemConfigs.infractionLevel4.longValue=40
abstracto.systemConfigs.infractionLevel5.name=infractionLevel5
abstracto.systemConfigs.infractionLevel5.longValue=50
abstracto.systemConfigs.infractionLevels.name=infractionLevels abstracto.systemConfigs.infractionLevels.name=infractionLevels
abstracto.systemConfigs.infractionLevels.longValue=5 abstracto.systemConfigs.infractionLevels.longValue=5

View File

@@ -1,80 +0,0 @@
package dev.sheldan.abstracto.moderation.command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.test.command.CommandConfigValidator;
import dev.sheldan.abstracto.core.test.command.CommandTestUtilities;
import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
import dev.sheldan.abstracto.moderation.service.KickServiceBean;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class KickTest {
@InjectMocks
private Kick testUnit;
@Mock
private TemplateService templateService;
@Mock
private KickServiceBean kickService;
@Mock
private Member memberToKick;
@Captor
private ArgumentCaptor<KickLogModel> logModelArgumentCaptor;
private static final String REASON = "reason";
private static final Long SERVER_ID = 1L;
@Test
public void testKickMemberWithoutReason() {
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(memberToKick));
when(memberToKick.getGuild()).thenReturn(parameters.getGuild());
when(parameters.getGuild().getIdLong()).thenReturn(SERVER_ID);
when(templateService.renderSimpleTemplate(Kick.KICK_DEFAULT_REASON_TEMPLATE, SERVER_ID)).thenReturn(REASON);
when(kickService.kickMember(eq(memberToKick), eq(REASON), logModelArgumentCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
KickLogModel usedLogModel = logModelArgumentCaptor.getValue();
Assert.assertEquals(REASON, usedLogModel.getReason());
Assert.assertEquals(memberToKick, usedLogModel.getKickedUser());
Assert.assertEquals(parameters.getAuthor(), usedLogModel.getMember());
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}
@Test
public void testKickMemberWithReason() {
String customReason = "reason2";
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(memberToKick, customReason));
when(memberToKick.getGuild()).thenReturn(parameters.getGuild());
when(kickService.kickMember(eq(memberToKick), eq(customReason), logModelArgumentCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
KickLogModel usedLogModel = logModelArgumentCaptor.getValue();
Assert.assertEquals(customReason, usedLogModel.getReason());
Assert.assertEquals(memberToKick, usedLogModel.getKickedUser());
Assert.assertEquals(parameters.getAuthor(), usedLogModel.getMember());
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}
@Test
public void validateCommand() {
CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration());
}
}

View File

@@ -1,78 +0,0 @@
package dev.sheldan.abstracto.moderation.command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.test.command.CommandConfigValidator;
import dev.sheldan.abstracto.core.test.command.CommandTestUtilities;
import dev.sheldan.abstracto.moderation.model.template.command.WarnContext;
import dev.sheldan.abstracto.moderation.service.WarnService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class WarnTest {
@InjectMocks
private Warn testUnit;
@Mock
private WarnService warnService;
@Mock
private TemplateService templateService;
private static final String DEFAULT_REASON = "defaultReason";
@Captor
private ArgumentCaptor<WarnContext> parameterCaptor;
private static final Long SERVER_ID = 1L;
@Test
public void testExecuteWarnCommandWithReason() {
Member warnedMember = Mockito.mock(Member.class);
String reason = "reason";
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(warnedMember, reason));
when(warnedMember.getGuild()).thenReturn(parameters.getGuild());
when(parameters.getGuild().getIdLong()).thenReturn(SERVER_ID);
when(templateService.renderSimpleTemplate(Warn.WARN_DEFAULT_REASON_TEMPLATE, SERVER_ID)).thenReturn(DEFAULT_REASON);
when(warnService.warnUserWithLog(parameterCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
WarnContext value = parameterCaptor.getValue();
Assert.assertEquals(reason, value.getReason());
Assert.assertEquals(warnedMember, value.getWarnedMember());
Assert.assertEquals(parameters.getAuthor(), value.getMember());
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}
@Test
public void testExecuteWarnCommandWithDefaultReason() {
Member warnedMember = Mockito.mock(Member.class);
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(warnedMember));
when(warnedMember.getGuild()).thenReturn(parameters.getGuild());
when(parameters.getGuild().getIdLong()).thenReturn(SERVER_ID);
when(templateService.renderSimpleTemplate(Warn.WARN_DEFAULT_REASON_TEMPLATE, SERVER_ID)).thenReturn(DEFAULT_REASON);
when(warnService.warnUserWithLog(parameterCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
WarnContext value = parameterCaptor.getValue();
Assert.assertEquals(DEFAULT_REASON, value.getReason());
Assert.assertEquals(warnedMember, value.getWarnedMember());
Assert.assertEquals(parameters.getAuthor(), value.getMember());
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}
@Test
public void validateCommand() {
CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration());
}
}

View File

@@ -1,58 +0,0 @@
package dev.sheldan.abstracto.moderation.command.mute;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.test.command.CommandConfigValidator;
import dev.sheldan.abstracto.core.test.command.CommandTestUtilities;
import dev.sheldan.abstracto.moderation.command.Mute;
import dev.sheldan.abstracto.moderation.model.template.command.MuteContext;
import dev.sheldan.abstracto.moderation.service.MuteService;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class MuteTest {
@InjectMocks
private Mute testUnit;
@Mock
private MuteService muteService;
@Mock
private TemplateService templateService;
@Captor
private ArgumentCaptor<MuteContext> muteLogArgumentCaptor;
@Test
public void testMuteMember() {
Member mutedMember = Mockito.mock(Member.class);
String reason = "reason";
Duration duration = Duration.ofMinutes(1);
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(mutedMember, duration, reason));
when(mutedMember.getGuild()).thenReturn(parameters.getGuild());
when(muteService.muteMemberWithLog(muteLogArgumentCaptor.capture())).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
MuteContext muteLog = muteLogArgumentCaptor.getValue();
Assert.assertEquals(mutedMember, muteLog.getMutedUser());
Assert.assertEquals(parameters.getAuthor(), muteLog.getMutingUser());
}
@Test
public void validateCommand() {
CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration());
}
}

View File

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

View File

@@ -38,6 +38,6 @@ public class ReportReactionFeatureConfig implements FeatureConfig {
@Override @Override
public List<FeatureMode> getAvailableModes() { public List<FeatureMode> getAvailableModes() {
return Arrays.asList(ReportReactionMode.SINGULAR_MESSAGE, ReportReactionMode.ANONYMOUS); return Arrays.asList(ReportReactionMode.values());
} }
} }

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