Compare commits

...

54 Commits

Author SHA1 Message Date
Sheldan
1b2e7654f9 [maven-release-plugin] prepare release abstracto-application-1.4.22 2023-03-19 00:14:16 +01:00
Sheldan
54976ed1d4 [AB-89] adding command to retrieve weather data 2023-03-19 00:09:43 +01:00
Sheldan
735816f5dd [AB-xxx] adding feature to split field value by configurable amount
using larger images for member avatar
2023-03-05 13:49:32 +01:00
Sheldan
a984bdb84e [AB-86] fixing exception in case of a re-joining user without experience role 2023-02-26 14:16:27 +01:00
Sheldan
21add6585d [maven-release-plugin] prepare for next development iteration 2023-02-26 10:58:01 +01:00
Sheldan
99e72245f3 [maven-release-plugin] prepare release abstracto-application-1.4.21 2023-02-26 10:57:57 +01:00
Sheldan
9d184ff560 [AB-85] adding feature mode to automatically create a thread for suggestions
reworking post target service to function with optionals
fixing trying to add reactions for suggestions if there was no message created
not showing votes in case the buttons feature mode is active
2023-02-26 10:50:36 +01:00
Sheldan
97895f5c56 [AB-84] fixing incorrect calculation for agreement percentage 2023-02-25 18:33:02 +01:00
Sheldan
f091559c49 [AB-84] adding necessary information to suggestion update message 2023-02-25 18:19:01 +01:00
Sheldan
fa7730975e [maven-release-plugin] prepare for next development iteration 2023-02-15 00:05:59 +01:00
Sheldan
03d7b9e2e2 [maven-release-plugin] prepare release abstracto-application-1.4.20 2023-02-15 00:05:51 +01:00
Sheldan
a0bff12263 [AB-xxx] adding default value for user experience object 2023-02-14 23:54:42 +01:00
Sheldan
70e708601e [AB-xxx] exception logging improvements for experience tracking 2023-02-14 21:14:18 +01:00
Sheldan
f01418d0de [AB-xxx] upgrading JDA version to version 5.0.0-beta.3 2023-02-04 20:57:02 +01:00
Sheldan
ad539adb2a [maven-release-plugin] prepare for next development iteration 2023-02-04 18:26:38 +01:00
Sheldan
3c9fec989f [maven-release-plugin] prepare release abstracto-application-1.4.19 2023-02-04 18:26:33 +01:00
Sheldan
21db3e3ee5 [AB-xxx] fixing compatible versions for installer 2023-02-04 18:22:06 +01:00
Sheldan
5b1ad2e075 [maven-release-plugin] prepare for next development iteration 2023-02-04 17:23:34 +01:00
Sheldan
63b3f68bdb [maven-release-plugin] prepare release abstracto-application-1.4.18 2023-02-04 17:23:29 +01:00
Sheldan
7c1537c4a7 Revert "[maven-release-plugin] prepare release abstracto-application-1.4.14"
This reverts commit 9836998087.
2023-02-04 17:21:15 +01:00
Sheldan
9836998087 [maven-release-plugin] prepare release abstracto-application-1.4.14 2023-02-04 17:13:57 +01:00
Sheldan
8a1bb4cad8 [AB-xxx] small installer logging improvement 2023-02-04 17:11:57 +01:00
Sheldan
a9dadec8ef [maven-release-plugin] prepare for next development iteration 2023-02-04 14:52:26 +01:00
Sheldan
3eaffaef87 [maven-release-plugin] prepare release abstracto-application-1.4.17 2023-02-04 14:52:20 +01:00
Sheldan
b3a943e155 [AB-20] adding level up notification configuration possibility 2023-01-28 13:13:38 +01:00
Sheldan
06dd4af131 [maven-release-plugin] prepare for next development iteration 2023-01-12 01:37:16 +01:00
Sheldan
0d51469975 [maven-release-plugin] prepare release abstracto-application-1.4.16 2023-01-12 01:37:12 +01:00
Sheldan
aa10c88588 [AB-81] adding support for choices in slash command parameters 2023-01-12 01:34:30 +01:00
Sheldan
db27f64832 [AB-80] fixing not cleaning up emote record when changing emote 2023-01-10 19:15:27 +01:00
Sheldan
3903039aac [AB-79] fixing test 2022-12-27 13:12:10 +01:00
Sheldan
41f42ee110 [AB-79] fix not applying role for re-joining users upon sync 2022-12-27 13:03:59 +01:00
Sheldan
74f54e1257 [maven-release-plugin] prepare for next development iteration 2022-12-21 13:27:21 +01:00
Sheldan
a72e48f690 [maven-release-plugin] prepare release abstracto-application-1.4.15 2022-12-21 13:27:16 +01:00
Sheldan
a813af8b1f [AB-xxx] fixing issue of not providing percentage to rank correctly 2022-12-21 01:34:32 +01:00
Sheldan
abee7b2732 [AB-xxx] providing more information in the rank model 2022-12-20 22:15:05 +01:00
Sheldan
4c71ffbb7e [maven-release-plugin] prepare for next development iteration 2022-12-10 16:54:36 +01:00
Sheldan
18050e2a8a [maven-release-plugin] prepare release abstracto-application-1.4.14 2022-12-10 16:54:32 +01:00
Sheldan
a0d83763d4 [AB-78] fixing users with previous experience not awarding the experience role if they join and are pending 2022-12-10 15:44:09 +01:00
Sheldan
0461c8e4ec [AB-77] deferring the reply for report with context interactions
do not defer the interaction all the time for modal interactions
2022-12-10 12:59:47 +01:00
Sheldan
072f32975a [maven-release-plugin] prepare for next development iteration 2022-12-03 20:17:11 +01:00
Sheldan
6810d28c50 [maven-release-plugin] prepare release abstracto-application-1.4.13 2022-12-03 20:16:57 +01:00
Sheldan
650a9099c4 [AB-66] fixing not reducing the bid on mines
fixing reaction report sometimes running into a timeout
2022-12-03 20:14:18 +01:00
Sheldan
1217e03725 [maven-release-plugin] prepare for next development iteration 2022-12-02 21:20:57 +01:00
Sheldan
2e837d8738 [maven-release-plugin] prepare release abstracto-application-1.4.12 2022-12-02 21:20:53 +01:00
Sheldan
9ddd386c6f [AB-66] adding min mines ratio and other input restrictions for mines game which should avoid some too easy setups 2022-12-02 21:16:32 +01:00
Sheldan
4e1db26df7 [AB-xxx] fixing java version in build file 2022-12-02 01:58:48 +01:00
Sheldan
de335a1e2a [AB-xxx] updating versions of GitHub actions in release/build job 2022-12-02 01:55:36 +01:00
Sheldan
9ba0ed711e [maven-release-plugin] prepare for next development iteration 2022-12-02 01:36:51 +01:00
Sheldan
b7376bf522 [maven-release-plugin] prepare release abstracto-application-1.4.11 2022-12-02 01:36:46 +01:00
Sheldan
0d6182c5d5 [AB-66] adding mines game
fixing suggestion evaluation job not getting the correct parameters
adding feature dependent parameters
2022-12-02 01:33:38 +01:00
Sheldan
96c38763b1 [maven-release-plugin] prepare for next development iteration 2022-11-20 23:22:21 +01:00
Sheldan
97b3099156 [maven-release-plugin] prepare release abstracto-application-1.4.10 2022-11-20 23:22:16 +01:00
Sheldan
a0b2fc9c31 [AB-25] artificial delay 2022-11-20 23:20:07 +01:00
Sheldan
2db36ef96b [maven-release-plugin] prepare for next development iteration 2022-11-20 22:39:40 +01:00
188 changed files with 2520 additions and 320 deletions

View File

@@ -19,11 +19,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up JDK 1.8
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: 1.8
distribution: 'corretto'
java-version: 8
- name: Build with Maven
run: mvn -B install --file abstracto-application/pom.xml
- name: Setup sonarqube

View File

