Compare commits

...

17 Commits

Author SHA1 Message Date
Sheldan
ed42940e29 [AB-xxx] adding ability to track emotes used in reactions
adding ability to have confirmations for slash commands
2025-01-27 01:31:56 +01:00
release-bot
2c3b16879e Commit from GitHub Actions (Publishes a new version of abstracto) 2025-01-15 21:25:59 +00:00
release-bot
bd7fc6aa65 [maven-release-plugin] prepare for next development iteration 2025-01-15 21:16:39 +00:00
release-bot
6dfcebb25b [maven-release-plugin] prepare release v1.5.57 2025-01-15 21:16:38 +00:00
Sheldan
0eaccb4b0f [AB-xxx] adding currency conversion command 2025-01-15 22:13:47 +01:00
Sheldan
4ae6a154c7 [AB-xxx] fixing not being able to delete a giveaway key for which there is no giveaway 2024-12-29 21:10:27 +01:00
release-bot
3ad8369ab3 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-12-25 22:41:02 +00:00
release-bot
6be1b7df04 [maven-release-plugin] prepare for next development iteration 2024-12-25 22:30:39 +00:00
release-bot
df8eb399f7 [maven-release-plugin] prepare release v1.5.56 2024-12-25 22:30:37 +00:00
Sheldan
3b7157714d [AB-xxx] fixing issue of server id not being provided when manually creating a giveaway 2024-12-25 23:27:27 +01:00
release-bot
ef7e5b4a46 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-12-25 19:09:24 +00:00
release-bot
c28286bf3f [maven-release-plugin] prepare for next development iteration 2024-12-25 19:00:30 +00:00
release-bot
15e998e05d [maven-release-plugin] prepare release v1.5.55 2024-12-25 19:00:28 +00:00
Sheldan
cdbdb395ac [AB-xxx] adding ability to automate giveaways using modmail and adding storage for give away keys
ignoring message updated events for messages not within servers
2024-12-25 19:54:24 +01:00
release-bot
8819c12b81 Commit from GitHub Actions (Publishes a new version of abstracto) 2024-11-26 23:25:17 +00:00
release-bot
a4e0971c27 [maven-release-plugin] prepare for next development iteration 2024-11-26 23:15:06 +00:00
release-bot
726fc27331 [maven-release-plugin] prepare release v1.5.54 2024-11-26 23:15:03 +00:00
209 changed files with 3506 additions and 441 deletions

