Compare commits

...

120 Commits

Author SHA1 Message Date
Sheldan
ed42940e29 [AB-xxx] adding ability to track emotes used in reactions
adding ability to have confirmations for slash commands
2025-01-27 01:31:56 +01:00
release-bot
2c3b16879e Commit from GitHub Actions (Publishes a new version of abstracto) 2025-01-15 21:25:59 +00:00
release-bot
bd7fc6aa65 [maven-release-plugin] prepare for next development iteration 2025-01-15 21:16:39 +00:00
release-bot
6dfcebb25b [maven-release-plugin] prepare release v1.5.57 2025-01-15 21:16:38 +00:00
Sheldan
0eaccb4b0f [AB-xxx] adding currency conversion command 2025-01-15 22:13:47 +01:00
Sheldan
4ae6a154c7 [AB-xxx] fixing not being able to delete a giveaway key for which there is no giveaway 2024-12-29 21:10:27 +01:00
release-bot
3ad8369ab3 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-12-25 22:41:02 +00:00
release-bot
6be1b7df04 [maven-release-plugin] prepare for next development iteration 2024-12-25 22:30:39 +00:00
release-bot
df8eb399f7 [maven-release-plugin] prepare release v1.5.56 2024-12-25 22:30:37 +00:00
Sheldan
3b7157714d [AB-xxx] fixing issue of server id not being provided when manually creating a giveaway 2024-12-25 23:27:27 +01:00
release-bot
ef7e5b4a46 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-12-25 19:09:24 +00:00
release-bot
c28286bf3f [maven-release-plugin] prepare for next development iteration 2024-12-25 19:00:30 +00:00
release-bot
15e998e05d [maven-release-plugin] prepare release v1.5.55 2024-12-25 19:00:28 +00:00
Sheldan
cdbdb395ac [AB-xxx] adding ability to automate giveaways using modmail and adding storage for give away keys
ignoring message updated events for messages not within servers
2024-12-25 19:54:24 +01:00
release-bot
8819c12b81 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-11-26 23:25:17 +00:00
release-bot
a4e0971c27 [maven-release-plugin] prepare for next development iteration 2024-11-26 23:15:06 +00:00
release-bot
726fc27331 [maven-release-plugin] prepare release v1.5.54 2024-11-26 23:15:03 +00:00
Sheldan
a9d6a1221e [AB-xxx] fixing passing userId parameter to leaderboard logic 2024-11-27 00:12:39 +01:00
release-bot
84cc32e3b3 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-11-26 22:50:45 +00:00
release-bot
983c747bc3 [maven-release-plugin] prepare for next development iteration 2024-11-26 22:38:20 +00:00
release-bot
a901d8a3ba [maven-release-plugin] prepare release v1.5.53 2024-11-26 22:38:18 +00:00
Sheldan
fed2d24a55 [AB-xxx] fixing ui code, and increasing window size in ui
updating dependency
2024-11-26 23:34:52 +01:00
release-bot
aa359f60f3 [maven-release-plugin] prepare for next development iteration 2024-11-26 22:21:02 +00:00
release-bot
353fda7770 [maven-release-plugin] prepare release v1.5.52 2024-11-26 22:21:01 +00:00
Sheldan
b88ae377b1 [AB-xxx] adding feature to focus on a particular user in both leaderboard command and leaderboard UI
adding button to view the specified user on loaderboard at the rank command
2024-11-26 23:17:24 +01:00
release-bot
3d915b96e7 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-11-22 21:47:46 +00:00
release-bot
9a1e0f330a [maven-release-plugin] prepare for next development iteration 2024-11-22 21:35:44 +00:00
release-bot
afeef80878 [maven-release-plugin] prepare release v1.5.51 2024-11-22 21:35:43 +00:00
Sheldan
453378f0b6 [AB-xxx] enabling localization by adding the localization as a system config key
fixing methods which did not provide the server id to template rendering
refactoring channel service method to remove a duplicate
fixing template loading method to not throw an exception when template was not found
2024-11-22 21:54:13 +01:00
Sheldan
0e95ddf198 [AB-xxx] fixing edge case in which a server is duplicated for modmail prompt, because an available server has modmail enabled and uses another available server as appeal server 2024-11-06 22:06:47 +01:00
Sheldan
90816649e2 [AB-xxx] adding information whether a modmail thread comes from an appeal to storage and to modmail notification 2024-11-06 21:46:19 +01:00
release-bot
5b90d429c2 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-11-05 22:25:28 +00:00
release-bot
7b1774b73e [maven-release-plugin] prepare for next development iteration 2024-11-05 22:15:13 +00:00
release-bot
618155a464 [maven-release-plugin] prepare release v1.5.50 2024-11-05 22:15:12 +00:00
Sheldan
feabe6426e [AB-xxx] fixing version after failed release 2024-11-05 23:12:45 +01:00
Sheldan
2dc21ce996 [AB-xxx] fixing typescript linter warning 2024-11-05 23:11:42 +01:00
release-bot
5643a41fd8 [maven-release-plugin] prepare for next development iteration 2024-11-05 22:00:25 +00:00
release-bot
08d0561998 [maven-release-plugin] prepare release v1.5.49 2024-11-05 22:00:24 +00:00
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
445 changed files with 7373 additions and 2450 deletions

2
.env
View File

@@ -1,2 +1,2 @@
REGISTRY_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.5.32
VERSION=1.5.57

View File

@@ -16,7 +16,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
@@ -37,15 +37,4 @@ jobs:
- name: Build ui application
working-directory: ./ui/experience-tracking
run: npm run build
- 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

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:
@@ -64,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
.gitignore vendored
View File

@@ -14,9 +14,7 @@ target/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ public class ShowAssignableRolePlaceConfig extends AbstractConditionableCommand
List<Object> parameters = commandContext.getParameters().getParameters();
String name = (String) parameters.get(0);
AssignableRolePlaceConfig config = service.getAssignableRolePlaceConfig(commandContext.getGuild(), name);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY, config, commandContext.getChannel()))
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY, config, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}

View File

@@ -39,7 +39,7 @@ public class ShowAssignableRolePlaces extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
AssignablePlaceOverview model = service.getAssignableRolePlaceOverview(commandContext.getGuild());
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY, model, commandContext.getChannel()))
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -53,7 +53,7 @@ public class Choose extends AbstractConditionableCommand {
.builder()
.chosenValue(choice)
.build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(CHOOSE_RESPONSE_TEMPLATE_KEY, responseModel, commandContext.getChannel()))
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(CHOOSE_RESPONSE_TEMPLATE_KEY, responseModel, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromIgnored());
}

View File

