Compare commits

...

35 Commits

Author SHA1 Message Date
Sheldan
373ed4cde5 [maven-release-plugin] prepare release v1.5.20 2023-12-28 22:30:41 +01:00
Sheldan
5eb9a0e674 [AB-xxx] preparing for release 2023-12-28 22:29:14 +01:00
Sheldan
d4f07bd719 [AB-xxx] adding wikipedia and dictionary features 2023-12-28 22:27:33 +01:00
Sheldan
8924b75530 [AB-xxx] adding check in case the modmail thread has been closed, this is needed because in thread mode one could potentially still execute commands
removing some message command implementations
refactoring the way modmail threads are loaded
2023-12-28 10:57:07 +01:00
Sheldan
25bdc98944 [maven-release-plugin] prepare for next development iteration 2023-12-26 21:04:49 +01:00
Sheldan
062581fce5 [maven-release-plugin] prepare release v1.5.19 2023-12-26 21:04:45 +01:00
Sheldan
be41551529 [AB-xxx] preparing for release 2023-12-26 21:03:11 +01:00
Sheldan
af8206c529 [AB-xxx] adding bonk image generation command
fixing offline streamer handling not being able to handle streamer without current sessions
2023-12-26 15:42:02 +01:00
Sheldan
8efedf6f6f [AB-xxx] renaming pat sprite png 2023-12-25 23:44:43 +01:00
Sheldan
80aff40054 [AB-xxx] adding pat gif generator command 2023-12-25 23:43:34 +01:00
Sheldan
c71f5f004d [AB-xxx] changing displayed user for already existing user in modmail to be the target user 2023-12-25 12:58:47 +01:00
Sheldan
350a634f50 [maven-release-plugin] prepare for next development iteration 2023-12-25 01:07:26 +01:00
Sheldan
f5de74c1e4 [maven-release-plugin] prepare release v1.5.18 2023-12-25 01:07:21 +01:00
Sheldan
6e3809bfd9 [AB-xxx] prepare for release 2023-12-25 01:06:02 +01:00
Sheldan
8e6339a99b [AB-109] fixing streamers not being marked online if the post target is disabled 2023-12-25 01:00:54 +01:00
Sheldan
befef1f61d [AB-xxx] refactoring modmail to offer a feature mode to use threads instead of channels
adding various utilities for thread channels in core
fixing enable feature not showing validation messages
restructuring feature mode collection to be a startup listener, because postconstruct might not have the appropriate values available, and therefore not initialize the map correctly
2023-12-24 23:25:03 +01:00
Sheldan
1f0bc493d9 [maven-release-plugin] prepare for next development iteration 2023-12-23 23:18:06 +01:00
Sheldan
48fa7a99dc [maven-release-plugin] prepare release v1.5.17 2023-12-23 23:18:02 +01:00
Sheldan
d12497753d [AB-xxx] preparing for release 2023-12-23 23:16:39 +01:00
Sheldan
20a6e29f1b [AB-xxx] fixing custom command alternative to filter when it should be executed more accurately 2023-12-23 23:16:01 +01:00
Sheldan
d2231b0934 [maven-release-plugin] prepare for next development iteration 2023-12-23 21:09:36 +01:00
Sheldan
0464afb7db [maven-release-plugin] prepare release v1.5.16 2023-12-23 21:09:32 +01:00
Sheldan
c473ca74d4 [AB-xxx] prepare for release 2023-12-23 21:08:20 +01:00
Sheldan
a70ac5aa94 [AB-xxx] fixing path for env file loading 2023-12-23 21:05:59 +01:00
Sheldan
d10faf4d3d [maven-release-plugin] prepare for next development iteration 2023-12-23 20:45:33 +01:00
Sheldan
a346e1372c [maven-release-plugin] prepare release v1.5.15 2023-12-23 20:45:28 +01:00
Sheldan
9a7c769e8f [AB-xxx] prepare for release 2023-12-23 20:43:57 +01:00
Sheldan
980ca9380c [AB-70] moving image generation functionality to separate image generation module
removing doge image generation from default base
split rest-api into separate modules (base and extensions)
2023-12-23 20:41:25 +01:00
Sheldan
e9d14ac417 [AB-xxx] refactoring rest-api to not be a maven project and restructuring python tool file structure 2023-12-20 21:05:22 +01:00
Sheldan
474e632fed [AB-xxx] adding rest-api base to abstracto with capability to load custom code if necessary 2023-12-19 01:25:07 +01:00
Sheldan
2e5c2c26b9 [maven-release-plugin] prepare for next development iteration 2023-12-12 19:18:51 +01:00
Sheldan
a8ca9a8055 [maven-release-plugin] prepare release v1.5.14 2023-12-12 19:18:46 +01:00
Sheldan
453ccf2e27 [AB-xxx] prepare for release 2023-12-12 19:17:33 +01:00
Sheldan
52c805f4ea [AB-63] adding pressF command
fixing not setting the won flag for giveaway participants
2023-12-11 01:26:07 +01:00
Sheldan
4b3038078e [maven-release-plugin] prepare for next development iteration 2023-12-10 14:32:10 +01:00
225 changed files with 4773 additions and 297 deletions

View File

@@ -1,2 +1,2 @@
REGISTRY_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.5.13
VERSION=1.5.20

View File

@@ -47,10 +47,9 @@ jobs:
id: dotenv
uses: falti/dotenv-action@v1.0.4
with:
path: ./deployment/installer/.env
path: .env
- name: Push container
run: docker-compose build && docker-compose push
working-directory: ./deployment/installer/
env:
REGISTRY_PREFIX: ${{ steps.dotenv.outputs.registry_prefix }}
VERSION: ${{ steps.dotenv.outputs.version }}

View File

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

View File