2
.env
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,7 +18,7 @@
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ 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.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
@@ -12,10 +11,8 @@ import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.service.WarnService;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -45,14 +42,6 @@ public class DecayAllWarnings extends AbstractConditionableCommand {
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
AServer server = serverManagementService.loadServer(event.getGuild());
return warnService.decayAllWarningsForServer(server)
.thenCompose(unused -> interactionService.replyEmbed(DECAY_ALL_WARNINGS_RESPONSE, event))
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
@@ -61,19 +50,11 @@ public class DecayAllWarnings extends AbstractConditionableCommand {
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModerationSlashCommandNames.WARN_DECAY)
.commandName(DECAY_ALL_WARNINGS_COMMAND)
.build();
return CommandConfiguration.builder()
.name(DECAY_ALL_WARNINGS_COMMAND)
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.requiresConfirmation(true)
.supportsEmbedException(true)
.causesReaction(true)

View File

@@ -4,18 +4,14 @@ 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.interaction.slash.SlashCommandConfig;
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.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.service.WarnService;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -27,7 +23,6 @@ import java.util.concurrent.CompletableFuture;
public class DecayWarnings extends AbstractConditionableCommand {
private static final String DECAY_WARNINGS_COMMAND = "decayWarnings";
private static final String DECAY_WARNINGS_RESPONSE = "decayWarnings_response";
@Autowired
private WarnService warnService;
@@ -35,9 +30,6 @@ public class DecayWarnings extends AbstractConditionableCommand {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private InteractionService interactionService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
@@ -45,14 +37,6 @@ public class DecayWarnings extends AbstractConditionableCommand {
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
AServer server = serverManagementService.loadServer(event.getGuild());
return warnService.decayWarningsForServer(server)
.thenCompose(unused -> interactionService.replyEmbed(DECAY_WARNINGS_RESPONSE, event))
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
@@ -61,18 +45,10 @@ public class DecayWarnings extends AbstractConditionableCommand {
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModerationSlashCommandNames.WARN_DECAY)
.commandName(DECAY_WARNINGS_COMMAND)
.build();
return CommandConfiguration.builder()
.name(DECAY_WARNINGS_COMMAND)
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.requiresConfirmation(true)
.async(true)
.supportsEmbedException(true)

View File

@@ -15,10 +15,10 @@ import dev.sheldan.abstracto.moderation.model.interaction.ModerationActionMutePa
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionMuteModalModel;
import dev.sheldan.abstracto.moderation.model.template.listener.ModerationActionPayloadModel;
import dev.sheldan.abstracto.moderation.service.ModerationActionServiceBean;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Slf4j

View File

@@ -217,7 +217,7 @@ public class PurgeServiceBean implements PurgeService {
}
log.debug("Setting status for {} out of {}", currentCount, totalCount);
MessageToSend finalUpdateMessage = getStatusMessageToSend(totalCount, channel.getGuild().getIdLong(), currentCount);
interactionService.editOriginal(finalUpdateMessage, interactionHook);
interactionService.replaceOriginal(finalUpdateMessage, interactionHook);
};
}
@@ -244,7 +244,7 @@ public class PurgeServiceBean implements PurgeService {
CompletableFuture<MessageHistory> historyFuture = channelService.getHistoryOfChannel(channel, startId, toDeleteInThisIteration);
MessageToSend statusMessageToSend = getStatusMessageToSend(totalCount, channel.getGuild().getIdLong(), 0);
CompletableFuture<Message> statusMessageFuture = interactionService.editOriginal(statusMessageToSend, interactionHook);
CompletableFuture<Message> statusMessageFuture = interactionService.replaceOriginal(statusMessageToSend, interactionHook);
CompletableFuture<Void> deletionFuture = new CompletableFuture<>();
CompletableFuture<Void> retrievalFuture = CompletableFuture.allOf(historyFuture, statusMessageFuture);

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.SnowflakeUtils;
import dev.sheldan.abstracto.moderation.service.BanService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureConfig;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
@@ -28,6 +29,7 @@ import dev.sheldan.abstracto.modmail.exception.ModMailThreadNotFoundException;
import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.dto.ServiceChoicesPayload;
import dev.sheldan.abstracto.modmail.model.database.*;
import dev.sheldan.abstracto.modmail.model.listener.ModmailThreadCreatedSendMessageModel;
import dev.sheldan.abstracto.modmail.model.template.*;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
import dev.sheldan.abstracto.modmail.service.management.ModMailRoleManagementService;
@@ -46,6 +48,7 @@ import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -56,6 +59,7 @@ import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.transaction.event.TransactionalEventListener;
import static dev.sheldan.abstracto.modmail.config.ModMailFeatureConfig.MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY;
@@ -159,6 +163,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Autowired
private InteractionService interactionService;
@Autowired
private ApplicationEventPublisher eventPublisher;
public static final String MODMAIL_THREAD_METRIC = "modmail.threads";
public static final String MODMAIL_MESSAGE_METRIC = "modmail.messges";
public static final String ACTION = "action";
@@ -189,8 +196,8 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
public static final String MODMAIL_INITIAL_ORIGIN = "modmailInitial";
@Override
public CompletableFuture<MessageChannel> createModMailThreadForUser(User user, Guild guild, Message initialMessage, boolean userInitiated, List<UndoActionInstance> undoActions, boolean appeal) {
public CompletableFuture<MessageChannel> createModMailThreadForUser(User user, Guild guild, Message initialMessage, boolean userInitiated, List<UndoActionInstance> undoActions,
boolean appeal, ModmailThreadCreatedSendMessageModel createdSendMessageModel) {
Long serverId = guild.getIdLong();
AServer server = serverManagementService.loadServer(serverId);
metricService.incrementCounter(MODMAIL_THREAD_CREATED_COUNTER);
@@ -220,7 +227,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.thenCompose(unused -> channelService.createThreadWithStarterMessage(textChannel, channelName, notificationMessage.get(0).join().getIdLong()))
.thenCompose(threadChannel -> {
undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, threadChannel.getIdLong()));
return self.performModMailThreadSetup(user, initialMessage, threadChannel, userInitiated, undoActions, appeal)
return self.performModMailThreadSetup(user, initialMessage, threadChannel, userInitiated, undoActions, appeal, createdSendMessageModel)
.thenCompose(unused -> CompletableFuture.completedFuture(threadChannel));
});
} else {
@@ -229,12 +236,18 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
CompletableFuture<TextChannel> textChannelFuture = channelService.createTextChannel(channelName, server, categoryId);
return textChannelFuture.thenCompose(channel -> {
undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, channel.getIdLong()));
return self.performModMailThreadSetup(user, initialMessage, channel, userInitiated, undoActions, appeal)
return self.performModMailThreadSetup(user, initialMessage, channel, userInitiated, undoActions, appeal, createdSendMessageModel)
.thenCompose(unused -> CompletableFuture.completedFuture(channel));
});
}
}
@Override
public CompletableFuture<MessageChannel> createModMailThreadForUser(User user, Guild guild, Message initialMessage, boolean userInitiated,
List<UndoActionInstance> undoActions, boolean appeal) {
return createModMailThreadForUser(user, guild, initialMessage, userInitiated, undoActions, appeal, null);
}
@Transactional
@Override
public CompletableFuture<Void> sendContactNotification(User user, MessageChannel messageChannel, MessageChannel feedBackChannel) {
@@ -265,10 +278,12 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @param userInitiated Whether the thread was initiated by a member
* @param undoActions The list of actions to undo, in case an exception occurs
* @param appeal Whether the modmail thread is for the purpose of an appeal
* @param createdSendMessageModel The information which message should be sent after the modmail thread is completely created
* @return A {@link CompletableFuture future} which completes when the setup is done
*/
@Transactional
public CompletableFuture<Void> performModMailThreadSetup(User user, Message initialMessage, GuildMessageChannel channel, boolean userInitiated, List<UndoActionInstance> undoActions, boolean appeal) {
public CompletableFuture<Void> performModMailThreadSetup(User user, Message initialMessage, GuildMessageChannel channel, boolean userInitiated, List<UndoActionInstance> undoActions,
boolean appeal, ModmailThreadCreatedSendMessageModel createdSendMessageModel) {
log.info("Performing modmail thread setup for channel {} for user {} in server {}. It was initiated by a user: {}.", channel.getIdLong(), user.getId(), channel.getGuild().getId(), userInitiated);
CompletableFuture<Void> headerFuture = sendModMailHeader(channel, user);
CompletableFuture<Message> userReplyMessage;
@@ -287,18 +302,22 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
return CompletableFuture.allOf(headerFuture, notificationFuture, userReplyMessage).thenAccept(aVoid -> {
undoActions.clear();
self.setupModMailThreadInDB(initialMessage, channel, user, userReplyMessage.join(), appeal);
self.setupModMailThreadInDB(initialMessage, channel, user, userReplyMessage.join(), appeal, createdSendMessageModel);
});
}
@Transactional
public void setupModMailThreadInDB(Message initialMessage, GuildMessageChannel channel, User user, Message sendMessage, boolean appeal) {
public void setupModMailThreadInDB(Message initialMessage, GuildMessageChannel channel, User user, Message sendMessage, boolean appeal,
ModmailThreadCreatedSendMessageModel createdSendMessageModel) {
log.info("Persisting info about modmail thread {} in database.", channel.getIdLong());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(channel.getGuild().getIdLong(), user.getIdLong());
ModMailThread thread = createThreadObject(channel, aUserInAServer, appeal);
if(initialMessage != null) {
log.debug("Adding initial message {} to modmail thread in channel {}.", initialMessage.getId(), channel.getId());
modMailMessageManagementService.addMessageToThread(thread, null, sendMessage, initialMessage, aUserInAServer, false, false);
modMailMessageManagementService.addMessageToThread(thread, null, sendMessage, initialMessage.getIdLong(), aUserInAServer, false, false);
}
if(createdSendMessageModel != null) {
eventPublisher.publishEvent(createdSendMessageModel);
}
}
@@ -527,6 +546,59 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
}
@Override
public CompletableFuture<Void> sendMessageToUser(AUserInAServer aUserInAServer, MessageToSend messageToSendToUser, User user) {
AServer aServer = aUserInAServer.getServerReference();
Long serverId = aServer.getId();
Long userId = user.getIdLong();
Guild guild = guildService.getGuildById(serverId);
if(modMailThreadManagementService.hasOpenModMailThreadForUser(aUserInAServer)) {
return sendMessageToExistingModmailThread(aUserInAServer, messageToSendToUser, user);
} else {
List<UndoActionInstance> undoActionInstances = new ArrayList<>();
ModmailThreadCreatedSendMessageModel createdSendMessageModel = ModmailThreadCreatedSendMessageModel
.builder()
.messageToSend(messageToSendToUser)
.userId(userId)
.serverId(serverId)
.build();
return createModMailThreadForUser(user, guild, null, false, undoActionInstances, false, createdSendMessageModel).thenAccept(messageChannel -> {
}).exceptionally(throwable -> {
log.error("Failed to setup modmail thread correctly", throwable);
undoActionService.performActions(undoActionInstances);
throw new AbstractoRunTimeException(throwable);
});
}
}
private CompletableFuture<Void> sendMessageToExistingModmailThread(AUserInAServer aUserInAServer, MessageToSend messageToSendToUser, User user) {
ModMailThread modmailThread = modMailThreadManagementService.getOpenModMailThreadForUser(aUserInAServer);
CompletableFuture<Message> future = messageService.sendMessageToSendToUser(user, messageToSendToUser);
CompletableFuture<Message> sameThreadMessageFuture = channelService.sendMessageEmbedToSendToAChannel(messageToSendToUser, modmailThread.getChannel()).get(0);
Long modmailThreadId = modmailThread.getId();
Guild guild = guildService.getGuildById(aUserInAServer.getServerReference().getId());
return CompletableFuture.allOf(future, sameThreadMessageFuture).thenAccept(avoid ->
self.saveSendMessagesAndUpdateState(modmailThreadId, false, future.join(), guild.getSelfMember(), SnowflakeUtils.createSnowFlake(), sameThreadMessageFuture.join())
);
}
@TransactionalEventListener
public void sendMessageAfterModmailThreadCreated(ModmailThreadCreatedSendMessageModel model) {
log.info("Sending message to {} in server {} after modmail thread was created.", model.getUserId(), model.getServerId());
userService.retrieveUserForId(model.getUserId())
.thenCompose(user -> self.loadUserAndSendtoModmail(model, user))
.exceptionally(throwable -> {
log.warn("Failed to load user to send modmail thread to.");
return null;
});
}
@Transactional
public CompletableFuture<Void> loadUserAndSendtoModmail(ModmailThreadCreatedSendMessageModel model, User user) {
AUserInAServer aUserInAServer = userInServerManagementService.onlyLoadUser(model.getServerId(), model.getUserId());
return sendMessageToExistingModmailThread(aUserInAServer, model.getMessageToSend(), user);
}
/**
* This message takes a received {@link Message} from a user, renders it to a new message to send and sends it to
* the appropriate {@link ModMailThread} channel, the returned promise only returns if the message was dealt with on the user
@@ -622,7 +694,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
if(modMailThreadOpt.isPresent()) {
ModMailThread modMailThread = modMailThreadOpt.get();
log.debug("Adding created message {} based on messeage {} sent from user to modmail thread {} and setting status to {}.", messageInModMailThread.getId(), messageFromUser.getId(), modMailThread.getId(), ModMailThreadState.USER_REPLIED);
modMailMessageManagementService.addMessageToThread(modMailThread, null, messageInModMailThread, messageFromUser, modMailThread.getUser(), false, false);
modMailMessageManagementService.addMessageToThread(modMailThread, null, messageInModMailThread, messageFromUser.getIdLong(), modMailThread.getUser(), false, false);
// update the state of the thread
modMailThreadManagementService.setModMailThreadState(modMailThread, ModMailThreadState.USER_REPLIED);
} else {
@@ -678,7 +750,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
sameThreadMessageFuture = CompletableFuture.completedFuture(null);
}
return CompletableFuture.allOf(future, sameThreadMessageFuture).thenAccept(avoid ->
self.saveSendMessagesAndUpdateState(modmailThreadId, anonymous, future.join(), replyCommandMessage, sameThreadMessageFuture.join())
self.saveSendMessagesAndUpdateState(modmailThreadId, anonymous, future.join(), replyCommandMessage.getMember(), replyCommandMessage.getIdLong(), sameThreadMessageFuture.join())
);
}
@@ -1009,17 +1081,18 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @param anonymous Whether or not the messages were send anonymous
* @param createdMessageInDM The {@link Message message} which was sent to the private channel with the {@link User user}
* @param modMailThreadMessage The {@link Message message} which was sent in the channel representing the {@link ModMailThread thread}. Might be null.
* @param replyCommandMessage The {@link Message message} which contained the command used to reply to the user
* @param author The {@link Member} which caused the message to be sent
* @param messageId The ID of the message that was sent
* @throws ModMailThreadNotFoundException in case the {@link ModMailThread} is not found by the ID
*/
@Transactional
public void saveSendMessagesAndUpdateState(Long modMailThreadId, Boolean anonymous, Message createdMessageInDM, Message replyCommandMessage, Message modMailThreadMessage) {
public void saveSendMessagesAndUpdateState(Long modMailThreadId, Boolean anonymous, Message createdMessageInDM, Member author, Long messageId, Message modMailThreadMessage) {
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByIdOptional(modMailThreadId);
AUserInAServer moderator = userInServerManagementService.loadOrCreateUser(replyCommandMessage.getMember());
AUserInAServer moderator = userInServerManagementService.loadOrCreateUser(author);
if(modMailThreadOpt.isPresent()) {
ModMailThread modMailThread = modMailThreadOpt.get();
log.debug("Adding (anonymous: {}) message {} of moderator to modmail thread {} and setting state to {}.", anonymous, createdMessageInDM.getId(), modMailThreadId, ModMailThreadState.MOD_REPLIED);
modMailMessageManagementService.addMessageToThread(modMailThread, createdMessageInDM, modMailThreadMessage, replyCommandMessage, moderator, anonymous, true);
modMailMessageManagementService.addMessageToThread(modMailThread, createdMessageInDM, modMailThreadMessage, messageId, moderator, anonymous, true);
modMailThreadManagementService.setModMailThreadState(modMailThread, ModMailThreadState.MOD_REPLIED);
} else {
throw new ModMailThreadNotFoundException(modMailThreadId);

View File

@@ -20,13 +20,13 @@ public class ModMailMessageManagementServiceBean implements ModMailMessageManage
private ModMailMessageRepository modMailMessageRepository;
@Override
public ModMailMessage addMessageToThread(ModMailThread modMailThread, Message createdMessageInDM, Message createdMessageInChannel, Message userPostedMessage, AUserInAServer author, Boolean anonymous, Boolean dmChannel) {
public ModMailMessage addMessageToThread(ModMailThread modMailThread, Message createdMessageInDM, Message createdMessageInChannel, Long messageId, AUserInAServer author, Boolean anonymous, Boolean dmChannel) {
Long dmId = createdMessageInDM != null ? createdMessageInDM.getIdLong() : null;
Long channelMessageId = createdMessageInChannel != null ? createdMessageInChannel.getIdLong() : null;
ModMailMessage modMailMessage = ModMailMessage
.builder()
.author(author)
.messageId(userPostedMessage.getIdLong())
.messageId(messageId)
.createdMessageInDM(dmId)
.server(modMailThread.getServer())
.createdMessageInChannel(channelMessageId)
@@ -35,7 +35,7 @@ public class ModMailMessageManagementServiceBean implements ModMailMessageManage
.anonymous(anonymous)
.build();
log.info("Storing created message in DM {} with created message in channel {} caused by message {} to modmail thread {} of user {} in server {}.",
dmId, channelMessageId, userPostedMessage.getId(), modMailThread.getId(), author.getUserReference().getId(), author.getServerReference().getId());
dmId, channelMessageId, messageId, modMailThread.getId(), author.getUserReference().getId(), author.getServerReference().getId());
return modMailMessageRepository.save(modMailMessage);
}

View File

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

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.modmail.model.listener;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class ModmailThreadCreatedSendMessageModel {
private MessageToSend messageToSend;
private Long serverId;
private Long userId;
}

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.UndoActionInstance;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import net.dv8tion.jda.api.entities.*;
@@ -62,6 +63,7 @@ public interface ModMailThreadService {
* @return A {@link CompletableFuture future} which completes when the message has been relayed to the channel
*/
CompletableFuture<Message> relayMessageToModMailThread(ModMailThread modMailThread, Message messageFromUser, List<UndoActionInstance> undoActions);
CompletableFuture<Void> sendMessageToUser(AUserInAServer aUserInAServer, MessageToSend messageToSend, User user);
/**
* Forwards a message send by a moderator to the direct message channel opened with the user. If the message is

View File

@@ -17,13 +17,13 @@ public interface ModMailMessageManagementService {
* @param modMailThread The {@link ModMailThread} the message should be attached to
* @param createdMessageInDM The {@link Message} which should be attached to the {@link ModMailThread} and was posted to the DM channel (might be null)
* @param createdMessageInChannel The {@link Message} which should be attached to the {@link ModMailThread} and was posted to the modmail thread (might be null)
* @param userPostedMessage The {@link Message} which caused this message to be created, the command or the message by the user
* @param messageId The ID of the {@link Message} which caused this message to be created, the command or the message by the user
* @param author The {@link AUserInAServer} who authored the {@link Message} originally
* @param anonymous Whether or not the message was sent anonymous (only possible by staff members)
* @param dmChannel Whether or not the message originated from the user, and therefore in an direct message channel
* @return The created {@link ModMailMessage message} instance
*/
ModMailMessage addMessageToThread(ModMailThread modMailThread, Message createdMessageInDM, Message createdMessageInChannel, Message userPostedMessage, AUserInAServer author, Boolean anonymous, Boolean dmChannel);
ModMailMessage addMessageToThread(ModMailThread modMailThread, Message createdMessageInDM, Message createdMessageInChannel, Long messageId, AUserInAServer author, Boolean anonymous, Boolean dmChannel);
/**
* Retrieves all messages which were sent in a {@link ModMailThread}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</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.5.54-SNAPSHOT</version>
<version>1.5.58-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

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

View File

@@ -7,11 +7,21 @@ 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.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandService;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition;
import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames;
import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition;
import dev.sheldan.abstracto.statistic.emote.exception.TrackedEmoteNotFoundException;
import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote;
import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService;
import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -30,6 +40,16 @@ public class DeleteTrackedEmote extends AbstractConditionableCommand {
@Autowired
private TrackedEmoteService trackedEmoteService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private SlashCommandService slashCommandService;
private static final String DELETE_TRACKED_EMOTE_TRACKED_EMOTE = "trackedEmote";
private static final String DELETE_TRACKED_EMOTE_COMMAND_NAME = "deleteTrackedEmote";
private static final String DELETE_TRACKED_EMOTE_RESPONSE = "deleteTrackedEmote_response";
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
@@ -40,24 +60,52 @@ public class DeleteTrackedEmote extends AbstractConditionableCommand {
return CommandResult.fromSuccess();
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String emote = slashCommandParameterService.getCommandOption(DELETE_TRACKED_EMOTE_TRACKED_EMOTE, event, String.class);
Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild());
if(!(emoji instanceof CustomEmoji)) {
throw new TrackedEmoteNotFoundException();
}
Long emoteId = ((CustomEmoji) emoji).getIdLong();
ServerSpecificId serverEmoteId = new ServerSpecificId(event.getGuild().getIdLong(), emoteId);
TrackedEmote trackedEmote = trackedEmoteManagementService.loadByTrackedEmoteServer(serverEmoteId);
trackedEmoteService.deleteTrackedEmote(trackedEmote);
return slashCommandService.completeConfirmableCommand(event, DELETE_TRACKED_EMOTE_RESPONSE);
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter trackedEmoteParameter = Parameter
.builder()
.name("trackedEmote")
.name(DELETE_TRACKED_EMOTE_TRACKED_EMOTE)
.templated(true)
.type(TrackedEmote.class)
.build();
parameters.add(trackedEmoteParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(StatisticSlashCommandNames.STATISTIC_INTERNAL)
.groupName("manage")
.commandName("deletetrackedemote")
.build();
return CommandConfiguration.builder()
.name("deleteTrackedEmote")
.name(DELETE_TRACKED_EMOTE_COMMAND_NAME)
.module(EmoteTrackingModuleDefinition.EMOTE_TRACKING)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.messageCommandOnly(true)
.slashCommandConfig(slashCommandConfig)
.requiresConfirmation(true)
.parameters(parameters)
.help(helpInfo)

View File

@@ -7,16 +7,25 @@ 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.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition;
import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames;
import dev.sheldan.abstracto.statistic.emote.command.parameter.UsedEmoteTypeParameter;
import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition;
import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel;
import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService;
import java.util.Arrays;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
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;
@@ -34,6 +43,7 @@ import java.util.concurrent.CompletableFuture;
@Slf4j
public class DeletedEmoteStats extends AbstractConditionableCommand {
public static final String DELETED_EMOTE_STATS_COMMAND_NAME = "deletedEmoteStats";
@Autowired
private UsedEmoteService usedEmoteService;
@@ -43,8 +53,16 @@ public class DeletedEmoteStats extends AbstractConditionableCommand {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
public static final String EMOTE_STATS_STATIC_DELETED_RESPONSE = "deletedEmoteStats_static_response";
public static final String EMOTE_STATS_ANIMATED_DELETED_RESPONSE = "deletedEmoteStats_animated_response";
private static final String DELETED_EMOTE_STATS_PERIOD = "period";
private static final String DELETED_EMOTE_STATS_USED_EMOTE_TYPE = "type";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
@@ -57,7 +75,7 @@ public class DeletedEmoteStats extends AbstractConditionableCommand {
statsSince = Instant.now().minus(duration);
}
AServer server = serverManagementService.loadServer(commandContext.getGuild());
EmoteStatsModel emoteStatsModel = usedEmoteService.getDeletedEmoteStatsForServerSince(server, statsSince);
EmoteStatsModel emoteStatsModel = usedEmoteService.getDeletedEmoteStatsForServerSince(server, statsSince, null);
List<CompletableFuture<Message>> messagePromises = new ArrayList<>();
// only show the embed, if there are static emotes to show
if(!emoteStatsModel.getStaticEmotes().isEmpty()) {
@@ -79,23 +97,95 @@ public class DeletedEmoteStats extends AbstractConditionableCommand {
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
UsedEmoteTypeParameter typeEnum;
if(slashCommandParameterService.hasCommandOption(DELETED_EMOTE_STATS_USED_EMOTE_TYPE, event)) {
String type = slashCommandParameterService.getCommandOption(DELETED_EMOTE_STATS_USED_EMOTE_TYPE, event, String.class);
typeEnum = UsedEmoteTypeParameter.valueOf(type);
} else {
typeEnum = null;
}
Instant startTime;
if(slashCommandParameterService.hasCommandOption(DELETED_EMOTE_STATS_PERIOD, event)) {
String durationString = slashCommandParameterService.getCommandOption(DELETED_EMOTE_STATS_PERIOD, event, Duration.class, String.class);
Duration durationSince = ParseUtils.parseDuration(durationString);
startTime = Instant.now().minus(durationSince);
} else {
startTime = Instant.EPOCH;
}
AServer server = serverManagementService.loadServer(event.getGuild());
EmoteStatsModel emoteStatsModel = usedEmoteService.getDeletedEmoteStatsForServerSince(server, startTime, UsedEmoteTypeParameter.convertToUsedEmoteType(typeEnum));
List<CompletableFuture<Message>> messagePromises = new ArrayList<>();
return event.deferReply().submit().thenCompose(interactionHook -> {
// only show embed if static emote stats are available
if(!emoteStatsModel.getStaticEmotes().isEmpty()) {
log.debug("Deleted emote stats has {} static emotes since {}.", emoteStatsModel.getStaticEmotes().size(), startTime);
messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_STATIC_DELETED_RESPONSE, emoteStatsModel, interactionHook));
}
// only show embed if animated emote stats are available
if(!emoteStatsModel.getAnimatedEmotes().isEmpty()) {
log.debug("Deleted emote stats has {} animated emotes since {}.", emoteStatsModel.getAnimatedEmotes(), startTime);
messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_ANIMATED_DELETED_RESPONSE, emoteStatsModel, interactionHook));
}
// show an embed if no emote stats are available indicating so
if(!emoteStatsModel.areStatsAvailable()) {
log.info("No delete emote stats available for guild {} since {}.", event.getGuild().getIdLong(), startTime);
messagePromises.addAll(interactionService.sendMessageToInteraction(EmoteStats.EMOTE_STATS_NO_STATS_AVAILABLE, emoteStatsModel, interactionHook));
}
return FutureUtils.toSingleFutureGeneric(messagePromises)
.thenApply(unused -> CommandResult.fromIgnored());
});
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter periodParameter = Parameter
.builder()
.name("period")
.name(DELETED_EMOTE_STATS_PERIOD)
.templated(true)
.optional(true)
.type(Duration.class)
.build();
parameters.add(periodParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
List<String> emoteTypes = Arrays
.stream(UsedEmoteTypeParameter.values())
.map(Enum::name)
.collect(Collectors.toList());
Parameter typeParameter = Parameter
.builder()
.name(DELETED_EMOTE_STATS_USED_EMOTE_TYPE)
.templated(true)
.slashCommandOnly(true)
.optional(true)
.choices(emoteTypes)
.type(String.class)
.build();
parameters.add(typeParameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(StatisticSlashCommandNames.STATISTIC)
.groupName("emotestats")
.commandName("deleted")
.build();
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name("deletedEmoteStats")
.name(DELETED_EMOTE_STATS_COMMAND_NAME)
.module(EmoteTrackingModuleDefinition.EMOTE_TRACKING)
.templated(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.supportsEmbedException(true)
.messageCommandOnly(true)
.causesReaction(true)

View File

@@ -7,11 +7,20 @@ 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.ServerSpecificId;
import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition;
import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames;
import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition;
import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote;
import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService;
import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -30,6 +39,17 @@ public class DisableEmoteTracking extends AbstractConditionableCommand {
@Autowired
private TrackedEmoteManagementService trackedEmoteManagementService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
private static final String DISABLE_EMOTE_TRACKING_COMMAND_NAME = "disableEmoteTracking";
private static final String DISABLE_EMOTE_TRACKING_TRACKED_EMOTE = "trackedEmote";
private static final String DISABLE_EMOTE_TRACKING_RESPONSE = "disableEmoteTracking_response";
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
@@ -44,22 +64,52 @@ public class DisableEmoteTracking extends AbstractConditionableCommand {
return CommandResult.fromSuccess();
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
if(slashCommandParameterService.hasCommandOption(DISABLE_EMOTE_TRACKING_TRACKED_EMOTE, event)) {
String emote = slashCommandParameterService.getCommandOption(DISABLE_EMOTE_TRACKING_TRACKED_EMOTE, event, String.class);
Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild());
Long emoteId = ((CustomEmoji) emoji).getIdLong();
ServerSpecificId serverEmoteId = new ServerSpecificId(event.getGuild().getIdLong(), emoteId);
TrackedEmote trackedEmote = trackedEmoteManagementService.loadByTrackedEmoteServer(serverEmoteId);
trackedEmoteManagementService.disableTrackedEmote(trackedEmote);
} else {
trackedEmoteService.disableEmoteTracking(event.getGuild());
}
return interactionService.replyEmbed(DISABLE_EMOTE_TRACKING_RESPONSE, event)
.thenApply(interactionHook -> CommandResult.fromIgnored());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter trackedEmoteParameter = Parameter
.builder()
.name("trackedEmote")
.name(DISABLE_EMOTE_TRACKING_TRACKED_EMOTE)
.templated(true)
.optional(true)
.type(TrackedEmote.class)
.build();
parameters.add(trackedEmoteParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(StatisticSlashCommandNames.STATISTIC_INTERNAL)
.groupName("manage")
.commandName("disableemotetracking")
.build();
return CommandConfiguration.builder()
.name("disableEmoteTracking")
.name(DISABLE_EMOTE_TRACKING_COMMAND_NAME)
.module(EmoteTrackingModuleDefinition.EMOTE_TRACKING)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.messageCommandOnly(true)
.supportsEmbedException(true)
.causesReaction(true)

View File

@@ -7,15 +7,28 @@ 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.ServerSpecificId;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition;
import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames;
import dev.sheldan.abstracto.statistic.emote.command.parameter.UsedEmoteTypeParameter;
import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition;
import dev.sheldan.abstracto.statistic.emote.exception.TrackedEmoteNotFoundException;
import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResultDisplay;
import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote;
import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService;
import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService;
import java.util.Arrays;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -40,7 +53,18 @@ public class EmoteStat extends AbstractConditionableCommand {
@Autowired
private TrackedEmoteManagementService trackedEmoteManagementService;
public static final String EMOTE_STAT_RESPONSE = "emoteStat_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
private static final String EMOTE_STAT_RESPONSE = "emoteStat_response";
private static final String EMOTE_STAT_USED_EMOTE_TYPE = "type";
private static final String EMOTE_STAT_DURATION = "period";
private static final String EMOTE_STAT_TRACKED_EMOTE = "trackedEmote";
private static final String EMOTE_STAT_COMMAND_NAME = "emoteStat";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
@@ -54,7 +78,7 @@ public class EmoteStat extends AbstractConditionableCommand {
Duration duration = (Duration) parameters.get(1);
statsSince = Instant.now().minus(duration);
}
EmoteStatsResultDisplay emoteStatsModel = usedEmoteService.getEmoteStatForEmote(trackedEmote, statsSince);
EmoteStatsResultDisplay emoteStatsModel = usedEmoteService.getEmoteStatForEmote(trackedEmote, statsSince, null);
if(emoteStatsModel.getResult().getAmount() == null) {
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_NO_STATS_AVAILABLE, new Object(), commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromIgnored());
@@ -63,29 +87,94 @@ public class EmoteStat extends AbstractConditionableCommand {
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
UsedEmoteTypeParameter typeEnum;
if(slashCommandParameterService.hasCommandOption(EMOTE_STAT_USED_EMOTE_TYPE, event)) {
String type = slashCommandParameterService.getCommandOption(EMOTE_STAT_USED_EMOTE_TYPE, event, String.class);
typeEnum = UsedEmoteTypeParameter.valueOf(type);
} else {
typeEnum = null;
}
Instant startTime;
if(slashCommandParameterService.hasCommandOption(EMOTE_STAT_DURATION, event)) {
String durationString = slashCommandParameterService.getCommandOption(EMOTE_STAT_DURATION, event, Duration.class, String.class);
Duration durationSince = ParseUtils.parseDuration(durationString);
startTime = Instant.now().minus(durationSince);
} else {
startTime = Instant.EPOCH;
}
String emote = slashCommandParameterService.getCommandOption(EMOTE_STAT_TRACKED_EMOTE, event, String.class);
Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild());
if(emoji instanceof CustomEmoji) {
Long emoteId = ((CustomEmoji) emoji).getIdLong();
TrackedEmote trackedEmote = trackedEmoteManagementService.loadByTrackedEmoteServer(new ServerSpecificId(event.getGuild().getIdLong(), emoteId));
EmoteStatsResultDisplay emoteStatsModel = usedEmoteService.getEmoteStatForEmote(trackedEmote, startTime, UsedEmoteTypeParameter.convertToUsedEmoteType(typeEnum));
if(emoteStatsModel.getResult().getAmount() == null) {
return interactionService.replyEmbed(EMOTE_STATS_NO_STATS_AVAILABLE, new Object(), event)
.thenApply(unused -> CommandResult.fromIgnored());
}
return interactionService.replyEmbed(EMOTE_STAT_RESPONSE, emoteStatsModel, event)
.thenApply(unused -> CommandResult.fromIgnored());
} else {
throw new TrackedEmoteNotFoundException();
}
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter trackedEmoteParameter = Parameter
.builder()
.name("trackedEmote")
.name(EMOTE_STAT_TRACKED_EMOTE)
.templated(true)
.type(TrackedEmote.class)
.build();
parameters.add(trackedEmoteParameter);
Parameter periodParameter = Parameter
.builder()
.name("period")
.name(EMOTE_STAT_DURATION)
.templated(true)
.optional(true)
.type(Duration.class)
.build();
parameters.add(periodParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
List<String> emoteTypes = Arrays
.stream(UsedEmoteTypeParameter.values())
.map(Enum::name)
.collect(Collectors.toList());
Parameter typeParameter = Parameter
.builder()
.name(EMOTE_STAT_USED_EMOTE_TYPE)
.templated(true)
.slashCommandOnly(true)
.optional(true)
.choices(emoteTypes)
.type(String.class)
.build();
parameters.add(typeParameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(StatisticSlashCommandNames.STATISTIC)
.groupName("emotestats")
.commandName("singular")
.build();
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name("emoteStat")
.name(EMOTE_STAT_COMMAND_NAME)
.module(EmoteTrackingModuleDefinition.EMOTE_TRACKING)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.messageCommandOnly(true)
.supportsEmbedException(true)

View File

@@ -7,16 +7,25 @@ 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.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition;
import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames;
import dev.sheldan.abstracto.statistic.emote.command.parameter.UsedEmoteTypeParameter;
import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition;
import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel;
import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService;
import java.util.Arrays;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
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;
@@ -43,9 +52,18 @@ public class EmoteStats extends AbstractConditionableCommand {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
public static final String EMOTE_STATS_STATIC_RESPONSE = "emoteStats_static_response";
public static final String EMOTE_STATS_ANIMATED_RESPONSE = "emoteStats_animated_response";
public static final String EMOTE_STATS_NO_STATS_AVAILABLE = "emoteStats_no_stats_available";
private static final String EMOTE_STATS_USED_EMOTE_TYPE = "type";
private static final String EMOTE_STATS_DURATION = "period";
private static final String EMOTE_STATS_COMMAND_NAME = "emoteStats";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
@@ -58,7 +76,7 @@ public class EmoteStats extends AbstractConditionableCommand {
statsSince = Instant.now().minus(duration);
}
AServer server = serverManagementService.loadServer(commandContext.getGuild());
EmoteStatsModel emoteStatsModel = usedEmoteService.getActiveEmoteStatsForServerSince(server, statsSince);
EmoteStatsModel emoteStatsModel = usedEmoteService.getActiveEmoteStatsForServerSince(server, statsSince, null);
List<CompletableFuture<Message>> messagePromises = new ArrayList<>();
// only show embed if static emote stats are available
if(!emoteStatsModel.getStaticEmotes().isEmpty()) {
@@ -80,24 +98,91 @@ public class EmoteStats extends AbstractConditionableCommand {
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
UsedEmoteTypeParameter typeEnum;
if(slashCommandParameterService.hasCommandOption(EMOTE_STATS_USED_EMOTE_TYPE, event)) {
String type = slashCommandParameterService.getCommandOption(EMOTE_STATS_USED_EMOTE_TYPE, event, String.class);
typeEnum = UsedEmoteTypeParameter.valueOf(type);
} else {
typeEnum = null;
}
Instant startTime;
if(slashCommandParameterService.hasCommandOption(EMOTE_STATS_DURATION, event)) {
String durationString = slashCommandParameterService.getCommandOption(EMOTE_STATS_DURATION, event, Duration.class, String.class);
Duration durationSince = ParseUtils.parseDuration(durationString);
startTime = Instant.now().minus(durationSince);
} else {
startTime = Instant.EPOCH;
}
AServer server = serverManagementService.loadServer(event.getGuild());
EmoteStatsModel emoteStatsModel = usedEmoteService.getActiveEmoteStatsForServerSince(server, startTime, UsedEmoteTypeParameter.convertToUsedEmoteType(typeEnum));
List<CompletableFuture<Message>> messagePromises = new ArrayList<>();
return event.deferReply().submit().thenCompose(interactionHook -> {
// only show embed if static emote stats are available
if(!emoteStatsModel.getStaticEmotes().isEmpty()) {
log.debug("Emote stats has {} static emotes since {}.", emoteStatsModel.getStaticEmotes().size(), startTime);
messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_STATIC_RESPONSE, emoteStatsModel, interactionHook));
}
// only show embed if animated emote stats are available
if(!emoteStatsModel.getAnimatedEmotes().isEmpty()) {
log.debug("Emote stats has {} animated emotes since {}.", emoteStatsModel.getAnimatedEmotes(), startTime);
messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_ANIMATED_RESPONSE, emoteStatsModel, interactionHook));
}
// show an embed if no emote stats are available indicating so
if(!emoteStatsModel.areStatsAvailable()) {
log.info("No emote stats available for guild {} since {}.", event.getGuild().getIdLong(), startTime);
messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_NO_STATS_AVAILABLE, emoteStatsModel, interactionHook));
}
return FutureUtils.toSingleFutureGeneric(messagePromises)
.thenApply(unused -> CommandResult.fromIgnored());
});
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter periodParameter = Parameter
.builder()
.name("period")
.name(EMOTE_STATS_DURATION)
.templated(true)
.optional(true)
.type(Duration.class)
.build();
parameters.add(periodParameter);
List<String> emoteTypes = Arrays
.stream(UsedEmoteTypeParameter.values())
.map(Enum::name)
.collect(Collectors.toList());
Parameter typeParameter = Parameter
.builder()
.name(EMOTE_STATS_USED_EMOTE_TYPE)
.templated(true)
.slashCommandOnly(true)
.optional(true)
.choices(emoteTypes)
.type(String.class)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(StatisticSlashCommandNames.STATISTIC)
.groupName("emotestats")
.commandName("current")
.build();
parameters.add(typeParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("emoteStats")
.name(EMOTE_STATS_COMMAND_NAME)
.module(EmoteTrackingModuleDefinition.EMOTE_TRACKING)
.templated(true)
.messageCommandOnly(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
@@ -109,4 +194,6 @@ public class EmoteStats extends AbstractConditionableCommand {
public FeatureDefinition getFeature() {
return StatisticFeatureDefinition.EMOTE_TRACKING;
}
}

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