@@ -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;
@@ -65,7 +66,7 @@ public class Mock extends AbstractConditionableCommand {
.originalText(messageText)
.mockingText(mockingText)
.build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(MOCK_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(MOCK_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@@ -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()

View File

@@ -61,7 +61,7 @@ public class PressFCommand extends AbstractConditionableCommand {
Long defaultDurationSeconds = configService.getLongValueOrConfigDefault(PRESS_F_DEFAULT_DURATION_SECONDS, commandContext.getGuild().getIdLong());
Duration duration = Duration.ofSeconds(defaultDurationSeconds);
PressFPromptModel pressFModel = entertainmentService.getPressFModel(text);
List<CompletableFuture<Message>> messages = channelService.sendEmbedTemplateInMessageChannelList(RESPONSE_TEMPLATE, pressFModel, commandContext.getChannel());
List<CompletableFuture<Message>> messages = channelService.sendEmbedTemplateInMessageChannel(RESPONSE_TEMPLATE, pressFModel, commandContext.getChannel());
return FutureUtils.toSingleFutureGeneric(messages)
.thenAccept(unused -> entertainmentService.persistPressF(text, duration, commandContext.getAuthor(),
pressFModel.getPressFComponentId(), commandContext.getChannel(), messages.get(0).join().getIdLong()))

View File

@@ -69,7 +69,7 @@ public class Roll extends AbstractConditionableCommand {
.builder()
.rolled(rolled)
.build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ROLL_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(ROLL_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromIgnored());
}

View File

@@ -45,7 +45,7 @@ public class Roulette extends AbstractConditionableCommand {
.builder()
.result(rouletteResult)
.build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ROULETTE_RESPONSE_TEMPLATE_KEY, responseModel, commandContext.getChannel()))
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(ROULETTE_RESPONSE_TEMPLATE_KEY, responseModel, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromIgnored());
}

View File

@@ -65,7 +65,7 @@ public class TransferCredits extends AbstractConditionableCommand {
.targetMember(MemberDisplay.fromMember(targetMember))
.credits(amount)
.build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(TRANSFER_CREDITS_RESPONSE, responseModel, commandContext.getChannel()))
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(TRANSFER_CREDITS_RESPONSE, responseModel, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}

View File

@@ -103,7 +103,7 @@ public class Mines extends AbstractConditionableCommand {
board.setUserId(event.getMember().getIdLong());
board.setServerId(serverId);
board.setCredits(credit);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MINE_BOARD_TEMPLATE_KEY, board);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MINE_BOARD_TEMPLATE_KEY, board, serverId);
return interactionService.replyMessageToSend(messageToSend, event)
.thenCompose(interactionHook -> interactionHook.retrieveOriginal().submit())
.thenApply(message -> {
@@ -151,7 +151,7 @@ public class Mines extends AbstractConditionableCommand {
board.setUserId(commandContext.getAuthor().getIdLong());
board.setServerId(serverId);
board.setCredits(credit);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MINE_BOARD_TEMPLATE_KEY, board);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MINE_BOARD_TEMPLATE_KEY, board, serverId);
List<CompletableFuture<Message>> futures = channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel());
return FutureUtils.toSingleFutureGeneric(futures)
.thenAccept(unused -> gameService.persistMineBoardMessage(board, futures.get(0).join()))

View File

@@ -54,8 +54,8 @@ public class MinesButtonClickedListener implements ButtonClickedListener {
}
gameService.uncoverBoard(mineBoard);
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(Mines.MINE_BOARD_TEMPLATE_KEY, mineBoard);
interactionService.editOriginal(messageToSend, model.getEvent().getHook()).thenAccept(message -> {
MessageToSend messageToSend = templateService.renderEmbedTemplate(Mines.MINE_BOARD_TEMPLATE_KEY, mineBoard, model.getServerId());
interactionService.replaceOriginal(messageToSend, model.getEvent().getHook()).thenAccept(message -> {
gameService.updateMineBoard(mineBoard);
log.info("Updated original mineboard for board {}.", mineBoard.getBoardId());
});

View File

@@ -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
@@ -191,8 +193,8 @@ public class EntertainmentServiceBean implements EntertainmentService {
.text(pressF.getText())
.messageId(pressF.getMessageId())
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(PRESS_F_RESULT_TEMPLATE_KEY, model);
Long serverId = pressF.getServer().getId();
MessageToSend messageToSend = templateService.renderEmbedTemplate(PRESS_F_RESULT_TEMPLATE_KEY, model, serverId);
Long channelId = pressF.getPressFChannel().getId();
Long messageId = pressF.getMessageId();
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageEmbedToSendToAChannel(messageToSend, pressF.getPressFChannel()))

View File

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

View File

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

View File

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

View File

@@ -56,7 +56,7 @@ public class ExperienceConfigController {
} else {
roleDisplay = RoleDisplay
.builder()
.id(levelRole.getRoleId())
.id(String.valueOf(levelRole.getRoleId()))
.build();
}
return ExperienceRoleDisplay

View File

@@ -1,14 +1,26 @@
package dev.sheldan.abstracto.experience.api;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
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.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.experience.model.api.UserExperienceDisplay;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
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 dev.sheldan.abstracto.experience.service.ExperienceLevelService;
import dev.sheldan.abstracto.experience.service.management.ExperienceLevelManagementService;
import dev.sheldan.abstracto.experience.service.management.ExperienceRoleManagementService;
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
@@ -22,6 +34,7 @@ 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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -37,9 +50,21 @@ public class LeaderboardController {
@Autowired
private GuildService guildService;
@Autowired
private ExperienceLevelService experienceLevelService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private ExperienceRoleManagementService experienceRoleManagementService;
@Autowired
private ExperienceLevelManagementService experienceLevelManagementService;
@GetMapping(value = "/leaderboards/{serverId}", produces = "application/json")
public Page<UserExperienceDisplay> getLeaderboard(@PathVariable("serverId") Long serverId,
@PageableDefault(value = 25, page = 0)
@PageableDefault(value = 50, page = 0)
@SortDefault(sort = "experience", direction = Sort.Direction.DESC)
Pageable pageable) {
AServer server = serverManagementService.loadServer(serverId);
@@ -49,12 +74,31 @@ public class LeaderboardController {
.map(userExperience -> convertFromUser(guild, userExperience, pageable, allElements));
}
@GetMapping(value = "/leaderboards/{serverId}/{userId}", produces = "application/json")
public List<UserExperienceDisplay> getLeaderboardForUser(@PathVariable("serverId") Long serverId, @PathVariable("userId") Long userId,
@RequestParam("windowSize") Integer windowSize) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(ServerUser.fromId(serverId, userId));
Map<Long, AExperienceRole> experienceRolesForServer = experienceRoleManagementService.getExperienceRolesForServer(aUserInAServer.getServerReference())
.stream()
.collect(Collectors.toMap(AExperienceRole::getId, Function.identity()));
Map<Integer, AExperienceLevel> levels =
experienceLevelManagementService.getLevelConfig().stream().collect(Collectors.toMap(AExperienceLevel::getLevel, Function.identity()));
Guild guild = guildService.getGuildById(serverId);
List<LeaderBoardEntryResult> allElements = userExperienceManagementService.getWindowedLeaderboardEntriesForUser(aUserInAServer, windowSize);
return allElements.stream()
.map(leaderboardEntry -> convertFromLeaderboardEntry(guild, leaderboardEntry, experienceRolesForServer, levels))
.toList();
}
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) {
@@ -66,16 +110,66 @@ public class LeaderboardController {
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(userId)
.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();
}
private UserExperienceDisplay convertFromLeaderboardEntry(Guild guild, LeaderBoardEntryResult leaderBoardEntryResult,
Map<Long, AExperienceRole> experienceRolesForServer, Map<Integer, AExperienceLevel> levels) {
Long userId = leaderBoardEntryResult.getUserId();
Member member = guild.getMember(UserSnowflake.fromId(userId));
UserDisplay userDisplay = null;
RoleDisplay roleDisplay = null;
Long experienceNeededToNextLevel = experienceLevelService.calculateExperienceToNextLevel(leaderBoardEntryResult.getLevel(),
leaderBoardEntryResult.getExperience());
AExperienceLevel currentExperienceLevel = levels.get(leaderBoardEntryResult.getLevel());
Long nextLevelExperience = experienceLevelService.calculateNextLevel(leaderBoardEntryResult.getLevel()).getExperienceNeeded();
if(experienceRolesForServer.containsKey(leaderBoardEntryResult.getRoleId())) {
AExperienceRole experienceRole = experienceRolesForServer.get(leaderBoardEntryResult.getRoleId());
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 = currentExperienceLevel.getExperienceNeeded();
Long experienceWithinLevel = leaderBoardEntryResult.getExperience() - currentExpNeeded;
Long experienceNeededForCurrentLevel = nextLevelExperience - currentExpNeeded;
return UserExperienceDisplay
.builder()
.id(String.valueOf(userId))
.messages(leaderBoardEntryResult.getMessageCount())
.level(leaderBoardEntryResult.getLevel())
.rank(leaderBoardEntryResult.getRank())
.experience(leaderBoardEntryResult.getExperience())
.experienceToNextLevel(experienceNeededToNextLevel)
.currentLevelExperienceNeeded(experienceNeededForCurrentLevel)
.experienceOnCurrentLevel(experienceWithinLevel)
.percentage(((float) experienceWithinLevel / experienceNeededForCurrentLevel) * 100)
.nextLevelExperienceNeeded(nextLevelExperience)
.role(roleDisplay)
.member(userDisplay)
.build();
}
}

View File

@@ -18,6 +18,7 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.config.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());

View File

@@ -7,12 +7,14 @@ import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.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);
}
}

View File