@@ -45,8 +45,16 @@ public class CustomCommandAlternative implements CommandAlternative {
private CustomCommandFeatureConfig customCommandFeatureConfig;
@Override
public boolean shouldExecute(UnParsedCommandParameter parameter, Guild guild) {
return featureFlagService.isFeatureEnabled(customCommandFeatureConfig, guild.getIdLong());
public boolean shouldExecute(UnParsedCommandParameter parameter, Guild guild, Message message) {
boolean featureEnabled = featureFlagService.isFeatureEnabled(customCommandFeatureConfig, guild.getIdLong());
if(featureEnabled) {
String contentStripped = message.getContentRaw();
List<String> parameters = Arrays.asList(contentStripped.split(" "));
String commandName = commandRegistry.getCommandName(parameters.get(0), message.getGuild().getIdLong());
Optional<CustomCommand> customCommandOptional = customCommandManagementService.getCustomCommandByName(commandName, message.getGuild().getIdLong());
return customCommandOptional.isPresent();
}
return false;
}
@Override

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.5.13</version>
<version>1.5.20</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.13</version>
<version>1.5.20</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.13</version>
<version>1.5.20</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.13</version>
<version>1.5.20</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.13</version>
<version>1.5.20</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,17 +3,12 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.13</version>
<version>1.5.20</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>entertainment-impl</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
@@ -57,6 +52,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>

View File

@@ -0,0 +1,140 @@
package dev.sheldan.abstracto.entertainment.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.entertainment.config.EntertainmentFeatureDefinition;
import dev.sheldan.abstracto.entertainment.config.EntertainmentModuleDefinition;
import dev.sheldan.abstracto.entertainment.config.EntertainmentSlashCommandNames;
import dev.sheldan.abstracto.entertainment.model.command.PressFPromptModel;
import dev.sheldan.abstracto.entertainment.service.EntertainmentService;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.entertainment.config.EntertainmentFeatureConfig.PRESS_F_DEFAULT_DURATION_SECONDS;
@Component
public class PressFCommand extends AbstractConditionableCommand {
public static final String TEXT_PARAMETER = "text";
public static final String DURATION_PARAMETER = "duration";
private static final String RESPONSE_TEMPLATE = "pressF_response";
public static final String PRESS_F_COMMAND_NAME = "pressF";
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private ConfigService configService;
@Autowired
private EntertainmentService entertainmentService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
String text = (String) commandContext.getParameters().getParameters().get(0);
Long defaultDurationSeconds = configService.getLongValueOrConfigDefault(PRESS_F_DEFAULT_DURATION_SECONDS, commandContext.getGuild().getIdLong());
Duration duration = Duration.ofSeconds(defaultDurationSeconds);
PressFPromptModel pressFModel = entertainmentService.getPressFModel(text);
List<CompletableFuture<Message>> messages = channelService.sendEmbedTemplateInMessageChannelList(RESPONSE_TEMPLATE, pressFModel, commandContext.getChannel());
return FutureUtils.toSingleFutureGeneric(messages)
.thenAccept(unused -> entertainmentService.persistPressF(text, duration, commandContext.getAuthor(),
pressFModel.getPressFComponentId(), commandContext.getChannel(), messages.get(0).join().getIdLong()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String text = slashCommandParameterService.getCommandOption(TEXT_PARAMETER, event, String.class);
Duration duration;
if(slashCommandParameterService.hasCommandOption(DURATION_PARAMETER, event)) {
String durationString = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, String.class);
duration = ParseUtils.parseDuration(durationString);
} else {
Long defaultDurationSeconds = configService.getLongValueOrConfigDefault(PRESS_F_DEFAULT_DURATION_SECONDS, event.getGuild().getIdLong());
duration = Duration.ofSeconds(defaultDurationSeconds);
}
PressFPromptModel pressFModel = entertainmentService.getPressFModel(text);
return interactionService.replyEmbed(RESPONSE_TEMPLATE, pressFModel, event)
.thenCompose(interactionHook -> interactionHook.retrieveOriginal().submit())
.thenAccept(message -> {
entertainmentService.persistPressF(text, duration, event.getMember(), pressFModel.getPressFComponentId(), event.getGuildChannel(), message.getIdLong());
})
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter textParameter = Parameter
.builder()
.name(TEXT_PARAMETER)
.type(String.class)
.templated(true)
.remainder(true)
.build();
parameters.add(textParameter);
Parameter durationParameter = Parameter
.builder()
.name(DURATION_PARAMETER)
.type(Duration.class)
.slashCommandOnly(true)
.optional(true)
.templated(true)
.build();
parameters.add(durationParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(EntertainmentSlashCommandNames.ENTERTAINMENT)
.commandName("pressf")
.build();
return CommandConfiguration.builder()
.name(PRESS_F_COMMAND_NAME)
.module(EntertainmentModuleDefinition.ENTERTAINMENT)
.templated(true)
.causesReaction(false)
.async(true)
.slashCommandConfig(slashCommandConfig)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return EntertainmentFeatureDefinition.ENTERTAINMENT;
}
}

View File

@@ -0,0 +1,35 @@
package dev.sheldan.abstracto.entertainment.job;
import dev.sheldan.abstracto.entertainment.service.EntertainmentService;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class PressFEvaluationJob extends QuartzJobBean {
@Getter
@Setter
private Long pressFId;
@Autowired
private EntertainmentService entertainmentService;
@Override
protected void executeInternal(JobExecutionContext context) {
try {
log.info("Executing press f evaluation job for pressf instance {}.", pressFId);
entertainmentService.evaluatePressF(pressFId);
} catch (Exception exception) {
log.error("Press f evaluation job failed.", exception);
}
}
}

View File

@@ -0,0 +1,107 @@
package dev.sheldan.abstracto.entertainment.listener.interaction;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.entertainment.config.EntertainmentFeatureDefinition;
import dev.sheldan.abstracto.entertainment.exception.AlreadyPressedFException;
import dev.sheldan.abstracto.entertainment.model.PressFPayload;
import dev.sheldan.abstracto.entertainment.model.command.PressFJoinModel;
import dev.sheldan.abstracto.entertainment.model.database.PressF;
import dev.sheldan.abstracto.entertainment.service.EntertainmentServiceBean;
import dev.sheldan.abstracto.entertainment.service.management.PressFManagementService;
import dev.sheldan.abstracto.entertainment.service.management.PressFPresserManagementServiceBean;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class PressFClickedListener implements ButtonClickedListener {
private static final String PRESS_F_CLICK_RESPONSE_TEMPLATE_KEY = "pressF_join";
@Autowired
private PressFPresserManagementServiceBean pressFPresserManagementServiceBean;
@Autowired
private PressFManagementService pressFManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private InteractionService interactionService;
@Autowired
private PressFClickedListener self;
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return EntertainmentServiceBean.PRESS_F_BUTTON_ORIGIN.equals(model.getOrigin());
}
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
PressFPayload payload = (PressFPayload) model.getDeserializedPayload();
Optional<PressF> pressFOptional = pressFManagementService.getPressFById(payload.getPressFId());
pressFOptional.ifPresent(pressF -> {
Member presserMember = model.getEvent().getMember();
AUserInAServer presser = userInServerManagementService.loadOrCreateUser(presserMember);
Long userInServerId = presser.getUserInServerId();
if(!pressFPresserManagementServiceBean.didUserAlreadyPress(pressF, presser)) {
PressFJoinModel joinModel = PressFJoinModel
.builder()
.messageId(pressF.getMessageId())
.memberDisplay(MemberDisplay.fromMember(presserMember))
.build();
interactionService.replyEmbed(PRESS_F_CLICK_RESPONSE_TEMPLATE_KEY, joinModel, model.getEvent().getInteraction()).thenAccept(interactionHook -> {
self.persistPresser(payload.getPressFId(), userInServerId);
log.info("Send message about pressing to user {} for pressF {}.", presserMember.getIdLong(), payload.getPressFId());
}).exceptionally(throwable -> {
log.error("Failed to send message or persist press user {} in pressF {}.", presserMember.getIdLong(), payload.getPressFId(), throwable);
return null;
});
} else {
log.debug("User {} already pressed for pressF {}.", presserMember.getIdLong(), payload.getPressFId());
throw new AlreadyPressedFException();
}
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
}
@Transactional
public void persistPresser(Long pressFId, Long userInServerId) {
log.info("Persisting pressing of user {} for pressF {}.", userInServerId, pressFId);
AUserInAServer presser = userInServerManagementService.loadOrCreateUser(userInServerId);
Optional<PressF> pressFByIdOptional = pressFManagementService.getPressFById(pressFId);
pressFByIdOptional.ifPresent(pressF -> {
pressFPresserManagementServiceBean.addPresser(pressF, presser);
});
}
@Override
public FeatureDefinition getFeature() {
return EntertainmentFeatureDefinition.ENTERTAINMENT;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public Boolean autoAcknowledgeEvent() {
return false;
}
}

View File

@@ -0,0 +1,11 @@
package dev.sheldan.abstracto.entertainment.model;
import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class PressFPayload implements ButtonPayload {
private Long pressFId;
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.entertainment.repository;
import dev.sheldan.abstracto.entertainment.model.database.PressFPresser;
import dev.sheldan.abstracto.entertainment.model.database.embed.PressFPresserId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PressFPresserRepository extends JpaRepository<PressFPresser, PressFPresserId> {
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.entertainment.repository;
import dev.sheldan.abstracto.entertainment.model.database.PressF;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PressFRepository extends JpaRepository<PressF, Long> {
}

View File

@@ -2,12 +2,33 @@ package dev.sheldan.abstracto.entertainment.service;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.MessageService;
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.FutureUtils;
import dev.sheldan.abstracto.entertainment.config.EntertainmentFeatureConfig;
import dev.sheldan.abstracto.entertainment.exception.ReactDuplicateCharacterException;
import dev.sheldan.abstracto.entertainment.model.PressFPayload;
import dev.sheldan.abstracto.entertainment.model.ReactMapping;
import dev.sheldan.abstracto.entertainment.model.command.PressFPromptModel;
import dev.sheldan.abstracto.entertainment.model.command.PressFResultModel;
import dev.sheldan.abstracto.entertainment.model.database.PressF;
import dev.sheldan.abstracto.entertainment.service.management.PressFManagementService;
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.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
@@ -17,7 +38,10 @@ import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
@@ -30,6 +54,9 @@ public class EntertainmentServiceBean implements EntertainmentService {
"DONT_COUNT", "REPLY_NO", "SOURCES_NO", "OUTLOOK_NOT_GOOD", "DOUBTFUL" // negative
);
public static final String PRESS_F_BUTTON_ORIGIN = "PRESS_F_BUTTON";
private static final String PRESS_F_RESULT_TEMPLATE_KEY = "pressF_result";
private ReactMapping reactMapping;
@Autowired
@@ -38,6 +65,33 @@ public class EntertainmentServiceBean implements EntertainmentService {
@Autowired
private ConfigService configService;
@Autowired
private ComponentService componentService;
@Autowired
private PressFManagementService pressFManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private SchedulerService schedulerService;
@Autowired
private ComponentPayloadService componentPayloadService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private ChannelService channelService;
@Autowired
private TemplateService templateService;
@Autowired
private MessageService messageService;
@Value("classpath:react_mappings.json")
private Resource reactMappingSource;
@@ -91,6 +145,66 @@ public class EntertainmentServiceBean implements EntertainmentService {
return sb.toString();
}
@Override
public PressFPromptModel getPressFModel(String text) {
String pressFComponent = componentService.generateComponentId();
return PressFPromptModel
.builder()
.pressFComponentId(pressFComponent)
.text(text)
.build();
}
@Transactional
public void persistPressF(String text, Duration duration, Member executingMember, String componentId, GuildMessageChannel guildMessageChannel, Long messageId) {
Instant targetDate = Instant.now().plus(duration);
log.info("Persisting pressF started by {} in server {} with due date {}.", executingMember.getIdLong(), executingMember.getGuild().getIdLong(), targetDate);
AUserInAServer creator = userInServerManagementService.loadOrCreateUser(executingMember);
AChannel channel = channelManagementService.loadChannel(guildMessageChannel);
PressF pressF = pressFManagementService.createPressF(text, targetDate, creator, channel, messageId);
HashMap<Object, Object> parameters = new HashMap<>();
parameters.put("pressFId", pressF.getId().toString());
JobParameters jobParameters = JobParameters
.builder()
.parameters(parameters)
.build();
log.debug("Starting scheduled job for pressF {}", pressF.getId());
schedulerService.executeJobWithParametersOnce("pressFEvaluationJob", "entertainment", jobParameters, Date.from(targetDate));
PressFPayload pressFPayload = PressFPayload
.builder()
.pressFId(pressF.getId())
.build();
log.debug("Persisting payload for pressF {}", pressF.getId());
componentPayloadService.createButtonPayload(componentId, pressFPayload, PRESS_F_BUTTON_ORIGIN, creator.getServerReference());
}
@Override
@Transactional
public CompletableFuture<Void> evaluatePressF(Long pressFId) {
Optional<PressF> pressFOptional = pressFManagementService.getPressFById(pressFId);
if(pressFOptional.isPresent()) {
log.info("Evaluating pressF with id {}", pressFId);
PressF pressF = pressFOptional.get();
PressFResultModel model = PressFResultModel
.builder()
.userCount((long) pressF.getPresser().size())
.text(pressF.getText())
.messageId(pressF.getMessageId())
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(PRESS_F_RESULT_TEMPLATE_KEY, model);
Long serverId = pressF.getServer().getId();
Long channelId = pressF.getPressFChannel().getId();
Long messageId = pressF.getMessageId();
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageEmbedToSendToAChannel(messageToSend, pressF.getPressFChannel()))
.thenCompose(unused -> messageService.loadMessage(serverId, channelId, messageId).thenCompose(message -> {
log.info("Clearing buttons from pressF {} in with message {} in channel {} in server {}.", pressFId, pressFId, channelId, serverId);
return componentService.clearButtons(message);
}));
} else {
throw new AbstractoRunTimeException(String.format("PressF with id %s not found.", pressFId));
}
}
@Override
public List<String> convertTextToEmojis(String text) {
return convertTextToEmojis(text, false);

View File

@@ -0,0 +1,36 @@
package dev.sheldan.abstracto.entertainment.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.entertainment.model.database.PressF;
import dev.sheldan.abstracto.entertainment.repository.PressFRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Optional;
@Component
public class PressFManagementServiceBean implements PressFManagementService {
@Autowired
private PressFRepository pressFRepository;
@Override
public PressF createPressF(String text, Instant targetDate, AUserInAServer creator, AChannel channel, Long messageId) {
PressF pressF = PressF
.builder()
.server(creator.getServerReference())
.creator(creator)
.messageId(messageId)
.pressFChannel(channel)
.text(text)
.targetDate(targetDate)
.build();
return pressFRepository.save(pressF);
}
@Override
public Optional<PressF> getPressFById(Long pressFId) {
return pressFRepository.findById(pressFId);
}
}

View File

@@ -0,0 +1,32 @@
package dev.sheldan.abstracto.entertainment.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.entertainment.model.database.PressF;
import dev.sheldan.abstracto.entertainment.model.database.PressFPresser;
import dev.sheldan.abstracto.entertainment.model.database.embed.PressFPresserId;
import dev.sheldan.abstracto.entertainment.repository.PressFPresserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class PressFPresserManagementServiceBean implements PressFPresserManagementService {
@Autowired
private PressFPresserRepository repository;
@Override
public PressFPresser addPresser(PressF pressF, AUserInAServer presser) {
PressFPresser pressFPresser = PressFPresser
.builder()
.presser(presser)
.id(new PressFPresserId(presser.getUserInServerId(), pressF.getId()))
.build();
return repository.save(pressFPresser);
}
@Override
public boolean didUserAlreadyPress(PressF pressF, AUserInAServer aUserInAServer) {
return repository.existsById(new PressFPresserId(aUserInAServer.getUserInServerId(), pressF.getId()));
}
}

View File

@@ -4,6 +4,9 @@ abstracto.systemConfigs.rouletteBullets.longValue=6
abstracto.systemConfigs.rollDefaultHigh.name=rollDefaultHigh
abstracto.systemConfigs.rollDefaultHigh.longValue=6
abstracto.systemConfigs.pressFDefaultDurationSeconds.name=pressFDefaultDurationSeconds
abstracto.systemConfigs.pressFDefaultDurationSeconds.longValue=90
abstracto.featureFlags.entertainment.featureName=entertainment
abstracto.featureFlags.entertainment.enabled=false

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="press_f_evaluation_job-insert">
<insert tableName="scheduler_job">
<column name="name" value="pressFEvaluationJob"/>
<column name="group_name" value="entertainment"/>
<column name="clazz" value="dev.sheldan.abstracto.entertainment.job.PressFEvaluationJob"/>
<column name="active" value="true"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,56 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="press_f-table">
<createTable tableName="press_f">
<column name="id" autoIncrement="true" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="press_f_pkey"/>
</column>
<column name="creator_user_id" type="INTEGER">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="target_date" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="text" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="press_f_channel_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="message_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="creator_user_id" baseTableName="press_f" constraintName="fk_press_f_creator" deferrable="false"
initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id"
referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="press_f" constraintName="fk_press_f_server"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="press_f_channel_id" baseTableName="press_f" constraintName="fk_press_f_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS press_f_update_trigger ON press_f;
CREATE TRIGGER press_f_update_trigger BEFORE UPDATE ON press_f FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS press_f_insert_trigger ON press_f;
CREATE TRIGGER press_f_insert_trigger BEFORE INSERT ON press_f FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,39 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="press_f_presser-table">
<createTable tableName="press_f_presser">
<column name="press_f_presser_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="press_f_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addPrimaryKey columnNames="press_f_presser_user_in_server_id, press_f_id" tableName="press_f_presser" constraintName="pk_press_f_presser" validate="true"/>
<addForeignKeyConstraint baseColumnNames="press_f_presser_user_in_server_id" baseTableName="press_f_presser" constraintName="fk_press_f_presser_user"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="user_in_server_id"
referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="press_f_id" baseTableName="press_f_presser" constraintName="fk_press_f_presser_press_f"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id"
referencedTableName="press_f" validate="true"/>
<sql>
DROP TRIGGER IF EXISTS press_f_presser_update_trigger ON press_f_presser;
CREATE TRIGGER press_f_presser_update_trigger BEFORE UPDATE ON press_f_presser FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS press_f_presser_insert_trigger ON press_f_presser;
CREATE TRIGGER press_f_presser_insert_trigger BEFORE INSERT ON press_f_presser FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -13,4 +13,5 @@
<include file="1.4.3/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.11/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.8/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.14/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -3,15 +3,10 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.13</version>
<version>1.5.20</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>entertainment-int</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@@ -12,6 +12,7 @@ public class EntertainmentFeatureConfig implements FeatureConfig {
public static final String ROULETTE_BULLETS_CONFIG_KEY = "rouletteBullets";
public static final String ROLL_DEFAULT_HIGH_KEY = "rollDefaultHigh";
public static final String PRESS_F_DEFAULT_DURATION_SECONDS = "pressFDefaultDurationSeconds";
@Override
public FeatureDefinition getFeature() {
return EntertainmentFeatureDefinition.ENTERTAINMENT;
@@ -19,6 +20,6 @@ public class EntertainmentFeatureConfig implements FeatureConfig {
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(ROULETTE_BULLETS_CONFIG_KEY, ROLL_DEFAULT_HIGH_KEY);
return Arrays.asList(ROULETTE_BULLETS_CONFIG_KEY, ROLL_DEFAULT_HIGH_KEY, PRESS_F_DEFAULT_DURATION_SECONDS);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.entertainment.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class AlreadyPressedFException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "already_pressed_f_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.entertainment.model.command;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Builder
@Setter
@Getter
public class PressFJoinModel {
private MemberDisplay memberDisplay;
private Long messageId;
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.entertainment.model.command;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Builder
@Setter
@Getter
public class PressFPromptModel {
private String text;
private String pressFComponentId;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.entertainment.model.command;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Builder
@Setter
@Getter
public class PressFResultModel {
private Long userCount;
private String text;
private Long messageId;
}

View File

@@ -0,0 +1,62 @@
package dev.sheldan.abstracto.entertainment.model.database;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "press_f")
@Getter
@Setter
@EqualsAndHashCode
public class PressF {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "creator_user_id", nullable = false)
private AUserInAServer creator;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", nullable = false)
private AServer server;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "press_f_channel_id", nullable = false)
private AChannel pressFChannel;
@Column(name = "message_id", nullable = false)
private Long messageId;
@Column(name = "text")
private String text;
@Column(name = "target_date", nullable = false)
private Instant targetDate;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
@OneToMany(
fetch = FetchType.LAZY,
orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "pressF")
@Builder.Default
private List<PressFPresser> presser = new ArrayList<>();
}

View File

@@ -0,0 +1,38 @@
package dev.sheldan.abstracto.entertainment.model.database;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.entertainment.model.database.embed.PressFPresserId;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "press_f_presser")
@Getter
@Setter
@EqualsAndHashCode
public class PressFPresser {
@EmbeddedId
@Getter
private PressFPresserId id;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("presserId")
@JoinColumn(name = "press_f_presser_user_in_server_id", nullable = false)
private AUserInAServer presser;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(updatable = false, insertable = false, name = "press_f_id", referencedColumnName = "id")
private PressF pressF;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.entertainment.model.database.embed;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import lombok.*;
import java.io.Serializable;
@Embeddable
@Getter
@Setter
@Builder
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class PressFPresserId implements Serializable {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "press_f_presser_user_in_server_id")
private Long presserId;
@Column(name = "press_f_id")
private Long pressFId;
}

View File

@@ -1,9 +1,13 @@
package dev.sheldan.abstracto.entertainment.service;
import dev.sheldan.abstracto.entertainment.exception.ReactDuplicateCharacterException;
import dev.sheldan.abstracto.entertainment.model.command.PressFPromptModel;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface EntertainmentService {
String getEightBallValue(String text);
@@ -12,6 +16,10 @@ public interface EntertainmentService {
boolean executeRoulette(Member memberExecuting);
String takeChoice(List<String> choices, Member memberExecuting);
String createMockText(String text, Member memberExecuting, Member mockedUser);
PressFPromptModel getPressFModel(String text);
void persistPressF(String text, Duration duration, Member executingMember, String componentId, GuildMessageChannel guildMessageChannel, Long messageId);
CompletableFuture<Void> evaluatePressF(Long pressFId);
/**
* Converts the given text to unicode characters (with predefined values from a manual mapping) and returns the matched

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.entertainment.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.entertainment.model.database.PressF;
import java.time.Instant;
import java.util.Optional;
public interface PressFManagementService {
PressF createPressF(String text, Instant targetDate, AUserInAServer creator, AChannel channel, Long messageId);
Optional<PressF> getPressFById(Long pressFId);
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.entertainment.service.management;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.entertainment.model.database.PressF;
import dev.sheldan.abstracto.entertainment.model.database.PressFPresser;
public interface PressFPresserManagementService {
PressFPresser addPresser(PressF pressF, AUserInAServer presser);
boolean didUserAlreadyPress(PressF pressF, AUserInAServer aUserInAServer);
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.13</version>
<version>1.5.20</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -23,9 +23,4 @@
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.5.13</version>
<version>1.5.20</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.13</version>
<version>1.5.20</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.13</version>
<version>1.5.20</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.13</version>
<version>1.5.20</version>
</parent>
<artifactId>giveaway-impl</artifactId>

View File

@@ -148,6 +148,7 @@ public class GiveawayServiceBean implements GiveawayService {
Giveaway giveaway = giveAwayOptional.get();
Set<Long> winners = new HashSet<>();
Integer winnerCount = giveaway.getWinnerCount();
giveaway.getParticipants().forEach(giveawayParticipant -> giveawayParticipant.setWon(false));
List<Long> potentialWinners = new ArrayList<>(giveaway
.getParticipants()
.stream()
@@ -170,6 +171,7 @@ public class GiveawayServiceBean implements GiveawayService {
.stream()
.filter(giveawayParticipant -> winners.contains(giveawayParticipant.getParticipant().getUserInServerId()))
.toList();
winningParticipants.forEach(giveawayParticipant -> giveawayParticipant.setWon(true));
List<MemberDisplay> winnerDisplays = winningParticipants
.stream()
.map(giveawayParticipant -> MemberDisplay.fromAUserInAServer(giveawayParticipant.getParticipant()))

View File

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

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.13</version>
<version>1.5.20</version>
</parent>
<artifactId>giveaway</artifactId>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.20</version>
</parent>
<artifactId>image-generation-impl</artifactId>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>image-generation-int</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,147 @@
package dev.sheldan.abstracto.imagegeneration.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.templating.model.AttachedFile;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FileService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationFeatureDefinition;
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames;
import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class Bonk extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member";
@Autowired
private ImageGenerationService imageGenerationService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private FileService fileService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
private static final String BONK_EMBED_TEMPLATE_KEY = "bonk_response";
@Value("${abstracto.feature.imagegeneration.bonk.imagesize}")
private Integer imageSize;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
Member member;
List<Object> parameters = commandContext.getParameters().getParameters();
if(parameters.isEmpty()) {
member = commandContext.getAuthor();
} else {
member = (Member) parameters.get(0);
}
File bonkGifFile = imageGenerationService.getBonkGif(member.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(BONK_EMBED_TEMPLATE_KEY, new Object());
// template support does not support binary files
AttachedFile file = AttachedFile
.builder()
.file(bonkGifFile)
.fileName("bonk.gif")
.build();
messageToSend.getAttachedFiles().add(file);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
event.deferReply().queue();
Member targetMember;
if(slashCommandParameterService.hasCommandOption(MEMBER_PARAMETER_KEY, event)) {
targetMember = slashCommandParameterService.getCommandOption(MEMBER_PARAMETER_KEY, event, Member.class);
} else {
targetMember = event.getMember();
}
File bonkGifFile = imageGenerationService.getBonkGif(targetMember.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(BONK_EMBED_TEMPLATE_KEY, new Object());
// template support does not support binary files
AttachedFile file = AttachedFile
.builder()
.file(bonkGifFile)
.fileName("bonk.gif")
.build();
messageToSend.getAttachedFiles().add(file);
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(messageToSend, event.getHook()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter memberParameter = Parameter
.builder()
.name(MEMBER_PARAMETER_KEY)
.type(Member.class)
.templated(true)
.optional(true)
.build();
parameters.add(memberParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ImageGenerationSlashCommandNames.IMAGE_GENERATION)
.groupName("memes")
.commandName("bonk")
.build();
return CommandConfiguration.builder()
.name("bonk")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.supportsEmbedException(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ImageGenerationFeatureDefinition.IMAGE_GENERATION;
}
}

View File

@@ -0,0 +1,147 @@
package dev.sheldan.abstracto.imagegeneration.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.templating.model.AttachedFile;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FileService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationFeatureDefinition;
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames;
import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class Pat extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member";
@Autowired
private ImageGenerationService imageGenerationService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private FileService fileService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
private static final String PAT_EMBED_TEMPLATE_KEY = "pat_response";
@Value("${abstracto.feature.imagegeneration.pat.imagesize}")
private Integer imageSize;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
Member member;
List<Object> parameters = commandContext.getParameters().getParameters();
if(parameters.isEmpty()) {
member = commandContext.getAuthor();
} else {
member = (Member) parameters.get(0);
}
File patGifFile = imageGenerationService.getPatGif(member.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(PAT_EMBED_TEMPLATE_KEY, new Object());
// template support does not support binary files
AttachedFile file = AttachedFile
.builder()
.file(patGifFile)
.fileName("pat.gif")
.build();
messageToSend.getAttachedFiles().add(file);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
event.deferReply().queue();
Member targetMember;
if(slashCommandParameterService.hasCommandOption(MEMBER_PARAMETER_KEY, event)) {
targetMember = slashCommandParameterService.getCommandOption(MEMBER_PARAMETER_KEY, event, Member.class);
} else {
targetMember = event.getMember();
}
File patGifFile = imageGenerationService.getPatGif(targetMember.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(PAT_EMBED_TEMPLATE_KEY, new Object());
// template support does not support binary files
AttachedFile file = AttachedFile
.builder()
.file(patGifFile)
.fileName("pat.gif")
.build();
messageToSend.getAttachedFiles().add(file);
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(messageToSend, event.getHook()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter memberParameter = Parameter
.builder()
.name(MEMBER_PARAMETER_KEY)
.type(Member.class)
.templated(true)
.optional(true)
.build();
parameters.add(memberParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ImageGenerationSlashCommandNames.IMAGE_GENERATION)
.groupName("memes")
.commandName("pat")
.build();
return CommandConfiguration.builder()
.name("pat")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.supportsEmbedException(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ImageGenerationFeatureDefinition.IMAGE_GENERATION;
}
}

View File

@@ -0,0 +1,147 @@
package dev.sheldan.abstracto.imagegeneration.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.templating.model.AttachedFile;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FileService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationFeatureDefinition;
import dev.sheldan.abstracto.imagegeneration.config.ImageGenerationSlashCommandNames;
import dev.sheldan.abstracto.imagegeneration.service.ImageGenerationService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class Triggered extends AbstractConditionableCommand {
public static final String MEMBER_PARAMETER_KEY = "member";
@Autowired
private ImageGenerationService imageGenerationService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private FileService fileService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
private static final String TRIGGERED_EMBED_TEMPLATE_KEY = "triggered_response";
@Value("${abstracto.feature.imagegeneration.triggered.imagesize}")
private Integer imageSize;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
Member member;
List<Object> parameters = commandContext.getParameters().getParameters();
if(parameters.isEmpty()) {
member = commandContext.getAuthor();
} else {
member = (Member) parameters.get(0);
}
File triggeredGifFile = imageGenerationService.getTriggeredGif(member.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(TRIGGERED_EMBED_TEMPLATE_KEY, new Object());
// template support does not support binary files
AttachedFile file = AttachedFile
.builder()
.file(triggeredGifFile)
.fileName("triggered.gif")
.build();
messageToSend.getAttachedFiles().add(file);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
event.deferReply().queue();
Member targetMember;
if(slashCommandParameterService.hasCommandOption(MEMBER_PARAMETER_KEY, event)) {
targetMember = slashCommandParameterService.getCommandOption(MEMBER_PARAMETER_KEY, event, Member.class);
} else {
targetMember = event.getMember();
}
File triggeredGifFile = imageGenerationService.getTriggeredGif(targetMember.getEffectiveAvatar().getUrl(imageSize));
MessageToSend messageToSend = templateService.renderEmbedTemplate(TRIGGERED_EMBED_TEMPLATE_KEY, new Object());
// template support does not support binary files
AttachedFile file = AttachedFile
.builder()
.file(triggeredGifFile)
.fileName("triggered.gif")
.build();
messageToSend.getAttachedFiles().add(file);
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(messageToSend, event.getHook()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter memberParameter = Parameter
.builder()
.name(MEMBER_PARAMETER_KEY)
.type(Member.class)
.templated(true)
.optional(true)
.build();
parameters.add(memberParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ImageGenerationSlashCommandNames.IMAGE_GENERATION)
.groupName("memes")
.commandName("triggered")
.build();
return CommandConfiguration.builder()
.name("triggered")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.supportsEmbedException(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ImageGenerationFeatureDefinition.IMAGE_GENERATION;
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.imagegeneration.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:image-generation-config.properties")
public class ImageGenerationConfig {
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.abstracto.imagegeneration.service;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.service.HttpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
@Component
public class ImageGenerationServiceBean implements ImageGenerationService {
@Value("${abstracto.feature.imagegeneration.triggered.url}")
private String triggeredUrl;
@Value("${abstracto.feature.imagegeneration.pat.url}")
private String patUrl;
@Value("${abstracto.feature.imagegeneration.bonk.url}")
private String bonkUrl;
@Autowired
private HttpService httpService;
@Override
public File getTriggeredGif(String imageUrl) {
try {
return httpService.downloadFileToTempFile(triggeredUrl.replace("{1}", imageUrl));
} catch (IOException e) {
throw new AbstractoRunTimeException(String.format("Failed to download triggered gif for url %s with error %s", imageUrl, e.getMessage()));
}
}
@Override
public File getPatGif(String imageUrl) {
try {
return httpService.downloadFileToTempFile(patUrl.replace("{1}", imageUrl));
} catch (IOException e) {
throw new AbstractoRunTimeException(String.format("Failed to download pat gif for url %s with error %s", imageUrl, e.getMessage()));
}
}
@Override
public File getBonkGif(String imageUrl) {
try {
return httpService.downloadFileToTempFile(bonkUrl.replace("{1}", imageUrl));
} catch (IOException e) {
throw new AbstractoRunTimeException(String.format("Failed to download bonk gif for url %s with error %s", imageUrl, e.getMessage()));
}
}
}

View File

@@ -0,0 +1,11 @@
abstracto.featureFlags.imageGeneration.featureName=imageGeneration
abstracto.featureFlags.imageGeneration.enabled=false
abstracto.feature.imagegeneration.triggered.url=http://${PRIVATE_REST_API_HOST}:${PRIVATE_REST_API_PORT}/memes/triggered/file.gif?url={1}
abstracto.feature.imagegeneration.triggered.imagesize=128
abstracto.feature.imagegeneration.pat.url=http://${PRIVATE_REST_API_HOST}:${PRIVATE_REST_API_PORT}/memes/pat/file.gif?url={1}
abstracto.feature.imagegeneration.pat.imagesize=128
abstracto.feature.imagegeneration.bonk.url=http://${PRIVATE_REST_API_HOST}:${PRIVATE_REST_API_PORT}/memes/bonk/file.gif?url={1}
abstracto.feature.imagegeneration.bonk.imagesize=128

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.20</version>
</parent>
<artifactId>image-generation-int</artifactId>
</project>

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.imagegeneration.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import org.springframework.stereotype.Component;
@Component
public class ImageGenerationFeatureConfig implements FeatureConfig {
@Override
public FeatureDefinition getFeature() {
return ImageGenerationFeatureDefinition.IMAGE_GENERATION;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.imagegeneration.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum ImageGenerationFeatureDefinition implements FeatureDefinition {
IMAGE_GENERATION("imageGeneration");
private String key;
ImageGenerationFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.imagegeneration.config;
public class ImageGenerationSlashCommandNames {
public static final String IMAGE_GENERATION = "imagegeneration";
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.imagegeneration.service;
import java.io.File;
public interface ImageGenerationService {
File getTriggeredGif(String imageUrl);
File getPatGif(String imageUrl);
File getBonkGif(String imageUrl);
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.5.20</version>
</parent>
<artifactId>image-generation</artifactId>
<packaging>pom</packaging>
<modules>
<module>image-generation-int</module>
<module>image-generation-impl</module>
</modules>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-int</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

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

View File

@@ -3,17 +3,12 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.5.13</version>
<version>1.5.20</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>invite-filter-int</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>

View File

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

View File

@@ -8,12 +8,12 @@ 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.models.database.AChannel;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -42,18 +42,17 @@ public class AnonReply extends AbstractConditionableCommand {
@Autowired
private MemberService memberService;
@Autowired
private ChannelManagementService channelManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
// text is optional, for example if only an attachment is sent
String text = parameters.size() == 1 ? (String) parameters.get(0) : "";
AChannel channel = channelManagementService.loadChannel(commandContext.getChannel());
ModMailThread thread = modMailThreadManagementService.getByChannel(channel);
Long threadId = thread.getId();
return memberService.getMemberInServerAsync(thread.getUser()).thenCompose(member ->
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(commandContext.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
Long threadId = modMailThread.getId();
return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
modMailThreadService.loadExecutingMemberAndRelay(threadId, text, commandContext.getMessage(), true, member)
).thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -11,13 +11,13 @@ import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
@@ -59,9 +59,6 @@ public class Close extends AbstractConditionableCommand {
@Autowired
private TemplateService templateService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private InteractionService interactionService;
@@ -76,8 +73,10 @@ public class Close extends AbstractConditionableCommand {
List<Object> parameters = commandContext.getParameters().getParameters();
// the default value of the note is configurable via template
String note = parameters.size() == 1 ? (String) parameters.get(0) : templateService.renderTemplate(MODMAIL_CLOSE_DEFAULT_NOTE_TEMPLATE_KEY, new Object());
AChannel channel = channelManagementService.loadChannel(commandContext.getChannel());
ModMailThread thread = modMailThreadManagementService.getByChannel(channel);
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(commandContext.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
ClosingContext context = ClosingContext
.builder()
.closingMember(commandContext.getAuthor())
@@ -86,7 +85,7 @@ public class Close extends AbstractConditionableCommand {
.log(true)
.note(note)
.build();
return modMailThreadService.closeModMailThread(thread, context, commandContext.getUndoActions())
return modMailThreadService.closeModMailThread(modMailThread, context, commandContext.getUndoActions())
.thenApply(aVoid -> CommandResult.fromIgnored());
}
@@ -125,9 +124,11 @@ public class Close extends AbstractConditionableCommand {
@Transactional
public CompletableFuture<Void> closeThread(ClosingContext closingContext) {
AChannel channel = channelManagementService.loadChannel(closingContext.getChannel());
ModMailThread thread = modMailThreadManagementService.getByChannel(channel);
return modMailThreadService.closeModMailThread(thread, closingContext, new ArrayList<>());
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(closingContext.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
return modMailThreadService.closeModMailThread(modMailThread, closingContext, new ArrayList<>());
}
@Override

View File

@@ -8,12 +8,12 @@ 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.models.database.AChannel;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
@@ -43,16 +43,15 @@ public class CloseSilently extends AbstractConditionableCommand {
@Autowired
private TemplateService templateService;
@Autowired
private ChannelManagementService channelManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
// default note text is configurable via template, because the note is optional
String note = parameters.size() == 1 ? (String) parameters.get(0) : templateService.renderTemplate("modmail_close_default_note", new Object());
AChannel channel = channelManagementService.loadChannel(commandContext.getChannel());
ModMailThread thread = modMailThreadManagementService.getByChannel(channel);
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(commandContext.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
ClosingContext context = ClosingContext
.builder()
.closingMember(commandContext.getAuthor())
@@ -60,7 +59,7 @@ public class CloseSilently extends AbstractConditionableCommand {
.log(true)
.note(note)
.build();
return modMailThreadService.closeModMailThread(thread, context, commandContext.getUndoActions())
return modMailThreadService.closeModMailThread(modMailThread, context, commandContext.getUndoActions())
.thenApply(aVoid -> CommandResult.fromIgnored());
}

View File

@@ -82,7 +82,7 @@ public class Contact extends AbstractConditionableCommand {
ModMailThreadExistsModel model = ModMailThreadExistsModel
.builder()
.existingModMailThread(existingThread)
.executingMemberDisplay(MemberNameDisplay.fromMember(commandContext.getAuthor()))
.executingMemberDisplay(MemberNameDisplay.fromMember(targetUser))
.build();
List<CompletableFuture<Message>> futures = channelService.sendEmbedTemplateInTextChannelList(MODMAIL_THREAD_ALREADY_EXISTS_TEMPLATE, model, commandContext.getChannel());
return FutureUtils.toSingleFutureGeneric(futures).thenApply(aVoid -> CommandResult.fromIgnored());

View File

@@ -8,12 +8,12 @@ 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.models.database.AChannel;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -41,17 +41,16 @@ public class Reply extends AbstractConditionableCommand {
@Autowired
private MemberService memberService;
@Autowired
private ChannelManagementService channelManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
String text = parameters.size() == 1 ? (String) parameters.get(0) : "";
AChannel channel = channelManagementService.loadChannel(commandContext.getChannel());
ModMailThread thread = modMailThreadManagementService.getByChannel(channel);
Long threadId = thread.getId();
return memberService.getMemberInServerAsync(thread.getUser()).thenCompose(member ->
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(commandContext.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
Long threadId = modMailThread.getId();
return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
modMailThreadService.loadExecutingMemberAndRelay(threadId, text, commandContext.getMessage(), false, member)
).thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -5,7 +5,6 @@ import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
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;
@@ -13,7 +12,9 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.service.ModMailSubscriptionService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@@ -49,16 +50,12 @@ public class Subscribe extends AbstractConditionableCommand {
@Autowired
private InteractionService interactionService;
@Override
public CommandResult execute(CommandContext commandContext) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(commandContext.getChannel().getIdLong());
modMailSubscriptionService.subscribeToThread(userInServerManagementService.loadOrCreateUser(commandContext.getAuthor()), modMailThread);
return CommandResult.fromSuccess();
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(event.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
modMailSubscriptionService.subscribeToThread(userInServerManagementService.loadOrCreateUser(event.getMember()), modMailThread);
return interactionService.replyEmbed(SUBSCRIBE_RESPONSE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
@@ -83,6 +80,7 @@ public class Subscribe extends AbstractConditionableCommand {
.slashCommandConfig(slashCommandConfig)
.module(ModMailModuleDefinition.MODMAIL)
.help(helpInfo)
.slashCommandOnly(true)
.supportsEmbedException(true)
.templated(true)
.causesReaction(true)

View File

@@ -5,7 +5,6 @@ import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
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;
@@ -14,7 +13,9 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.service.ModMailSubscriptionService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@@ -50,17 +51,12 @@ public class UnSubscribe extends AbstractConditionableCommand {
@Autowired
private InteractionService interactionService;
@Override
public CommandResult execute(CommandContext commandContext) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(commandContext.getChannel().getIdLong());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor());
modMailSubscriptionService.unsubscribeFromThread(aUserInAServer, modMailThread);
return CommandResult.fromSuccess();
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(event.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(event.getMember());
modMailSubscriptionService.unsubscribeFromThread(aUserInAServer, modMailThread);
return interactionService.replyEmbed(UN_SUBSCRIBE_RESPONSE, event)
@@ -86,6 +82,7 @@ public class UnSubscribe extends AbstractConditionableCommand {
.slashCommandConfig(slashCommandConfig)
.module(ModMailModuleDefinition.MODMAIL)
.help(helpInfo)
.slashCommandOnly(true)
.supportsEmbedException(true)
.templated(true)
.causesReaction(true)

View File

@@ -40,6 +40,7 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
@@ -56,14 +57,12 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
import static dev.sheldan.abstracto.modmail.config.ModMailFeatureConfig.MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY;
@Component
@Slf4j
public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* The config key to use for the closing text
*/
public static final String MODMAIL_CLOSING_MESSAGE_TEXT = "modMailClosingText";
/**
* The config key to use for the ID of the category to create {@link GuildMessageChannel} in
*/
@@ -190,11 +189,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Override
public CompletableFuture<MessageChannel> createModMailThreadForUser(Member member, Message initialMessage, boolean userInitiated, List<UndoActionInstance> undoActions) {
Long serverId = member.getGuild().getIdLong();
Long categoryId = configService.getLongValue(MODMAIL_CATEGORY, serverId);
User user = member.getUser();
AServer server = serverManagementService.loadServer(member.getGuild().getIdLong());
metricService.incrementCounter(MODMAIL_THREAD_CREATED_COUNTER);
User user = member.getUser();
log.info("Creating modmail channel for user {} in category {} on server {}.", user.getId(), categoryId, serverId);
ModMailChannelNameModel model = ModMailChannelNameModel
.builder()
.serverId(serverId)
@@ -204,12 +201,36 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.currentDate(Instant.now())
.build();
String channelName = templateService.renderTemplate(TEXT_CHANNEL_NAME_TEMPLATE_KEY, model, serverId);
CompletableFuture<TextChannel> textChannelFuture = channelService.createTextChannel(channelName, server, categoryId);
return textChannelFuture.thenCompose(channel -> {
undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, channel.getIdLong()));
return self.performModMailThreadSetup(member, initialMessage, channel, userInitiated, undoActions)
.thenCompose(unused -> CompletableFuture.completedFuture(channel));
});
if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, serverId, ModMailMode.THREAD_CONTAINER)) {
MessageToSend notificationMessageToSend = getModmailNotificationMessageToSend(member, null, serverId, false);
Optional<GuildMessageChannel> modmailContainerOptional = postTargetService.getPostTargetChannel(ModMailPostTargets.MOD_MAIL_CONTAINER, serverId);
if(modmailContainerOptional.isEmpty()) {
throw new AbstractoRunTimeException("Modmail thread container not setup.");
}
GuildMessageChannel modmailContainer = modmailContainerOptional.get();
Optional<TextChannel> textChannelOptional = channelService.getTextChannelFromServerOptional(serverId, modmailContainer.getIdLong());
if(textChannelOptional.isEmpty()) {
throw new AbstractoRunTimeException("Modmail thread container text channel not found.");
}
TextChannel textChannel = textChannelOptional.get();
List<CompletableFuture<Message>> notificationMessage = channelService.sendMessageToSendToChannel(notificationMessageToSend, modmailContainer);
return FutureUtils.toSingleFutureGeneric(notificationMessage)
.thenCompose(unused -> channelService.createThreadWithStarterMessage(textChannel, channelName, notificationMessage.get(0).join().getIdLong()))
.thenCompose(threadChannel -> {
undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, threadChannel.getIdLong()));
return self.performModMailThreadSetup(member, initialMessage, threadChannel, userInitiated, undoActions)
.thenCompose(unused -> CompletableFuture.completedFuture(threadChannel));
});
} else {
Long categoryId = configService.getLongValue(MODMAIL_CATEGORY, serverId);
log.info("Creating modmail channel for user {} in category {} on server {}.", user.getId(), categoryId, serverId);
CompletableFuture<TextChannel> textChannelFuture = channelService.createTextChannel(channelName, server, categoryId);
return textChannelFuture.thenCompose(channel -> {
undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, channel.getIdLong()));
return self.performModMailThreadSetup(member, initialMessage, channel, userInitiated, undoActions)
.thenCompose(unused -> CompletableFuture.completedFuture(channel));
});
}
}
@Transactional
@@ -239,12 +260,12 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @param member The {@link Member} for which a {@link ModMailThread} is being created
* @param initialMessage The {@link Message} which was sent by the user to open a thread, this is null, if the thread was opened via a command
* @param channel The created {@link TextChannel} in which the mod mail thread is dealt with
* @param userInitiated Whether or not the thread was initiated by a member
* @param userInitiated Whether the thread was initiated by a member
* @param undoActions The list of actions to undo, in case an exception occurs
* @return A {@link CompletableFuture future} which completes when the setup is done
*/
@Transactional
public CompletableFuture<Void> performModMailThreadSetup(Member member, Message initialMessage, TextChannel channel, boolean userInitiated, List<UndoActionInstance> undoActions) {
public CompletableFuture<Void> performModMailThreadSetup(Member member, Message initialMessage, GuildMessageChannel channel, boolean userInitiated, List<UndoActionInstance> undoActions) {
log.info("Performing modmail thread setup for channel {} for user {} in server {}. It was initiated by a user: {}.", channel.getIdLong(), member.getId(), channel.getGuild().getId(), userInitiated);
CompletableFuture<Void> headerFuture = sendModMailHeader(channel, member);
CompletableFuture<Message> userReplyMessage;
@@ -268,7 +289,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
@Transactional
public void setupModMailThreadInDB(Message initialMessage, TextChannel channel, Member member, Message sendMessage) {
public void setupModMailThreadInDB(Message initialMessage, GuildMessageChannel channel, Member member, Message sendMessage) {
log.info("Persisting info about modmail thread {} in database.", channel.getIdLong());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
ModMailThread thread = createThreadObject(channel, aUserInAServer);
@@ -281,15 +302,25 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* Sends the message containing the pings to notify the staff members to handle the opened {@link ModMailThread}
* @param member The {@link FullUserInServer} which opened the thread
* @param channel The created {@link TextChannel} in which the mod mail thread is dealt with
* @return A {@link CompletableFuture future} which complets when the notification has been sent
* @param channel The created {@link GuildMessageChannel} in which the mod mail thread is dealt with
* @return A {@link CompletableFuture future} which completes when the notification has been sent
*/
@Transactional
public CompletableFuture<Void> sendModMailNotification(Member member, TextChannel channel) {
public CompletableFuture<Void> sendModMailNotification(Member member, GuildMessageChannel channel) {
Long serverId = member.getGuild().getIdLong();
MessageToSend messageToSend = getModmailNotificationMessageToSend(member, channel, serverId, true);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId));
}
private MessageToSend getModmailNotificationMessageToSend(Member member, GuildMessageChannel channel, Long serverId, boolean pingRole) {
log.info("Sending modmail notification for new modmail thread about user {} in server {}.", member.getId(), serverId);
AServer server = serverManagementService.loadServer(serverId);
List<ModMailRole> rolesToPing = modMailRoleManagementService.getRolesForServer(server);
List<ModMailRole> rolesToPing;
if(pingRole) {
rolesToPing = modMailRoleManagementService.getRolesForServer(server);
} else {
rolesToPing = new ArrayList<>();
}
log.debug("Pinging {} roles to notify about modmail thread about user {} in server {}.", rolesToPing.size(), member.getId(), serverId);
ModMailNotificationModel modMailNotificationModel = ModMailNotificationModel
.builder()
@@ -297,29 +328,31 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.roles(rolesToPing)
.channel(channel)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_notification_message", modMailNotificationModel, channel.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId));
return templateService.renderEmbedTemplate("modmail_notification_message", modMailNotificationModel, serverId);
}
/**
* Creates the instance of the {@link ModMailThread} in the database.
* @param channel The {@link TextChannel} in which the {@link ModMailThread} is being done
* @param channel The {@link GuildMessageChannel} in which the {@link ModMailThread} is being done
* @param user The {@link AUserInAServer} which the thread is about
* @return The created instance of {@link ModMailThread}
*/
public ModMailThread createThreadObject(TextChannel channel, AUserInAServer user) {
public ModMailThread createThreadObject(GuildMessageChannel channel, AUserInAServer user) {
log.info("Creating database objects related to modmail thread in channel {} and about user {} in server {}.", channel.getIdLong(), user.getUserReference().getId(), channel.getGuild().getId());
AChannel channel2 = channelManagementService.createChannel(channel.getIdLong(), AChannelType.TEXT, user.getServerReference());
log.info("Creating mod mail thread in channel {} with db channel {}", channel.getIdLong(), channel2.getId());
return modMailThreadManagementService.createModMailThread(user, channel2);
boolean useThreads = featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, channel.getGuild().getIdLong(), ModMailMode.THREAD_CONTAINER);
AChannel aChannel = channelManagementService.createChannel(channel.getIdLong(), useThreads ? AChannelType.PUBLIC_THREAD : AChannelType.TEXT, user.getServerReference());
log.info("Creating mod mail thread in channel {} with db channel {}", channel.getIdLong(), aChannel.getId());
return modMailThreadManagementService.createModMailThread(user, aChannel);
}
@Override
public void setModMailCategoryTo(Guild guild, Long categoryId) {
log.info("Trying to set modmail category to {} in guild {}.", categoryId, guild.getId());
FeatureValidationResult result = FeatureValidationResult.builder().build();
FeatureValidationResult result = FeatureValidationResult
.builder()
.build();
modMailFeatureValidator.validateModMailCategory(result, guild, categoryId);
if(result.getValidationResult()) {
if(!result.getValidationResult()) {
throw new ModMailCategoryIdException(categoryId);
}
configService.setLongValue(MODMAIL_CATEGORY, guild.getIdLong(), categoryId);
@@ -416,10 +449,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* Method used to send the header of a newly created mod mail thread. This message contains information about
* the user which the thread is about
* @param channel The {@link TextChannel} in which the mod mail thread is present in
* @param channel The {@link GuildMessageChannel} in which the mod mail thread is present in
* @param member The {@link Member} which the {@link ModMailThread} is about
*/
private CompletableFuture<Void> sendModMailHeader(TextChannel channel, Member member) {
private CompletableFuture<Void> sendModMailHeader(GuildMessageChannel channel, Member member) {
log.debug("Sending modmail thread header for tread in channel {} on server {}.", channel.getIdLong(), channel.getGuild().getId());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
ModMailThread latestThread = modMailThreadManagementService.getLatestModMailThread(aUserInAServer);
@@ -472,8 +505,8 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @param modMailThreadId The id of the modmail thread to which the received {@link Message} is a reply to, can be null, if it is null, its the initial message
* @param messageFromUser The received message from the user
* @param member The {@link Member} instance from the user the thread is about. It is used as author
* @param modMailThreadExists Whether or not the modmail thread already exists and is persisted.
* @return A {@link CompletableFuture} which resolves when the post processing of the message is completed (adding read notification, and storing messageIDs)
* @param modMailThreadExists Whether the modmail thread already exists and is persisted.
* @return A {@link CompletableFuture} which resolves when the postprocessing of the message is completed (adding read notification, and storing messageIDs)
*/
public CompletableFuture<Message> sendUserReply(GuildMessageChannel messageChannel, Long modMailThreadId, Message messageFromUser, Member member, boolean modMailThreadExists) {
List<CompletableFuture<Member>> subscriberMemberFutures = new ArrayList<>();
@@ -548,7 +581,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
/**
* This message handles the post processing of the messages received by the user. This includes: saving the messageIDs
* This message handles the postprocessing of the messages received by the user. This includes: saving the messageIDs
* in the database, updating the state of the {@link ModMailThread} and adding the read reaction to the user message
* @param textChannel The channel in which the message
* @param messageInModMailThread The actual {@link Message} instance which was sent to the mod mail thread
@@ -640,22 +673,31 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
List<ModMailMessage> modMailMessages = modMailThread.getMessages();
Long userId = modMailThread.getUser().getUserReference().getId();
Long serverId = modMailThread.getServer().getId();
if(closingConfig.getLog()) {
if(!modMailMessages.isEmpty()) {
return modMailMessageService.loadModMailMessages(modMailMessages)
.thenAccept(loadedModmailThreadMessages -> self.logMessagesToModMailLog(closingConfig, modMailThreadId, undoActions, loadedModmailThreadMessages, serverId, userId));
} else {
log.info("Modmail thread {} in server {} has no messages. Only logging header.", modMailThreadId, serverId);
return loadUserAndSendClosingHeader(modMailThread, closingConfig)
.thenAccept(unused -> memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions)
));
}
if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, serverId, ModMailMode.THREAD_CONTAINER)) {
ThreadChannel threadChannel = channelService.getThreadChannel(modMailThread.getChannel().getId());
log.info("Archiving thread {} for modmail thread closing.", modMailThread.getChannel().getId());
return loadUserAndSendClosingHeader(modMailThread, closingConfig)
.thenCompose(unused -> channelService.archiveThreadChannel(threadChannel))
.thenCompose(unused -> memberService.getMemberInServerAsync(serverId, userId))
.thenAccept(member -> self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions));
} else {
log.debug("Not logging modmail thread {}.", modMailThreadId);
return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions)
);
if(closingConfig.getLog()) {
if(!modMailMessages.isEmpty()) {
return modMailMessageService.loadModMailMessages(modMailMessages)
.thenAccept(loadedModmailThreadMessages -> self.logMessagesToModMailLog(closingConfig, modMailThreadId, undoActions, loadedModmailThreadMessages, serverId, userId));
} else {
log.info("Modmail thread {} in server {} has no messages. Only logging header.", modMailThreadId, serverId);
return loadUserAndSendClosingHeader(modMailThread, closingConfig)
.thenAccept(unused -> memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions)
));
}
} else {
log.debug("Not logging modmail thread {}.", modMailThreadId);
return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member ->
self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions)
);
}
}
}
@@ -711,7 +753,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* This message is executed after the thread has been logged and notifies the user about the closed {@link ModMailThread}
* which a configurable closing text. This method then calls the method to delete the channel.
* @param modMailThreadId The ID of the {@link ModMailThread} which is being closed.
* @param notifyUser Whether or not the user should be notified
* @param notifyUser Whether the user should be notified
* @param undoActions The list of {@link UndoActionInstance} to execute in case of exceptions
* @param modMailThreaduser The {@link Member member} for which the {@link ModMailThread thread} was for
* @throws ModMailThreadNotFoundException in case the {@link ModMailThread} is not found by the ID
@@ -727,7 +769,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
ModMailThread modMailThread = modMailThreadOpt.get();
HashMap<String, String> closingMessage = new HashMap<>();
String defaultValue = templateService.renderSimpleTemplate("modmail_closing_user_message_description");
closingMessage.put("closingMessage", configService.getStringValue(MODMAIL_CLOSING_MESSAGE_TEXT, modMailThread.getServer().getId(), defaultValue));
closingMessage.put("closingMessage", configService.getStringValue(MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY, modMailThread.getServer().getId(), defaultValue));
return messageService.sendEmbedToUser(modMailThreaduser.getUser(), "modmail_closing_user_message", closingMessage).thenAccept(message ->
self.deleteChannelAndClose(modMailThreadId, undoActions)
).exceptionally(throwable -> {
@@ -758,11 +800,17 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
ModMailThread modMailThread = modMailThreadOpt.get();
String failureMessage = "Failed to delete text channel containing mod mail thread {}";
try {
log.debug("Deleting channel {} which contained the modmail thread {}.", modMailThread.getChannel().getId(), modMailThreadId);
return channelService.deleteTextChannel(modMailThread.getChannel()).thenAccept(avoid -> {
if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, modMailThread.getServer().getId(), ModMailMode.THREAD_CONTAINER)) {
undoActions.clear();
self.closeModMailThreadInDb(modMailThreadId);
});
return CompletableFuture.completedFuture(null);
} else {
log.debug("Deleting channel {} which contained the modmail thread {}.", modMailThread.getChannel().getId(), modMailThreadId);
return channelService.deleteTextChannel(modMailThread.getChannel()).thenAccept(avoid -> {
undoActions.clear();
self.closeModMailThreadInDb(modMailThreadId);
});
}
} catch (InsufficientPermissionException ex) {
log.error(failureMessage, modMailThreadId, ex);
String message = "Failed To delete mod mail thread channel because no permissions.";
@@ -864,16 +912,22 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.serverId(modMailThread.getServer().getId())
.userId(modMailThread.getUser().getUserReference().getId())
.build();
Long modmailThreadId = modMailThread.getId();
return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenApply(user -> {
headerModel.setUser(user);
return self.sendClosingHeader(headerModel).get(0);
return self.sendClosingHeader(headerModel, modmailThreadId).get(0);
}).thenCompose(Function.identity());
}
@Transactional
public List<CompletableFuture<Message>> sendClosingHeader(ModMailClosingHeaderModel model) {
public List<CompletableFuture<Message>> sendClosingHeader(ModMailClosingHeaderModel model, Long modmailThreadId) {
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_close_header", model, model.getServerId());
return postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, model.getServerId());
if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, model.getServerId(), ModMailMode.THREAD_CONTAINER)) {
ModMailThread modMailThread = modMailThreadManagementService.getById(modmailThreadId);
return channelService.sendMessageEmbedToSendToAChannel(messageToSend, modMailThread.getChannel());
} else {
return postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, model.getServerId());
}
}
/**
@@ -899,7 +953,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* sends this to the appropriate logging {@link PostTarget}
* @param modMailThreadId The ID of {@link ModMailThread} to which the loaded messages belong to
* @param loadedMessages The list of {@link ModMailLoggedMessageModel} which can be rendered
* @return A list of {@link CompletableFuture} which represent each of the messages being send to the {@link PostTarget}
* @return A list of {@link CompletableFuture} which represent each of the messages being sent to the {@link PostTarget}
*/
public List<CompletableFuture<Message>> sendMessagesToPostTarget(Long modMailThreadId, List<ModMailLoggedMessageModel> loadedMessages, Message updateMessage) {
List<CompletableFuture<Message>> messageFutures = new ArrayList<>();

View File

@@ -3,21 +3,25 @@ package dev.sheldan.abstracto.modmail.validator;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.FeatureValidatorService;
import dev.sheldan.abstracto.core.service.GuildService;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailMode;
import dev.sheldan.abstracto.modmail.config.ModMailPostTargets;
import dev.sheldan.abstracto.modmail.model.template.ModMailCategoryValidationErrorModel;
import dev.sheldan.abstracto.modmail.model.template.ModMailThreadContainerValidationErrorModel;
import dev.sheldan.abstracto.modmail.service.ModMailThreadServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
import net.dv8tion.jda.api.entities.channel.concrete.Category;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* This component is used to validate whether or not the mod mail feature has a mod mail category configured, which points to
* This component is used to validate whether the mod mail feature has a mod mail category configured, which points to
* a category on the server it is configured for. This and other {@link dev.sheldan.abstracto.core.service.FeatureValidator}
* are used to fully validate the mod mail feature.
*/
@@ -34,8 +38,16 @@ public class ModMailFeatureValidatorBean implements ModMailFeatureValidator {
@Autowired
private FeatureValidatorService featureValidatorService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private PostTargetService postTargetService;
/**
* Checks if the mod mail category contains a value and whether or not this valid also points to a {@link Category}
* Checks if the mod mail category contains a value and whether this valid also points to a {@link Category}.
* Additionally, if the thread container feature mode is already enabled. This will check if the current posttarget
* for threads can hold threads.
* @param featureConfig The instance of {@link FeatureConfig} of mod mail
* @param server The {@link AServer} to check the config for
* @param validationResult The current {@link FeatureValidationResult} used to accumulate the wrong values
@@ -52,6 +64,29 @@ public class ModMailFeatureValidatorBean implements ModMailFeatureValidator {
Long modMailCategory = configService.getLongValue(ModMailThreadServiceBean.MODMAIL_CATEGORY, server.getId());
validateModMailCategory(validationResult, guild, modMailCategory);
}
if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, server.getId(), ModMailMode.THREAD_CONTAINER)) {
Optional<GuildMessageChannel> modmailContainerOptional = postTargetService.getPostTargetChannel(ModMailPostTargets.MOD_MAIL_CONTAINER, server.getId());
if(modmailContainerOptional.isEmpty()) {
ModMailThreadContainerValidationErrorModel newError = ModMailThreadContainerValidationErrorModel
.builder()
.currentChannelId(null)
.build();
validationResult.getValidationErrorModels().add(newError);
validationResult.setValidationResult(false);
} else {
GuildMessageChannel threadContainer = modmailContainerOptional.get();
if(!(threadContainer instanceof IThreadContainer)) {
validationResult.setValidationResult(false);
ModMailThreadContainerValidationErrorModel newError = ModMailThreadContainerValidationErrorModel
.builder()
.currentChannelId(threadContainer.getIdLong())
.build();
validationResult.getValidationErrorModels().add(newError);
} else {
validationResult.setValidationResult(true);
}
}
}
}
}
@@ -70,6 +105,8 @@ public class ModMailFeatureValidatorBean implements ModMailFeatureValidator {
.currentCategoryId(modMailCategory)
.build();
validationResult.getValidationErrorModels().add(newError);
} else {
validationResult.setValidationResult(true);
}
}
}

View File

@@ -9,11 +9,16 @@ abstracto.featureFlags.modmail.enabled=false
abstracto.postTargets.modmailLog.name=modmailLog
abstracto.postTargets.modmailPing.name=modmailPing
abstracto.postTargets.modmailContainer.name=modmailContainer
abstracto.featureModes.log.featureName=modmail
abstracto.featureModes.log.mode=log
abstracto.featureModes.log.enabled=true
abstracto.featureModes.threadContainer.featureName=modmail
abstracto.featureModes.threadContainer.mode=threadContainer
abstracto.featureModes.threadContainer.enabled=false
abstracto.featureModes.threadMessage.featureName=modmail
abstracto.featureModes.threadMessage.mode=threadMessage
abstracto.featureModes.threadMessage.enabled=true

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