Compare commits

...
Author SHA1 Message Date
Sheldan 879c1b0173 [AB-xxx] displaying more information on the external leaderboard 2024-11-05 22:57:51 +01:00
Sheldan d5bf70f586 [AB-xxx] fixing variable naming 2024-11-04 23:14:16 +01:00
release-bot 4525fbc861 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-10-29 21:53:24 +00:00
release-bot 4671a78ff9 [maven-release-plugin] prepare for next development iteration 2024-10-29 21:43:24 +00:00
release-bot c4174562c6 [maven-release-plugin] prepare release v1.5.48 2024-10-29 21:43:22 +00:00
Sheldan bfaa5d6140 [AB-xxx] making lovecalc deterministic (simple approach) 2024-10-29 22:36:04 +01:00
release-bot d2e8398fa4 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-09-29 19:56:51 +00:00
release-bot c67209925a [maven-release-plugin] prepare for next development iteration 2024-09-29 19:46:50 +00:00
release-bot f3ac7895eb [maven-release-plugin] prepare release v1.5.47 2024-09-29 19:46:48 +00:00
Sheldan 6d893e39bb [AB-xxx] downgrading to java 8 for liquibase 2024-09-29 21:44:28 +02:00
release-bot 94140104de Commit from GitHub Actions (Publishes a new version of abstracto) 2024-09-29 19:23:42 +00:00
release-bot 6d80423244 [maven-release-plugin] prepare for next development iteration 2024-09-29 19:13:23 +00:00
release-bot 27040e506a [maven-release-plugin] prepare release v1.5.46 2024-09-29 19:13:22 +00:00
Sheldan 3bbf5a2391 [AB-xxx] restructuring docker file for liquibase deployment to abide to the upload limits per individual file 2024-09-29 21:11:04 +02:00
release-bot 11cb3b9ee1 [maven-release-plugin] prepare for next development iteration 2024-09-29 09:36:43 +00:00
release-bot dccf314e53 [maven-release-plugin] prepare release v1.5.45 2024-09-29 09:36:42 +00:00
Sheldan a0daeee3a1 [AB-xxx] removing discord webhook due to deprecated github action 2024-09-29 11:30:28 +02:00
Sheldan 1b7c383ced [AB-xxx] allowing reminders to not have a text
code cleanup
2024-09-29 11:22:47 +02:00
release-bot 087dd266cc Commit from GitHub Actions (Publishes a new version of abstracto) 2024-08-06 20:59:49 +00:00
release-bot beda17f672 [maven-release-plugin] prepare for next development iteration 2024-08-06 20:50:04 +00:00
release-bot 5c234295aa [maven-release-plugin] prepare release v1.5.44 2024-08-06 20:50:02 +00:00
Sheldan 9cfe4bf353 [AB-xxx] commit for release 2024-08-06 22:47:31 +02:00
release-bot 75c45541c9 [maven-release-plugin] prepare for next development iteration 2024-08-06 20:30:22 +00:00
release-bot 11ac33ad4a [maven-release-plugin] prepare release v1.5.43 2024-08-06 20:30:20 +00:00
Sheldan a37a0f87a0 [AB-xxx] commit for release 2024-08-02 23:30:18 +02:00
release-bot 69353d32db [maven-release-plugin] prepare for next development iteration 2024-08-02 20:59:38 +00:00
release-bot b973c4660c [maven-release-plugin] prepare release v1.5.42 2024-08-02 20:59:36 +00:00
Sheldan 34692d22eb [AB-xxx] fixing year 2024-08-02 22:57:15 +02:00
release-bot 33268cded6 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-08-02 20:44:20 +00:00
release-bot 40d66df9b0 [maven-release-plugin] prepare for next development iteration 2024-08-02 20:40:14 +00:00
release-bot 92508d7a1d [maven-release-plugin] prepare release v1.5.41 2024-08-02 20:40:12 +00:00
Sheldan ccbf6147e9 [AB-xxx] changing github actions image version to a concrete version and upgrading to docker compose 2024-08-02 22:36:04 +02:00
release-bot a3545c4af0 [maven-release-plugin] prepare for next development iteration 2024-08-02 20:25:03 +00:00
release-bot fa187f8817 [maven-release-plugin] prepare release v1.5.40 2024-08-02 20:25:00 +00:00
Sheldan c39b7ebeec [AB-xxx] increasing version due to failed release 2024-08-02 22:22:35 +02:00
release-bot 5cbad801ff [maven-release-plugin] prepare for next development iteration 2024-08-02 20:09:33 +00:00
release-bot 826bee1f81 [maven-release-plugin] prepare release v1.5.39 2024-08-02 20:09:26 +00:00
Sheldan 99bf9a9be0 [AB-xxx] fixed send message action sending for every level change 2024-07-31 00:11:57 +02:00
Sheldan 5b5e4973a7 [AB-117] increasing length of supported reminder text 2024-07-30 21:24:44 +02:00
Sheldan 65e956827c [AB-xxx] adding generic input format exception handling
adding level_action to send a message to a channel once a level is reached
2024-07-27 19:51:49 +02:00
release-bot b258a8bc54 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-06-13 16:33:09 +00:00
release-bot 9864b7d875 [maven-release-plugin] prepare for next development iteration 2024-06-13 16:21:34 +00:00
release-bot 27466b7333 [maven-release-plugin] prepare release v1.5.38 2024-06-13 16:21:31 +00:00
Sheldan bfb8969d1f [AB-xxx] fixing passing wrong ID in contact slash command 2024-06-13 18:18:51 +02:00
Sheldan 0097ff801a [AB-xxx] changing hikari max live time 2024-06-12 20:07:55 +02:00
Sheldan b6a188c04d [AB-xxx] using the suggestion message as the thread starter for suggestion threads 2024-06-03 22:24:35 +02:00
release-bot 2fa1adde02 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-05-30 23:29:17 +00:00
release-bot cb8b64cc01 [maven-release-plugin] prepare for next development iteration 2024-05-30 23:19:23 +00:00
release-bot 3b3dd0dbb7 [maven-release-plugin] prepare release v1.5.37 2024-05-30 23:19:22 +00:00
Sheldan 388fead2a6 [AB-xxx] typo change
java doc update
removal of todo
2024-05-31 01:14:26 +02:00
Sheldan 336c3d0bd8 [AB-xxx] adding modmail support for ban appeals
refactoring modmail to use user objects instead of member in various places
2024-05-31 01:14:26 +02:00
Sheldan 4991ad8f1c [AB-xxx] making intents, cache policies, and cache flags configurable 2024-05-18 18:22:33 +02:00
Sheldan c5136a1808 [AB-xxx] removing example implementation repository from readme 2024-05-17 17:34:33 +02:00
release-bot 446d882eec Commit from GitHub Actions (Publishes a new version of abstracto) 2024-05-05 22:24:18 +00:00
release-bot b3e207a967 [maven-release-plugin] prepare for next development iteration 2024-05-05 22:12:03 +00:00
release-bot 5c25345cf8 [maven-release-plugin] prepare release v1.5.36 2024-05-05 22:12:01 +00:00
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
347 changed files with 22468 additions and 2280 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
REGISTRY_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.5.26
VERSION=1.5.48
+12 -13
View File
@@ -16,7 +16,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
@@ -25,17 +25,16 @@ jobs:
with:
distribution: 'corretto'
java-version: 17
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: '21.x'
- name: Build with Maven
run: mvn -B install --file abstracto-application/pom.xml
- uses: actions/setup-ruby@v1
- name: Send Webhook Notification
if: always()
env:
JOB_STATUS: ${{ job.status }}
WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
HOOK_OS_NAME: ${{ runner.os }}
WORKFLOW_NAME: ${{ github.workflow }}
run: |
git clone https://github.com/DiscordHooks/github-actions-discord-webhook.git webhook
bash webhook/send.sh $JOB_STATUS $WEBHOOK_URL
shell: bash
- 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
+14 -2
View File
@@ -5,7 +5,7 @@ permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
@@ -15,6 +15,10 @@ jobs:
with:
distribution: 'corretto'
java-version: 17
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: '21.x'
- name: Load current version
id: version
working-directory: ./abstracto-application
@@ -37,6 +41,14 @@ jobs:
release-branch-name: master
maven-args: "-Dmaven.javadoc.skip=true -s settings.xml -DskipTests"
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
uses: docker/login-action@v2
with:
@@ -52,7 +64,7 @@ jobs:
with:
path: .env
- name: Build and push Docker containers
run: docker-compose build && docker-compose push
run: docker compose build && docker compose push
env:
REGISTRY_PREFIX: ${{ steps.dotenv.outputs.registry_prefix }}
VERSION: ${{ steps.dotenv.outputs.version }}
-2
View File
@@ -14,9 +14,7 @@ target/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 Sheldan
Copyright (c) 2024 Sheldan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -2
View File
@@ -8,11 +8,10 @@ Abstracto represents a framework to be used as a basis for a Discord bot. It use
and provides an extensive tool set to create new commands and a wide range of commands out of the box.
This repository does not provide the full functionality in order to start a discord bot, because it requires a Main class.
An example implementation of this bot can be seen [here](https://github.com/Sheldan/Crimson). This repository contains the required configuration in order to run a bot and example customizations.
## Technologies
* [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)
* [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.
@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assignable-roles-int</artifactId>
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>dynamic-activity</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>dynamic-activity</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.entertainment.command;
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.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
@@ -86,7 +87,7 @@ public class Mock extends AbstractConditionableCommand {
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
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
.builder()
@@ -105,7 +105,9 @@ public class EntertainmentServiceBean implements EntertainmentService {
@Override
public Integer getLoveCalcValue(String firstPart, String secondPart) {
return secureRandom.nextInt(100);
String fullInput = firstPart.toLowerCase() + secondPart.toLowerCase();
Random random = new Random(fullInput.hashCode());
return random.nextInt(100);
}
@Override
@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -38,6 +38,16 @@
<version>${project.version}</version>
</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>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@@ -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();
}
}
@@ -0,0 +1,95 @@
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.ExperienceLevelService;
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;
@Autowired
private ExperienceLevelService experienceLevelService;
@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;
Long experienceNeededToNextLevel = experienceLevelService.calculateExperienceToNextLevel(aUserExperience.getCurrentLevel().getLevel(), aUserExperience.getExperience());
Long nextLevelExperience = experienceLevelService.calculateNextLevel(aUserExperience.getCurrentLevel().getLevel()).getExperienceNeeded();
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);
}
Long currentExpNeeded = aUserExperience.getCurrentLevel().getExperienceNeeded();
Long experienceWithinLevel = aUserExperience.getExperience() - currentExpNeeded;
Long experienceNeededForCurrentLevel = nextLevelExperience - currentExpNeeded;
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())
.experienceToNextLevel(experienceNeededToNextLevel)
.currentLevelExperienceNeeded(experienceNeededForCurrentLevel)
.experienceOnCurrentLevel(experienceWithinLevel)
.percentage(((float) experienceWithinLevel / experienceNeededForCurrentLevel) * 100)
.nextLevelExperienceNeeded(nextLevelExperience)
.role(roleDisplay)
.member(userDisplay)
.build();
}
}
@@ -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.ExperienceFeatureMode;
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.listener.LevelActionListener;
import dev.sheldan.abstracto.experience.model.LevelActionPayload;
@@ -99,6 +100,9 @@ public class AddLevelAction extends AbstractConditionableCommand {
return userExperienceManagementService.saveUser(user);
});
}
if(levelActionManagementService.getLevelAction(actionName, level, server, userExperience).isPresent()) {
throw new LevelActionAlreadyExistsException();
}
levelActionManagementService.createLevelAction(level, server, actionName, userExperience, payload);
return interactionService.replyEmbed(RESPONSE_TEMPLATE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
@@ -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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
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.service.AUserExperienceService;
import net.dv8tion.jda.api.entities.Member;
@@ -100,4 +102,10 @@ public class ExpLevelUpNotification extends AbstractConditionableCommand {
.help(helpInfo)
.build();
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(ExperienceFeatureMode.LEVEL_UP_NOTIFICATION);
}
}
@@ -28,7 +28,9 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@@ -70,6 +72,9 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
@Autowired
private InteractionService interactionService;
@Value("${abstracto.experience.leaderboard.externalUrl}")
private String leaderboardExternalURL;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
@@ -91,11 +96,18 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
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 -> {
List<LeaderBoardEntryModel> finalModels = completableFutures.join();
LeaderBoardModel leaderBoardModel = LeaderBoardModel
.builder()
.userExperiences(finalModels)
.leaderboardUrl(leaderboardUrl)
.userExecuting(userRankFuture.join().get(0))
.build();
return CompletableFuture.completedFuture(templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, actorUser.getGuild().getIdLong()));
@@ -107,13 +107,13 @@ public class Rank extends AbstractConditionableCommand {
Long currentExpNeeded = experienceObj.getCurrentLevel().getExperienceNeeded();
Long experienceNeededToNextLevel = experienceLevelService.calculateExperienceToNextLevel(experienceObj.getCurrentLevel().getLevel(), experienceObj.getExperience());
Long nextLevelExperience = experienceLevelService.calculateNextLevel(experienceObj.getCurrentLevel().getLevel()).getExperienceNeeded();
Long levelExperience = nextLevelExperience - currentExpNeeded;
Long inLevelExperience = experienceObj.getExperience() - currentExpNeeded;
Long experienceNeededForCurrentLevel = nextLevelExperience - currentExpNeeded;
Long experienceWithinLevel = experienceObj.getExperience() - currentExpNeeded;
rankModel.setExperienceForCurrentLevel(currentExpNeeded);
rankModel.setCurrentLevelPercentage(((float) inLevelExperience / levelExperience) * 100);
rankModel.setLevelExperience(levelExperience);
rankModel.setCurrentLevelPercentage(((float) experienceWithinLevel / experienceNeededForCurrentLevel) * 100);
rankModel.setLevelExperience(experienceNeededForCurrentLevel);
rankModel.setExperienceToNextLevel(experienceNeededToNextLevel);
rankModel.setInLevelExperience(inLevelExperience);
rankModel.setInLevelExperience(experienceWithinLevel);
rankModel.setNextLevelExperience(nextLevelExperience);
return templateService.renderEmbedTemplate(RANK_POST_EMBED_TEMPLATE, rankModel, toRender.getGuild().getIdLong());
}
@@ -48,7 +48,7 @@ public class AddMemberToChannelLevelAction implements LevelActionListener {
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction) {
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}
@@ -48,7 +48,7 @@ public class AddRoleLevelAction implements LevelActionListener {
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction) {
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}
@@ -48,7 +48,7 @@ public class RemoveMemberFromChannelLevelAction implements LevelActionListener {
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction) {
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}
@@ -49,7 +49,7 @@ public class RemoveRoleLevelAction implements LevelActionListener {
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction) {
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}
@@ -0,0 +1,106 @@
package dev.sheldan.abstracto.experience.listener;
import com.google.gson.Gson;
import dev.sheldan.abstracto.core.exception.InputFormatException;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.experience.model.LevelActionPayload;
import dev.sheldan.abstracto.experience.model.SendMessageToChannelLevelActionMessageModel;
import dev.sheldan.abstracto.experience.model.SendMessageToChannelLevelActionPayload;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LevelAction;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class SendMessageToChannelLevelAction implements LevelActionListener {
public static final String ACTION_NAME = "send_message_to_channel_above_level";
private static final String LEVEL_ACTION_SEND_MESSAGE_TEMPLATE_KEY = "levelAction_sendMessageToChannel_template";
@Autowired
private Gson gson;
@Autowired
private ChannelService channelService;
@Autowired
private TemplateService templateService;
@Autowired
private MemberService memberService;
@Override
public String getName() {
return ACTION_NAME;
}
@Override
public void apply(AUserExperience userExperience, LevelAction levelAction, MemberActionModification container) {
SendMessageToChannelLevelActionPayload payload = (SendMessageToChannelLevelActionPayload) levelAction.getLoadedPayload();
SendMessageToChannelLevelActionMessageModel.SendMessageToChannelLevelActionMessageModelBuilder messageModelBuilder = SendMessageToChannelLevelActionMessageModel
.builder()
.level(userExperience.getLevelOrDefault())
.templateKey(payload.getTemplateKey())
.experience(userExperience.getExperience());
ServerUser serverUser = ServerUser.fromAUserInAServer(userExperience.getUser());
memberService.getMemberInServerAsync(serverUser).thenAccept(member -> {
messageModelBuilder.memberDisplay(MemberDisplay.fromMember(member));
SendMessageToChannelLevelActionMessageModel model = messageModelBuilder.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(LEVEL_ACTION_SEND_MESSAGE_TEMPLATE_KEY, model, serverUser.getServerId());
GuildMessageChannel targetChannel = channelService.getMessageChannelFromServer(serverUser.getServerId(), payload.getChannelId());
FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, targetChannel)).thenAccept(unused -> {
log.info("Send message to channel action sent a message to channel {} for user {} in server {}.", payload.getChannelId(), serverUser.getUserId(), serverUser.getServerId());
}).exceptionally(throwable -> {
log.warn("Send message to channel action failed to send a message to channel {} for user {} in server {}.", payload.getChannelId(), serverUser.getUserId(), serverUser.getServerId(), throwable);
return null;
});
}).exceptionally(throwable -> {
log.warn("Failed to load member {} in server {} for send message level action towards channel {}.", serverUser.getUserId(), serverUser.getServerId(), payload.getChannelId());
return null;
});
}
@Override
public boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction) {
if(!oldLevel.equals(aUserExperience.getLevelOrDefault())) { // this means the user changed level now, this is the path from gaining a lot of experience
boolean jumpedLevelToMatch = oldLevel < levelAction.getLevel().getLevel() && aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
// this boolean means that the user did NOT have the action earlier, but does now (and more than that)
return jumpedLevelToMatch || aUserExperience.getLevelOrDefault().equals(levelAction.getLevel().getLevel()); // or the user matches the level _exactly_, this is the path from normally gaining experience
} else {
// This case is useful for re-joining, because this means, that the user did _not_ change level, and already is somewhere way above
return aUserExperience.getLevelOrDefault() >= levelAction.getLevel().getLevel();
}
}
@Override
public void prepareAction(LevelAction levelAction) {
levelAction.setLoadedPayload(gson.fromJson(levelAction.getPayload(), SendMessageToChannelLevelActionPayload.class));
}
@Override
public LevelActionPayload createPayload(Guild guild, String input) {
if(!input.contains(";")) {
throw new InputFormatException(input, "<#channel>;template_key");
}
String channelPart = input.substring(0, input.indexOf(";"));
GuildChannel channel = ParseUtils.parseGuildChannelFromText(channelPart, guild);
String templateKey = input.substring(input.indexOf(";") + 1);
return SendMessageToChannelLevelActionPayload
.builder()
.channelId(channel.getIdLong())
.templateKey(templateKey)
.build();
}
}
@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.experience.model;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class SendMessageToChannelLevelActionMessageModel implements LevelActionPayload {
private MemberDisplay memberDisplay;
private Integer level;
private Long experience;
private String templateKey;
}
@@ -0,0 +1,11 @@
package dev.sheldan.abstracto.experience.model;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class SendMessageToChannelLevelActionPayload implements LevelActionPayload {
private Long channelId;
private String templateKey;
}
@@ -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;
}
@@ -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;
}
@@ -0,0 +1,24 @@
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;
private Long experienceToNextLevel;
private Long experienceOnCurrentLevel;
private Long currentLevelExperienceNeeded;
private Float percentage;
private Long nextLevelExperienceNeeded;
}
@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.experience.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
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}
*/
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.
@@ -336,8 +336,9 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
RoleCalculationResult result = RoleCalculationResult
.builder()
.build();
if(!Objects.equals(newLevel.getLevel(), aUserExperience.getCurrentLevel().getLevel())) {
Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0;
boolean userChangesLevel = !Objects.equals(newLevel.getLevel(), aUserExperience.getCurrentLevel().getLevel());
Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0;
if(userChangesLevel) {
log.info("User {} in server {} changed level. New {}, Old {}.", member.getIdLong(),
member.getGuild().getIdLong(), newLevel.getLevel(),
oldLevel);
@@ -371,8 +372,8 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
aUserExperience.setCurrentExperienceRole(calculatedNewRole);
}
aUserExperience.setMessageCount(aUserExperience.getMessageCount() + 1L);
if(featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, server, ExperienceFeatureMode.LEVEL_ACTION)) {
levelActionService.applyLevelActionsToUser(aUserExperience)
if(userChangesLevel && featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, server, ExperienceFeatureMode.LEVEL_ACTION)) {
levelActionService.applyLevelActionsToUser(aUserExperience, oldLevel)
.thenAccept(unused -> {
log.info("Executed level actions for user {}.", userInServerId);
})
@@ -166,7 +166,10 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
List<LevelRole> levelRoles = new ArrayList<>();
roles.forEach(aExperienceRole -> {
Role role = roleService.getRoleFromGuild(aExperienceRole.getRole());
Role role = null;
if(!aExperienceRole.getRole().getDeleted()) {
role = roleService.getRoleFromGuild(aExperienceRole.getRole());
}
LevelRole levelRole = LevelRole
.builder()
.role(role)
@@ -49,6 +49,11 @@ public class LevelActionServiceBean implements LevelActionService {
@Override
public CompletableFuture<Void> applyLevelActionsToUser(AUserExperience user) {
return applyLevelActionsToUser(user, user.getLevelOrDefault());
}
@Override
public CompletableFuture<Void> applyLevelActionsToUser(AUserExperience user, Integer oldLevel) {
if(levelActions == null || levelActions.isEmpty()) {
return CompletableFuture.completedFuture(null);
}
@@ -60,8 +65,16 @@ public class LevelActionServiceBean implements LevelActionService {
Map<Integer, List<LevelAction>> actionConfigMap = new HashMap<>();
Map<String, LevelActionListener> actionStringListenerMap = levelActions
.stream()
.collect(Collectors.toMap(a -> a.getName().toLowerCase(), Function.identity()));
levelActionsOfUserInServer.forEach(levelAction -> {
if(levelAction.getLevel().getLevel() > user.getLevelOrDefault()) {
LevelActionListener listener = actionStringListenerMap.get(levelAction.getAction());
if(listener == null) { // if for some reason the config is still in the database, but we don't have code for it anymore
return;
}
if(!listener.shouldExecute(user, oldLevel, levelAction)) {
return;
}
if(actionConfigMap.containsKey(levelAction.getLevel().getLevel())) {
@@ -73,9 +86,6 @@ public class LevelActionServiceBean implements LevelActionService {
}
});
Map<String, LevelActionListener> actionStringListenerMap = levelActions
.stream()
.collect(Collectors.toMap(a -> a.getName().toLowerCase(), Function.identity()));
List<Integer> levels = actionConfigMap
.keySet()
@@ -11,7 +11,9 @@ import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult;
import dev.sheldan.abstracto.experience.repository.UserExperienceRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import java.util.List;
@@ -70,6 +72,11 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
return repository.findByUser_ServerReference(server);
}
@Override
public Page<AUserExperience> loadAllUsersPaginated(AServer server, Pageable pageable) {
return repository.findAllByServer(server, pageable);
}
@Override
public List<AUserExperience> findLeaderBoardUsersPaginated(AServer aServer, Integer page, Integer size) {
return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(page, size));
@@ -17,4 +17,6 @@ abstracto.featureModes.levelUpNotification.enabled=false
abstracto.featureModes.levelAction.featureName=experience
abstracto.featureModes.levelAction.mode=levelAction
abstracto.featureModes.levelAction.enabled=false
abstracto.featureModes.levelAction.enabled=false
abstracto.experience.leaderboard.externalUrl=${FRONTEND_BASE:}
@@ -0,0 +1,84 @@
package dev.sheldan.abstracto.experience.listener;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LevelAction;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class AddRoleLevelActionTest {
@InjectMocks
private AddRoleLevelAction action;
@Mock
private AUserExperience exp;
@Mock
private LevelAction levelAction;
@Mock
private AExperienceLevel level;
private final Integer LOW_LEVEL = 1;
private final Integer MID_LEVEL = 2;
private final Integer HIGH_LEVEL = 3;
@Before
public void setup() {
when(levelAction.getLevel()).thenReturn(level);
}
@Test // rejoin too low case
public void noLevelChangeActionNotReached() {
executeTest(LOW_LEVEL, LOW_LEVEL, HIGH_LEVEL, false);
}
@Test // re-join exact case
public void noLevelChangeActionReached() {
executeTest(LOW_LEVEL, LOW_LEVEL, LOW_LEVEL, true);
}
@Test // normal leveling, higher action
public void levelChangeActionNotReached() {
executeTest(LOW_LEVEL, MID_LEVEL, HIGH_LEVEL, false);
}
@Test // normal leveling
public void levelChangeActionReached() {
executeTest(LOW_LEVEL, MID_LEVEL, MID_LEVEL, true);
}
@Test // a case for this is a large experience jump
public void levelChangeActionOverJumped() {
executeTest(LOW_LEVEL, HIGH_LEVEL, MID_LEVEL, true);
}
@Test // a case for this is a re-join
public void noLevelChangeActionOverJumped() {
executeTest(HIGH_LEVEL, HIGH_LEVEL, LOW_LEVEL, true);
}
@Test // we dont want to re-execute previous actions (previous = lower level)
public void levelChangeActionEqualsPrevious() {
executeTest(LOW_LEVEL, MID_LEVEL, LOW_LEVEL, true);
}
@Test // we dont want to re-execute previous actions (previous = way lower level)
public void levelChangeActionBelow() {
executeTest(MID_LEVEL, HIGH_LEVEL, LOW_LEVEL, true);
}
private void executeTest(Integer oldLevel, Integer currentLevel, Integer actionLevel, boolean expected) {
when(exp.getLevelOrDefault()).thenReturn(currentLevel);
when(level.getLevel()).thenReturn(actionLevel);
Assertions.assertThat(action.shouldExecute(exp, oldLevel, levelAction)).isEqualTo(expected);
}
}
@@ -0,0 +1,86 @@
package dev.sheldan.abstracto.experience.listener;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LevelAction;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class RemoveMemberFromChannelLevelActionTest {
@InjectMocks
private SendMessageToChannelLevelAction action;
@Mock
private AUserExperience exp;
@Mock
private LevelAction levelAction;
@Mock
private AExperienceLevel level;
private final Integer LOW_LEVEL = 1;
private final Integer MID_LEVEL = 2;
private final Integer HIGH_LEVEL = 3;
@Before
public void setup() {
when(levelAction.getLevel()).thenReturn(level);
}
@Test // rejoin too low case
public void noLevelChangeActionNotReached() {
executeTest(LOW_LEVEL, LOW_LEVEL, HIGH_LEVEL, false);
}
@Test // re-join exact case
public void noLevelChangeActionReached() {
executeTest(LOW_LEVEL, LOW_LEVEL, LOW_LEVEL, true);
}
@Test // normal leveling, higher action
public void levelChangeActionNotReached() {
executeTest(LOW_LEVEL, MID_LEVEL, HIGH_LEVEL, false);
}
@Test // normal leveling
public void levelChangeActionReached() {
executeTest(LOW_LEVEL, MID_LEVEL, MID_LEVEL, true);
}
@Test // a case for this is a large experience jump
public void levelChangeActionOverJumped() {
executeTest(LOW_LEVEL, HIGH_LEVEL, MID_LEVEL, true);
}
@Test // a case for this is a re-join
public void noLevelChangeActionOverJumped() {
executeTest(HIGH_LEVEL, HIGH_LEVEL, LOW_LEVEL, true);
}
@Test // we dont want to re-execute previous actions (previous = lower level)
public void levelChangeActionEqualsPrevious() {
executeTest(LOW_LEVEL, MID_LEVEL, LOW_LEVEL, false);
}
@Test // we dont want to re-execute previous actions (previous = way lower level)
public void levelChangeActionBelow() {
executeTest(MID_LEVEL, HIGH_LEVEL, LOW_LEVEL, false);
}
private void executeTest(Integer oldLevel, Integer currentLevel, Integer actionLevel, boolean expected) {
when(exp.getLevelOrDefault()).thenReturn(currentLevel);
when(level.getLevel()).thenReturn(actionLevel);
Assertions.assertThat(action.shouldExecute(exp, oldLevel, levelAction)).isEqualTo(expected);
}
}
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -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();
}
}
@@ -4,15 +4,13 @@ import dev.sheldan.abstracto.experience.model.LevelActionPayload;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LevelAction;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.stereotype.Component;
@Component
public interface LevelActionListener {
String getName();
void apply(AUserExperience userExperience, LevelAction levelAction, MemberActionModification container);
boolean shouldExecute(AUserExperience aUserExperience, LevelAction levelAction);
boolean shouldExecute(AUserExperience aUserExperience, Integer oldLevel, LevelAction levelAction);
void prepareAction(LevelAction levelAction);
@@ -24,4 +24,5 @@ public class LeaderBoardModel extends SlimUserInitiatedServerContext {
* The {@link LeaderBoardEntryModel} containing the leaderboard information executing the command.
*/
private LeaderBoardEntryModel userExecuting;
private String leaderboardUrl;
}
@@ -12,6 +12,7 @@ import java.util.concurrent.CompletableFuture;
public interface LevelActionService {
CompletableFuture<Void> applyLevelActionsToUser(AUserExperience user);
CompletableFuture<Void> applyLevelActionsToUser(AUserExperience user, Integer oldLevel);
List<String> getAvailableLevelActions();
Optional<LevelActionListener> getLevelActionListenerForName(String name);
@@ -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.AUserExperience;
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.Optional;
@@ -50,6 +52,7 @@ public interface UserExperienceManagementService {
* @return A list of {@link AUserExperience} objects associated with the given {@link AServer}
*/
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}.
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<artifactId>giveaway-impl</artifactId>
@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<artifactId>giveaway-int</artifactId>
@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<artifactId>giveaway</artifactId>
@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<artifactId>image-generation-impl</artifactId>
@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.imagegeneration.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
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.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.handler.parameter.CombinedParameter;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
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.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.command.config.Parameter.ADDITIONAL_TYPES_KEY;
@Component
public class Bonk extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member";
@@ -65,7 +69,11 @@ public class Bonk extends AbstractConditionableCommand {
if(parameters.isEmpty()) {
member = commandContext.getAuthor();
} 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));
MessageToSend messageToSend = templateService.renderEmbedTemplate(BONK_EMBED_TEMPLATE_KEY, new Object());
@@ -107,10 +115,13 @@ public class Bonk extends AbstractConditionableCommand {
@Override
public CommandConfiguration getConfiguration() {
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
.builder()
.name(MEMBER_PARAMETER_KEY)
.type(Member.class)
.type(CombinedParameter.class)
.additionalInfo(parameterAlternatives)
.templated(true)
.optional(true)
.build();
@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.imagegeneration.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
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.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.handler.parameter.CombinedParameter;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
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.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.command.config.Parameter.ADDITIONAL_TYPES_KEY;
@Component
public class Pat extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member";
@@ -65,7 +69,11 @@ public class Pat extends AbstractConditionableCommand {
if(parameters.isEmpty()) {
member = commandContext.getAuthor();
} 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));
MessageToSend messageToSend = templateService.renderEmbedTemplate(PAT_EMBED_TEMPLATE_KEY, new Object());
@@ -107,10 +115,13 @@ public class Pat extends AbstractConditionableCommand {
@Override
public CommandConfiguration getConfiguration() {
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
.builder()
.name(MEMBER_PARAMETER_KEY)
.type(Member.class)
.type(CombinedParameter.class)
.additionalInfo(parameterAlternatives)
.templated(true)
.optional(true)
.build();
@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<artifactId>image-generation-int</artifactId>
@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<artifactId>image-generation</artifactId>
@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -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.service.management.AllowedInviteLinkManagement;
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.Getter;
import lombok.Setter;
@@ -78,6 +80,9 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
@Autowired
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);
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);
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
.builder()
.author(message.getMember())
.guild(message.getGuild())
.moderationActionComponents(moderationActionComponents)
.message(message)
.channel(message.getChannel())
.invites(groupInvites(codes))
@@ -11,3 +11,6 @@ abstracto.featureModes.filterNotifications.featureName=inviteFilter
abstracto.featureModes.filterNotifications.mode=filterNotifications
abstracto.featureModes.filterNotifications.enabled=true
abstracto.featureModes.filterModerationActions.featureName=inviteFilter
abstracto.featureModes.filterModerationActions.mode=filterModerationActions
abstracto.featureModes.filterModerationActions.enabled=false
@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,12 @@
<artifactId>core-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>1.5.49-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -23,6 +23,6 @@ public class InviteFilterFeatureConfig implements FeatureConfig {
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(InviteFilterMode.FILTER_NOTIFICATIONS, InviteFilterMode.TRACK_USES);
return Arrays.asList(InviteFilterMode.values());
}
}
@@ -5,7 +5,7 @@ import lombok.Getter;
@Getter
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;
@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.invitefilter.model.template.listener;
import dev.sheldan.abstracto.moderation.model.ModerationActionButton;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -17,4 +18,5 @@ public class DeletedInvitesNotificationModel {
private Member author;
private Message message;
private List<DeletedInvite> invites;
private List<ModerationActionButton> moderationActionComponents;
}
@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -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.models.ServerUser;
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.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MemberLeaveLogModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -40,10 +41,10 @@ public class LeaveLogger implements AsyncLeaveListener {
.userId(listenerModel.getUser().getIdLong())
.serverId(listenerModel.getServerId())
.build();
MemberLeaveModel model = MemberLeaveModel
MemberLeaveLogModel model = MemberLeaveLogModel
.builder()
.leavingUser(leavingUser)
.user(listenerModel.getUser())
.user(UserDisplay.fromUser(listenerModel.getUser()))
.build();
log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), listenerModel.getServerId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_LEAVE_TEMPLATE, model, listenerModel.getServerId());
@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -1,13 +1,15 @@
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.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
@Getter
@Setter
@Builder
public class MemberLeaveLogModel {
private Member member;
private ServerUser leavingUser;
private UserDisplay user;
}
@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.5.26</version>
<version>1.5.49-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -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.config.FeatureDefinition;
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.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.ParseUtils;
@@ -70,7 +70,7 @@ public class Ban extends AbstractConditionableCommand {
}
if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) {
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 -> {
if(banResult == NOTIFICATION_FAILED) {
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);
Long userId = Long.parseLong(userIdStr);
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 -> {
if(banResult == NOTIFICATION_FAILED) {
String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong());
@@ -116,7 +116,7 @@ public class Infractions extends AbstractConditionableCommand {
} else if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.STRING)){
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
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);
} else {
@@ -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.CommandCondition;
import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.EffectConfig;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.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.exception.EntityGuildMismatchException;
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.ModerationSlashCommandNames;
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.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
@@ -47,30 +48,6 @@ public class Kick extends AbstractConditionableCommand {
@Autowired
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
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
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());
}
KickLogModel kickLogModel = KickLogModel
.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)
return kickService.kickMember(member, event.getMember(), reason)
.thenCompose(unused -> interactionService.replyEmbed(KICK_RESPONSE, event))
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@@ -143,6 +110,7 @@ public class Kick extends AbstractConditionableCommand {
.slashCommandConfig(slashCommandConfig)
.supportsEmbedException(true)
.async(true)
.slashCommandOnly(true)
.effects(effectConfig)
.causesReaction(true)
.parameters(parameters)
@@ -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.exception.EntityGuildMismatchException;
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.utils.FutureUtils;
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.ModerationSlashCommandNames;
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 net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
@@ -29,12 +34,14 @@ import java.util.Arrays;
import java.util.List;
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;
@Component
public class Mute extends AbstractConditionableCommand {
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 MUTE_COMMAND = "mute";
private static final String USER_PARAMETER = "user";
@@ -53,6 +60,9 @@ public class Mute extends AbstractConditionableCommand {
@Autowired
private InteractionService interactionService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
@@ -64,15 +74,23 @@ public class Mute extends AbstractConditionableCommand {
Duration duration = (Duration) parameters.get(1);
String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason;
MuteContext muteLogModel = MuteContext
.builder()
.muteTargetDate(Instant.now().plus(duration))
.mutedUser(member)
.channelId(commandContext.getChannel().getIdLong())
.reason(reason)
.mutingUser(commandContext.getAuthor())
.build();
return muteService.muteMemberWithLog(muteLogModel)
Instant oldTimeoutDate = null;
if(member.getTimeOutEnd() != null && member.isTimedOut()) {
oldTimeoutDate = member.getTimeOutEnd().toInstant();
}
ServerUser userToMute = ServerUser.fromMember(member);
ServerUser mutingUser = ServerUser.fromMember(commandContext.getAuthor());
Long serverId = commandContext.getGuild().getIdLong();
ServerChannelMessage serverChannelMessage = ServerChannelMessage.fromMessage(commandContext.getMessage());
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());
}
@@ -88,16 +106,23 @@ public class Mute extends AbstractConditionableCommand {
} else {
reason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
}
MuteContext muteLogModel = MuteContext
Long serverId = event.getGuild().getIdLong();
ServerChannelMessage commandMessage = ServerChannelMessage
.builder()
.muteTargetDate(Instant.now().plus(duration))
.mutedUser(targetMember)
.reason(reason)
.serverId(serverId)
.channelId(event.getChannel().getIdLong())
.mutingUser(event.getMember())
.messageId(SnowflakeUtils.createSnowFlake())
.build();
return muteService.muteMemberWithLog(muteLogModel)
.thenCompose(unused -> interactionService.replyEmbed(MUTE_RESPONSE, event))
ServerUser userToMute = ServerUser.fromMember(targetMember);
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());
}
@@ -50,7 +50,7 @@ public class UnBan extends AbstractConditionableCommand {
String userIdStr = (String) parameters.get(0);
Long userId = Long.parseLong(userIdStr);
return userService.retrieveUserForId(userId)
.thenCompose(user -> banService.unBanUserWithNotification(user, commandContext.getAuthor()))
.thenCompose(user -> banService.unbanUser(commandContext.getGuild(), user, commandContext.getAuthor()))
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@@ -59,7 +59,7 @@ public class UnBan extends AbstractConditionableCommand {
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, String.class);
Long userId = Long.parseLong(userIdStr);
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))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@@ -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.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
@@ -42,9 +41,6 @@ public class UnMute extends AbstractConditionableCommand {
@Autowired
private InteractionService interactionService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
@@ -52,8 +48,9 @@ public class UnMute extends AbstractConditionableCommand {
if(!member.getGuild().equals(commandContext.getGuild())) {
throw new EntityGuildMismatchException();
}
AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(member);
return muteService.unMuteUser(userToUnMute, commandContext.getAuthor()).thenApply(aVoid ->
ServerUser userToUnmute = ServerUser.fromMember(member);
ServerUser unMutingMember = ServerUser.fromMember(commandContext.getAuthor());
return muteService.unMuteUser(userToUnmute, unMutingMember, commandContext.getGuild()).thenApply(aVoid ->
CommandResult.fromSuccess()
);
}
@@ -64,8 +61,9 @@ public class UnMute extends AbstractConditionableCommand {
if(!targetMember.getGuild().equals(event.getGuild())) {
throw new EntityGuildMismatchException();
}
AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(targetMember);
return muteService.unMuteUser(userToUnMute, event.getMember())
ServerUser userToUnmute = ServerUser.fromMember(targetMember);
ServerUser unMutingMember = ServerUser.fromMember(event.getMember());
return muteService.unMuteUser(userToUnmute, unMutingMember, event.getGuild())
.thenCompose(unused -> interactionService.replyEmbed(UN_MUTE_RESPONSE, event))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@@ -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.exception.EntityGuildMismatchException;
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.ModerationSlashCommandNames;
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.core.templating.service.TemplateService;
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 reason = parameters.size() == 2 ? (String) parameters.get(1) : defaultReason;
WarnContext warnLogModel = WarnContext
.builder()
.reason(reason)
.warnedMember(member)
.channel(commandContext.getChannel())
.member(commandContext.getAuthor())
.guild(commandContext.getGuild())
.message(commandContext.getMessage())
.build();
return warnService.warnUserWithLog(warnLogModel)
ServerChannelMessage commandMessage = ServerChannelMessage.fromMessage(commandContext.getMessage());
return warnService.warnUserWithLog(commandContext.getGuild(), ServerUser.fromMember(member), ServerUser.fromMember(commandContext.getAuthor()), reason, commandMessage)
.thenApply(warning -> CommandResult.fromSuccess());
}
@@ -84,15 +78,13 @@ public class Warn extends AbstractConditionableCommand {
} else {
reason = templateService.renderSimpleTemplate(WARN_DEFAULT_REASON_TEMPLATE, event.getGuild().getIdLong());
}
WarnContext warnLogModel = WarnContext
ServerChannelMessage commandMessage = ServerChannelMessage
.builder()
.reason(reason)
.warnedMember(member)
.member(event.getMember())
.channel(event.getGuildChannel())
.guild(event.getGuild())
.serverId(event.getGuild().getIdLong())
.channelId(event.getChannel().getIdLong())
.messageId(SnowflakeUtils.createSnowFlake())
.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))
.thenApply(warning -> CommandResult.fromSuccess());
}
@@ -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;
}
}
@@ -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;
}
}
@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.sync.jda.RoleAddedListener;
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.listener.RoleAddedModel;
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.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
@@ -77,7 +79,8 @@ public class HoneyPotRoleAddedListener implements RoleAddedListener {
.roleDisplay(RoleDisplay.fromRole(model.getRole()))
.build();
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());
}).exceptionally(throwable -> {
log.error("Failed to ban user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId(), throwable);
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -3,82 +3,37 @@ 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.AsyncUserBannedListener;
import dev.sheldan.abstracto.core.models.listener.UserBannedModel;
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.core.models.listener.UserBannedListenerModel;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedListenerModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserBannedLogModel;
import dev.sheldan.abstracto.moderation.service.BanService;
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.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class UserBannedListener implements AsyncUserBannedListener {
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private UserBannedListener self;
private static final String USER_BANNED_NOTIFICATION_TEMPLATE = "userBanned_listener_notification";
private BanService banService;
@Override
public DefaultListenerResult execute(UserBannedModel model) {
model.getGuild()
.retrieveAuditLogs()
.type(ActionType.BAN)
.limit(5)
.queue(auditLogEntries -> {
if(auditLogEntries.isEmpty()) {
log.info("Did not find recent bans in guild {}.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
return;
}
Optional<AuditLogEntry> banEntryOptional = auditLogEntries
.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
public DefaultListenerResult execute(UserBannedListenerModel eventModel) {
log.info("Notifying about ban of user {} in guild {}.", eventModel.getBannedServerUser().getUserId(), eventModel.getServerId());
if(eventModel.getBanningServerUser().getUserId() == eventModel.getGuild().getJDA().getSelfUser().getIdLong()) {
log.info("Skipping logging banned event about user {} in guild {}, because it was done by us.", eventModel.getBannedServerUser().getUserId(), eventModel.getGuild().getIdLong());
return DefaultListenerResult.IGNORED;
}
UserBannedLogModel model = UserBannedLogModel
.builder()
.bannedUser(bannedUser)
.banningUser(banningUser)
.reason(reason)
.bannedUser(eventModel.getBannedUser() != null ? UserDisplay.fromUser(eventModel.getBannedUser()) : UserDisplay.fromId(eventModel.getBannedServerUser().getUserId()))
.banningUser(eventModel.getBanningUser() != null ? UserDisplay.fromUser(eventModel.getBanningUser()) : UserDisplay.fromServerUser(eventModel.getBanningServerUser()))
.reason(eventModel.getReason())
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
banService.sendBanLogMessage(model, eventModel.getServerId());
return DefaultListenerResult.PROCESSED;
}
@Override
@@ -3,88 +3,37 @@ 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.AsyncUserUnBannedListener;
import dev.sheldan.abstracto.core.models.listener.UserUnBannedModel;
import dev.sheldan.abstracto.core.service.FeatureModeService;
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.core.models.listener.UserUnBannedListenerModel;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedListenerModel;
import dev.sheldan.abstracto.moderation.model.template.listener.UserUnBannedLogModel;
import dev.sheldan.abstracto.moderation.service.BanService;
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.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class UserUnBannedListener implements AsyncUserUnBannedListener {
@Autowired
private FeatureModeService featureModeService;
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private UserUnBannedListener self;
private static final String USER_UN_BANNED_NOTIFICATION_TEMPLATE = "userUnBanned_listener_notification";
private BanService banService;
@Override
public DefaultListenerResult execute(UserUnBannedModel model) {
log.info("Notifying about unban of user {} in guild {}.", model.getUnbannedUser().getUserId(), model.getServerId());
model.getGuild()
.retrieveAuditLogs()
.type(ActionType.UNBAN)
.limit(5)
.queue(auditLogEntries -> {
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
public DefaultListenerResult execute(UserUnBannedListenerModel eventModel) {
log.info("Notifying about unban of user {} in guild {}.", eventModel.getUnBannedServerUser().getUserId(), eventModel.getServerId());
if(eventModel.getUnBanningUser().getIdLong() == eventModel.getGuild().getSelfMember().getIdLong()) {
log.info("Skipping logging banned event about user {} in guild {}, because it was done by us.", eventModel.getUnBannedServerUser().getUserId(), eventModel.getGuild().getIdLong());
return DefaultListenerResult.IGNORED;
}
UserUnBannedLogModel model = UserUnBannedLogModel
.builder()
.unBannedUser(unbannedUser)
.unBanningUser(unbanningUser)
.unBannedUser(eventModel.getUnBannedUser() != null ? UserDisplay.fromUser(eventModel.getUnBannedUser()) : UserDisplay.fromId(eventModel.getUnBannedServerUser().getUserId()))
.unBanningUser(eventModel.getUnBanningUser() != null ? UserDisplay.fromUser(eventModel.getUnBanningUser()) : UserDisplay.fromServerUser(eventModel.getUnBanningServerUser()))
.reason(eventModel.getReason())
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_UN_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
banService.sendUnBanLogMessage(model, eventModel.getServerId());
return DefaultListenerResult.PROCESSED;
}
@Override
@@ -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;
}
}

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