@@ -1,19 +1,24 @@
package dev.sheldan.abstracto.experience.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.*;
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.config.ParameterValidator;
import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
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.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.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames;
@@ -23,18 +28,17 @@ import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel;
import dev.sheldan.abstracto.experience.model.template.LeaderBoardModel;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
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;
/**
* Shows the experience gain information of the top 10 users in the server, or if a page number is provided as a parameter, only the members which are on this page.
@@ -46,6 +50,7 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
public static final String LEADER_BOARD_POST_EMBED_TEMPLATE = "leaderboard_post";
private static final String LEDERBOARD_COMMAND_NAME = "leaderboard";
private static final String PAGE_PARAMETER = "page";
private static final String FOCUS_PARAMETER = "focus";
@Autowired
private AUserExperienceService userExperienceService;
@@ -70,32 +75,48 @@ 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();
// parameter is optional, in case its not present, we default to the 0th page
Integer page = !parameters.isEmpty() ? (Integer) parameters.get(0) : 1;
return getMessageToSend(commandContext.getAuthor(), page)
return getMessageToSend(commandContext.getAuthor(), page, false)
.thenCompose(messageToSend -> FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel())))
.thenApply(aVoid -> CommandResult.fromIgnored());
}
private CompletableFuture<MessageToSend> getMessageToSend(Member actorUser, Integer page) {
private CompletableFuture<MessageToSend> getMessageToSend(Member actorUser, Integer page, boolean focusMe) {
AServer server = serverManagementService.loadServer(actorUser.getGuild());
LeaderBoard leaderBoard = userExperienceService.findLeaderBoardData(server, page);
LeaderBoard leaderBoard;
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(actorUser);
if (focusMe) {
leaderBoard = userExperienceService.findLeaderBoardDataForUserFocus(aUserInAServer);
} else {
leaderBoard = userExperienceService.findLeaderBoardData(server, page);
}
List<CompletableFuture> futures = new ArrayList<>();
CompletableFuture<List<LeaderBoardEntryModel>> completableFutures = converter.fromLeaderBoard(leaderBoard);
CompletableFuture<List<LeaderBoardEntryModel>> completableFutures = converter.fromLeaderBoard(leaderBoard, actorUser.getGuild().getIdLong());
futures.add(completableFutures);
log.info("Rendering leaderboard for page {} in server {} for user {}.", page, actorUser.getId(), actorUser.getGuild().getId());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(actorUser);
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank), actorUser.getGuild().getIdLong());
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)
.showPlacement(!focusMe)
.userExecuting(userRankFuture.join().get(0))
.build();
return CompletableFuture.completedFuture(templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, actorUser.getGuild().getIdLong()));
@@ -106,12 +127,18 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Integer page;
boolean focusMe;
if (slashCommandParameterService.hasCommandOption(FOCUS_PARAMETER, event)) {
focusMe = slashCommandParameterService.getCommandOption(FOCUS_PARAMETER, event, Boolean.class);
} else {
focusMe = false;
}
if(slashCommandParameterService.hasCommandOption(PAGE_PARAMETER, event)) {
page = slashCommandParameterService.getCommandOption(PAGE_PARAMETER, event, Integer.class);
} else {
page = 1;
}
return getMessageToSend(event.getMember(), page)
return getMessageToSend(event.getMember(), page, focusMe)
.thenCompose(messageToSend -> interactionService.replyMessageToSend(messageToSend, event))
.thenApply(aVoid -> CommandResult.fromIgnored());
}
@@ -127,7 +154,17 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
.templated(true)
.type(Integer.class)
.build();
List<Parameter> parameters = Arrays.asList(pageParameter);
Parameter focusMe = Parameter
.builder()
.name(FOCUS_PARAMETER)
.validators(leaderBoardPageValidators)
.optional(true)
.slashCommandOnly(true)
.templated(true)
.type(Boolean.class)
.build();
List<Parameter> parameters = Arrays.asList(pageParameter, focusMe);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)

View File

@@ -30,7 +30,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 org.springframework.transaction.annotation.Transactional;
@@ -78,6 +80,9 @@ public class Rank 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();
@@ -87,7 +92,7 @@ public class Rank extends AbstractConditionableCommand {
}
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(targetMember);
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank), commandContext.getGuild().getIdLong());
RankModel rankModel = RankModel
.builder()
.member(targetMember)
@@ -107,14 +112,21 @@ 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);
String leaderboardUrl;
if(!StringUtils.isBlank(leaderboardExternalURL)) {
leaderboardUrl = String.format("%s/experience/leaderboards/%s/%s", leaderboardExternalURL, toRender.getGuild().getIdLong(), toRender.getId());
} else {
leaderboardUrl = null;
}
rankModel.setLeaderboardUrl(leaderboardUrl);
return templateService.renderEmbedTemplate(RANK_POST_EMBED_TEMPLATE, rankModel, toRender.getGuild().getIdLong());
}
@@ -128,7 +140,7 @@ public class Rank extends AbstractConditionableCommand {
}
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(targetMember);
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank), event.getGuild().getIdLong());
RankModel rankModel = RankModel
.builder()
.member(targetMember)

View File

@@ -3,7 +3,6 @@ package dev.sheldan.abstracto.experience.converter;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.experience.model.LeaderBoard;
import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel;
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
import lombok.extern.slf4j.Slf4j;
@@ -42,26 +41,23 @@ public class LeaderBoardModelConverter {
* @return The list of {@link LeaderBoardEntryModel leaderboarEntryModels} which contain the fully fledged information provided to the
* leader board template
*/
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoard(LeaderBoard leaderBoard) {
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoard(LeaderBoard leaderBoard, Long serverId) {
log.debug("Converting {} entries to a list of leaderboard entries.", leaderBoard.getEntries().size());
return fromLeaderBoardEntry(leaderBoard.getEntries());
return fromLeaderBoardEntry(leaderBoard.getEntries(), serverId);
}
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoardEntry(List<LeaderBoardEntry> leaderBoardEntries) {
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoardEntry(List<LeaderBoardEntry> leaderBoardEntries, Long serverId) {
List<Long> userIds = new ArrayList<>();
Long serverId = leaderBoardEntries.get(0).getExperience().getServer().getId();
Map<Long, LeaderBoardEntryModel> models = leaderBoardEntries
.stream()
.map(leaderBoardEntry -> {
AUserExperience experience = leaderBoardEntry.getExperience();
Long userId = experience.getUser().getUserReference().getId();
userIds.add(userId);
userIds.add(leaderBoardEntry.getUserId());
return LeaderBoardEntryModel
.builder()
.userId(userId)
.experience(experience.getExperience())
.messageCount(experience.getMessageCount())
.level(experience.getLevelOrDefault())
.userId(leaderBoardEntry.getUserId())
.experience(leaderBoardEntry.getExperience())
.messageCount(leaderBoardEntry.getMessageCount())
.level(leaderBoardEntry.getLevel())
.rank(leaderBoardEntry.getRank())
.build();
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,10 +10,15 @@ import lombok.Getter;
@Builder
public class UserExperienceDisplay {
private UserDisplay member;
private Long id;
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;
}

View File

@@ -55,6 +55,33 @@ public interface UserExperienceRepository extends JpaRepository<AUserExperience
"WHERE rank.id = :userInServerId", nativeQuery = true)
LeaderBoardEntryResult getRankOfUserInServer(@Param("userInServerId") Long id, @Param("serverId") Long serverId);
@Query(value = "WITH user_experience_ranked AS" +
" ( " +
" SELECT us.id, uis.user_id, us.experience, us.role_id, us.level_id, us.message_count, ROW_NUMBER() OVER ( ORDER BY experience DESC ) as rank" +
" FROM user_experience us INNER JOIN user_in_server uis ON us.id = uis.user_in_server_id " +
" INNER JOIN server s ON s.id = uis.server_id WHERE s.id = :serverId" +
" )," +
" user_experience_target as (" +
" SELECT ranking.id as id, ranking.user_id, ranking.experience as experience, ranking.message_count, ranking.level_id as level, ranking.rank " +
" FROM user_experience_ranked ranking" +
" WHERE ranking.id = :userInServerId" +
" )" +
" (SELECT r.id, r.user_id as userId, r.experience, r.level_id as level, r.role_id as roleId, r.message_count as messageCount, r.rank as rank" +
" FROM user_experience_ranked r " +
" CROSS JOIN user_experience_target t" +
" WHERE r.rank <= t.rank " +
" ORDER BY r.rank DESC " +
" LIMIT :before) " +
"UNION ALL " +
" (SELECT r.id, r.user_id as userId, r.experience, r.level_id as level, r.role_id as roleId, r.message_count as messageCount, r.rank as rank" +
" FROM user_experience_ranked r" +
" CROSS JOIN user_experience_target t" +
" WHERE r.rank > t.rank " +
" ORDER BY r.rank " +
" LIMIT :after) " +
"ORDER BY rank", nativeQuery = true)
List<LeaderBoardEntryResult> getRankOfUserWithWindow(@Param("userInServerId") Long id, @Param("serverId") Long serverId, @Param("before") Long beforeInclusive, @Param("after") Long after);
@Modifying(clearAutomatically = true)
@Query("update AUserExperience u set u.currentExperienceRole = null where u.currentExperienceRole.id = :roleId")
void removeExperienceRoleFromUsers(@Param("roleId") Long experienceRoleId);

View File

@@ -337,8 +337,8 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
.builder()
.build();
boolean userChangesLevel = !Objects.equals(newLevel.getLevel(), aUserExperience.getCurrentLevel().getLevel());
Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0;
if(userChangesLevel) {
Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0;
log.info("User {} in server {} changed level. New {}, Old {}.", member.getIdLong(),
member.getGuild().getIdLong(), newLevel.getLevel(),
oldLevel);
@@ -361,7 +361,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
.newRole(oldRoleId != null ? RoleDisplay.fromRole(oldRoleId) : null)
.newRole(newRoleId != null ? RoleDisplay.fromRole(newRoleId) : null)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("experience_level_up_notification", model);
MessageToSend messageToSend = templateService.renderEmbedTemplate("experience_level_up_notification", model, serverId);
FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, message.getChannel())).thenAccept(unused -> {
log.info("Sent level up notification to user {} in server {} in channel {}.", member.getIdLong(), serverId, message.getChannel().getIdLong());
}).exceptionally(throwable -> {
@@ -373,7 +373,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
}
aUserExperience.setMessageCount(aUserExperience.getMessageCount() + 1L);
if(userChangesLevel && featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, server, ExperienceFeatureMode.LEVEL_ACTION)) {
levelActionService.applyLevelActionsToUser(aUserExperience)
levelActionService.applyLevelActionsToUser(aUserExperience, oldLevel)
.thenAccept(unused -> {
log.info("Executed level actions for user {}.", userInServerId);
})
@@ -491,11 +491,32 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
int pageOffset = page * pageSize;
for (int i = 0; i < experiences.size(); i++) {
AUserExperience userExperience = experiences.get(i);
entries.add(LeaderBoardEntry.builder().experience(userExperience).rank(pageOffset + i + 1).build());
LeaderBoardEntry entry = LeaderBoardEntry.fromAUserExperience(userExperience);
entry.setRank(pageOffset + i + 1);
entries.add(entry);
}
return LeaderBoard.builder().entries(entries).build();
}
@Override
public LeaderBoard findLeaderBoardDataForUserFocus(AUserInAServer aUserInAServer) {
List<LeaderBoardEntry> allEntries =
userExperienceManagementService.getWindowedLeaderboardEntriesForUser(aUserInAServer, 10)
.stream().map(leaderBoardEntryResult -> LeaderBoardEntry
.builder()
.experience(leaderBoardEntryResult.getExperience())
.level(leaderBoardEntryResult.getLevel())
.userId(leaderBoardEntryResult.getUserId())
.messageCount(leaderBoardEntryResult.getMessageCount())
.rank(leaderBoardEntryResult.getRank())
.build())
.collect(Collectors.toList());
return LeaderBoard
.builder()
.entries(allEntries)
.build();
}
@Override
public LeaderBoardEntry getRankOfUserInServer(AUserInAServer userInAServer) {
log.debug("Retrieving rank for {}", userInAServer.getUserReference().getId());
@@ -509,7 +530,9 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
if(rankOfUserInServer != null) {
rank = rankOfUserInServer.getRank();
}
return LeaderBoardEntry.builder().experience(aUserExperience).rank(rank).build();
LeaderBoardEntry entry = LeaderBoardEntry.fromAUserExperience(aUserExperience);
entry.setRank(rank);
return entry;
}
}

View File

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

View File

@@ -82,6 +82,12 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(page, size));
}
@Override
public List<LeaderBoardEntryResult> getWindowedLeaderboardEntriesForUser(AUserInAServer aUserInAServer, Integer windowSize) {
return repository.getRankOfUserWithWindow(aUserInAServer.getUserInServerId(), aUserInAServer.getServerReference().getId(), windowSize.longValue() / 2 + 1,
windowSize.longValue() / 2);
}
@Override
public LeaderBoardEntryResult getRankOfUserInServer(AUserExperience userExperience) {
return repository.getRankOfUserInServer(userExperience.getId(), userExperience.getServer().getId());

View File

@@ -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:}

View File