@@ -6,13 +6,14 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
persist-credentials: false
- name: Set up Java for publishing to GitHub Packages
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: 1.8
distribution: 'corretto'
java-version: 8
- name: Load current version
id: version
run: echo "version=$(mvn --file abstracto-application/pom.xml -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec)" >> $GITHUB_ENV
@@ -21,7 +22,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy documentation to GitHub pages
uses: JamesIves/github-pages-deploy-action@4.1.0
uses: JamesIves/github-pages-deploy-action@v4
with:
repository-name: Sheldan/abstracto-docs
target-folder: docs/${{ env.version }}
@@ -29,7 +30,7 @@ jobs:
ssh-key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
folder: abstracto-application/documentation/target/generated-docs
- name: Deploy documentation to GitHub pages latest
uses: JamesIves/github-pages-deploy-action@4.1.0
uses: JamesIves/github-pages-deploy-action@v4
with:
repository-name: Sheldan/abstracto-docs
target-folder: docs/current
@@ -37,7 +38,7 @@ jobs:
ssh-key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
folder: abstracto-application/documentation/target/generated-docs
- name: Login to GitHub Packages Docker Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: docker.pkg.github.com
username: ${{ github.repository_owner }}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -89,14 +89,14 @@ public class Slots extends AbstractConditionableCommand {
.templated(true)
.build();
Parameter lowParameter = Parameter
Parameter bidParameter = Parameter
.builder()
.name(BID_PARAMETER)
.type(Integer.class)
.templated(true)
.validators(Arrays.asList(MinIntegerValueValidator.min(0L)))
.build();
parameters.add(lowParameter);
parameters.add(bidParameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()

View File

@@ -0,0 +1,230 @@
package dev.sheldan.abstracto.entertainment.command.games;
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.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
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.ServerUser;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
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.entertainment.config.EntertainmentFeatureDefinition;
import dev.sheldan.abstracto.entertainment.config.EntertainmentModuleDefinition;
import dev.sheldan.abstracto.entertainment.config.EntertainmentSlashCommandNames;
import dev.sheldan.abstracto.entertainment.exception.NotEnoughCreditsException;
import dev.sheldan.abstracto.entertainment.model.command.games.MineBoard;
import dev.sheldan.abstracto.entertainment.model.database.EconomyUser;
import dev.sheldan.abstracto.entertainment.service.GameService;
import dev.sheldan.abstracto.entertainment.service.management.EconomyUserManagementService;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class Mines extends AbstractConditionableCommand {
private static final String MINES_COMMAND_NAME = "mines";
private static final String WIDTH_PARAMETER = "width";
private static final String HEIGHT_PARAMETER = "height";
private static final String MINES_PARAMETER = "mines";
private static final String CREDITS_PARAMETER = "credits";
public static final String MINE_BOARD_TEMPLATE_KEY = "mines_board_response";
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private GameService gameService;
@Autowired
private TemplateService templateService;
@Autowired
private EconomyUserManagementService economyUserManagementService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Integer width = 5;
if(slashCommandParameterService.hasCommandOption(WIDTH_PARAMETER, event)) {
width = slashCommandParameterService.getCommandOption(WIDTH_PARAMETER, event, Integer.class);
}
Integer height = 5;
if(slashCommandParameterService.hasCommandOption(HEIGHT_PARAMETER, event)) {
height = slashCommandParameterService.getCommandOption(HEIGHT_PARAMETER, event, Integer.class);
}
Integer mines = 5;
if(slashCommandParameterService.hasCommandOption(MINES_PARAMETER, event)) {
mines = slashCommandParameterService.getCommandOption(MINES_PARAMETER, event, Integer.class);
}
Integer credit = null;
long serverId = event.getGuild().getIdLong();
boolean economyEnabled = featureFlagService.getFeatureFlagValue(EntertainmentFeatureDefinition.ECONOMY, serverId);
if(economyEnabled){
credit = 50;
if(slashCommandParameterService.hasCommandOption(CREDITS_PARAMETER, event)) {
credit = slashCommandParameterService.getCommandOption(CREDITS_PARAMETER, event, Integer.class);
}
Optional<EconomyUser> userOptional = economyUserManagementService.getUser(ServerUser.fromMember(event.getMember()));
if(!userOptional.isPresent()) {
throw new NotEnoughCreditsException();
}
EconomyUser user = userOptional.get();
if(user.getCredits() < credit) {
throw new NotEnoughCreditsException();
}
}
MineBoard board = gameService.createBoard(width, height, mines, serverId);
board.setCreditsEnabled(economyEnabled);
board.setUserId(event.getMember().getIdLong());
board.setServerId(serverId);
board.setCredits(credit);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MINE_BOARD_TEMPLATE_KEY, board);
return interactionService.replyMessageToSend(messageToSend, event)
.thenCompose(interactionHook -> interactionHook.retrieveOriginal().submit())
.thenApply(message -> {
gameService.persistMineBoardMessage(board, message);
return CommandResult.fromSuccess();
});
}
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
Integer width = 5;
List<Object> parameters = commandContext.getParameters().getParameters();
if(!parameters.isEmpty()) {
width = (Integer) parameters.get(0);
}
Integer height = 5;
if(parameters.size() >= 2) {
height = (Integer) parameters.get(1);
}
Integer mines = 5;
if(parameters.size() >= 3) {
mines = (Integer) parameters.get(2);
}
Integer credit = null;
long serverId = commandContext.getGuild().getIdLong();
boolean economyEnabled = featureFlagService.getFeatureFlagValue(EntertainmentFeatureDefinition.ECONOMY, serverId);
if(economyEnabled){
credit = 50;
if(parameters.size() == 4) {
credit = (Integer) parameters.get(3);
}
Optional<EconomyUser> userOptional = economyUserManagementService.getUser(ServerUser.fromMember(commandContext.getAuthor()));
if(!userOptional.isPresent()) {
throw new NotEnoughCreditsException();
}
EconomyUser user = userOptional.get();
if(user.getCredits() < credit) {
throw new NotEnoughCreditsException();
}
}
MineBoard board = gameService.createBoard(width, height, mines, serverId);
board.setCreditsEnabled(economyEnabled);
board.setUserId(commandContext.getAuthor().getIdLong());
board.setServerId(serverId);
board.setCredits(credit);
MessageToSend messageToSend = templateService.renderEmbedTemplate(MINE_BOARD_TEMPLATE_KEY, board);
List<CompletableFuture<Message>> futures = channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel());
return FutureUtils.toSingleFutureGeneric(futures)
.thenAccept(unused -> gameService.persistMineBoardMessage(board, futures.get(0).join()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
Parameter widthParameter = Parameter
.builder()
.name(WIDTH_PARAMETER)
.type(Integer.class)
.optional(true)
.templated(true)
.build();
parameters.add(widthParameter);
Parameter heightParameter = Parameter
.builder()
.name(HEIGHT_PARAMETER)
.type(Integer.class)
.optional(true)
.templated(true)
.build();
parameters.add(heightParameter);
Parameter minesParameter = Parameter
.builder()
.name(MINES_PARAMETER)
.type(Integer.class)
.optional(true)
.templated(true)
.build();
parameters.add(minesParameter);
Parameter creditsParameter = Parameter
.builder()
.name(CREDITS_PARAMETER)
.type(Integer.class)
.optional(true)
.templated(true)
.dependentFeatures(Arrays.asList(EntertainmentFeatureDefinition.ECONOMY.getKey()))
.build();
parameters.add(creditsParameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(EntertainmentSlashCommandNames.GAME)
.commandName(MINES_COMMAND_NAME)
.build();
return CommandConfiguration.builder()
.name(MINES_COMMAND_NAME)
.slashCommandConfig(slashCommandConfig)
.async(true)
.module(EntertainmentModuleDefinition.ENTERTAINMENT)
.templated(true)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return EntertainmentFeatureDefinition.GAMES;
}
}

View File

@@ -0,0 +1,79 @@
package dev.sheldan.abstracto.entertainment.listener.interaction;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.entertainment.command.games.Mines;
import dev.sheldan.abstracto.entertainment.config.EntertainmentFeatureDefinition;
import dev.sheldan.abstracto.entertainment.model.command.games.MineBoard;
import dev.sheldan.abstracto.entertainment.model.command.games.MineBoardPayload;
import dev.sheldan.abstracto.entertainment.service.GameService;
import dev.sheldan.abstracto.entertainment.service.GameServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MinesButtonClickedListener implements ButtonClickedListener {
@Autowired
private GameService gameService;
@Autowired
private TemplateService templateService;
@Autowired
private InteractionService interactionService;
@Autowired
private FeatureFlagService featureFlagService;
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
MineBoardPayload payload = (MineBoardPayload) model.getDeserializedPayload();
if(model.getEvent().getUser().getIdLong() != payload.getMineBoard().getUserId()) {
return ButtonClickedListenerResult.IGNORED;
}
MineBoard mineBoard = payload.getMineBoard();
if(!mineBoard.getState().equals(GameService.MineResult.CONTINUE)) {
return ButtonClickedListenerResult.IGNORED;
}
GameService.MineResult mineResult = gameService.uncoverField(mineBoard, payload.getX(), payload.getY());
mineBoard.setState(mineResult);
if(mineBoard.getState() != GameService.MineResult.CONTINUE) {
if(featureFlagService.getFeatureFlagValue(EntertainmentFeatureDefinition.ECONOMY, model.getServerId())){
gameService.evaluateCreditChanges(mineBoard);
}
gameService.uncoverBoard(mineBoard);
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(Mines.MINE_BOARD_TEMPLATE_KEY, mineBoard);
interactionService.editOriginal(messageToSend, model.getEvent().getHook()).thenAccept(message -> {
gameService.updateMineBoard(mineBoard);
log.info("Updated original mineboard for board {}.", mineBoard.getBoardId());
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
}
@Override
public FeatureDefinition getFeature() {
return EntertainmentFeatureDefinition.GAMES;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return model.getOrigin().equals(GameServiceBean.MINES_BUTTON_ORIGIN);
}
}

View File

@@ -16,6 +16,7 @@ import java.util.Optional;
@Repository
public interface EconomyUserRepository extends JpaRepository<EconomyUser, Long> {
Optional<EconomyUser> findByUser(AUserInAServer aUserInAServer);
Optional<EconomyUser> findByServer_IdAndUser_UserReference_Id(Long serverId, Long userId);
@Query(value = "WITH economy_user_ranked AS" +
"( " +

View File

@@ -0,0 +1,312 @@
package dev.sheldan.abstracto.entertainment.service;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.entertainment.exception.InvalidGameBoardException;
import dev.sheldan.abstracto.entertainment.model.command.games.MineBoard;
import dev.sheldan.abstracto.entertainment.model.command.games.MineBoardField;
import dev.sheldan.abstracto.entertainment.model.command.games.MineBoardPayload;
import dev.sheldan.abstracto.entertainment.model.command.games.MineBoardRow;
import dev.sheldan.abstracto.entertainment.model.database.EconomyUser;
import dev.sheldan.abstracto.entertainment.service.management.EconomyUserManagementService;
import net.dv8tion.jda.api.entities.Message;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.security.SecureRandom;
import java.util.*;
import java.util.stream.Collectors;
import static dev.sheldan.abstracto.entertainment.config.GamesFeatureConfig.MINES_CREDITS_FACTOR;
import static dev.sheldan.abstracto.entertainment.config.GamesFeatureConfig.MINES_MINIMUM_MINES_RATIO;
@Component
public class GameServiceBean implements GameService {
public static final String MINES_BUTTON_ORIGIN = "MINES_BUTTON";
@Autowired
private SecureRandom secureRandom;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadService componentPayloadService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private EconomyService economyService;
@Autowired
private EconomyUserManagementService economyUserManagementService;
@Autowired
private ConfigService configService;
@Override
public MineBoard createBoard(Integer width, Integer height, Integer mines, Long serverId) {
double minMinesRatio = configService.getDoubleValueOrConfigDefault(MINES_MINIMUM_MINES_RATIO, serverId);
if(mines >= width * height || width > 5 || height > 5 || mines <= 1 || height <= 1 || width <= 1) {
throw new InvalidGameBoardException(minMinesRatio);
}
if((double) mines / (width * height) < minMinesRatio) {
throw new InvalidGameBoardException(minMinesRatio);
}
MineBoard mineBoard = generateEmptyBoard(width, height);
mineBoard.setMineCount(mines);
fillWithMines(mineBoard);
evaluateCounters(mineBoard);
return mineBoard;
}
@Override
@Transactional
public void persistMineBoardMessage(MineBoard mineBoard, Message message) {
mineBoard.setMessageId(message.getIdLong());
mineBoard.setChannelId(message.getChannel().getIdLong());
AServer server = serverManagementService.loadServer(message.getGuild());
mineBoard.getFields().forEach(mineBoardField -> {
MineBoardPayload payload = MineBoardPayload
.builder()
.x(mineBoardField.getX())
.y(mineBoardField.getY())
.mineBoard(mineBoard)
.build();
String componentId = mineBoard.getBoardId() + "_" + mineBoardField.getX() + "_" + mineBoardField.getY();
componentPayloadService.createButtonPayload(componentId, payload, MINES_BUTTON_ORIGIN, server);
});
}
@Override
@Transactional
public void updateMineBoard(MineBoard mineBoard) {
mineBoard.getFields().forEach(mineBoardField -> {
MineBoardPayload newPayload = MineBoardPayload
.builder()
.x(mineBoardField.getX())
.y(mineBoardField.getY())
.mineBoard(mineBoard)
.build();
String componentId = mineBoard.getBoardId() + "_" + mineBoardField.getX() + "_" + mineBoardField.getY();
componentPayloadService.updateButtonPayload(componentId, newPayload);
});
}
@Override
public void uncoverBoard(MineBoard mineBoard) {
mineBoard.getFields().forEach(mineBoardField -> {
if(mineBoardField.getType().equals(MineBoardField.MineBoardFieldType.COVERED)) {
mineBoardField.setType(MineBoardField.MineBoardFieldType.UNCOVERED);
}
});
}
@Override
public void evaluateCreditChanges(MineBoard mineBoard) {
Long credits = mineBoard.getCredits().longValue();
Integer mineCount = mineBoard.getMineCount();
List<MineBoardField> allFields = mineBoard.getFields();
Integer leftFields = (int) allFields
.stream()
.filter(mineBoardField -> mineBoardField.getType().equals(MineBoardField.MineBoardFieldType.COVERED)
|| mineBoardField.getType().equals(MineBoardField.MineBoardFieldType.MINE))
.count();
Integer fieldCount = allFields.size();
Integer uncoveredFields = fieldCount - leftFields;
Long creditChange = (long) (calculateCreditFactor(fieldCount, mineCount, uncoveredFields) * credits);
if(mineBoard.getState().equals(MineResult.WON)) {
Double factor = configService.getDoubleValueOrConfigDefault(MINES_CREDITS_FACTOR, mineBoard.getServerId());
creditChange = (long) (creditChange * factor);
}
ServerUser serverUser = ServerUser
.builder()
.serverId(mineBoard.getServerId())
.userId(mineBoard.getUserId())
.build();
Optional<EconomyUser> economyUserOptional = economyUserManagementService.getUser(serverUser);
if(economyUserOptional.isPresent()) {
economyService.addCredits(economyUserOptional.get(), -credits);
economyService.addCredits(economyUserOptional.get(), creditChange);
}
mineBoard.setCreditChange(creditChange);
}
private double calculateCreditFactor(int totalFields, int mines, int uncovered) {
return ((double) uncovered / (totalFields - mines)) + totalFields / 25.0 - ((totalFields - mines) / 25.0);
}
@Override
public MineResult uncoverField(MineBoard board, Integer x, Integer y) {
return uncoverFieldOnBoard(board, x, y);
}
public GameService.MineResult uncoverFieldOnBoard(MineBoard board, Integer x, Integer y) {
MineBoardField field = board.getField(x, y);
if(!MineBoardField.canInteract(field.getType())) {
return GameService.MineResult.CONTINUE;
}
if(field.getType().equals(MineBoardField.MineBoardFieldType.MINE)) {
field.setType(MineBoardField.MineBoardFieldType.EXPLODED);
return GameService.MineResult.LOST;
}
if(field.getType().equals(MineBoardField.MineBoardFieldType.COVERED)) {
if(field.getCounterValue() == 0) {
Set<String> alreadyConsidered = new HashSet<>();
Queue<MineBoardField> toUncover = new LinkedList<>();
toUncover.add(field);
while(!toUncover.isEmpty()) {
MineBoardField fieldToHandle = toUncover.poll();
fieldToHandle.setType(MineBoardField.MineBoardFieldType.UNCOVERED);
alreadyConsidered.add(fieldToHandle.getX() + "_" + fieldToHandle.getY());
// only when we actually got a free field, we should add its neighbors to the next one to uncover
if(fieldToHandle.getCounterValue() == 0) {
List<MineBoardField> neighbors = getNeighbors(board, fieldToHandle.getX(), fieldToHandle.getY(), false);
List<MineBoardField> uncoverableNeighbors = neighbors
.stream().filter(mineBoardField -> mineBoardField.getType().equals(MineBoardField.MineBoardFieldType.COVERED))
.collect(Collectors.toList());
uncoverableNeighbors.forEach(mineBoardField -> {
if(!alreadyConsidered.contains(mineBoardField.getX() + "_" + mineBoardField.getY())) {
mineBoardField.setType(MineBoardField.MineBoardFieldType.UNCOVERED);
// only if t he newly found neighbor is a free field, we should discover its neighbors
if(mineBoardField.getCounterValue() == 0) {
toUncover.addAll(uncoverableNeighbors);
}
}
});
}
}
} else {
field.setType(MineBoardField.MineBoardFieldType.UNCOVERED);
}
if(hasWon(board)) {
return GameService.MineResult.WON;
}
return GameService.MineResult.CONTINUE;
}
throw new IllegalStateException("Did not find correct type of field.");
}
private List<MineBoardField> getNeighbors(MineBoard mineBoard, int xPosition, int yPosition) {
return getNeighbors(mineBoard, xPosition, yPosition, false);
}
private List<MineBoardField> getNeighbors(MineBoard board, int xPosition, int yPosition, boolean directOnly) {
List<MineBoardField> neighbors = new ArrayList<>();
boolean isFirstRow = yPosition == 0;
boolean isLastRow = yPosition == board.getRowCount() - 1;
boolean isFirstColumn = xPosition == 0;
boolean isLastColumn = xPosition == board.getColumnCount() - 1;
if(!isFirstColumn) {
if(!isFirstRow && !directOnly) {
neighbors.add(board.getField(xPosition - 1, yPosition - 1));
}
neighbors.add(board.getField(xPosition - 1, yPosition));
if(!isLastRow && !directOnly) {
neighbors.add(board.getField(xPosition - 1, yPosition + 1));
}
}
if(!isFirstRow && !directOnly) {
neighbors.add(board.getField(xPosition, yPosition - 1));
}
if(!isLastRow && !directOnly) {
neighbors.add(board.getField(xPosition, yPosition + 1));
}
if(!isLastColumn) {
if(!isFirstRow && !directOnly) {
neighbors.add(board.getField(xPosition + 1, yPosition - 1));
}
neighbors.add(board.getField(xPosition + 1, yPosition));
if(!isLastRow && !directOnly) {
neighbors.add(board.getField(xPosition + 1, yPosition + 1));
}
}
return neighbors;
}
public MineBoard generateEmptyBoard(Integer width, Integer height) {
List<MineBoardRow> rows = new ArrayList<>();
for (int y = 0; y < height; y++) {
List<MineBoardField> fields = new ArrayList<>();
for (int x = 0; x < width; x++) {
MineBoardField field = MineBoardField
.builder()
.y(y)
.x(x)
.counterValue(0)
.type(MineBoardField.MineBoardFieldType.COVERED)
.build();
fields.add(field);
}
MineBoardRow row = MineBoardRow
.builder()
.fields(fields)
.build();
rows.add(row);
}
return MineBoard
.builder()
.rows(rows)
.state(MineResult.CONTINUE)
.columnCount(width)
.creditChange(0L)
.rowCount(height)
.boardId(UUID.randomUUID().toString())
.build();
}
public void fillWithMines(MineBoard board) {
Set<String> usedKeys = new HashSet<>();
int maxIterations = 1_0000_000;
int iterations = 0;
List<Pair<Integer, Integer>> foundPositions = new ArrayList<>();
do {
int x = secureRandom.nextInt(board.getColumnCount());
int y = secureRandom.nextInt(board.getRowCount());
String positionKey = x + "_" + y;
if(!usedKeys.contains(positionKey)) {
foundPositions.add(Pair.of(x, y));
usedKeys.add(positionKey);
iterations = 0;
}
iterations++;
} while(foundPositions.size() < board.getMineCount() && iterations < maxIterations);
foundPositions.forEach(xYPair -> board.getRows().get(xYPair.getRight()).getFields().get(xYPair.getLeft()).setType(MineBoardField.MineBoardFieldType.MINE));
}
public void evaluateCounters(MineBoard board) {
board.getRows().forEach(mineBoardRow -> mineBoardRow.getFields().forEach(mineBoardField -> {
if(!mineBoardField.getType().equals(MineBoardField.MineBoardFieldType.MINE)) {
long mineCounts = getMineCounts(board, mineBoardField.getX(), mineBoardField.getY());
mineBoardField.setCounterValue((int) mineCounts);
}
}));
}
private long getMineCounts(MineBoard board, int xPosition, int yPosition) {
List<MineBoardField> neighbors = getNeighbors(board, xPosition, yPosition);
return neighbors
.stream()
.filter(mineBoardField -> mineBoardField.getType().equals(MineBoardField.MineBoardFieldType.MINE))
.count();
}
private boolean hasWon(MineBoard board) {
return board
.getFields()
.stream()
.noneMatch(mineBoardField ->
mineBoardField.getType().equals(MineBoardField.MineBoardFieldType.COVERED)
&& !mineBoardField.getType().equals(MineBoardField.MineBoardFieldType.MINE));
}
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.entertainment.service.management;
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.entertainment.model.database.EconomyLeaderboardResult;
@@ -39,6 +40,11 @@ public class EconomyUserManagementServiceBean implements EconomyUserManagementSe
return repository.findByUser(aUserInAServer);
}
@Override
public Optional<EconomyUser> getUser(ServerUser serverUser) {
return repository.findByServer_IdAndUser_UserReference_Id(serverUser.getServerId(), serverUser.getUserId());
}
@Override
public EconomyLeaderboardResult getRankOfUserInServer(AUserInAServer aUserInAServer) {
return repository.getRankOfUserInServer(aUserInAServer.getUserInServerId(), aUserInAServer.getServerReference().getId());

View File

@@ -20,6 +20,15 @@ abstracto.systemConfigs.slotsCooldown.longValue=30
abstracto.featureFlags.economy.featureName=economy
abstracto.featureFlags.economy.enabled=false
abstracto.featureFlags.games.featureName=games
abstracto.featureFlags.games.enabled=false
abstracto.systemConfigs.minesCreditsFactor.name=minesCreditsFactor
abstracto.systemConfigs.minesCreditsFactor.doubleValue=5
abstracto.systemConfigs.minesMinMineRatio.name=minesMinMineRatio
abstracto.systemConfigs.minesMinMineRatio.doubleValue=0.2
# for now this is fine
abstracto.systemConfigs.creditGambleJackpot.name=creditGambleJackpot
abstracto.systemConfigs.creditGambleJackpot.longValue=1000

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,4 +11,5 @@
<include file="1.2.9-entertainment/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.0/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.3/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.11/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

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

View File

@@ -5,7 +5,7 @@ import lombok.Getter;
@Getter
public enum EntertainmentFeatureDefinition implements FeatureDefinition {
ENTERTAINMENT("entertainment"), ECONOMY("economy");
ENTERTAINMENT("entertainment"), ECONOMY("economy"), GAMES("games");
private String key;

View File

@@ -4,4 +4,5 @@ public class EntertainmentSlashCommandNames {
public static final String ENTERTAINMENT = "entertainment";
public static final String UTILITY = "utility";
public static final String ECONOMY = "economy";
public static final String GAME = "game";
}

View File

@@ -0,0 +1,26 @@
package dev.sheldan.abstracto.entertainment.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class GamesFeatureConfig implements FeatureConfig {
public static final String MINES_CREDITS_FACTOR = "minesCreditsFactor";
public static final String MINES_MINIMUM_MINES_RATIO = "minesMinMineRatio";
@Override
public FeatureDefinition getFeature() {
return EntertainmentFeatureDefinition.GAMES;
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(MINES_CREDITS_FACTOR, MINES_MINIMUM_MINES_RATIO);
}
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.entertainment.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
import dev.sheldan.abstracto.entertainment.model.exception.InvalidGameBoardExceptionModel;
public class InvalidGameBoardException extends AbstractoTemplatableException {
private final InvalidGameBoardExceptionModel model;
public InvalidGameBoardException(Double minRatio) {
super();
this.model = InvalidGameBoardExceptionModel
.builder()
.minMinesRatio(minRatio)
.build();
}
@Override
public String getTemplateName() {
return "invalid_mine_board_config_exception";
}
@Override
public Object getTemplateModel() {
return model;
}
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.abstracto.entertainment.model.command.games;
import dev.sheldan.abstracto.entertainment.service.GameService;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@Builder
public class MineBoard {
private String boardId;
private Long userId;
private Long serverId;
private Long messageId;
private Long channelId;
private Integer rowCount;
private Integer columnCount;
private Integer credits;
private Long creditChange;
private boolean creditsEnabled;
private Integer mineCount;
private List<MineBoardRow> rows;
private GameService.MineResult state;
public MineBoardField getField(int x, int y) {
if(x > columnCount || y > rowCount) {
throw new IllegalArgumentException("Out of bounds access to board.");
}
MineBoardRow mineBoardRow = rows.get(y);
return mineBoardRow.getFields().get(x);
}
public List<MineBoardField> getFields() {
List<MineBoardField> fields = new ArrayList<>();
rows.forEach(mineBoardRow -> fields.addAll(mineBoardRow.getFields()));
return fields;
}
}

View File

@@ -0,0 +1,25 @@
package dev.sheldan.abstracto.entertainment.model.command.games;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Builder
@Setter
public class MineBoardField {
private MineBoardField.MineBoardFieldType type;
private Integer x;
private Integer y;
private Integer counterValue;
public enum MineBoardFieldType {
MINE, UNCOVERED, COVERED, EXPLODED
}
public static boolean canInteract(MineBoardFieldType currentType) {
return currentType != MineBoardFieldType.UNCOVERED && currentType != MineBoardFieldType.EXPLODED;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.entertainment.model.command.games;
import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Builder
@Setter
public class MineBoardPayload implements ButtonPayload {
private MineBoard mineBoard;
private Integer x;
private Integer y;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.entertainment.model.command.games;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
@Builder
public class MineBoardRow {
private List<MineBoardField> fields;
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.entertainment.model.exception;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class InvalidGameBoardExceptionModel {
private Double minMinesRatio;
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.entertainment.service;
import dev.sheldan.abstracto.entertainment.model.command.games.MineBoard;
import net.dv8tion.jda.api.entities.Message;
public interface GameService {
MineBoard createBoard(Integer width, Integer height, Integer mines, Long serverId);
void persistMineBoardMessage(MineBoard mineBoard, Message message);
void updateMineBoard(MineBoard mineBoard);
void uncoverBoard(MineBoard mineBoard);
void evaluateCreditChanges(MineBoard mineBoard);
MineResult uncoverField(MineBoard board, Integer x, Integer y);
enum MineResult {
WON, LOST, CONTINUE
}
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.entertainment.service.management;
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.entertainment.model.database.EconomyLeaderboardResult;
@@ -11,6 +12,7 @@ import java.util.Optional;
public interface EconomyUserManagementService {
EconomyUser createUser(AUserInAServer aUserInAServer);
Optional<EconomyUser> getUser(AUserInAServer aUserInAServer);
Optional<EconomyUser> getUser(ServerUser serverUser);
EconomyLeaderboardResult getRankOfUserInServer(AUserInAServer aUserInAServer);
List<EconomyUser> getRanksInServer(AServer server, Integer page, Integer size);
}

View File

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

View File

@@ -0,0 +1,103 @@
package dev.sheldan.abstracto.experience.command;
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.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
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.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
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.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class ExpLevelUpNotification extends AbstractConditionableCommand {
private static final String FLAG_PARAMETER = "newValue";
private static final String EXP_LEVEL_UP_NOTIFICATION_COMMAND = "expLevelUpNotification";
private static final String EXP_LEVEL_UP_NOTIFICATION_RESPONSE = "expLevelUpNotification_response";
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private AUserExperienceService aUserExperienceService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Override
public CommandResult execute(CommandContext commandContext) {
Boolean newValue = (Boolean) commandContext.getParameters().getParameters().get(0);
updateExpLevelNotification(commandContext.getAuthor(), newValue);
return CommandResult.fromSuccess();
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Boolean newValue = slashCommandParameterService.getCommandOption(FLAG_PARAMETER, event, Boolean.class);
updateExpLevelNotification(event.getMember(), newValue);
return interactionService.replyEmbed(EXP_LEVEL_UP_NOTIFICATION_RESPONSE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
private void updateExpLevelNotification(Member member, Boolean newValue) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
aUserExperienceService.setLevelUpNotification(aUserInAServer, newValue);
}
@Override
public FeatureDefinition getFeature() {
return ExperienceFeatureDefinition.EXPERIENCE;
}
@Override
public CommandConfiguration getConfiguration() {
Parameter memberParameter = Parameter
.builder()
.name(FLAG_PARAMETER)
.templated(true)
.type(Boolean.class)
.build();
List<Parameter> parameters = Arrays.asList(memberParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ExperienceSlashCommandNames.EXPERIENCE)
.commandName(EXP_LEVEL_UP_NOTIFICATION_COMMAND)
.build();
return CommandConfiguration.builder()
.name(EXP_LEVEL_UP_NOTIFICATION_COMMAND)
.module(ExperienceModuleDefinition.EXPERIENCE)
.slashCommandConfig(slashCommandConfig)
.causesReaction(true)
.supportsEmbedException(true)
.templated(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
}

View File

@@ -104,7 +104,17 @@ public class Rank extends AbstractConditionableCommand {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(toRender);
AUserExperience experienceObj = userExperienceManagementService.findUserInServer(aUserInAServer);
log.info("Rendering rank for user {} in server {}.", toRender.getId(), toRender.getGuild().getId());
rankModel.setExperienceToNextLevel(experienceLevelService.calculateExperienceToNextLevel(experienceObj.getCurrentLevel().getLevel(), experienceObj.getExperience()));
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;
rankModel.setExperienceForCurrentLevel(currentExpNeeded);
rankModel.setCurrentLevelPercentage(((float) inLevelExperience / levelExperience) * 100);
rankModel.setLevelExperience(levelExperience);
rankModel.setExperienceToNextLevel(experienceNeededToNextLevel);
rankModel.setInLevelExperience(inLevelExperience);
rankModel.setNextLevelExperience(nextLevelExperience);
return templateService.renderEmbedTemplate(RANK_POST_EMBED_TEMPLATE, rankModel, toRender.getGuild().getIdLong());
}

View File

@@ -37,12 +37,16 @@ public class JoiningUserRoleListener implements AsyncJoinListener {
@Override
public DefaultListenerResult execute(MemberJoinModel model) {
if(model.getMember().isPending()) {
log.info("Joining member {} in guild {} is still pending - ignoring for experience role assignment.", model.getJoiningUser().getUserId(), model.getJoiningUser().getServerId());
return DefaultListenerResult.IGNORED;
}
Optional<AUserInAServer> userInAServerOptional = userInServerManagementService.loadUserOptional(model.getServerId(), model.getJoiningUser().getUserId());
userInAServerOptional.ifPresent(aUserInAServer -> {
Optional<AUserExperience> userExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(aUserInAServer.getUserInServerId());
if(userExperienceOptional.isPresent()) {
log.info("User {} joined {} with previous experience. Setting up experience role again (if necessary).", model.getJoiningUser().getUserId(), model.getServerId());
userExperienceService.syncForSingleUser(userExperienceOptional.get(), model.getMember()).thenAccept(result ->
userExperienceService.syncForSingleUser(userExperienceOptional.get(), model.getMember(), true).thenAccept(result ->
log.info("Finished re-assigning experience for re-joining user {} in server {}.", model.getJoiningUser().getUserId(), model.getServerId())
);
} else {

View File

@@ -0,0 +1,60 @@
package dev.sheldan.abstracto.experience.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncUpdatePendingListener;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.MemberUpdatePendingModel;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* If a {@link Member member} updates the pending status, this {@link AsyncUpdatePendingListener listener} retrieves the previously stored {@link AUserExperience experience} and gives the
* member the necessary {@link net.dv8tion.jda.api.entities.Role role} according to the current configuration, if any
*/
@Component
@Slf4j
public class MemberPendingRoleListener implements AsyncUpdatePendingListener {
@Autowired
private UserExperienceManagementService userExperienceManagementService;
@Autowired
private AUserExperienceService userExperienceService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override
public DefaultListenerResult execute(MemberUpdatePendingModel model) {
Optional<AUserInAServer> userInAServerOptional = userInServerManagementService.loadUserOptional(model.getServerId(), model.getUser().getUserId());
userInAServerOptional.ifPresent(aUserInAServer -> {
Optional<AUserExperience> userExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(aUserInAServer.getUserInServerId());
if(userExperienceOptional.isPresent()) {
log.info("User {} updated pending status {} with previous experience. Setting up experience role again (if necessary).", model.getUser().getUserId(), model.getServerId());
userExperienceService.syncForSingleUser(userExperienceOptional.get(), model.getMember(), true).thenAccept(result ->
log.info("Finished re-assigning experience for update pending user {} in server {}.", model.getUser().getUserId(), model.getServerId())
);
} else {
log.info("Member updating pending {} in server {} does not have any previous experience. Not setting up anything.", model.getUser().getUserId(), model.getServerId());
}
});
return DefaultListenerResult.PROCESSED;
}
@Override
public FeatureDefinition getFeature() {
return ExperienceFeatureDefinition.EXPERIENCE;
}
}

View File

@@ -138,7 +138,10 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
// we store when the user is eligible for experience _again_
Long maxSeconds = configService.getLongValueOrConfigDefault(EXP_COOLDOWN_SECONDS_KEY, serverId);
serverExperience.put(userId, Instant.now().plus(maxSeconds, ChronoUnit.SECONDS));
CompletableFuture.runAsync(() -> self.addExperienceToMember(member, message), experienceUpdateExecutor);
CompletableFuture.runAsync(() -> self.addExperienceToMember(member, message), experienceUpdateExecutor).exceptionally(throwable -> {
log.error("Failed to add experience to member {} in server {}.", message.getAuthor().getId(), message.getGuild().getIdLong(), throwable);
return null;
});
}
} finally {
runTimeExperienceService.releaseLock();
@@ -188,9 +191,14 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
List<List<Long>> partitionedUserIds = ListUtils.partition(userIds, 100);
List<CompletableFuture<List<Member>>> memberLoadingFutures = new ArrayList<>();
partitionedUserIds.forEach(userIdsPart -> {
for (List<Long> userIdsPart : partitionedUserIds) {
memberLoadingFutures.add(memberService.getMembersInServerAsync(server.getId(), userIdsPart));
});
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
log.error("Failed to sleep.", e);
}
}
CompletableFutureList<List<Member>> listCompletableFutureList = new CompletableFutureList<>(memberLoadingFutures);
listCompletableFutureList.getMainFuture().whenComplete((result, throwable) -> {
List<Member> members = new ArrayList<>();
@@ -247,17 +255,17 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
public CompletableFuture<Void> syncUser(Member member, List<AExperienceRole> roles) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
AUserExperience userExperience = userExperienceManagementService.findByUserInServerId(aUserInAServer.getUserInServerId());
return calculateAndApplyExperienceRole(userExperience, member, roles);
return calculateAndApplyExperienceRole(userExperience, member, roles, false);
}
@Override
public CompletableFuture<Void> syncForSingleUser(AUserExperience userExperience, Member member) {
public CompletableFuture<Void> syncForSingleUser(AUserExperience userExperience, Member member, boolean forceRoles) {
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(userExperience.getServer());
roles.sort(Comparator.comparing(role -> role.getLevel().getLevel()));
return calculateAndApplyExperienceRole(userExperience, member, roles);
return calculateAndApplyExperienceRole(userExperience, member, roles, forceRoles);
}
private CompletableFuture<Void> calculateAndApplyExperienceRole(AUserExperience userExperience, Member member, List<AExperienceRole> roles) {
private CompletableFuture<Void> calculateAndApplyExperienceRole(AUserExperience userExperience, Member member, List<AExperienceRole> roles, boolean forceRoles) {
AExperienceRole calculatedNewRole = experienceRoleService.calculateRole(roles, userExperience.getCurrentLevel().getLevel());
Long oldRoleId = userExperience.getCurrentExperienceRole() != null && userExperience.getCurrentExperienceRole().getRole() != null ? userExperience.getCurrentExperienceRole().getRole().getId() : null;
Long newRoleId = calculatedNewRole != null ? calculatedNewRole.getRole().getId() : null;
@@ -265,7 +273,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
userExperience.setCurrentExperienceRole(calculatedNewRole);
CompletableFuture<Void> returningFuture;
if(!Objects.equals(oldRoleId, newRoleId)) {
if(!Objects.equals(oldRoleId, newRoleId) || forceRoles) {
CompletableFuture<Void> addingFuture;
if(oldRoleId != null) {
addingFuture = roleService.removeRoleFromMemberAsync(member, oldRoleId);
@@ -331,11 +339,13 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
oldLevel);
aUserExperience.setCurrentLevel(newLevel);
AExperienceRole calculatedNewRole = experienceRoleService.calculateRole(roles, newLevel.getLevel());
Long oldRoleId = aUserExperience.getCurrentExperienceRole() != null ? aUserExperience.getCurrentExperienceRole().getRole().getId() : null;
Long newRoleId = calculatedNewRole != null ? calculatedNewRole.getRole().getId() : null;
Long oldRoleId = aUserExperience.getCurrentExperienceRole() != null && aUserExperience.getCurrentExperienceRole().getRole() != null ? aUserExperience.getCurrentExperienceRole().getRole().getId() : null;
Long newRoleId = calculatedNewRole != null && calculatedNewRole.getRole() != null ? calculatedNewRole.getRole().getId() : null;
result.setOldRoleId(oldRoleId);
result.setNewRoleId(newRoleId);
if(message != null && featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, serverId, ExperienceFeatureMode.LEVEL_UP_NOTIFICATION)) {
if(message != null
&& aUserExperience.getLevelUpNotification()
&& featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, serverId, ExperienceFeatureMode.LEVEL_UP_NOTIFICATION)) {
LevelUpNotificationModel model = LevelUpNotificationModel
.builder()
.memberDisplay(MemberDisplay.fromMember(member))
@@ -424,6 +434,13 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
userExperience.setExperienceGainDisabled(false);
}
@Override
public void setLevelUpNotification(AUserInAServer aUserInAServer, Boolean newValue) {
Optional<AUserExperience> aUserExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(aUserInAServer.getUserInServerId());
AUserExperience aUserExperience = aUserExperienceOptional.orElseGet(() -> userExperienceManagementService.createUserInServer(aUserInAServer));
aUserExperience.setLevelUpNotification(newValue);
}
/**
* Renders a {@link MessageToSend messageToSend} to be used as a status message for the ongoing user synchronization
* @param current The amount of users which have been synced

View File

@@ -57,9 +57,14 @@ public class ExperienceLevelServiceBean implements ExperienceLevelService {
@Override
public Long calculateExperienceToNextLevel(Integer level, Long currentExperience) {
AExperienceLevel nextLevel = experienceLevelManagementService.getLevelOptional(level + 1)
.orElseThrow(() -> new AbstractoRunTimeException(String.format("Could not find level %s", level)));
AExperienceLevel nextLevel = calculateNextLevel(level);
return nextLevel.getExperienceNeeded() - currentExperience;
}
@Override
public AExperienceLevel calculateNextLevel(Integer level) {
return experienceLevelManagementService.getLevelOptional(level + 1)
.orElseThrow(() -> new AbstractoRunTimeException(String.format("Could not find level %s", level)));
}
}

View File

@@ -58,6 +58,7 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
.messageCount(0L)
.server(aUserInAServer.getServerReference())
.experienceGainDisabled(false)
.levelUpNotification(true)
.user(aUserInAServer)
.id(aUserInAServer.getUserInServerId())
.currentLevel(startingLevel)

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="user_experience-add_level_up_notification">
<addColumn tableName="user_experience">
<column name="level_up_notification" type="BOOLEAN" defaultValueBoolean="true"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -9,4 +9,5 @@
<include file="1.0-experience/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.15/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.8/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.17/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -67,7 +67,7 @@ public class JoiningUserRoleListenerTest {
public void testUserWithExperienceRejoining() {
AUserExperience experience = Mockito.mock(AUserExperience.class);
when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(experience));
when(userExperienceService.syncForSingleUser(experience, member)).thenReturn(CompletableFuture.completedFuture(null));
when(userExperienceService.syncForSingleUser(experience, member, true)).thenReturn(CompletableFuture.completedFuture(null));
when(model.getMember()).thenReturn(member);
DefaultListenerResult result = testUnit.execute(model);
Assert.assertEquals(DefaultListenerResult.PROCESSED, result);
@@ -75,9 +75,18 @@ public class JoiningUserRoleListenerTest {
@Test
public void testUserWithOutExperienceRejoining() {
when(model.getMember()).thenReturn(member);
when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.empty());
testUnit.execute(model);
verify(userExperienceService, times(0)).syncForSingleUser(any(), any());
verify(userExperienceService, times(0)).syncForSingleUser(any(), any(), eq(true));
}
@Test
public void testPendingUserJoining() {
when(member.isPending()).thenReturn(true);
when(model.getMember()).thenReturn(member);
testUnit.execute(model);
verify(userExperienceService, times(0)).syncForSingleUser(any(), any(), eq(true));
}
}

View File

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

View File

@@ -60,6 +60,9 @@ public class AUserExperience implements Serializable {
@Column(name = "experience_gain_disabled", nullable = false)
private Boolean experienceGainDisabled = false;
@Column(name = "level_up_notification")
private Boolean levelUpNotification;
/**
* The {@link AExperienceLevel level} which the user currently has.
*/

View File

@@ -23,6 +23,26 @@ public class RankModel {
* The necessary experience to the next level up.
*/
private Long experienceToNextLevel;
/**
* Total experience needed for this level
*/
private Long experienceForCurrentLevel;
/**
* Percentage of progress within this level
*/
private Float currentLevelPercentage;
/**
* The total amount of experience needed for this level
*/
private Long levelExperience;
/**
* The experience which has been reached _within_ this level
*/
private Long inLevelExperience;
/**
* The experience needed to reach the next level
*/
private Long nextLevelExperience;
/**
* The member to show the rank for
*/

View File

@@ -44,7 +44,7 @@ public interface AUserExperienceService {
CompletableFuture<Void> syncUserRolesWithFeedback(AServer server, MessageChannel messageChannel);
CompletableFuture<Void> syncForSingleUser(AUserExperience userExperience, Member member);
CompletableFuture<Void> syncForSingleUser(AUserExperience userExperience, Member member, boolean changeRoles);
/**
* Loads the desired page of the ordered complete leaderboard from the {@link AServer} and returns the information as a {@link LeaderBoard}
@@ -75,6 +75,7 @@ public interface AUserExperienceService {
* @param userInAServer The {@link AUserInAServer} to enable experience for
*/
void enableExperienceForUser(AUserInAServer userInAServer);
void setLevelUpNotification(AUserInAServer aUserInAServer, Boolean newValue);
boolean experienceGainEnabledInChannel(MessageChannel messageChannel);

View File

@@ -1,5 +1,7 @@
package dev.sheldan.abstracto.experience.service;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
/**
* Service responsible for operations on {@link dev.sheldan.abstracto.experience.model.database.AExperienceLevel experienceLevel}
* This includes creating and calculations.
@@ -20,6 +22,7 @@ public interface ExperienceLevelService {
* @return The amount of experience required necessary to reach the next level
*/
Long calculateExperienceToNextLevel(Integer level, Long currentExperience);
AExperienceLevel calculateNextLevel(Integer level);
/**
* Calculates the required experience to reach this level. This calculated experience is relative, in the sense that

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -33,33 +33,32 @@ public class ReportContextCommandListener implements MessageContextCommandListen
@Override
public DefaultListenerResult execute(MessageContextInteractionModel model) {
Message targetMessage = model.getEvent().getTarget();
if(targetMessage.getAuthor().getIdLong() == model.getEvent().getUser().getIdLong()) {
interactionService.replyEmbed(ReactionReportServiceBean.REACTION_REPORT_OWN_MESSAGE_RESPONSE_TEMPLATE, new Object(), model.getEvent());
return DefaultListenerResult.IGNORED;
}
ServerUser userReporting = ServerUser
.builder()
.serverId(model.getServerId())
.userId(model.getEvent().getUser().getIdLong())
.isBot(model.getEvent().getUser().isBot())
.build();
if(!reactionReportService.allowedToReport(userReporting)) {
log.info("User {} was reported on message {} in server {} within the cooldown. Ignoring.",
targetMessage.getAuthor().getIdLong(), targetMessage.getIdLong(), targetMessage.getGuild().getIdLong());
interactionService.replyEmbed(ReactionReportServiceBean.REACTION_REPORT_COOLDOWN_RESPONSE_TEMPLATE, new Object(), model.getEvent());
return DefaultListenerResult.IGNORED;
}
reactionReportService.createReactionReport(targetMessage, userReporting, null).exceptionally(throwable -> {
log.error("Failed to create reaction report in server {} on message {} in channel {} with interaction.",
model.getServerId(), targetMessage.getIdLong(), model.getEvent().getChannel().getIdLong(), throwable);
return null;
model.getEvent().deferReply(true).queue(interactionHook -> {
Message targetMessage = model.getEvent().getTarget();
if(targetMessage.getAuthor().getIdLong() == model.getEvent().getUser().getIdLong()) {
interactionService.sendMessageToInteraction(ReactionReportServiceBean.REACTION_REPORT_OWN_MESSAGE_RESPONSE_TEMPLATE, new Object(), interactionHook);
return;
}
ServerUser userReporting = ServerUser
.builder()
.serverId(model.getServerId())
.userId(model.getEvent().getUser().getIdLong())
.isBot(model.getEvent().getUser().isBot())
.build();
if(!reactionReportService.allowedToReport(userReporting)) {
log.info("User {} was reported on message {} in server {} within the cooldown. Ignoring.",
targetMessage.getAuthor().getIdLong(), targetMessage.getIdLong(), targetMessage.getGuild().getIdLong());
interactionService.sendMessageToInteraction(ReactionReportServiceBean.REACTION_REPORT_COOLDOWN_RESPONSE_TEMPLATE, new Object(),interactionHook);
return;
}
reactionReportService.createReactionReport(targetMessage, userReporting, null).exceptionally(throwable -> {
log.error("Failed to create reaction report in server {} on message {} in channel {} with interaction.",
model.getServerId(), targetMessage.getIdLong(), model.getEvent().getChannel().getIdLong(), throwable);
return null;
});
interactionService.sendMessageToInteraction(REACTION_REPORT_RESPONSE_TEMPLATE, new Object(), interactionHook);
});
interactionService.replyEmbed(REACTION_REPORT_RESPONSE_TEMPLATE, new Object(), model.getEvent());
return DefaultListenerResult.PROCESSED;
}

View File

@@ -17,6 +17,7 @@ import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static dev.sheldan.abstracto.moderation.service.ReactionReportServiceBean.REACTION_REPORT_FAILURE_RESPONSE_TEMPLATE;
import static dev.sheldan.abstracto.moderation.service.ReactionReportServiceBean.REACTION_REPORT_RESPONSE_TEMPLATE;
@Component
@@ -46,27 +47,35 @@ public class ReportContextModalListener implements ModalInteractionListener {
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
messageCache.getMessageFromCache(payload.getServerId(), payload.getChannelId(), payload.getMessageId()).thenAccept(cachedMessage -> {
ServerUser userReporting = ServerUser
.builder()
.serverId(model.getServerId())
.userId(cachedMessage.getAuthor().getAuthorId())
.isBot(cachedMessage.getAuthor().getIsBot())
.build();
reactionReportService.createReactionReport(cachedMessage, userReporting, context)
.thenAccept(unused -> {
interactionService.replyEmbed(REACTION_REPORT_RESPONSE_TEMPLATE, new Object(), model.getEvent());
log.info("Handled modal for message report with id {} in guild {} in channel {} on message {}",
model.getEvent().getModalId(), payload.getServerId(), payload.getChannelId(), payload.getMessageId());
componentPayloadManagementService.deletePayload(payload.getModalId());
}).exceptionally(throwable -> {
log.error("Failed to create reaction report in server {} on message {} in channel {} with interaction.",
model.getServerId(), cachedMessage.getMessageId(), model.getEvent().getChannel().getIdLong(), throwable);
model.getEvent().deferReply(true).queue(interactionHook -> {
messageCache.getMessageFromCache(payload.getServerId(), payload.getChannelId(), payload.getMessageId()).thenAccept(cachedMessage -> {
ServerUser userReporting = ServerUser
.builder()
.serverId(model.getServerId())
.userId(cachedMessage.getAuthor().getAuthorId())
.isBot(cachedMessage.getAuthor().getIsBot())
.build();
reactionReportService.createReactionReport(cachedMessage, userReporting, context)
.thenAccept(unused -> {
interactionService.sendMessageToInteraction(REACTION_REPORT_RESPONSE_TEMPLATE, new Object(), interactionHook);
log.info("Handled modal for message report with id {} in guild {} in channel {} on message {}",
model.getEvent().getModalId(), payload.getServerId(), payload.getChannelId(), payload.getMessageId());
componentPayloadManagementService.deletePayload(payload.getModalId());
}).exceptionally(throwable -> {
interactionService.sendMessageToInteraction(REACTION_REPORT_FAILURE_RESPONSE_TEMPLATE, new Object(), interactionHook);
log.error("Failed to create reaction report in server {} on message {} in channel {} with interaction.",
model.getServerId(), cachedMessage.getMessageId(), model.getEvent().getChannel().getIdLong(), throwable);
return null;
});
}).exceptionally(throwable -> {
interactionService.sendMessageToInteraction(REACTION_REPORT_FAILURE_RESPONSE_TEMPLATE, new Object(), interactionHook);
log.error("Failed to load reported message for reporting message {} in channel {} with context.",
model.getEvent().getMessage().getIdLong(), model.getEvent().getChannel().getIdLong(), throwable);
return null;
});
}).exceptionally(throwable -> {
log.error("Failed to load reported message.", throwable);
return null;
}, throwable -> {
log.error("Failed to acknowledge modal interaction for report context modal listener in guild {} on message {}.", model.getServerId(),
model.getEvent().getMessage() != null ? model.getEvent().getMessage().getIdLong() : 0, throwable);
});
return ModalInteractionListenerResult.ACKNOWLEDGED;

View File

@@ -71,6 +71,7 @@ public class ReactionReportServiceBean implements ReactionReportService {
private static final String REACTION_REPORT_TEMPLATE_KEY = "reactionReport_notification";
public static final String REACTION_REPORT_MODAL_ORIGIN = "reportMessageModal";
public static final String REACTION_REPORT_RESPONSE_TEMPLATE = "reactionReport_response";
public static final String REACTION_REPORT_FAILURE_RESPONSE_TEMPLATE = "reactionReport_failure_response";
public static final String REACTION_REPORT_COOLDOWN_RESPONSE_TEMPLATE = "reactionReport_cooldown_response";
public static final String REACTION_REPORT_OWN_MESSAGE_RESPONSE_TEMPLATE = "reactionReport_own_message_response";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>profanity-filter</artifactId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -249,6 +249,7 @@ public class RemindServiceBeanTest {
when(guild.getIdLong()).thenReturn(8L);
when(remindedMember.getIdLong()).thenReturn(9L);
when(remindedMember.getUser()).thenReturn(jdaUser);
when(remindedMember.getEffectiveAvatar()).thenReturn(Mockito.mock(ImageProxy.class));
when(jdaUser.getDefaultAvatar()).thenReturn(Mockito.mock(ImageProxy.class));
Reminder remindedReminder = Mockito.mock(Reminder.class);
when(remindedReminder.getTargetDate()).thenReturn(Instant.now());

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.4.9</version>
<version>1.4.22</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.4.9</version>
<version>1.4.22</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -1,6 +1,8 @@
package dev.sheldan.abstracto.suggestion.job;
import dev.sheldan.abstracto.suggestion.service.SuggestionService;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
@@ -13,6 +15,8 @@ import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@Getter
@Setter
@PersistJobDataAfterExecution
public class SuggestionEvaluationJob extends QuartzJobBean {

View File

@@ -29,6 +29,8 @@ import dev.sheldan.abstracto.suggestion.service.management.SuggestionManagementS
import dev.sheldan.abstracto.suggestion.service.management.SuggestionVoteManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -37,9 +39,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@Component
@@ -131,6 +131,7 @@ public class SuggestionServiceBean implements SuggestionService {
if(autoEvaluationEnabled) {
autoEvaluateDays = configService.getLongValueOrConfigDefault(SUGGESTION_AUTO_EVALUATE_DAYS_CONFIG_KEY, serverId);
}
Instant autoEvaluationTargetDate = autoEvaluationEnabled ? Instant.now().plus(autoEvaluateDays, ChronoUnit.DAYS) : null;
SuggestionLog model = SuggestionLog
.builder()
.suggestionId(newSuggestionId)
@@ -143,7 +144,7 @@ public class SuggestionServiceBean implements SuggestionService {
.suggester(suggester.getUser())
.text(text)
.autoEvaluationEnabled(autoEvaluationEnabled)
.autoEvaluationTargetDate(autoEvaluationEnabled ? Instant.now().plus(autoEvaluateDays, ChronoUnit.DAYS) : null)
.autoEvaluationTargetDate(autoEvaluationTargetDate)
.build();
if(useButtons) {
setupButtonIds(model);
@@ -151,32 +152,73 @@ public class SuggestionServiceBean implements SuggestionService {
MessageToSend messageToSend = templateService.renderEmbedTemplate(SUGGESTION_CREATION_TEMPLATE, model, serverId);
log.info("Creating suggestion with id {} in server {} from member {}.", newSuggestionId, serverId, suggester.getIdLong());
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(messageToSend, SuggestionPostTarget.SUGGESTION, serverId);
List<ButtonConfigModel> buttonConfigModels = Arrays.asList(model.getAgreeButtonModel(), model.getDisAgreeButtonModel(), model.getRemoveVoteButtonModel());
return FutureUtils.toSingleFutureGeneric(completableFutures)
.thenCompose(aVoid -> self.addDeletionPossibility(suggestionChannelId, suggestionMessageId, text, suggester, serverId, newSuggestionId, completableFutures, model));
.thenCompose(aVoid -> self.addVotingPossibility(suggestionChannelId, suggestionMessageId, text, suggester, serverId, newSuggestionId, completableFutures, buttonConfigModels, useButtons))
.thenCompose(aVoid -> self.createSuggestionThread(serverId, newSuggestionId, suggester, text, autoEvaluationEnabled, autoEvaluationTargetDate));
}
@Transactional
public CompletableFuture<Void> addDeletionPossibility(Long suggestionChannelId, Long suggestionMessageId, String text, Member suggester, Long serverId,
Long newSuggestionId, List<CompletableFuture<Message>> completableFutures, SuggestionLog model) {
Message message = completableFutures.get(0).join();
if(model.getUseButtons()) {
configureDecisionButtonPayload(serverId, newSuggestionId, model.getAgreeButtonModel(), SuggestionDecision.AGREE);
configureDecisionButtonPayload(serverId, newSuggestionId, model.getDisAgreeButtonModel(), SuggestionDecision.DISAGREE);
configureDecisionButtonPayload(serverId, newSuggestionId, model.getRemoveVoteButtonModel(), SuggestionDecision.REMOVE_VOTE);
AServer server = serverManagementService.loadServer(serverId);
componentPayloadManagementService.createButtonPayload(model.getAgreeButtonModel(), server);
componentPayloadManagementService.createButtonPayload(model.getDisAgreeButtonModel(), server);
componentPayloadManagementService.createButtonPayload(model.getRemoveVoteButtonModel(), server);
self.persistSuggestionInDatabase(suggester, text, message, newSuggestionId, suggestionChannelId, suggestionMessageId);
return CompletableFuture.completedFuture(null);
public CompletableFuture<Void> createSuggestionThread(Long serverId, Long suggestionId, Member suggester, String suggestionText,
Boolean autoEvaluationEnabled, Instant autoEvaluationTargetDate) {
if(featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, serverId, SuggestionFeatureMode.SUGGESTION_THREAD)) {
Optional<GuildMessageChannel> suggestionTargetChannelOptional = postTargetService.getPostTargetChannel(SuggestionPostTarget.SUGGESTION, serverId);
log.info("Trying to create thread for suggestion {} in server {}.", suggestionId, serverId);
if (suggestionTargetChannelOptional.isPresent()) {
GuildMessageChannel messageChannel = suggestionTargetChannelOptional.get();
if(messageChannel instanceof IThreadContainer) {
SuggestionThreadModel model = SuggestionThreadModel
.builder()
.suggestionId(suggestionId)
.suggester(suggester.getUser())
.member(suggester)
.autoEvaluationEnabled(autoEvaluationEnabled)
.text(suggestionText)
.autoEvaluationTargetDate(autoEvaluationTargetDate)
.build();
IThreadContainer threadContainer = (IThreadContainer) messageChannel;
String threadName = templateService.renderTemplate("suggestion_thread_name", model, serverId);
return threadContainer.createThreadChannel(threadName).submit()
.thenAccept(threadChannel -> log.info("Created thread for suggestion {} in server {}.", suggestionId, serverId));
} else {
log.info("Suggestion thread was not created - post target for suggestions does not allow to create threads");
}
} else {
log.info("Suggestion thread was not created - post target did not evaluate to a channel or post target is disabled.");
}
} else {
log.debug("Posted message, adding reaction for suggestion {} to message {}.", newSuggestionId, message.getId());
CompletableFuture<Void> firstReaction = reactionService.addReactionToMessageAsync(SUGGESTION_YES_EMOTE, serverId, message);
CompletableFuture<Void> secondReaction = reactionService.addReactionToMessageAsync(SUGGESTION_NO_EMOTE, serverId, message);
return CompletableFuture.allOf(firstReaction, secondReaction).thenAccept(aVoid1 -> {
log.debug("Reaction added to message {} for suggestion {}.", message.getId(), newSuggestionId);
log.info("Suggestion thread feature disabled - not creating thread for suggestion {} in server {}.", suggestionId, serverId);
}
return CompletableFuture.completedFuture(null);
}
@Transactional
public CompletableFuture<Void> addVotingPossibility(Long suggestionChannelId, Long suggestionMessageId, String text, Member suggester, Long serverId,
Long newSuggestionId, List<CompletableFuture<Message>> completableFutures, List<ButtonConfigModel> buttonConfigModels,
Boolean useButtons) {
if(!completableFutures.isEmpty()) {
Message message = completableFutures.get(0).join();
if(useButtons) {
configureDecisionButtonPayload(serverId, newSuggestionId, buttonConfigModels.get(0), SuggestionDecision.AGREE);
configureDecisionButtonPayload(serverId, newSuggestionId, buttonConfigModels.get(1), SuggestionDecision.DISAGREE);
configureDecisionButtonPayload(serverId, newSuggestionId, buttonConfigModels.get(2), SuggestionDecision.REMOVE_VOTE);
AServer server = serverManagementService.loadServer(serverId);
componentPayloadManagementService.createButtonPayload(buttonConfigModels.get(0), server);
componentPayloadManagementService.createButtonPayload(buttonConfigModels.get(1), server);
componentPayloadManagementService.createButtonPayload(buttonConfigModels.get(2), server);
self.persistSuggestionInDatabase(suggester, text, message, newSuggestionId, suggestionChannelId, suggestionMessageId);
});
return CompletableFuture.completedFuture(null);
} else {
log.debug("Posted message, adding reaction for suggestion {} to message {}.", newSuggestionId, message.getId());
CompletableFuture<Void> firstReaction = reactionService.addReactionToMessageAsync(SUGGESTION_YES_EMOTE, serverId, message);
CompletableFuture<Void> secondReaction = reactionService.addReactionToMessageAsync(SUGGESTION_NO_EMOTE, serverId, message);
return CompletableFuture.allOf(firstReaction, secondReaction).thenAccept(aVoid1 -> {
log.debug("Reaction added to message {} for suggestion {}.", message.getId(), newSuggestionId);
self.persistSuggestionInDatabase(suggester, text, message, newSuggestionId, suggestionChannelId, suggestionMessageId);
});
}
} else {
return CompletableFuture.completedFuture(null);
}
}
@@ -275,14 +317,23 @@ public class SuggestionServiceBean implements SuggestionService {
Long serverId = suggestion.getServer().getId();
Long channelId = suggestion.getChannel().getId();
Long originalMessageId = suggestion.getMessageId();
boolean buttonsActive = featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, serverId, SuggestionFeatureMode.SUGGESTION_BUTTONS);
Long agreements = suggestionVoteManagementService.getDecisionsForSuggestion(suggestion, SuggestionDecision.AGREE);
Long disagreements = suggestionVoteManagementService.getDecisionsForSuggestion(suggestion, SuggestionDecision.DISAGREE);
Long suggestionId = suggestion.getSuggestionId().getId();
Long totalVotes = disagreements + agreements;
float agreementPercentage = 0;
float disAgreementPercentage = 0;
if(totalVotes > 0) {
agreementPercentage = (((float) agreements) / totalVotes) * 100;
disAgreementPercentage = 100f - agreementPercentage;
}
SuggestionUpdateModel model = SuggestionUpdateModel
.builder()
.suggestionId(suggestionId)
.state(suggestion.getState())
.serverId(serverId)
.buttonsActive(buttonsActive)
.member(memberExecutingCommand)
.agreeVotes(agreements)
.disAgreeVotes(disagreements)
@@ -290,6 +341,9 @@ public class SuggestionServiceBean implements SuggestionService {
.text(suggestion.getSuggestionText())
.originalChannelId(channelId)
.reason(reason)
.totalVotes(totalVotes)
.agreementPercentage(agreementPercentage)
.disAgreementPercentage(disAgreementPercentage)
.build();
log.info("Updated posted suggestion {} in server {}.", suggestionId, suggestion.getServer().getId());
CompletableFuture<User> memberById = userService.retrieveUserForId(suggestion.getSuggester().getUserReference().getId());

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