@@ -1,87 +0,0 @@
package dev.sheldan.abstracto.experience.converter;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.experience.model.LeaderBoard;
import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class LeaderBoardModelConverterTest {
@InjectMocks
public LeaderBoardModelConverter testUnit;
@Mock
private MemberService memberService;
@Mock
private LeaderBoardModelConverter self;
private static final Long SERVER_ID = 4L;
private static final Long USER_ID = 5L;
private static final Long USER_ID_2 = 6L;
private static final Long USER_IN_SERVER_ID = 7L;
private static final Long USER_IN_SERVER_ID_2 = 8L;
private static final Long EXPERIENCE = 9L;
private static final Long MESSAGES = 10L;
private static final Integer LEVEL = 54;
@Test
public void testFromLeaderBoard() {
Integer firstRank = 1;
LeaderBoardEntry entry = getEntry(firstRank, USER_ID, USER_IN_SERVER_ID);
Integer secondRank = 2;
LeaderBoardEntry entry2 = getEntry(secondRank, USER_ID_2, USER_IN_SERVER_ID_2);
List<LeaderBoardEntry> entries = Arrays.asList(entry, entry2);
LeaderBoard leaderBoard = Mockito.mock(LeaderBoard.class);
when(leaderBoard.getEntries()).thenReturn(entries);
Member member = Mockito.mock(Member.class);
when(member.getIdLong()).thenReturn(USER_ID);
when(memberService.getMembersInServerAsync(SERVER_ID, Arrays.asList(USER_ID, USER_ID_2))).thenReturn(CompletableFuture.completedFuture(Arrays.asList(member)));
CompletableFuture<List<LeaderBoardEntryModel>> leaderBoardEntryModels = testUnit.fromLeaderBoard(leaderBoard);
LeaderBoardEntryModel firstEntry = leaderBoardEntryModels.join().get(0);
Assert.assertEquals(USER_ID, firstEntry.getUserId());
LeaderBoardEntryModel secondEntry = leaderBoardEntryModels.join().get(1);
Assert.assertEquals(USER_ID_2, secondEntry.getUserId());
Assert.assertEquals(entries.size(), leaderBoardEntryModels.join().size());
}
private LeaderBoardEntry getEntry(Integer rank, Long userId, Long userInServerId) {
AUserExperience experience = Mockito.mock(AUserExperience.class);
when(experience.getMessageCount()).thenReturn(MESSAGES);
when(experience.getExperience()).thenReturn(EXPERIENCE);
AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class);
when(experience.getUser()).thenReturn(userInAServer);
AUser user = Mockito.mock(AUser.class);
when(userInAServer.getUserReference()).thenReturn(user);
when(user.getId()).thenReturn(userId);
AServer server = Mockito.mock(AServer.class);
when(experience.getServer()).thenReturn(server);
when(server.getId()).thenReturn(SERVER_ID);
LeaderBoardEntry entry = Mockito.mock(LeaderBoardEntry.class);
when(entry.getRank()).thenReturn(rank);
when(entry.getExperience()).thenReturn(experience);
return entry;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,12 +12,19 @@ import lombok.Setter;
@Setter
@Builder
public class LeaderBoardEntry {
/**
* Object representing the current experience status of a user in a guild.
*/
private AUserExperience experience;
/**
* The rank this user has in the respective guild.
*/
private Long userId;
private Integer level;
private Long experience;
private Long messageCount;
private Integer rank;
public static LeaderBoardEntry fromAUserExperience(AUserExperience aUserExperience) {
return LeaderBoardEntry
.builder()
.experience(aUserExperience.getExperience())
.userId(aUserExperience.getUser().getUserReference().getId())
.messageCount(aUserExperience.getMessageCount())
.level(aUserExperience.getLevelOrDefault())
.build();
}
}

View File

@@ -7,11 +7,8 @@ public interface LeaderBoardEntryResult {
Long getId();
/**
* The {@link dev.sheldan.abstracto.core.models.database.AUserInAServer} id of the user
* @return The ID of the user in a server
*/
Long getUserInServerId();
Long getUserId();
Long getRoleId();
/**
* The experience of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.experience.model.template;
import dev.sheldan.abstracto.core.models.context.SlimUserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
@@ -24,4 +25,10 @@ public class LeaderBoardModel extends SlimUserInitiatedServerContext {
* The {@link LeaderBoardEntryModel} containing the leaderboard information executing the command.
*/
private LeaderBoardEntryModel userExecuting;
private String leaderboardUrl;
/**
* Whether to show the users own placement
*/
@Builder.Default
private boolean showPlacement = true;
}

View File

@@ -47,4 +47,5 @@ public class RankModel {
* The member to show the rank for
*/
private Member member;
private String leaderboardUrl;
}

View File

@@ -54,6 +54,7 @@ public interface AUserExperienceService {
* from the desired page
*/
LeaderBoard findLeaderBoardData(AServer server, Integer page);
LeaderBoard findLeaderBoardDataForUserFocus(AUserInAServer aUserInAServer);
/**
* Retrieves the {@link LeaderBoardEntry} from a specific {@link AUserInAServer} containing information about the

View File

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

View File

@@ -62,6 +62,7 @@ public interface UserExperienceManagementService {
* @return A list desc ordered by {@link AUserExperience} experience only containing the elements between {@code start} and @{code end}
*/
List<AUserExperience> findLeaderBoardUsersPaginated(AServer server, Integer page, Integer size);
List<LeaderBoardEntryResult> getWindowedLeaderboardEntriesForUser(AUserInAServer aUserInAServer, Integer windowSize);
/**
* Returns the {@link LeaderBoardEntryResult} of the given {@link AUserExperience}.

View File

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

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.5.32</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<artifactId>giveaway-impl</artifactId>
@@ -38,6 +38,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>metrics-int</artifactId>

View File

@@ -0,0 +1,136 @@
package dev.sheldan.abstracto.giveaway.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.giveaway.config.GiveawayFeatureDefinition;
import dev.sheldan.abstracto.giveaway.config.GiveawayMode;
import dev.sheldan.abstracto.giveaway.config.GiveawaySlashCommandNames;
import dev.sheldan.abstracto.giveaway.service.management.GiveawayKeyManagementService;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AddGiveawayKey extends AbstractConditionableCommand {
private static final String KEY_PARAMETER = "key";
private static final String DESCRIPTION_PARAMETER = "description";
private static final String BENEFACTOR_PARAMETER = "benefactor";
private static final String NAME_PARAMETER = "name";
private static final String ADD_GIVEAWAY_KEY_COMMAND_NAME = "addGiveawayKey";
private static final String ADD_GIVEAWAY_KEY_RESPONSE = "addGiveawayKey_response";
@Autowired
private InteractionService interactionService;
@Autowired
private GiveawayKeyManagementService giveawayKeyManagementService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String key = slashCommandParameterService.getCommandOption(KEY_PARAMETER, event, String.class);
String name = slashCommandParameterService.getCommandOption(NAME_PARAMETER, event, String.class);
String description;
if(slashCommandParameterService.hasCommandOption(DESCRIPTION_PARAMETER, event)) {
description = slashCommandParameterService.getCommandOption(DESCRIPTION_PARAMETER, event, String.class);
} else {
description = null;
}
Member benefactor;
if(slashCommandParameterService.hasCommandOption(BENEFACTOR_PARAMETER, event)) {
benefactor = slashCommandParameterService.getCommandOption(BENEFACTOR_PARAMETER, event, Member.class);
} else {
benefactor = null;
}
giveawayKeyManagementService.createGiveawayKey(event.getMember(), benefactor, key, description, name);
return interactionService.replyEmbed(ADD_GIVEAWAY_KEY_RESPONSE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter giveawayKey = Parameter
.builder()
.templated(true)
.name(KEY_PARAMETER)
.type(String.class)
.build();
Parameter giveawayKeyDescription = Parameter
.builder()
.templated(true)
.name(DESCRIPTION_PARAMETER)
.type(String.class)
.optional(true)
.build();
Parameter giveawayKeyBenefactor = Parameter
.builder()
.templated(true)
.name(BENEFACTOR_PARAMETER)
.type(Member.class)
.optional(true)
.build();
Parameter giveawayKeyName = Parameter
.builder()
.templated(true)
.name(NAME_PARAMETER)
.type(String.class)
.build();
List<Parameter> parameters = Arrays.asList(giveawayKeyName, giveawayKey, giveawayKeyBenefactor, giveawayKeyDescription);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(GiveawaySlashCommandNames.GIVEAWAY)
.groupName("keys")
.commandName("add")
.build();
return CommandConfiguration.builder()
.name(ADD_GIVEAWAY_KEY_COMMAND_NAME)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandOnly(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return GiveawayFeatureDefinition.GIVEAWAY;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(GiveawayMode.KEY_GIVEAWAYS);
}
}

View File

@@ -80,6 +80,7 @@ public class CancelGiveaway extends AbstractConditionableCommand {
.builder()
.enabled(true)
.rootCommandName(GiveawaySlashCommandNames.GIVEAWAY)
.groupName("management")
.commandName("cancel")
.build();

View File

@@ -18,6 +18,7 @@ import dev.sheldan.abstracto.giveaway.service.GiveawayService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -25,6 +26,7 @@ import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.springframework.transaction.annotation.Transactional;
@Component
public class GreateGiveaway extends AbstractConditionableCommand {
@@ -48,55 +50,63 @@ public class GreateGiveaway extends AbstractConditionableCommand {
@Autowired
private GiveawayService giveawayService;
@Autowired
private GreateGiveaway self;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
return event.deferReply()
.submit()
.thenCompose(interactionHook -> {
String title = slashCommandParameterService.getCommandOption(TITLE_PARAMETER, event, String.class);
String description;
if(slashCommandParameterService.hasCommandOption(DESCRIPTION_PARAMETER, event)) {
description = slashCommandParameterService.getCommandOption(DESCRIPTION_PARAMETER, event, String.class);
} else {
description = null;
}
.thenCompose(interactionHook -> self.createGiveaway(event, interactionHook))
.thenApply(unused -> CommandResult.fromSuccess());
String durationString = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, Duration.class, String.class);
Duration duration = ParseUtils.parseDuration(durationString);
}
GuildMessageChannel target = null;
if(slashCommandParameterService.hasCommandOption(CHANNEL_PARAMETER, event)) {
target = slashCommandParameterService.getCommandOption(CHANNEL_PARAMETER, event, GuildMessageChannel.class);
}
@Transactional
public CompletableFuture<Void> createGiveaway(SlashCommandInteractionEvent event, InteractionHook interactionHook) {
String title = slashCommandParameterService.getCommandOption(TITLE_PARAMETER, event, String.class);
String description;
if(slashCommandParameterService.hasCommandOption(DESCRIPTION_PARAMETER, event)) {
description = slashCommandParameterService.getCommandOption(DESCRIPTION_PARAMETER, event, String.class);
} else {
description = null;
}
Integer winners = 1;
if(slashCommandParameterService.hasCommandOption(WINNERS_PARAMETER, event)) {
winners = slashCommandParameterService.getCommandOption(WINNERS_PARAMETER, event, Integer.class);
}
String durationString = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, Duration.class, String.class);
Duration duration = ParseUtils.parseDuration(durationString);
Member benefactor;
if(slashCommandParameterService.hasCommandOption(BENEFACTOR_PARAMETER, event)) {
benefactor = slashCommandParameterService.getCommandOption(BENEFACTOR_PARAMETER, event, Member.class);
} else {
benefactor = null;
}
GuildMessageChannel target = null;
if(slashCommandParameterService.hasCommandOption(CHANNEL_PARAMETER, event)) {
target = slashCommandParameterService.getCommandOption(CHANNEL_PARAMETER, event, GuildMessageChannel.class);
}
Member creator = event.getMember();
GiveawayCreationRequest request = GiveawayCreationRequest
.builder()
.benefactor(benefactor)
.creator(creator)
.description(description)
.duration(duration)
.targetChannel(target)
.winnerCount(winners)
.title(title)
.build();
Integer winners = 1;
if(slashCommandParameterService.hasCommandOption(WINNERS_PARAMETER, event)) {
winners = slashCommandParameterService.getCommandOption(WINNERS_PARAMETER, event, Integer.class);
}
return giveawayService.createGiveaway(request)
.thenAccept(unused -> interactionService.sendEmbed(CREATE_GIVEAWAY_RESPONSE_TEMPLATE_KEY, interactionHook));
}).thenApply(unused -> CommandResult.fromSuccess());
Member benefactor;
if(slashCommandParameterService.hasCommandOption(BENEFACTOR_PARAMETER, event)) {
benefactor = slashCommandParameterService.getCommandOption(BENEFACTOR_PARAMETER, event, Member.class);
} else {
benefactor = null;
}
Member creator = event.getMember();
GiveawayCreationRequest request = GiveawayCreationRequest
.builder()
.benefactorId(benefactor != null ? benefactor.getIdLong() : null)
.creatorId(creator.getIdLong())
.description(description)
.duration(duration)
.targetChannel(target)
.winnerCount(winners)
.serverId(creator.getGuild().getIdLong())
.title(title)
.build();
return giveawayService.createGiveaway(request)
.thenAccept(unused -> interactionService.sendEmbed(CREATE_GIVEAWAY_RESPONSE_TEMPLATE_KEY, interactionHook));
}
@Override
@@ -159,6 +169,7 @@ public class GreateGiveaway extends AbstractConditionableCommand {
.builder()
.enabled(true)
.rootCommandName(GiveawaySlashCommandNames.GIVEAWAY)
.groupName("management")
.commandName("create")
.build();

View File

@@ -0,0 +1,160 @@
package dev.sheldan.abstracto.giveaway.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.giveaway.config.GiveawayFeatureConfig;
import dev.sheldan.abstracto.giveaway.config.GiveawayFeatureDefinition;
import dev.sheldan.abstracto.giveaway.config.GiveawayMode;
import dev.sheldan.abstracto.giveaway.config.GiveawaySlashCommandNames;
import dev.sheldan.abstracto.giveaway.exception.GiveawayNotPossibleException;
import dev.sheldan.abstracto.giveaway.exception.GiveawayKeyNotFoundException;
import dev.sheldan.abstracto.giveaway.model.GiveawayCreationRequest;
import dev.sheldan.abstracto.giveaway.model.database.GiveawayKey;
import dev.sheldan.abstracto.giveaway.service.GiveawayService;
import dev.sheldan.abstracto.giveaway.service.management.GiveawayKeyManagementService;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class GreateKeyGiveaway extends AbstractConditionableCommand {
private static final String COMMAND_NAME = "createKeyGiveaway";
private static final String KEY_ID_PARAMETER = "keyId";
private static final String DURATION_PARAMETER = "duration";
private static final String CREATE_KEY_GIVEAWAY_RESPONSE_TEMPLATE_KEY = "createKeyGiveaway_response";
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private GiveawayService giveawayService;
@Autowired
private GiveawayKeyManagementService giveawayKeyManagementService;
@Autowired
private ConfigService configService;
@Autowired
private GreateKeyGiveaway self;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long id = slashCommandParameterService.getCommandOption(KEY_ID_PARAMETER, event, Integer.class).longValue();
String durationString;
if(slashCommandParameterService.hasCommandOption(DURATION_PARAMETER, event)) {
durationString = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, Duration.class, String.class);
} else {
durationString = configService.getStringValueOrConfigDefault(GiveawayFeatureConfig.KEY_GIVEAWAYS_DURATION, event.getGuild().getIdLong()).trim();
}
Duration duration = ParseUtils.parseDuration(durationString);
return event.deferReply()
.submit()
.thenCompose(interactionHook -> self.createKeyGiveaway(id, interactionHook, duration))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Transactional
public CompletableFuture<Void> createKeyGiveaway(Long giveawayKeyId, InteractionHook interactionHook, Duration duration) {
Long serverId = interactionHook.getInteraction().getGuild().getIdLong();
GiveawayKey giveawayKey = giveawayKeyManagementService.getById(giveawayKeyId, serverId)
.orElseThrow(GiveawayKeyNotFoundException::new);
if((giveawayKey.getGiveaway() != null && giveawayKey.getGiveaway().getTargetDate().isAfter(Instant.now())) || giveawayKey.getUsed()) {
throw new GiveawayNotPossibleException();
}
GiveawayCreationRequest request = GiveawayCreationRequest
.builder()
.benefactorId(giveawayKey.getBenefactor() != null ? giveawayKey.getBenefactor().getUserReference().getId() : null)
.creatorId(giveawayKey.getCreator().getUserReference().getId())
.description(giveawayKey.getDescription())
.duration(duration)
.serverId(serverId)
.giveawayKeyId(giveawayKeyId)
.winnerCount(1)
.title(giveawayKey.getName())
.build();
return giveawayService.createGiveaway(request)
.thenAccept(giveawayId -> {
interactionService.sendEmbed(CREATE_KEY_GIVEAWAY_RESPONSE_TEMPLATE_KEY, interactionHook);
});
}
@Override
public CommandConfiguration getConfiguration() {
Parameter keyParameter = Parameter
.builder()
.templated(true)
.name(KEY_ID_PARAMETER)
.type(Long.class)
.build();
Parameter durationParameter = Parameter
.builder()
.templated(true)
.name(DURATION_PARAMETER)
.optional(true)
.type(Duration.class)
.build();
List<Parameter> parameters = Arrays.asList(keyParameter, durationParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(GiveawaySlashCommandNames.GIVEAWAY)
.groupName("key")
.commandName("creategiveaway")
.build();
return CommandConfiguration.builder()
.name(COMMAND_NAME)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return GiveawayFeatureDefinition.GIVEAWAY;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(GiveawayMode.KEY_GIVEAWAYS);
}
}

View File

@@ -80,6 +80,7 @@ public class ReRollGiveaway extends AbstractConditionableCommand {
.builder()
.enabled(true)
.rootCommandName(GiveawaySlashCommandNames.GIVEAWAY)
.groupName("management")
.commandName("reroll")
.build();

View File

@@ -0,0 +1,96 @@
package dev.sheldan.abstracto.giveaway.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.giveaway.config.GiveawayFeatureDefinition;
import dev.sheldan.abstracto.giveaway.config.GiveawayMode;
import dev.sheldan.abstracto.giveaway.config.GiveawaySlashCommandNames;
import dev.sheldan.abstracto.giveaway.service.management.GiveawayKeyManagementService;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RemoveGiveawayKey extends AbstractConditionableCommand {
private static final String ID_PARAMETER = "id";
private static final String REMOVE_GIVEAWAY_KEY_COMMAND_NAME = "removeGiveawayKey";
private static final String REMOVE_GIVEAWAY_KEY_RESPONSE = "removeGiveawayKey_response";
@Autowired
private GiveawayKeyManagementService giveawayKeyManagementService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long id = slashCommandParameterService.getCommandOption(ID_PARAMETER, event, Integer.class).longValue();
giveawayKeyManagementService.deleteById(id, event.getGuild().getIdLong());
return interactionService.replyEmbed(REMOVE_GIVEAWAY_KEY_RESPONSE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter giveawayKeyId = Parameter
.builder()
.templated(true)
.name(ID_PARAMETER)
.type(Long.class)
.build();
List<Parameter> parameters = Arrays.asList(giveawayKeyId);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(GiveawaySlashCommandNames.GIVEAWAY)
.groupName("keys")
.commandName("remove")
.build();
return CommandConfiguration.builder()
.name(REMOVE_GIVEAWAY_KEY_COMMAND_NAME)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandOnly(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return GiveawayFeatureDefinition.GIVEAWAY;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(GiveawayMode.KEY_GIVEAWAYS);
}
}

View File

@@ -0,0 +1,140 @@
package dev.sheldan.abstracto.giveaway.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.PaginatorService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.giveaway.config.GiveawayFeatureDefinition;
import dev.sheldan.abstracto.giveaway.config.GiveawayMode;
import dev.sheldan.abstracto.giveaway.config.GiveawaySlashCommandNames;
import dev.sheldan.abstracto.giveaway.model.database.GiveawayKey;
import dev.sheldan.abstracto.giveaway.model.template.GiveawayKeyDisplayModel;
import dev.sheldan.abstracto.giveaway.model.template.GiveawayKeysDisplayModel;
import dev.sheldan.abstracto.giveaway.service.management.GiveawayKeyManagementService;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ShowGiveawayKeys extends AbstractConditionableCommand {
public static final String SHOW_ALL_PARAMETER_NAME = "all";
private static final String SHOW_GIVEAWAY_KEYS_RESPONSE_TEMPLATE = "showGiveawayKeys_response";
private static final String SHOW_GIVEAWAY_NO_KEYS_FOUND_TEMPLATE = "showGiveawayKeys_no_keys_found";
@Autowired
private PaginatorService paginatorService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private GiveawayKeyManagementService giveawayKeyManagementService;
@Autowired
private TemplateService templateService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
boolean showAll;
if(slashCommandParameterService.hasCommandOption(SHOW_ALL_PARAMETER_NAME, event)) {
showAll = slashCommandParameterService.getCommandOption(SHOW_ALL_PARAMETER_NAME, event, Boolean.class);
} else {
showAll = false;
}
List<GiveawayKey> giveawayKeys = giveawayKeyManagementService.getGiveawayKeys(event.getGuild().getIdLong(), showAll);
if(giveawayKeys.isEmpty()) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(SHOW_GIVEAWAY_NO_KEYS_FOUND_TEMPLATE, new Object(), event.getGuild().getIdLong());
return interactionService.replyMessageToSend(messageToSend, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
List<GiveawayKeyDisplayModel> models = giveawayKeys
.stream()
.map(giveawayKey -> GiveawayKeyDisplayModel
.builder()
.key(giveawayKey.getKey())
.id(giveawayKey.getId().getKeyId())
.used(giveawayKey.getUsed())
.description(giveawayKey.getDescription())
.name(giveawayKey.getName())
.winner(giveawayKey.getWinner() != null ? MemberDisplay.fromAUserInAServer(giveawayKey.getWinner()) : null)
.creator(MemberDisplay.fromAUserInAServer(giveawayKey.getCreator()))
.benefactor(giveawayKey.getBenefactor() != null ? MemberDisplay.fromAUserInAServer(giveawayKey.getBenefactor()) : null)
.build())
.toList();
GiveawayKeysDisplayModel model = GiveawayKeysDisplayModel
.builder()
.keys(models)
.build();
return paginatorService.createPaginatorFromTemplate(SHOW_GIVEAWAY_KEYS_RESPONSE_TEMPLATE, model, event)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter showAllParameter = Parameter
.builder()
.templated(true)
.name(SHOW_ALL_PARAMETER_NAME)
.type(Boolean.class)
.optional(true)
.build();
List<Parameter> parameters = Arrays.asList(showAllParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(GiveawaySlashCommandNames.GIVEAWAY)
.groupName("keys")
.commandName("show")
.build();
return CommandConfiguration.builder()
.name("showGiveawayKeys")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return GiveawayFeatureDefinition.GIVEAWAY;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(GiveawayMode.KEY_GIVEAWAYS);
}
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.giveaway.repository;
import dev.sheldan.abstracto.giveaway.model.database.GiveawayKey;
import dev.sheldan.abstracto.giveaway.model.database.embed.GiveawayKeyId;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface GiveawayKeyRepository extends JpaRepository<GiveawayKey, GiveawayKeyId> {
List<GiveawayKey> findGiveawayKeysByUsedAndServer_IdOrderById(Boolean used, Long serverId);
List<GiveawayKey> findGiveawayKeysByServer_IdOrderById(Long serverId);
}

View File

@@ -7,28 +7,38 @@ import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.CounterService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.giveaway.config.GiveawayFeatureDefinition;
import dev.sheldan.abstracto.giveaway.config.GiveawayMode;
import dev.sheldan.abstracto.giveaway.config.GiveawayPostTarget;
import dev.sheldan.abstracto.giveaway.exception.GiveawayKeyNotFoundException;
import dev.sheldan.abstracto.giveaway.exception.GiveawayNotFoundException;
import dev.sheldan.abstracto.giveaway.model.GiveawayCreationRequest;
import dev.sheldan.abstracto.giveaway.model.JoinGiveawayPayload;
import dev.sheldan.abstracto.giveaway.model.database.Giveaway;
import dev.sheldan.abstracto.giveaway.model.database.GiveawayKey;
import dev.sheldan.abstracto.giveaway.model.database.GiveawayParticipant;
import dev.sheldan.abstracto.giveaway.model.template.GiveawayMessageModel;
import dev.sheldan.abstracto.giveaway.model.template.GiveawayResultMessageModel;
import dev.sheldan.abstracto.giveaway.model.template.GiveawayWinnerNotificationMessageModel;
import dev.sheldan.abstracto.giveaway.service.management.GiveawayKeyManagementService;
import dev.sheldan.abstracto.giveaway.service.management.GiveawayManagementService;
import dev.sheldan.abstracto.giveaway.service.management.GiveawayParticipantManagementService;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.scheduling.model.JobParameters;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
@@ -44,6 +54,7 @@ import java.util.concurrent.CompletableFuture;
public class GiveawayServiceBean implements GiveawayService {
private static final String GIVEAWAY_MESSAGE_TEMPLATE_KEY = "giveaway_post";
private static final String GIVEAWAY_WINNER_MODMAIL_NOTIFICATION = "giveaway_winner_modmail_notification";
private static final String GIVEAWAY_RESULT_MESSAGE_TEMPLATE_KEY = "giveaway_result";
public static final String GIVEAWAY_JOIN_ORIGIN = "JOIN_GIVEAWAY";
@@ -85,42 +96,55 @@ public class GiveawayServiceBean implements GiveawayService {
@Autowired
private CounterService counterService;
@Autowired
private GiveawayKeyManagementService giveawayKeyManagementService;
@Autowired
private UserService userService;
@Autowired
private FeatureModeService featureModeService;
@Autowired(required = false)
private ModMailThreadService modMailThreadService;
@Autowired
private GiveawayServiceBean self;
@Override
public CompletableFuture<Void> createGiveaway(GiveawayCreationRequest giveawayCreationRequest) {
public CompletableFuture<Long> createGiveaway(GiveawayCreationRequest giveawayCreationRequest) {
String componentId = componentService.generateComponentId();
Instant targetDate = Instant.now().plus(giveawayCreationRequest.getDuration());
Long serverId = giveawayCreationRequest.getCreator().getGuild().getIdLong();
Long serverId = giveawayCreationRequest.getServerId();
Long giveawayId = counterService.getNextCounterValue(serverId, GIVEAWAY_COUNTER);
GiveawayMessageModel model = GiveawayMessageModel
.builder()
.title(giveawayCreationRequest.getTitle())
.description(giveawayCreationRequest.getDescription())
.giveawayId(giveawayId)
.benefactor(giveawayCreationRequest.getBenefactor() != null ? MemberDisplay.fromMember(giveawayCreationRequest.getBenefactor()) : null)
.creator(MemberDisplay.fromMember(giveawayCreationRequest.getCreator()))
.benefactor(giveawayCreationRequest.getBenefactorId() != null ? MemberDisplay.fromIds(giveawayCreationRequest.getServerId(), giveawayCreationRequest.getBenefactorId()) : null)
.creator(MemberDisplay.fromIds(giveawayCreationRequest.getServerId(), giveawayCreationRequest.getCreatorId()))
.winnerCount(giveawayCreationRequest.getWinnerCount())
.targetDate(targetDate)
.joinComponentId(componentId)
.build();
List<CompletableFuture<Message>> messageFutures;
log.info("Rendering giveaway message in server {} by user {}", serverId, giveawayCreationRequest.getCreator().getIdLong());
log.info("Rendering giveaway message in server {} by user {}", serverId, giveawayCreationRequest.getCreatorId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(GIVEAWAY_MESSAGE_TEMPLATE_KEY, model, serverId);
if(giveawayCreationRequest.getTargetChannel() == null) {
log.info("Sending giveaway to post target in server {}", serverId);
postTargetService.validatePostTarget(GiveawayPostTarget.GIVEAWAYS, giveawayCreationRequest.getCreator().getGuild().getIdLong());
postTargetService.validatePostTarget(GiveawayPostTarget.GIVEAWAYS, serverId);
messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, GiveawayPostTarget.GIVEAWAYS, serverId);
} else {
log.info("Sending giveaway to channel {} in server {}.", giveawayCreationRequest.getTargetChannel().getId(), serverId);
messageFutures = channelService.sendMessageToSendToChannel(messageToSend, giveawayCreationRequest.getTargetChannel());
}
CompletableFutureList<Message> messageFutureList = new CompletableFutureList<>(messageFutures);
return messageFutureList.getMainFuture().thenAccept(o -> {
return messageFutureList.getMainFuture().thenApply(o -> {
Message createdMessage = messageFutureList.getFutures().get(0).join();
giveawayCreationRequest.setTargetChannel(createdMessage.getGuildChannel());
self.persistGiveaway(giveawayCreationRequest, giveawayId, createdMessage.getIdLong(), componentId);
return giveawayId;
});
}
@@ -130,7 +154,7 @@ public class GiveawayServiceBean implements GiveawayService {
giveawayMessageModel.setJoinedUserCount(giveaway.getParticipants().size() + 1L);
Long giveawayId = giveaway.getGiveawayId().getId();
log.info("Adding giveaway participating of user {} to giveaway {} in server {}.", member.getIdLong(), giveawayId, member.getGuild().getIdLong());
MessageToSend messageToSend = templateService.renderEmbedTemplate(GIVEAWAY_MESSAGE_TEMPLATE_KEY, giveawayMessageModel);
MessageToSend messageToSend = templateService.renderEmbedTemplate(GIVEAWAY_MESSAGE_TEMPLATE_KEY, giveawayMessageModel, member.getGuild().getIdLong());
return channelService.editEmbedMessageInAChannel(messageToSend.getEmbeds().get(0), messageChannel, giveaway.getMessageId())
.thenAccept(message -> {
self.persistAddedParticipant(member, giveawayId);
@@ -140,13 +164,9 @@ public class GiveawayServiceBean implements GiveawayService {
@Override
@Transactional
public CompletableFuture<Void> evaluateGiveaway(Long giveawayId, Long serverId) {
Optional<Giveaway> giveAwayOptional = giveawayManagementService.loadGiveawayById(giveawayId, serverId);
if(giveAwayOptional.isEmpty()) {
throw new GiveawayNotFoundException();
}
Giveaway giveaway = giveawayManagementService.loadGiveawayById(giveawayId, serverId).orElseThrow(GiveawayNotFoundException::new);
log.info("Evaluating giveaway {} in server {}.", giveawayId, serverId);
Giveaway giveaway = giveAwayOptional.get();
Set<Long> winners = new HashSet<>();
Set<Long> winnerUserInServerIds = new HashSet<>();
Integer winnerCount = giveaway.getWinnerCount();
giveaway.getParticipants().forEach(giveawayParticipant -> giveawayParticipant.setWon(false));
List<Long> potentialWinners = new ArrayList<>(giveaway
@@ -156,20 +176,20 @@ public class GiveawayServiceBean implements GiveawayService {
.toList());
if(potentialWinners.size() <= winnerCount) {
winners.addAll(potentialWinners);
winnerUserInServerIds.addAll(potentialWinners);
log.debug("Less participants than total winners - selecting all for giveaway {} in server {}.", giveawayId, serverId);
} else {
for (int i = 0; i < winnerCount; i++) {
int winnerIndex = secureRandom.nextInt(potentialWinners.size());
Long winner = potentialWinners.get(winnerIndex);
potentialWinners.remove(winnerIndex);
winners.add(winner);
winnerUserInServerIds.add(winner);
}
}
List<GiveawayParticipant> winningParticipants = giveaway
.getParticipants()
.stream()
.filter(giveawayParticipant -> winners.contains(giveawayParticipant.getParticipant().getUserInServerId()))
.filter(giveawayParticipant -> winnerUserInServerIds.contains(giveawayParticipant.getParticipant().getUserInServerId()))
.toList();
winningParticipants.forEach(giveawayParticipant -> giveawayParticipant.setWon(true));
List<MemberDisplay> winnerDisplays = winningParticipants
@@ -183,18 +203,57 @@ public class GiveawayServiceBean implements GiveawayService {
.winners(winnerDisplays)
.build();
log.info("Sending result message for giveaway {} in server {}.", giveawayId, serverId);
MessageToSend messageToSend = templateService.renderEmbedTemplate(GIVEAWAY_RESULT_MESSAGE_TEMPLATE_KEY, resultModel);
MessageToSend messageToSend = templateService.renderEmbedTemplate(GIVEAWAY_RESULT_MESSAGE_TEMPLATE_KEY, resultModel, serverId);
List<CompletableFuture<Message>> resultFutures = channelService.sendMessageEmbedToSendToAChannel(messageToSend, giveaway.getGiveawayChannel());
long actualWinnerCount = winnerUserInServerIds.size();
Long winnerUserId;
if(giveaway.getGiveawayKey() != null && !winningParticipants.isEmpty()) {
GiveawayParticipant winnerParticipant = winningParticipants.get(0);
GiveawayKey giveawayKey = giveaway.getGiveawayKey();
giveawayKey.setWinner(winnerParticipant.getParticipant());
giveawayKey.setUsed(true);
winnerUserId = winnerParticipant.getParticipant().getUserReference().getId();
} else {
winnerUserId = null;
}
GiveawayMessageModel giveawayMessageModel = GiveawayMessageModel.fromGiveaway(giveaway);
giveawayMessageModel.setWinners(winnerDisplays);
giveawayMessageModel.setEnded(true);
MessageToSend giveawayMessageToSend = templateService.renderEmbedTemplate(GIVEAWAY_MESSAGE_TEMPLATE_KEY, giveawayMessageModel);
boolean createGiveawayKeyNotification = giveaway.getGiveawayKey() != null
&& featureModeService.featureModeActive(GiveawayFeatureDefinition.GIVEAWAY, serverId, GiveawayMode.AUTO_NOTIFY_GIVEAWAY_KEY_WINNERS)
&& featureModeService.featureModeActive(GiveawayFeatureDefinition.GIVEAWAY, serverId, GiveawayMode.KEY_GIVEAWAYS)
&& actualWinnerCount > 0
&& modMailThreadService != null;
MessageToSend giveawayMessageToSend = templateService.renderEmbedTemplate(GIVEAWAY_MESSAGE_TEMPLATE_KEY, giveawayMessageModel, serverId);
log.info("Updating original giveaway message for giveaway {} in server {}.", giveawayId, serverId);
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(giveaway.getServer().getId(), giveaway.getGiveawayChannel().getId());
CompletableFuture<Message> giveawayUpdateFuture = channelService.editMessageInAChannelFuture(giveawayMessageToSend, messageChannel, giveaway.getMessageId());
resultFutures.add(giveawayUpdateFuture);
return new CompletableFutureList<>(resultFutures).getMainFuture();
return new CompletableFutureList<>(resultFutures).getMainFuture().thenCompose(unused -> {
if(createGiveawayKeyNotification) {
return userService.retrieveUserForId(winnerUserId)
.thenCompose(user -> self.handleKeyGiveawayNotifications(giveawayId, serverId, winnerUserInServerIds.iterator().next(), user))
.exceptionally(throwable -> {
log.error("Failed to notify winner of giveaway {} in server {}.", giveawayId, serverId, throwable);
return null;
});
} else {
return CompletableFuture.completedFuture(null);
}
});
}
@Transactional
public CompletableFuture<Void> handleKeyGiveawayNotifications(Long giveawayId, Long serverId, Long winnerInServerId, User user) {
if(modMailThreadService == null) {
log.info("Modmail service not available - skipping notifications about giveaway {} in server {}.", giveawayId, serverId);
return CompletableFuture.completedFuture(null);
}
Giveaway giveaway = giveawayManagementService.loadGiveawayById(giveawayId, serverId).orElseThrow(GiveawayNotFoundException::new);
GiveawayWinnerNotificationMessageModel messageModel = GiveawayWinnerNotificationMessageModel.fromGiveaway(giveaway, giveaway.getGiveawayKey().getKey());
AUserInAServer winner = userInServerManagementService.loadOrCreateUser(winnerInServerId);
MessageToSend giveawayWinnerNotification = templateService.renderEmbedTemplate(GIVEAWAY_WINNER_MODMAIL_NOTIFICATION, messageModel, serverId);
return modMailThreadService.sendMessageToUser(winner, giveawayWinnerNotification, user);
}
@Override
@@ -208,7 +267,7 @@ public class GiveawayServiceBean implements GiveawayService {
GiveawayMessageModel giveawayMessageModel = GiveawayMessageModel.fromGiveaway(giveaway);
giveawayMessageModel.setCancelled(true);
schedulerService.stopTrigger(giveaway.getReminderTriggerKey());
MessageToSend giveawayMessageToSend = templateService.renderEmbedTemplate(GIVEAWAY_MESSAGE_TEMPLATE_KEY, giveawayMessageModel);
MessageToSend giveawayMessageToSend = templateService.renderEmbedTemplate(GIVEAWAY_MESSAGE_TEMPLATE_KEY, giveawayMessageModel, serverId);
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(giveaway.getServer().getId(), giveaway.getGiveawayChannel().getId());
log.debug("Updating original giveaway message to consider cancellation for giveaway {} in server {}.", giveawayId, serverId);
@@ -241,14 +300,13 @@ public class GiveawayServiceBean implements GiveawayService {
@Transactional
public void persistGiveaway(GiveawayCreationRequest giveawayCreationRequest, Long giveawayId, Long messageId, String componentId) {
Member creatorMember = giveawayCreationRequest.getCreator();
log.info("Persisting giveaway in server {} with message id {}.", creatorMember.getGuild().getIdLong(), messageId);
log.info("Persisting giveaway in server {} with message id {}.", giveawayCreationRequest.getServerId(), messageId);
Instant targetDate = Instant.now().plus(giveawayCreationRequest.getDuration());
AChannel targetChannel = channelManagementService.loadChannel(giveawayCreationRequest.getTargetChannel().getIdLong());
AUserInAServer creator = userInServerManagementService.loadOrCreateUser(creatorMember);
AUserInAServer creator = userInServerManagementService.loadOrCreateUser(giveawayCreationRequest.getServerId(), giveawayCreationRequest.getCreatorId());
AUserInAServer benefactor;
if(giveawayCreationRequest.getBenefactor() != null) {
benefactor = userInServerManagementService.loadOrCreateUser(giveawayCreationRequest.getBenefactor());
if(giveawayCreationRequest.getBenefactorId() != null) {
benefactor = userInServerManagementService.loadOrCreateUser(giveawayCreationRequest.getServerId(), giveawayCreationRequest.getBenefactorId());
} else {
benefactor = null;
}
@@ -257,6 +315,12 @@ public class GiveawayServiceBean implements GiveawayService {
giveawayCreationRequest.getTitle(), giveawayCreationRequest.getDescription(), giveawayCreationRequest.getWinnerCount(),
messageId, componentId, giveawayId);
if(giveawayCreationRequest.getGiveawayKeyId() != null) {
GiveawayKey giveawayKey = giveawayKeyManagementService.getById(giveawayCreationRequest.getGiveawayKeyId(), giveawayCreationRequest.getServerId())
.orElseThrow(GiveawayKeyNotFoundException::new);
giveawayKey.setGiveaway(giveaway);
giveawayKeyManagementService.saveGiveawayKey(giveawayKey);
}
HashMap<Object, Object> parameters = new HashMap<>();
parameters.put("giveawayId", giveaway.getGiveawayId().getId().toString());
parameters.put("serverId", giveaway.getGiveawayId().getServerId().toString());
@@ -264,7 +328,7 @@ public class GiveawayServiceBean implements GiveawayService {
.builder()
.parameters(parameters)
.build();
log.info("Scheduling giveaway reminder for giveaway {} originating from message {} in server {}.", giveaway.getGiveawayId().getId(), messageId, creatorMember.getGuild().getIdLong());
log.info("Scheduling giveaway reminder for giveaway {} originating from message {} in server {}.", giveaway.getGiveawayId().getId(), messageId, giveawayCreationRequest.getServerId());
String triggerKey = schedulerService.executeJobWithParametersOnce("giveawayEvaluationJob", "giveaway", jobParameters, Date.from(giveaway.getTargetDate()));
giveaway.setReminderTriggerKey(triggerKey);
JoinGiveawayPayload joinPayload = JoinGiveawayPayload

View File

@@ -0,0 +1,85 @@
package dev.sheldan.abstracto.giveaway.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.CounterService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.giveaway.exception.GiveawayKeyNotFoundException;
import dev.sheldan.abstracto.giveaway.model.database.GiveawayKey;
import dev.sheldan.abstracto.giveaway.model.database.embed.GiveawayKeyId;
import dev.sheldan.abstracto.giveaway.repository.GiveawayKeyRepository;
import java.util.List;
import java.util.Optional;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class GiveawayKeyManagementServiceBean implements GiveawayKeyManagementService {
@Autowired
private GiveawayKeyRepository giveawayKeyRepository;
@Autowired
private CounterService counterService;
@Autowired
private UserInServerManagementService userInServerManagementService;
public static final String GIVEAWAY_KEYS_COUNTER = "giveaway_keys";
@Override
public GiveawayKey createGiveawayKey(Member creator, Member benefactor, String key, String description, String name) {
Long counterValue = counterService.getNextCounterValue(creator.getGuild().getIdLong(), GIVEAWAY_KEYS_COUNTER);
GiveawayKeyId id = new GiveawayKeyId(counterValue, creator.getGuild().getIdLong());
AUserInAServer creatorUser = userInServerManagementService.loadOrCreateUser(creator);
AUserInAServer benefactorUser;
if(benefactor != null) {
benefactorUser = userInServerManagementService.loadOrCreateUser(benefactor);
} else {
benefactorUser = null;
}
GiveawayKey giveawayKey = GiveawayKey
.builder()
.id(id)
.creator(creatorUser)
.used(false)
.server(creatorUser.getServerReference())
.key(key)
.description(description)
.benefactor(benefactorUser)
.name(name)
.build();
return giveawayKeyRepository.save(giveawayKey);
}
@Override
public void deleteById(Long id, Long serverId) {
GiveawayKey key = giveawayKeyRepository.findById(new GiveawayKeyId(id, serverId)).orElseThrow(GiveawayKeyNotFoundException::new);
if(key.getGiveaway() != null) {
key.getGiveaway().setGiveawayKey(null);
}
giveawayKeyRepository.delete(key);
}
@Override
public Optional<GiveawayKey> getById(Long id, Long serverId) {
return giveawayKeyRepository.findById(new GiveawayKeyId(id, serverId));
}
@Override
public GiveawayKey saveGiveawayKey(GiveawayKey giveawayKey) {
return giveawayKeyRepository.save(giveawayKey);
}
@Override
public List<GiveawayKey> getGiveawayKeys(Long serverId, Boolean showAll) {
if(showAll) {
return giveawayKeyRepository.findGiveawayKeysByServer_IdOrderById(serverId);
} else {
return giveawayKeyRepository.findGiveawayKeysByUsedAndServer_IdOrderById(false, serverId);
}
}
}

View File

@@ -1,4 +1,15 @@
abstracto.featureFlags.giveaway.featureName=giveaway
abstracto.featureFlags.giveaway.enabled=false
abstracto.postTargets.giveaways.name=giveaways
abstracto.postTargets.giveaways.name=giveaways
abstracto.featureModes.keyGiveaways.featureName=giveaway
abstracto.featureModes.keyGiveaways.mode=keyGiveaways
abstracto.featureModes.keyGiveaways.enabled=false
abstracto.featureModes.autoNotifyGiveawayKeyWinners.featureName=giveaway
abstracto.featureModes.autoNotifyGiveawayKeyWinners.mode=autoNotifyGiveawayKeyWinners
abstracto.featureModes.autoNotifyGiveawayKeyWinners.enabled=false
abstracto.systemConfigs.keyGiveawaysDuration.name=keyGiveawaysDuration
abstracto.systemConfigs.keyGiveawaysDuration.stringValue=24h

View File

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

View File

@@ -0,0 +1,30 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<property name="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="giveawayFeature" value="(SELECT id FROM feature WHERE key = 'giveaway')"/>
<changeSet author="Sheldan" id="giveawayKey-commands">
<insert tableName="command">
<column name="name" value="addGiveawayKey"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${giveawayFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="removeGiveawayKey"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${giveawayFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="showGiveawayKeys"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${giveawayFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="createKeyGiveaway"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${giveawayFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -0,0 +1,69 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="giveaway_key-table">
<createTable tableName="giveaway_key">
<column name="id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="creator_user_id" type="INTEGER">
<constraints nullable="false"/>
</column>
<column name="benefactor_user_id" type="INTEGER">
<constraints nullable="true"/>
</column>
<column name="winner_user_id" type="INTEGER">
<constraints nullable="true"/>
</column>
<column name="key" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="name" type="VARCHAR(100)">
<constraints nullable="false"/>
</column>
<column name="description" type="VARCHAR(255)">
<constraints nullable="true"/>
</column>
<column name="used" type="BOOLEAN">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="giveaway_id" type="BIGINT">
<constraints nullable="true" />
</column>
<column name="giveaway_server_id" type="BIGINT">
<constraints nullable="true" />
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addPrimaryKey tableName="giveaway_key" columnNames="id, server_id"/>
<addForeignKeyConstraint baseColumnNames="creator_user_id" baseTableName="giveaway_key" constraintName="fk_giveaway_key_creator" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id"
referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="benefactor_user_id" baseTableName="giveaway_key" constraintName="fk_giveaway_key_benefactor" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id"
referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="winner_user_id" baseTableName="giveaway_key" constraintName="fk_giveaway_key_winner" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id"
referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="giveaway_id,giveaway_server_id" baseTableName="giveaway_key" constraintName="fk_giveaway_key_giveaway" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id,server_id"
referencedTableName="giveaway" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="giveaway_key" constraintName="fk_giveaway_key_server" deferrable="false" initiallyDeferred="false"
onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="server" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS giveaway_key_update_trigger ON giveaway_key;
CREATE TRIGGER giveaway_update_trigger BEFORE UPDATE ON giveaway_key FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS giveaway_key_insert_trigger ON giveaway_key;
CREATE TRIGGER giveaway_insert_trigger BEFORE INSERT ON giveaway_key FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.giveaway.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import org.springframework.stereotype.Component;
@@ -11,6 +12,8 @@ import java.util.List;
@Component
public class GiveawayFeatureConfig implements FeatureConfig {
public static final String KEY_GIVEAWAYS_DURATION = "keyGiveawaysDuration";
@Override
public FeatureDefinition getFeature() {
return GiveawayFeatureDefinition.GIVEAWAY;
@@ -20,4 +23,14 @@ public class GiveawayFeatureConfig implements FeatureConfig {
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(GiveawayPostTarget.GIVEAWAYS);
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(GiveawayMode.KEY_GIVEAWAYS, GiveawayMode.AUTO_NOTIFY_GIVEAWAY_KEY_WINNERS);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(KEY_GIVEAWAYS_DURATION);
}
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.abstracto.giveaway.config;
import dev.sheldan.abstracto.core.config.FeatureMode;
import lombok.Getter;
@Getter
public enum GiveawayMode implements FeatureMode {
KEY_GIVEAWAYS("keyGiveaways"),
AUTO_NOTIFY_GIVEAWAY_KEY_WINNERS("autoNotifyGiveawayKeyWinners");
private final String key;
GiveawayMode(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.giveaway.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class GiveawayKeyNotFoundException extends AbstractoTemplatableException {
public GiveawayKeyNotFoundException() {
super("Giveaway key not found.");
}
@Override
public String getTemplateName() {
return "giveaway_key_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.giveaway.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class GiveawayNotPossibleException extends AbstractoTemplatableException {
public GiveawayNotPossibleException() {
super("Giveaway not possible.");
}
@Override
public String getTemplateName() {
return "giveaway_not_possible_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -3,7 +3,6 @@ package dev.sheldan.abstracto.giveaway.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import java.time.Duration;
@@ -12,8 +11,10 @@ import java.time.Duration;
@Getter
public class GiveawayCreationRequest {
private Member creator;
private Member benefactor;
private Long creatorId;
private Long serverId;
private Long benefactorId;
private Long giveawayKeyId;
private String title;
private String description;
private Duration duration;

View File

@@ -65,6 +65,9 @@ public class Giveaway {
@Column(name = "message_id", nullable = false)
private Long messageId;
@OneToOne(mappedBy = "giveaway")
private GiveawayKey giveawayKey;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;

View File

@@ -0,0 +1,75 @@
package dev.sheldan.abstracto.giveaway.model.database;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.giveaway.model.database.embed.GiveawayKeyId;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapsId;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "giveaway_key")
@Getter
@Setter
@EqualsAndHashCode
public class GiveawayKey {
@Id
@EmbeddedId
private GiveawayKeyId id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumns(
{
@JoinColumn(name = "giveaway_id", referencedColumnName = "id"),
@JoinColumn(name = "giveaway_server_id", referencedColumnName = "server_id")
})
private Giveaway giveaway;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@MapsId("serverId")
@JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false)
private AServer server;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "creator_user_id", nullable = false)
private AUserInAServer creator;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "winner_user_id")
private AUserInAServer winner;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "benefactor_user_id")
private AUserInAServer benefactor;
@Column(name = "key", nullable = false)
private String key;
@Column(name = "name")
private String name;
@Column(name = "used")
private Boolean used;
@Column(name = "description")
private String description;
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.giveaway.model.database.embed;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Embeddable
@Getter
@Setter
@Builder
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class GiveawayKeyId implements Serializable {
@Column(name = "id")
private Long keyId;
@Column(name = "server_id")
private Long serverId;
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.giveaway.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class GiveawayKeyDisplayModel {
private String key;
private String name;
private Long id;
private String description;
private Boolean used;
private MemberDisplay creator;
private MemberDisplay benefactor;
private MemberDisplay winner;
}

View File

@@ -0,0 +1,11 @@
package dev.sheldan.abstracto.giveaway.model.template;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class GiveawayKeysDisplayModel {
private List<GiveawayKeyDisplayModel> keys;
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.abstracto.giveaway.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.giveaway.model.database.Giveaway;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Builder
@Getter
@Setter
public class GiveawayWinnerNotificationMessageModel {
private String title;
private String description;
private String key;
private Long giveawayId;
private MemberDisplay creator;
private MemberDisplay benefactor;
public static GiveawayWinnerNotificationMessageModel fromGiveaway(Giveaway giveaway, String key) {
return GiveawayWinnerNotificationMessageModel
.builder()
.title(giveaway.getTitle())
.description(giveaway.getDescription())
.key(key)
.benefactor(giveaway.getBenefactor() != null ? MemberDisplay.fromAUserInAServer(giveaway.getBenefactor()) : null)
.creator(MemberDisplay.fromAUserInAServer(giveaway.getCreator()))
.giveawayId(giveaway.getGiveawayId().getId())
.build();
}
}

View File

@@ -8,7 +8,7 @@ import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import java.util.concurrent.CompletableFuture;
public interface GiveawayService {
CompletableFuture<Void> createGiveaway(GiveawayCreationRequest giveawayCreationRequest);
CompletableFuture<Long> createGiveaway(GiveawayCreationRequest giveawayCreationRequest);
CompletableFuture<Void> addGiveawayParticipant(Giveaway giveaway, Member member, MessageChannel messageChannel);
CompletableFuture<Void> evaluateGiveaway(Long giveawayId, Long serverId);
CompletableFuture<Void> cancelGiveaway(Long giveawayId, Long serverId);

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.giveaway.service.management;
import dev.sheldan.abstracto.giveaway.model.database.GiveawayKey;
import java.util.List;
import java.util.Optional;
import net.dv8tion.jda.api.entities.Member;
public interface GiveawayKeyManagementService {
GiveawayKey createGiveawayKey(Member creator, Member benefactor, String key, String description, String name);
void deleteById(Long id, Long serverId);
Optional<GiveawayKey> getById(Long id, Long serverId);
GiveawayKey saveGiveawayKey(GiveawayKey giveawayKey);
List<GiveawayKey> getGiveawayKeys(Long serverId, Boolean ignoreUsedFlag);
}

View File

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

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