[AB-358] upgrading to JDA 5

removal of jda-utils
adding message context commands
[AB-360] fixing confirmation buttons being triggered by somebody not the author
This commit is contained in:
Sheldan
2022-02-27 23:51:06 +01:00
parent 17470f9718
commit 09450429dd
248 changed files with 2362 additions and 1482 deletions

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core</artifactId>
<version>1.3.14-SNAPSHOT</version>
<version>1.4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

View File

@@ -139,25 +139,32 @@ public class CommandReceivedHandler extends ListenerAdapter {
@Override
@Transactional
public void onMessageReceived(MessageReceivedEvent event) {
if(!event.isFromGuild()) {
if (!event.isFromGuild()) {
return;
}
Message message = event.getMessage();
if(!commandManager.isCommand(message)) {
if (!commandManager.isCommand(message)) {
return;
}
metricService.incrementCounter(COMMANDS_PROCESSED_COUNTER);
try {
UnParsedCommandResult result = getUnparsedCommandResult(message);
CompletableFuture<CommandParseResult> parsingFuture = getParametersFromMessage(message, result);
parsingFuture.thenAccept(parsedParameters -> self.executeCommand(event, parsedParameters.getCommand(), parsedParameters.getParameters()));
parsingFuture.thenAccept(parsedParameters -> {
try {
self.executeCommand(event, parsedParameters.getCommand(), parsedParameters.getParameters());
} catch (Exception e) {
reportException(event, null, e, String.format("Exception when executing command from message %d in message %d in guild %d."
, message.getIdLong(), event.getChannel().getIdLong(), event.getGuild().getIdLong()));
}
});
parsingFuture.exceptionally(throwable -> {
self.reportException(event, result.getCommand(), throwable, "Exception when parsing command.");
return null;
});
} catch (Exception e) {
reportException(event, null, e, String.format("Exception when executing command from message %d in message %d in guild %d."
, message.getIdLong(), event.getChannel().getIdLong(), event.getGuild().getIdLong()));
, message.getIdLong(), event.getChannel().getIdLong(), event.getGuild().getIdLong()));
}
}
@@ -244,7 +251,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
.author(event.getMember())
.guild(event.getGuild())
.undoActions(new ArrayList<>())
.channel(event.getTextChannel())
.channel(event.getGuildChannel())
.message(event.getMessage())
.jda(event.getJDA())
.userInitiatedContext(userInitiatedContext);
@@ -253,9 +260,9 @@ public class CommandReceivedHandler extends ListenerAdapter {
CompletableFuture<ConditionResult> conditionResultFuture = commandService.isCommandExecutable(foundCommand, commandContext);
conditionResultFuture.thenAccept(conditionResult -> {
CommandResult commandResult = null;
if(conditionResult.isResult()) {
if (conditionResult.isResult()) {
CommandConfiguration commandConfiguration = foundCommand.getConfiguration();
if(commandConfiguration.isRequiresConfirmation()) {
if (commandConfiguration.isRequiresConfirmation()) {
DriedCommandContext driedCommandContext = DriedCommandContext.buildFromCommandContext(commandContext);
driedCommandContext.setCommandName(commandConfiguration.getName());
String confirmId = componentService.generateComponentId();
@@ -272,7 +279,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
FutureUtils.toSingleFutureGeneric(confirmationMessageFutures)
.thenAccept(unused -> self.persistConfirmationCallbacks(model, confirmationMessageFutures.get(0).join()))
.exceptionally(throwable -> self.handleFailedCommand(foundCommand, commandContext, throwable));
} else if(commandConfiguration.isAsync()) {
} else if (commandConfiguration.isAsync()) {
log.info("Executing async command {} for server {} in channel {} based on message {} by user {}.",
commandConfiguration.getName(), commandContext.getGuild().getId(), commandContext.getChannel().getId(), commandContext.getMessage().getId(), commandContext.getAuthor().getId());
@@ -284,7 +291,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
} else {
commandResult = CommandResult.fromCondition(conditionResult);
}
if(commandResult != null) {
if (commandResult != null) {
self.executePostCommandListener(foundCommand, commandContext, commandResult);
}
}).exceptionally(throwable -> handleFailedCommand(foundCommand, commandContext, throwable));
@@ -301,7 +308,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
@Transactional(isolation = Isolation.SERIALIZABLE)
public CompletableFuture<Void> executeAsyncCommand(Command foundCommand, CommandContext commandContext) {
return foundCommand.executeAsync(commandContext).thenAccept(result ->
executePostCommandListener(foundCommand, commandContext, result)
executePostCommandListener(foundCommand, commandContext, result)
).exceptionally(throwable -> {
handleFailedCommand(foundCommand, commandContext, throwable);
return null;
@@ -314,13 +321,13 @@ public class CommandReceivedHandler extends ListenerAdapter {
}
@Transactional
public void reportException(Message message, TextChannel textChannel, Member member, Command foundCommand, Throwable throwable, String s) {
UserInitiatedServerContext userInitiatedContext = buildUserInitiatedServerContext(member, textChannel, member.getGuild());
public void reportException(Message message, MessageChannel channel, Member member, Command foundCommand, Throwable throwable, String s) {
UserInitiatedServerContext userInitiatedContext = buildUserInitiatedServerContext(member, channel, member.getGuild());
CommandContext.CommandContextBuilder commandContextBuilder = CommandContext.builder()
.author(member)
.guild(message.getGuild())
.undoActions(new ArrayList<>())
.channel(message.getTextChannel())
.channel(message.getGuildChannel())
.message(message)
.jda(message.getJDA())
.userInitiatedContext(userInitiatedContext);
@@ -332,7 +339,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
@Transactional
public void reportException(MessageReceivedEvent event, Command foundCommand, Throwable throwable, String s) {
reportException(event.getMessage(), event.getTextChannel(), event.getMember(), foundCommand, throwable, s);
reportException(event.getMessage(), event.getChannel(), event.getMember(), foundCommand, throwable, s);
}
private void validateCommandParameters(Parameters parameters, Command foundCommand) {
@@ -344,13 +351,13 @@ public class CommandReceivedHandler extends ListenerAdapter {
Parameter parameter = parameterList.get(Math.min(i, parameterList.size() - 1));
for (ParameterValidator parameterValidator : parameter.getValidators()) {
boolean validate = parameterValidator.validate(parameters.getParameters().get(i));
if(!validate) {
if (!validate) {
log.debug("Parameter {} in command {} failed to validate.", parameter.getName(), commandConfiguration.getName());
throw new CommandParameterValidationException(parameterValidator.getParameters(), parameterValidator.getExceptionTemplateName(), parameter);
}
}
}
if(commandConfiguration.getNecessaryParameterCount() > parameters.getParameters().size()) {
if (commandConfiguration.getNecessaryParameterCount() > parameters.getParameters().size()) {
String nextParameterName = commandConfiguration.getParameters().get(commandConfiguration.getNecessaryParameterCount() - 1).getName();
throw new InsufficientParametersException(foundCommand, nextParameterName);
}
@@ -371,23 +378,23 @@ public class CommandReceivedHandler extends ListenerAdapter {
return foundCommand.execute(commandContext);
}
private UserInitiatedServerContext buildUserInitiatedServerContext(Member member, TextChannel textChannel, Guild guild) {
private UserInitiatedServerContext buildUserInitiatedServerContext(Member member, MessageChannel channel, Guild guild) {
return UserInitiatedServerContext
.builder()
.member(member)
.messageChannel(textChannel)
.messageChannel(channel)
.guild(guild)
.build();
}
private UserInitiatedServerContext buildUserInitiatedServerContext(MessageReceivedEvent event) {
return buildUserInitiatedServerContext(event.getMember(), event.getTextChannel(), event.getGuild());
return buildUserInitiatedServerContext(event.getMember(), event.getChannel(), event.getGuild());
}
public CompletableFuture<Parameters> getParsedParameters(UnParsedCommandParameter unParsedCommandParameter, Command command, Message message){
public CompletableFuture<Parameters> getParsedParameters(UnParsedCommandParameter unParsedCommandParameter, Command command, Message message) {
List<ParseResult> parsedParameters = new ArrayList<>();
List<Parameter> parameters = command.getConfiguration().getParameters();
if(parameters == null || parameters.isEmpty()) {
if (parameters == null || parameters.isEmpty()) {
return CompletableFuture.completedFuture(Parameters.builder().parameters(new ArrayList<>()).build());
}
log.debug("Parsing parameters for command {} based on message {}.", command.getConfiguration().getName(), message.getId());
@@ -403,9 +410,9 @@ public class CommandReceivedHandler extends ListenerAdapter {
// because we might ignore some parameters (for example referenced messages) in case the command does not use this as a parameter
int parsedParameter = 0;
for (int i = 0; i < unParsedCommandParameter.getParameters().size(); i++) {
if(parsedParameter < parameters.size() && !param.isRemainder()) {
if (parsedParameter < parameters.size() && !param.isRemainder()) {
param = parameters.get(parsedParameter);
} else if(param.isRemainder()) {
} else if (param.isRemainder()) {
param = parameters.get(parameters.size() - 1);
} else {
break;
@@ -421,7 +428,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
parsedParameters.add(ParseResult.builder().parameter(param).result(future).build());
} else {
Object result = handler.handle(value, iterators, param, message, command);
if(result != null) {
if (result != null) {
parsedParameters.add(ParseResult.builder().parameter(param).result(result).build());
}
}
@@ -438,12 +445,12 @@ public class CommandReceivedHandler extends ListenerAdapter {
}
}
if(!futures.isEmpty()) {
if (!futures.isEmpty()) {
CompletableFuture<Parameters> multipleFuturesFuture = new CompletableFuture<>();
CompletableFuture<Void> combinedFuture = FutureUtils.toSingleFuture(futures);
combinedFuture.thenAccept(aVoid -> {
List<Object> allParamResults = parsedParameters.stream().map(o -> {
if(o.getResult() instanceof CompletableFuture) {
if (o.getResult() instanceof CompletableFuture) {
return ((CompletableFuture) o.getResult()).join();
} else {
return o.getResult();
@@ -451,7 +458,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
}).collect(Collectors.toList());
List<ParseResult> parseResults = new ArrayList<>();
for (int i = 0; i < allParamResults.size(); i++) {
if(allParamResults.get(i) != null) {
if (allParamResults.get(i) != null) {
ParseResult parseResult = ParseResult
.builder()
.result(allParamResults.get(i))
@@ -501,28 +508,27 @@ public class CommandReceivedHandler extends ListenerAdapter {
private List<Object> extractParametersFromParsed(List<ParseResult> results) {
List<Object> usableParameters = new ArrayList<>();
results.forEach(parseResult -> {
if(parseResult.getParameter().isRemainder() && !parseResult.getParameter().isListParam() && parseResult.getResult() instanceof String) {
if(usableParameters.isEmpty() || !(usableParameters.get(usableParameters.size() -1) instanceof String)) {
if (parseResult.getParameter().isRemainder() && !parseResult.getParameter().isListParam() && parseResult.getResult() instanceof String) {
if (usableParameters.isEmpty() || !(usableParameters.get(usableParameters.size() - 1) instanceof String)) {
usableParameters.add(parseResult.getResult());
} else {
int lastIndex = usableParameters.size() - 1;
usableParameters.set(lastIndex, usableParameters.get(lastIndex).toString() + " " + parseResult.getResult().toString());
}
} else if(parseResult.getParameter().isListParam()) {
if(usableParameters.isEmpty()) {
} else if (parseResult.getParameter().isListParam()) {
if (usableParameters.isEmpty()) {
ArrayList<Object> list = new ArrayList<>();
list.add(parseResult.getResult().toString());
usableParameters.add(list);
} else {
int lastIndex = usableParameters.size() - 1;
((List)usableParameters.get(lastIndex)).add(parseResult.getResult());
((List) usableParameters.get(lastIndex)).add(parseResult.getResult());
}
}
else {
} else {
usableParameters.add(parseResult.getResult());
}
});
return usableParameters;
return usableParameters;
}
@PostConstruct

View File

@@ -49,6 +49,9 @@ public class ConfirmationButtonClickedListener implements ButtonClickedListener
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
CommandConfirmationPayload payload = (CommandConfirmationPayload) model.getDeserializedPayload();
DriedCommandContext commandCtx = payload.getCommandContext();
if(commandCtx.getUserId() != model.getEvent().getUser().getIdLong()) {
return ButtonClickedListenerResult.IGNORED;
}
if(payload.getAction().equals(CommandConfirmationPayload.CommandConfirmationAction.CONFIRM)) {
log.info("Confirming command {} in server {} from message {} in channel {} with event {}.",
commandCtx.getCommandName(), commandCtx.getServerId(), commandCtx.getMessageId(),
@@ -103,7 +106,7 @@ public class ConfirmationButtonClickedListener implements ButtonClickedListener
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return CommandReceivedHandler.COMMAND_CONFIRMATION_ORIGIN.equals(model.getOrigin());
return CommandReceivedHandler.COMMAND_CONFIRMATION_ORIGIN.equals(model.getOrigin()) && model.getEvent().isFromGuild();
}
@Override

View File

@@ -248,7 +248,7 @@ public class CommandServiceBean implements CommandService {
.build();
CommandContext context = CommandContext
.builder()
.channel(message.getTextChannel())
.channel(message.getGuildChannel())
.author(author)
.guild(message.getGuild())
.jda(message.getJDA())

View File

@@ -21,8 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -96,7 +95,7 @@ public class ExceptionServiceBean implements ExceptionService {
}
@Override
public void reportExceptionToGuildMessageReceivedContext(Throwable exception, GuildMessageReceivedEvent event) {
public void reportExceptionToGuildMessageReceivedContext(Throwable exception, MessageReceivedEvent event) {
if(exception instanceof Templatable){
GenericExceptionModel model = buildMemberContext(exception, event.getMember());
String text = templateService.renderTemplate(MODEL_WRAPPER_TEMPLATE_KEY, model);
@@ -107,7 +106,7 @@ public class ExceptionServiceBean implements ExceptionService {
}
@Override
public void reportExceptionToPrivateMessageReceivedContext(Throwable exception, PrivateMessageReceivedEvent event) {
public void reportExceptionToPrivateMessageReceivedContext(Throwable exception, MessageReceivedEvent event) {
if(exception instanceof Templatable){
GenericExceptionModel model = buildPrivateMessageReceivedModel(exception, event.getAuthor());
String text = templateService.renderTemplate(MODEL_WRAPPER_TEMPLATE_KEY, model);

View File

@@ -21,9 +21,7 @@ import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -61,7 +59,7 @@ public class PostTargetCommand extends AbstractConditionableCommand {
posttargetDisplayModel.setPostTargets(new ArrayList<>());
List<PostTargetModelEntry> postTargetEntries = posttargetDisplayModel.getPostTargets();
postTargets.forEach(target -> {
Optional<TextChannel> channelFromAChannel = channelService.getChannelFromAChannel(target.getChannelReference());
Optional<GuildMessageChannel> channelFromAChannel = channelService.getGuildMessageChannelFromAChannelOptional(target.getChannelReference());
PostTargetModelEntry targetEntry = PostTargetModelEntry
.builder()
.channel(channelFromAChannel.orElse(null))

View File

@@ -3,13 +3,11 @@ package dev.sheldan.abstracto.core.config;
import ch.qos.logback.core.net.ssl.SecureRandomFactoryBean;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import dev.sheldan.abstracto.core.logging.OkHttpLogger;
import dev.sheldan.abstracto.core.metric.OkHttpMetrics;
import dev.sheldan.abstracto.core.service.BotService;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -18,8 +16,7 @@ import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.List;
@Configuration
public class CoreConfig {
@@ -27,29 +24,33 @@ public class CoreConfig {
@Autowired
private BotService botService;
@Value("${abstracto.eventWaiter.threads}")
private Integer threadCount;
@Autowired
private OkHttpMetrics okHttpMetrics;
@Autowired
private OkHttpLogger okHttpLogger;
@Autowired
private List<CustomJsonSerializer> customJsonSerializers;
@Autowired
private List<CustomJsonDeSerializer> customJsonDeSerializers;
@Bean
public Gson gson() {
return new GsonBuilder()
GsonBuilder builder = new GsonBuilder()
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter())
.registerTypeAdapter(Instant.class, new InstantTimeAdapter())
.setPrettyPrinting().create();
}
@Bean
public EventWaiter eventWaiter() {
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(threadCount);
return new EventWaiter(scheduledExecutorService, true);
.setPrettyPrinting();
if(customJsonDeSerializers != null) {
customJsonDeSerializers.forEach(customJsonSerializer ->
builder.registerTypeAdapter(customJsonSerializer.getType(), customJsonSerializer));
}
if(customJsonSerializers != null) {
customJsonSerializers.forEach(customJsonSerializer ->
builder.registerTypeAdapter(customJsonSerializer.getType(), customJsonSerializer));
}
return builder.create();
}
@Bean

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.core.config;
public interface CustomJsonDeSerializer {
Class getType();
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.core.config;
public interface CustomJsonSerializer {
Class getType();
}

View File

@@ -62,6 +62,11 @@ public class ListenerExecutorConfig {
return executorService.setupExecutorFor("buttonClickedListener");
}
@Bean(name = "messageContextCommandExecutor")
public TaskExecutor messageContextCommandExecutor() {
return executorService.setupExecutorFor("messageContextCommandListener");
}
@Bean(name = "emoteDeletedExecutor")
public TaskExecutor emoteDeletedExecutor() {
return executorService.setupExecutorFor("emoteDeletedListener");
@@ -117,6 +122,16 @@ public class ListenerExecutorConfig {
return executorService.setupExecutorFor("channelGroupCreatedListener");
}
@Bean(name = "featureActivationExecutor")
public TaskExecutor featureActivationListener() {
return executorService.setupExecutorFor("featureActivationListener");
}
@Bean(name = "featureDeactivationExecutor")
public TaskExecutor featureDeactivationListener() {
return executorService.setupExecutorFor("featureDeactivationListener");
}
@Bean(name = "serverJoinExecutor")
public TaskExecutor serverJoinExecutor() {
return executorService.setupExecutorFor("serverJoinListener");

View File

@@ -0,0 +1,86 @@
package dev.sheldan.abstracto.core.interactive;
import com.google.gson.Gson;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interactive.setup.payload.SetupConfirmationPayload;
import dev.sheldan.abstracto.core.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.service.DelayedActionService;
import dev.sheldan.abstracto.core.service.FeatureSetupServiceBean;
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Component
@Slf4j
public class InteractiveButtonClickedListener implements ButtonClickedListener {
@Autowired
private InteractiveButtonClickedListener self;
@Autowired
private DelayedActionService delayedActionService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private FeatureSetupServiceBean featureSetupServiceBean;
@Autowired
private Gson gson;
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
SetupConfirmationPayload payload = (SetupConfirmationPayload) model.getDeserializedPayload();
try {
if(payload.getAction().equals(SetupConfirmationPayload.SetupConfirmationAction.CONFIRM)) {
self.executeDelayedSteps(payload.getActions());
featureSetupServiceBean.notifyAboutCompletion(payload.getOrigin(), payload.getFeatureKey(), SetupStepResult.fromSuccess());
return ButtonClickedListenerResult.ACKNOWLEDGED;
} else {
featureSetupServiceBean.notifyAboutCompletion(payload.getOrigin(), payload.getFeatureKey(), SetupStepResult.fromCancelled());
return ButtonClickedListenerResult.IGNORED;
}
} finally {
cleanup(model, payload);
}
}
private void cleanup(ButtonClickedListenerModel model, SetupConfirmationPayload payload) {
log.debug("Cleaning up component {} and {}.", payload.getOtherButtonComponentId(), model.getEvent().getComponentId());
componentPayloadManagementService.deletePayloads(Arrays.asList(payload.getOtherButtonComponentId(), model.getEvent().getComponentId()));
}
@Transactional
public void executeDelayedSteps(List<DelayedActionConfigContainer> actions) {
List<DelayedActionConfig> delayedActionConfigs = new ArrayList<>();
actions.forEach(container -> delayedActionConfigs.add(container.getObject()));
delayedActionService.executeDelayedActions(delayedActionConfigs);
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return model.getDeserializedPayload() instanceof SetupConfirmationPayload && model.getEvent().isFromGuild();
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public FeatureDefinition getFeature() {
return CoreFeatureDefinition.CORE_FEATURE;
}
}

View File

@@ -0,0 +1,84 @@
package dev.sheldan.abstracto.core.interactive;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interactive.setup.callback.MessageInteractionCallback;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Component
public class InteractiveMessageReceivedListener implements AsyncMessageReceivedListener {
// server -> channel -> user
// TODO timeout
private Map<Long, Map<Long, Map<Long, MessageInteractionCallback>>> callbacks = new HashMap<>();
private static final Lock runTimeLock = new ReentrantLock();
@Override
public DefaultListenerResult execute(MessageReceivedModel model) {
if(executeCallback(model)) {
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
@Override
public FeatureDefinition getFeature() {
return CoreFeatureDefinition.CORE_FEATURE;
}
public boolean executeCallback(MessageReceivedModel model) {
runTimeLock.lock();
try {
if(callbacks.containsKey(model.getServerId())) {
Map<Long, Map<Long, MessageInteractionCallback>> channelMap = callbacks.get(model.getServerId());
if(channelMap.containsKey(model.getMessage().getChannel().getIdLong())) {
Map<Long, MessageInteractionCallback> userMap = channelMap.get(model.getMessage().getChannel().getIdLong());
if(userMap.containsKey(model.getMessage().getAuthor().getIdLong())) {
MessageInteractionCallback foundCallback = userMap.get(model.getMessage().getAuthor().getIdLong());
if(foundCallback.getCondition() == null || foundCallback.getCondition().test(model)) {
userMap.remove(model.getMessage().getAuthor().getIdLong());
foundCallback.getAction().accept(model);
return true;
}
}
}
}
} finally {
runTimeLock.unlock();
}
return false;
}
public void addCallback(MessageInteractionCallback interactiveCallBack) {
runTimeLock.lock();
try {
Map<Long, Map<Long, MessageInteractionCallback>> channelMap = new HashMap<>();
if(callbacks.containsKey(interactiveCallBack.getServerId())) {
channelMap = callbacks.get(interactiveCallBack.getServerId());
} else {
callbacks.put(interactiveCallBack.getServerId(), channelMap);
}
Map<Long, MessageInteractionCallback> userMap = new HashMap<>();
if(channelMap.containsKey(interactiveCallBack.getChannelId())) {
userMap = channelMap.get(interactiveCallBack.getChannelId());
} else {
channelMap.put(interactiveCallBack.getChannelId(), userMap);
}
if(!userMap.containsKey(interactiveCallBack.getUserId())) {
userMap.put(interactiveCallBack.getUserId(), interactiveCallBack);
}
} finally {
runTimeLock.unlock();
}
}
}

View File

@@ -1,24 +1,15 @@
package dev.sheldan.abstracto.core.interactive;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import com.jagrosh.jdautilities.menu.ButtonMenu;
import dev.sheldan.abstracto.core.interactive.setup.callback.MessageInteractionCallback;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.EmoteService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Emote;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@Component
@@ -29,76 +20,31 @@ public class InteractiveServiceBean implements InteractiveService {
private ChannelService channelService;
@Autowired
private EventWaiter eventWaiter;
@Autowired
private MessageService messageService;
@Autowired
private EmoteService emoteService;
private InteractiveMessageReceivedListener interactiveMessageReceivedListener;
@Override
public void createMessageWithResponse(String messageText, AUserInAServer responder, AChannel channel, Long messageId, Consumer<MessageReceivedEvent> action, Runnable finalAction) {
public void createMessageWithResponse(String messageText, AUserInAServer responder, AChannel channel, Consumer<MessageReceivedModel> action, Consumer<MessageReceivedModel> timeoutAction) {
channelService.sendTextToAChannel(messageText, channel);
Long channelId = channel.getId();
eventWaiter.waitForEvent(MessageReceivedEvent.class, event -> {
if(event != null) {
return event.getAuthor().getIdLong() == responder.getUserReference().getId() && event.getMessage().getIdLong() != messageId && event.getMessage().getChannel().getIdLong() == channelId;
}
return false;
}, action, 1, TimeUnit.MINUTES, finalAction);
createMessageReceivedCallback(channel, responder, action, timeoutAction);
}
@Override
public void createMessageWithResponse(MessageToSend messageToSend, AUserInAServer responder, AChannel channel, Long messageId, Consumer<MessageReceivedEvent> action, Runnable finalAction) {
public void createMessageWithResponse(MessageToSend messageToSend, AUserInAServer responder, AChannel channel,
Consumer<MessageReceivedModel> action, Consumer<MessageReceivedModel> timeoutAction) {
channelService.sendMessageEmbedToSendToAChannel(messageToSend, channel);
Long userId = responder.getUserReference().getId();
eventWaiter.waitForEvent(MessageReceivedEvent.class, event -> {
if(event != null) {
return event.getAuthor().getIdLong() == userId && event.getMessage().getIdLong() != messageId;
}
return false;
}, action, 1, TimeUnit.MINUTES, finalAction);
createMessageReceivedCallback(channel, responder, action, timeoutAction);
}
@Override
public void createMessageWithConfirmation(String text, AUserInAServer responder, AChannel channel, Long messageId, Consumer<Void> confirmation, Consumer<Void> denial, Runnable finalAction) {
Long serverId = responder.getServerReference().getId();
ButtonMenu.Builder builder = new ButtonMenu.Builder();
HashMap<String, Consumer<Void>> actions = new HashMap<>();
addEmoteToBuilder("confirmation", confirmation, serverId, builder, actions);
addEmoteToBuilder("denial", denial, serverId, builder, actions);
ButtonMenu menu = builder
.setEventWaiter(eventWaiter)
.setDescription(text)
.setAction(reactionEmote -> {
if(reactionEmote.isEmoji()) {
actions.get(reactionEmote.getEmoji()).accept(null);
} else {
actions.get(reactionEmote.getEmote().getId()).accept(null);
}
})
private void createMessageReceivedCallback(AChannel channel, AUserInAServer responder, Consumer<MessageReceivedModel> action, Consumer<MessageReceivedModel> timeoutAction) {
MessageInteractionCallback callBack = MessageInteractionCallback
.builder()
.serverId(channel.getServer().getId())
.channelId(channel.getId())
.userId(responder.getUserReference().getId())
.action(action)
.timeoutAction(timeoutAction)
.build();
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelFromServerOptional(serverId, channel.getId());
textChannelInGuild.ifPresent(menu::display);
interactiveMessageReceivedListener.addCallback(callBack);
}
private void addEmoteToBuilder(String key, Consumer<Void> consumer, Long serverId, ButtonMenu.Builder builder, HashMap<String, Consumer<Void>> actions) {
AEmote emoteOrFakeEmote = emoteService.getEmoteOrDefaultEmote(key, serverId);
if(Boolean.TRUE.equals(emoteOrFakeEmote.getCustom())){
Optional<Emote> emote = emoteService.getEmote(serverId, emoteOrFakeEmote);
emote.ifPresent(emote1 -> {
builder.addChoice(emote1);
actions.put(emote1.getId(), consumer);
});
} else {
builder.addChoice(emoteOrFakeEmote.getEmoteKey());
actions.put(emoteOrFakeEmote.getEmoteKey(), consumer);
}
}
}

View File

@@ -1,86 +0,0 @@
package dev.sheldan.abstracto.core.interactive;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.commands.SetupSummaryModel;
import dev.sheldan.abstracto.core.service.DelayedActionService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@Component
@Slf4j
public class SetupSummaryStep extends AbstractConfigSetupStep {
public static final String FEATURE_SETUP_CONFIRMATION_TEMPLATE_KEY = "feature_setup_confirmation";
@Autowired
private InteractiveService interactiveService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private DelayedActionService delayedActionService;
@Autowired
private SetupSummaryStep self;
@Override
public CompletableFuture<SetupStepResult> execute(AServerChannelUserId user, SetupStepParameter generalParameter) {
SetupSummaryStepParameter parameter = (SetupSummaryStepParameter) generalParameter;
SetupSummaryModel model = SetupSummaryModel
.builder()
.actionConfigs(parameter.getDelayedActionList())
.build();
String messageToSend = templateService.renderTemplate(FEATURE_SETUP_CONFIRMATION_TEMPLATE_KEY, model, user.getGuildId());
AChannel channel = channelManagementService.loadChannel(user.getChannelId());
CompletableFuture<SetupStepResult> future = new CompletableFuture<>();
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(user.getGuildId(), user.getUserId());
Runnable finalAction = super.getTimeoutRunnable(user.getGuildId(), user.getChannelId());
log.info("Executing setup summary question step in server {} in channel {} from user {}.", user.getGuildId(), user.getChannelId(), user.getUserId());
Consumer<Void> confirmation = (Void none) -> {
try {
log.info("Setup summary was confirmed. Executing {} steps.", parameter.getDelayedActionList().size());
self.executeDelayedSteps(parameter);
SetupStepResult result = SetupStepResult
.builder()
.result(SetupStepResultType.SUCCESS)
.build();
future.complete(result);
} catch (Exception e) {
log.error("Failed to execute {} delayed actions.", parameter.getDelayedActionList().size(), e);
future.completeExceptionally(e);
}
};
Consumer<Void> denial = (Void none) -> {
log.info("Setup summary was rejected. Cancelling execution of {} steps.", parameter.getDelayedActionList().size());
SetupStepResult result = SetupStepResult
.builder()
.result(SetupStepResultType.CANCELLED)
.build();
future.complete(result);
};
interactiveService.createMessageWithConfirmation(messageToSend, aUserInAServer, channel, parameter.getPreviousMessageId(), confirmation, denial, finalAction);
return future;
}
@Transactional
public void executeDelayedSteps(SetupSummaryStepParameter parameter) {
delayedActionService.executeDelayedActions(parameter.getDelayedActionList());
}
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.interactive;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -11,5 +12,6 @@ import java.util.List;
@Builder
public class SetupSummaryStepParameter implements SetupStepParameter {
private Long previousMessageId;
private List<DelayedActionConfig> delayedActionList;
private List<DelayedActionConfigContainer> delayedActionList;
private FeatureConfig featureConfig;
}

View File

@@ -0,0 +1,36 @@
package dev.sheldan.abstracto.core.interactive.setup.action;
import com.google.gson.*;
import dev.sheldan.abstracto.core.config.CustomJsonDeSerializer;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfigContainer;
import org.springframework.stereotype.Component;
import java.lang.reflect.Type;
@Component
public class DelayedActionDeSerializer implements JsonDeserializer<DelayedActionConfigContainer>, CustomJsonDeSerializer {
@Override
public DelayedActionConfigContainer deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jdc) throws JsonParseException {
String foundTypeString = jdc.deserialize(jsonElement.getAsJsonObject().get("type"), String.class);
Class foundType = null;
try {
foundType = Class.forName(foundTypeString);
} catch (ClassNotFoundException e) {
throw new AbstractoRunTimeException(String.format("Class %s for de-serializing button payload not found.", foundTypeString));
}
DelayedActionConfig foundObjectPayload = jdc.deserialize(jsonElement.getAsJsonObject().get("payload"), foundType);
return DelayedActionConfigContainer
.builder()
.type(foundType)
.object(foundObjectPayload)
.build();
}
@Override
public Class getType() {
return DelayedActionConfigContainer.class;
}
}

View File

@@ -0,0 +1,29 @@
package dev.sheldan.abstracto.core.interactive.setup.action;
import com.google.gson.*;
import dev.sheldan.abstracto.core.config.CustomJsonSerializer;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfigContainer;
import org.springframework.stereotype.Component;
import java.lang.reflect.Type;
@Component
public class DelayedActionSerializer implements CustomJsonSerializer, JsonSerializer<DelayedActionConfigContainer> {
@Override
public JsonElement serialize(DelayedActionConfigContainer container, Type type, JsonSerializationContext jsc) {
if(container == null) {
return null;
}
JsonObject messageObj = new JsonObject();
messageObj.add("type", jsc.serialize(container.getType().getCanonicalName()));
messageObj.add("payload", jsc.serialize(container.getObject()));
return messageObj;
}
@Override
public Class getType() {
return DelayedActionConfigContainer.class;
}
}

View File

@@ -1,5 +1,8 @@
package dev.sheldan.abstracto.core.interactive;
package dev.sheldan.abstracto.core.interactive.setup.action;
import dev.sheldan.abstracto.core.interactive.DelayedAction;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import dev.sheldan.abstracto.core.interactive.setup.action.config.PostTargetDelayedActionConfig;
import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -1,5 +1,8 @@
package dev.sheldan.abstracto.core.interactive;
package dev.sheldan.abstracto.core.interactive.setup.action;
import dev.sheldan.abstracto.core.interactive.DelayedAction;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import dev.sheldan.abstracto.core.interactive.setup.action.config.SystemConfigDelayedActionConfig;
import dev.sheldan.abstracto.core.service.ConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -9,7 +12,6 @@ import org.springframework.stereotype.Component;
@Slf4j
public class SystemConfigDelayedAction implements DelayedAction {
@Autowired
private ConfigService configService;

View File

@@ -1,10 +1,10 @@
package dev.sheldan.abstracto.core.interactive;
package dev.sheldan.abstracto.core.interactive.setup.action.config;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import dev.sheldan.abstracto.core.models.template.commands.PostTargetActionModel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.TextChannel;
@Getter
@Setter
@@ -14,7 +14,6 @@ public class PostTargetDelayedActionConfig implements DelayedActionConfig {
private String postTargetKey;
private Long serverId;
private Long channelId;
private TextChannel textChannel;
@Override
public String getTemplateName() {
@@ -26,7 +25,6 @@ public class PostTargetDelayedActionConfig implements DelayedActionConfig {
return PostTargetActionModel
.builder()
.channelId(channelId)
.channel(textChannel)
.postTargetKey(postTargetKey)
.build();
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.interactive;
package dev.sheldan.abstracto.core.interactive.setup.action.config;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import dev.sheldan.abstracto.core.models.database.AConfig;
import dev.sheldan.abstracto.core.models.template.commands.SystemConfigActionModel;
import lombok.Builder;
@@ -12,6 +13,7 @@ import lombok.Setter;
public class SystemConfigDelayedActionConfig implements DelayedActionConfig {
private String configKey;
private Long serverId;
// not an actual value stored in the database, just used as a container
private AConfig value;
@Override

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.core.interactive.setup.callback;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import lombok.Builder;
import lombok.Getter;
import java.time.Instant;
import java.util.function.Consumer;
import java.util.function.Predicate;
@Builder
@Getter
public class MessageInteractionCallback {
private Long serverId;
private Long channelId;
private Long userId;
private Consumer<MessageReceivedModel> action;
private Consumer<MessageReceivedModel> timeoutAction;
private Instant timeoutPoint;
public Predicate<MessageReceivedModel> condition;
}

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.core.interactive;
package dev.sheldan.abstracto.core.interactive.setup.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;

View File

@@ -0,0 +1,23 @@
package dev.sheldan.abstracto.core.interactive.setup.payload;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfigContainer;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.template.button.ButtonPayload;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
@Builder
public class SetupConfirmationPayload implements ButtonPayload {
private SetupConfirmationAction action;
private String featureKey;
private AServerChannelUserId origin;
private String otherButtonComponentId;
private List<DelayedActionConfigContainer> actions;
public enum SetupConfirmationAction {
CONFIRM, ABORT
}
}

View File

@@ -1,9 +1,13 @@
package dev.sheldan.abstracto.core.interactive;
package dev.sheldan.abstracto.core.interactive.setup.step;
import dev.sheldan.abstracto.core.interactive.*;
import dev.sheldan.abstracto.core.interactive.setup.action.config.PostTargetDelayedActionConfig;
import dev.sheldan.abstracto.core.interactive.setup.exception.NoChannelProvidedException;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import dev.sheldan.abstracto.core.models.template.commands.SetupPostTargetMessageModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
@@ -12,9 +16,9 @@ import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -55,10 +59,10 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
@Override
public CompletableFuture<SetupStepResult> execute(AServerChannelUserId user, SetupStepParameter parameter) {
PostTargetStepParameter postTargetStepParameter = (PostTargetStepParameter) parameter;
TextChannel currentTextChannel;
GuildMessageChannel currentTextChannel;
if(postTargetManagement.postTargetExists(postTargetStepParameter.getPostTargetKey(), user.getGuildId())) {
PostTarget postTarget = postTargetManagement.getPostTarget(postTargetStepParameter.getPostTargetKey(), user.getGuildId());
currentTextChannel = channelService.getTextChannelFromServerOptional(user.getGuildId(), postTarget.getChannelReference().getId()).orElse(null);
currentTextChannel = channelService.getMessageChannelFromServerOptional(user.getGuildId(), postTarget.getChannelReference().getId()).orElse(null);
} else {
currentTextChannel = null;
}
@@ -71,9 +75,9 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
AChannel channel = channelManagementService.loadChannel(user.getChannelId());
CompletableFuture<SetupStepResult> future = new CompletableFuture<>();
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(user.getGuildId(), user.getUserId());
Runnable finalAction = super.getTimeoutRunnable(user.getGuildId(), user.getChannelId());
Consumer<MessageReceivedModel> finalAction = super.getTimeoutConsumer(user.getGuildId(), user.getChannelId());
log.debug("Executing setup for post target {} in server {} for user {}.", postTargetStepParameter.getPostTargetKey(), user.getGuildId(), user.getUserId());
Consumer<MessageReceivedEvent> configAction = (MessageReceivedEvent event) -> {
Consumer<MessageReceivedModel> configAction = (MessageReceivedModel event) -> {
try {
SetupStepResult result;
@@ -82,7 +86,7 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
log.info("Setup has been cancelled, because of 'exit' message.");
result = SetupStepResult.fromCancelled();
} else {
if(message.getMentionedChannels().size() == 0) {
if(message.getMentionedChannels().isEmpty()) {
log.debug("No mentioned channel was seen in channel, nothing provided.");
throw new NoChannelProvidedException();
}
@@ -91,11 +95,15 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
.builder()
.postTargetKey(postTargetStepParameter.getPostTargetKey())
.serverId(user.getGuildId())
.textChannel(textChannel)
.channelId(textChannel.getIdLong())
.build();
log.debug("Setup for post target {} in server {} for user {} completed. Storing delayed action.", postTargetStepParameter.getPostTargetKey(), user.getGuildId(), user.getUserId());
List<DelayedActionConfig> delayedSteps = Arrays.asList(build);
DelayedActionConfigContainer container = DelayedActionConfigContainer
.builder()
.type(build.getClass())
.object(build)
.build();
List<DelayedActionConfigContainer> delayedSteps = Arrays.asList(container);
result = SetupStepResult
.builder()
.result(SetupStepResultType.SUCCESS)
@@ -109,7 +117,7 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
future.completeExceptionally(new SetupStepException(e));
}
};
interactiveService.createMessageWithResponse(messageText, aUserInAServer, channel, parameter.getPreviousMessageId(), configAction, finalAction);
interactiveService.createMessageWithResponse(messageText, aUserInAServer, channel, configAction, finalAction);
return future;
}

View File

@@ -0,0 +1,109 @@
package dev.sheldan.abstracto.core.interactive.setup.step;
import dev.sheldan.abstracto.core.interactive.*;
import dev.sheldan.abstracto.core.interactive.setup.payload.SetupConfirmationPayload;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.template.commands.SetupSummaryModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ComponentPayloadService;
import dev.sheldan.abstracto.core.service.ComponentService;
import dev.sheldan.abstracto.core.service.DelayedActionService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class SetupSummaryStep extends AbstractConfigSetupStep {
public static final String FEATURE_SETUP_CONFIRMATION_TEMPLATE_KEY = "feature_setup_confirmation";
public static final String SETUP_SUMMARY_ORIGIN = "setupSummary";
@Autowired
private InteractiveService interactiveService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private DelayedActionService delayedActionService;
@Autowired
private SetupSummaryStep self;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ComponentService componentService;
@Autowired
private ChannelService channelService;
@Autowired
private ComponentPayloadService componentPayloadService;
@Override
public CompletableFuture<SetupStepResult> execute(AServerChannelUserId user, SetupStepParameter generalParameter) {
SetupSummaryStepParameter parameter = (SetupSummaryStepParameter) generalParameter;
SetupSummaryModel model = SetupSummaryModel
.builder()
.actionConfigs(parameter.getDelayedActionList())
.build();
String confirmId = componentService.generateComponentId();
String abortId = componentService.generateComponentId();
model.setCancelButtonId(abortId);
model.setConfirmButtonId(confirmId);
MessageToSend message = templateService.renderEmbedTemplate(FEATURE_SETUP_CONFIRMATION_TEMPLATE_KEY, model, user.getGuildId());
AChannel channel = channelManagementService.loadChannel(user.getChannelId());
List<CompletableFuture<Message>> confirmationMessageFutures = channelService.sendMessageEmbedToSendToAChannel(message, channel);
return FutureUtils.toSingleFutureGeneric(confirmationMessageFutures)
.thenAccept(unused -> self.persistConfirmationCallbacks(model, user, parameter))
.thenApply(unused -> SetupStepResult.builder().result(SetupStepResultType.SUCCESS).build());
}
@Transactional
public void persistConfirmationCallbacks(SetupSummaryModel model, AServerChannelUserId origin, SetupSummaryStepParameter parameter) {
AServer server = serverManagementService.loadServer(origin.getGuildId());
SetupConfirmationPayload confirmPayload = SetupConfirmationPayload
.builder()
.otherButtonComponentId(model.getConfirmButtonId())
.origin(origin)
.actions(model.getActionConfigs())
.featureKey(parameter.getFeatureConfig().getFeature().getKey())
.action(SetupConfirmationPayload.SetupConfirmationAction.CONFIRM)
.build();
componentPayloadService.createButtonPayload(model.getConfirmButtonId(), confirmPayload, SETUP_SUMMARY_ORIGIN, server);
SetupConfirmationPayload cancelPayload = SetupConfirmationPayload
.builder()
.otherButtonComponentId(model.getCancelButtonId())
.origin(origin)
.actions(model.getActionConfigs())
.featureKey(parameter.getFeatureConfig().getFeature().getKey())
.action(SetupConfirmationPayload.SetupConfirmationAction.ABORT)
.build();
componentPayloadService.createButtonPayload(model.getCancelButtonId(), cancelPayload, SETUP_SUMMARY_ORIGIN, server);
}
}

View File

@@ -1,5 +1,8 @@
package dev.sheldan.abstracto.core.interactive;
package dev.sheldan.abstracto.core.interactive.setup.step;
import dev.sheldan.abstracto.core.interactive.*;
import dev.sheldan.abstracto.core.interactive.setup.action.config.SystemConfigDelayedActionConfig;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import dev.sheldan.abstracto.core.models.property.SystemConfigProperty;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.database.AChannel;
@@ -13,7 +16,6 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -64,8 +66,8 @@ public class SystemConfigSetupStep extends AbstractConfigSetupStep {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(user.getGuildId(), user.getUserId());
log.debug("Executing setup for system config {} in server {} for user {}.", systemConfigStepParameter.getConfigKey(), user.getGuildId(), user.getUserId());
Runnable finalAction = super.getTimeoutRunnable(user.getGuildId(), user.getChannelId());
Consumer<MessageReceivedEvent> configAction = (MessageReceivedEvent event) -> {
Consumer<MessageReceivedModel> finalAction = super.getTimeoutConsumer(user.getGuildId(), user.getChannelId());
Consumer<MessageReceivedModel> configAction = (MessageReceivedModel event) -> {
try {
SetupStepResult result;
Message message = event.getMessage();
@@ -88,7 +90,12 @@ public class SystemConfigSetupStep extends AbstractConfigSetupStep {
.value(config)
.build();
log.debug("Setup for system config {} in server {} for user {} completed. Storing delayed action.", systemConfigStepParameter.getConfigKey(), user.getGuildId(), user.getUserId());
List<DelayedActionConfig> delayedSteps = Arrays.asList(build);
DelayedActionConfigContainer container = DelayedActionConfigContainer
.builder()
.type(build.getClass())
.object(build)
.build();
List<DelayedActionConfigContainer> delayedSteps = Arrays.asList(container);
result = SetupStepResult
.builder()
.result(SetupStepResultType.SUCCESS)
@@ -101,7 +108,7 @@ public class SystemConfigSetupStep extends AbstractConfigSetupStep {
future.completeExceptionally(new SetupStepException(e));
}
};
interactiveService.createMessageWithResponse(messageText, aUserInAServer, channel, parameter.getPreviousMessageId(), configAction, finalAction);
interactiveService.createMessageWithResponse(messageText, aUserInAServer, channel, configAction, finalAction);
return future;
}
@@ -120,7 +127,7 @@ public class SystemConfigSetupStep extends AbstractConfigSetupStep {
}
@Transactional
public AConfig checkValidity(SystemConfigStepParameter systemConfigStepParameter, MessageReceivedEvent event) {
public AConfig checkValidity(SystemConfigStepParameter systemConfigStepParameter, MessageReceivedModel event) {
return configService.getFakeConfigForValue(systemConfigStepParameter.getConfigKey(), event.getMessage().getContentRaw());
}

View File

@@ -0,0 +1,50 @@
package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.listener.async.MessageContextCommandListener;
import dev.sheldan.abstracto.core.listener.async.entity.FeatureActivationListener;
import dev.sheldan.abstracto.core.listener.sync.jda.MessageContextCommandListenerBean;
import dev.sheldan.abstracto.core.models.listener.FeatureActivationListenerModel;
import dev.sheldan.abstracto.core.service.ContextCommandService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.GuildService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class MessageContextCommandFeatureActivationListener implements FeatureActivationListener {
@Autowired
private MessageContextCommandListenerBean listenerBean;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private ContextCommandService contextCommandService;
@Autowired
private GuildService guildService;
@Override
public DefaultListenerResult execute(FeatureActivationListenerModel model) {
List<MessageContextCommandListener> listeners = listenerBean.getListenerList();
if(listeners == null || listeners.isEmpty()) {
return DefaultListenerResult.IGNORED;
}
Guild guild = guildService.getGuildById(model.getServerId());
listeners.forEach(messageContextCommandListener -> {
if(featureModeService.necessaryFeatureModesMet(messageContextCommandListener, model.getServerId())) {
String contextCommandName = messageContextCommandListener.getConfig().getName();
log.info("Adding message context command {} in guild {}.", contextCommandName, model.getServerId());
contextCommandService.upsertGuildMessageContextCommand(guild, contextCommandName)
.thenAccept(command -> log.info("Created message context command {} in guild {} because feature {} was enabled.", contextCommandName, guild.getIdLong(), model.getFeatureName()));
}
});
return DefaultListenerResult.PROCESSED;
}
}

View File

@@ -0,0 +1,52 @@
package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.listener.async.MessageContextCommandListener;
import dev.sheldan.abstracto.core.listener.async.entity.FeatureActivationListener;
import dev.sheldan.abstracto.core.listener.async.entity.FeatureDeactivationListener;
import dev.sheldan.abstracto.core.listener.sync.jda.MessageContextCommandListenerBean;
import dev.sheldan.abstracto.core.models.listener.FeatureActivationListenerModel;
import dev.sheldan.abstracto.core.models.listener.FeatureDeactivationListenerModel;
import dev.sheldan.abstracto.core.service.ContextCommandService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.GuildService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class MessageContextCommandFeatureDeactivationListener implements FeatureDeactivationListener {
@Autowired
private MessageContextCommandListenerBean listenerBean;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private ContextCommandService contextCommandService;
@Autowired
private GuildService guildService;
@Override
public DefaultListenerResult execute(FeatureDeactivationListenerModel model) {
List<MessageContextCommandListener> listeners = listenerBean.getListenerList();
if(listeners == null || listeners.isEmpty()) {
return DefaultListenerResult.IGNORED;
}
Guild guild = guildService.getGuildById(model.getServerId());
listeners.forEach(messageContextCommandListener -> {
if(featureModeService.necessaryFeatureModesMet(messageContextCommandListener, model.getServerId())) {
String contextCommandName = messageContextCommandListener.getConfig().getName();
log.info("Adding message context command {} in guild {}.", contextCommandName, model.getServerId());
contextCommandService.deleteGuildContextCommandByName(guild, contextCommandName)
.thenAccept(unused -> log.info("Deleted command {} because feature {} was disabled in guild {}.", contextCommandName, model.getFeatureName(), guild.getIdLong()));
}
});
return DefaultListenerResult.PROCESSED;
}
}

View File

@@ -87,7 +87,7 @@ public class PaginatorButtonListener implements ButtonClickedListener {
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return PaginatorServiceBean.PAGINATOR_BUTTON.equals(model.getOrigin());
return PaginatorServiceBean.PAGINATOR_BUTTON.equals(model.getOrigin()) && model.getEvent().isFromGuild();
}
@Override

View File

@@ -7,8 +7,9 @@ import dev.sheldan.abstracto.core.models.listener.AChannelCreatedListenerModel;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.events.channel.text.TextChannelCreateEvent;
import net.dv8tion.jda.api.entities.Channel;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,17 +46,21 @@ public class AsyncAChannelCreatedListenerBean extends ListenerAdapter {
private AsyncAChannelCreatedListenerBean self;
@Override
public void onTextChannelCreate(@Nonnull TextChannelCreateEvent event) {
public void onChannelCreate(@Nonnull ChannelCreateEvent event) {
self.createChannelInDatabase(event);
}
@Transactional
public void createChannelInDatabase(@NotNull TextChannelCreateEvent event) {
public void createChannelInDatabase(@NotNull ChannelCreateEvent event) {
log.info("Creating text channel with ID {}.", event.getChannel().getIdLong());
AServer serverObject = serverManagementService.loadOrCreate(event.getChannel().getGuild().getIdLong());
TextChannel createdChannel = event.getChannel();
AChannelType type = AChannelType.getAChannelType(createdChannel.getType());
channelManagementService.createChannel(createdChannel.getIdLong(), type, serverObject);
if(event.getChannel() instanceof GuildChannel) {
AServer serverObject = serverManagementService.loadOrCreate(((GuildChannel)event.getChannel()).getGuild().getIdLong());
Channel createdChannel = event.getChannel();
AChannelType type = AChannelType.getAChannelType(createdChannel.getType());
channelManagementService.createChannel(createdChannel.getIdLong(), type, serverObject);
} else {
log.info("Guild independent channel created - doing nothing.");
}
}
@TransactionalEventListener

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.AChannelDeletedListenerModel;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.channel.text.TextChannelDeleteEvent;
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -37,12 +37,12 @@ public class AsyncAChannelDeletedListenerBean extends ListenerAdapter {
private AsyncAChannelDeletedListenerBean self;
@Override
public void onTextChannelDelete(@Nonnull TextChannelDeleteEvent event) {
public void onChannelDelete(@Nonnull ChannelDeleteEvent event) {
self.deleteChannelInDb(event);
}
@Transactional
public void deleteChannelInDb(TextChannelDeleteEvent event) {
public void deleteChannelInDb(ChannelDeleteEvent event) {
channelManagementService.markAsDeleted(event.getChannel().getIdLong());
}

View File

@@ -0,0 +1,32 @@
package dev.sheldan.abstracto.core.listener.async.entity;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.FeatureActivationListenerModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.List;
@Component
public class FeatureActivationListenerBean {
@Autowired(required = false)
private List<FeatureActivationListener> listener;
@Autowired
private ListenerService listenerService;
@Autowired
@Qualifier("featureActivationExecutor")
private TaskExecutor featureActivationExecutor;
@TransactionalEventListener
public void executeListener(FeatureActivationListenerModel activatedFeature){
if(listener == null) return;
listener.forEach(featureActivatedListener ->
listenerService.executeListener(featureActivatedListener, activatedFeature, featureActivationExecutor)
);
}
}

View File

@@ -0,0 +1,32 @@
package dev.sheldan.abstracto.core.listener.async.entity;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.FeatureDeactivationListenerModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.List;
@Component
public class FeatureDeactivationListenerBean {
@Autowired(required = false)
private List<FeatureDeactivationListener> listener;
@Autowired
private ListenerService listenerService;
@Autowired
@Qualifier("featureActivationExecutor")
private TaskExecutor featureDeactivationExecutor;
@TransactionalEventListener
public void executeListener(FeatureDeactivationListenerModel deactivatedFeature){
if(listener == null) return;
listener.forEach(featureActivatedListener ->
listenerService.executeListener(featureActivatedListener, deactivatedFeature, featureDeactivationExecutor)
);
}
}

View File

@@ -5,7 +5,7 @@ import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.listener.MessageDeletedModel;
import dev.sheldan.abstracto.core.service.MessageCache;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.GuildMessageDeleteEvent;
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -35,7 +35,7 @@ public class AsyncMessageDeletedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageDelete(@Nonnull GuildMessageDeleteEvent event) {
public void onMessageDelete(@Nonnull MessageDeleteEvent event) {
if(listenerList == null) return;
Consumer<CachedMessage> cachedMessageConsumer = cachedMessage -> {
MessageDeletedModel model = getModel(cachedMessage);

View File

@@ -3,7 +3,7 @@ package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.GuildMessageEmbedEventModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.GuildMessageEmbedEvent;
import net.dv8tion.jda.api.events.message.MessageEmbedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,13 +33,13 @@ public class AsyncMessageEmbeddedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageEmbed(@NotNull GuildMessageEmbedEvent event) {
public void onMessageEmbed(@NotNull MessageEmbedEvent event) {
if(listenerList == null) return;
GuildMessageEmbedEventModel model = getModel(event);
listenerList.forEach(leaveListener -> listenerService.executeFeatureAwareListener(leaveListener, model, messageEmbeddedListener));
}
private GuildMessageEmbedEventModel getModel(GuildMessageEmbedEvent event) {
private GuildMessageEmbedEventModel getModel(MessageEmbedEvent event) {
return GuildMessageEmbedEventModel
.builder()
.channelId(event.getChannel().getIdLong())

View File

@@ -8,7 +8,7 @@ import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -49,14 +49,15 @@ public class AsyncMessageReceivedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageReceived(@Nonnull GuildMessageReceivedEvent event) {
public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
if(listenerList == null) return;
if(!event.isFromGuild()) return;
messageCache.putMessageInCache(event.getMessage());
MessageReceivedModel model = getModel(event);
listenerList.forEach(leaveListener -> listenerService.executeFeatureAwareListener(leaveListener, model, messageReceivedExecutor));
}
private MessageReceivedModel getModel(GuildMessageReceivedEvent event) {
private MessageReceivedModel getModel(MessageReceivedEvent event) {
return MessageReceivedModel
.builder()
.message(event.getMessage())

View File

@@ -5,7 +5,8 @@ import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.CacheEntityService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
import net.dv8tion.jda.api.entities.ChannelType;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -44,7 +45,8 @@ public class AsyncPrivateMessageReceivedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onPrivateMessageReceived(@Nonnull PrivateMessageReceivedEvent event) {
public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
if(!event.isFromType(ChannelType.PRIVATE)) return;
if(privateMessageReceivedListeners == null) return;
if(event.getAuthor().getId().equals(botService.getInstance().getSelfUser().getId())) {
return;
@@ -61,7 +63,7 @@ public class AsyncPrivateMessageReceivedListenerBean extends ListenerAdapter {
}
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualPrivateMessageReceivedListener(CachedMessage cachedMessage, AsyncPrivateMessageReceivedListener messageReceivedListener, PrivateMessageReceivedEvent event) {
public void executeIndividualPrivateMessageReceivedListener(CachedMessage cachedMessage, AsyncPrivateMessageReceivedListener messageReceivedListener, MessageReceivedEvent event) {
try {
log.debug("Executing private message listener {} for member {}.", messageReceivedListener.getClass().getName(), cachedMessage.getAuthor().getAuthorId());
messageReceivedListener.execute(cachedMessage);

View File

@@ -9,7 +9,7 @@ import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -51,7 +51,7 @@ public class AsyncReactionAddedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageReactionAdd(@Nonnull GuildMessageReactionAddEvent event) {
public void onMessageReactionAdd(@Nonnull MessageReactionAddEvent event) {
if(listenerList == null) return;
if(event.getUserIdLong() == event.getJDA().getSelfUser().getIdLong()) {
return;
@@ -86,7 +86,7 @@ public class AsyncReactionAddedListenerBean extends ListenerAdapter {
}
@Transactional
public void callAddedListeners(GuildMessageReactionAddEvent event, CachedMessage cachedMessage, CachedReactions reaction, Member member) {
public void callAddedListeners(MessageReactionAddEvent event, CachedMessage cachedMessage, CachedReactions reaction, Member member) {
ServerUser serverUser = ServerUser.builder().serverId(event.getGuild().getIdLong()).userId(event.getUserIdLong()).build();
addReactionIfNotThere(cachedMessage, reaction, serverUser);
ReactionAddedModel model = getModel(event, cachedMessage, serverUser, member);
@@ -94,7 +94,7 @@ public class AsyncReactionAddedListenerBean extends ListenerAdapter {
listenerList.forEach(asyncReactionAddedListener -> listenerService.executeFeatureAwareListener(asyncReactionAddedListener, model, reactionAddedTaskExecutor));
}
private ReactionAddedModel getModel(GuildMessageReactionAddEvent event, CachedMessage cachedMessage, ServerUser userReacting, Member member) {
private ReactionAddedModel getModel(MessageReactionAddEvent event, CachedMessage cachedMessage, ServerUser userReacting, Member member) {
return ReactionAddedModel
.builder()
.reaction(event.getReaction())

View File

@@ -5,7 +5,7 @@ import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.listener.ReactionClearedModel;
import dev.sheldan.abstracto.core.service.MessageCache;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionRemoveAllEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveAllEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -36,7 +36,7 @@ public class AsyncReactionClearedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageReactionRemoveAll(@Nonnull GuildMessageReactionRemoveAllEvent event) {
public void onMessageReactionRemoveAll(@Nonnull MessageReactionRemoveAllEvent event) {
CompletableFuture<CachedMessage> asyncMessageFromCache = messageCache.getMessageFromCache(event.getGuild().getIdLong(), event.getChannel().getIdLong(), event.getMessageIdLong());
asyncMessageFromCache.thenAccept(cachedMessage -> {
cachedMessage.getReactions().clear();
@@ -52,7 +52,7 @@ public class AsyncReactionClearedListenerBean extends ListenerAdapter {
});
}
private ReactionClearedModel getModel(GuildMessageReactionRemoveAllEvent event, CachedMessage message) {
private ReactionClearedModel getModel(MessageReactionRemoveAllEvent event, CachedMessage message) {
return ReactionClearedModel
.builder()
.message(message)

View File

@@ -8,7 +8,7 @@ import dev.sheldan.abstracto.core.models.listener.ReactionRemovedModel;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionRemoveEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -61,7 +61,7 @@ public class AsyncReactionRemovedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageReactionRemove(@Nonnull GuildMessageReactionRemoveEvent event) {
public void onMessageReactionRemove(@Nonnull MessageReactionRemoveEvent event) {
if(reactionRemovedListeners == null) return;
if(event.getUserIdLong() == botService.getInstance().getSelfUser().getIdLong()) {
return;
@@ -82,14 +82,14 @@ public class AsyncReactionRemovedListenerBean extends ListenerAdapter {
}
@Transactional
public void callRemoveListeners(GuildMessageReactionRemoveEvent event, CachedMessage cachedMessage, CachedReactions reaction) {
public void callRemoveListeners(MessageReactionRemoveEvent event, CachedMessage cachedMessage, CachedReactions reaction) {
ServerUser serverUser = ServerUser.builder().serverId(cachedMessage.getServerId()).userId(event.getUserIdLong()).build();
removeReactionIfThere(cachedMessage, reaction, serverUser);
ReactionRemovedModel model = getModel(event, cachedMessage, serverUser);
reactionRemovedListeners.forEach(asyncReactionRemovedListener -> listenerServiceBean.executeFeatureAwareListener(asyncReactionRemovedListener, model, reactionRemovedExecutor));
}
private ReactionRemovedModel getModel(GuildMessageReactionRemoveEvent event, CachedMessage cachedMessage, ServerUser userRemoving) {
private ReactionRemovedModel getModel(MessageReactionRemoveEvent event, CachedMessage cachedMessage, ServerUser userRemoving) {
return ReactionRemovedModel
.builder()
.memberRemoving(event.getMember())

View File

@@ -3,7 +3,7 @@ package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.TextChannelCreatedModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.channel.text.TextChannelCreateEvent;
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -28,13 +28,13 @@ public class AsyncTextChannelCreatedListenerBean extends ListenerAdapter {
private TaskExecutor channelCreatedExecutor;
@Override
public void onTextChannelCreate(@Nonnull TextChannelCreateEvent event) {
public void onChannelCreate(@Nonnull ChannelCreateEvent event) {
if(listenerList == null) return;
TextChannelCreatedModel model = getModel(event);
listenerList.forEach(textChannelCreatedListener -> listenerService.executeFeatureAwareListener(textChannelCreatedListener, model, channelCreatedExecutor));
}
private TextChannelCreatedModel getModel(TextChannelCreateEvent event) {
private TextChannelCreatedModel getModel(ChannelCreateEvent event) {
return TextChannelCreatedModel
.builder()
.channel(event.getChannel())

View File

@@ -3,7 +3,7 @@ package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.TextChannelDeletedModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.channel.text.TextChannelDeleteEvent;
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -29,13 +29,13 @@ public class AsyncTextChannelDeletedListenerBean extends ListenerAdapter {
@Override
public void onTextChannelDelete(@Nonnull TextChannelDeleteEvent event) {
public void onChannelDelete(@Nonnull ChannelDeleteEvent event) {
if(listenerList == null) return;
TextChannelDeletedModel model = getModel(event);
listenerList.forEach(textChannelCreatedListener -> listenerService.executeFeatureAwareListener(textChannelCreatedListener, model, channelDeletedExecutor));
}
private TextChannelDeletedModel getModel(TextChannelDeleteEvent event) {
private TextChannelDeletedModel getModel(ChannelDeleteEvent event) {
return TextChannelDeletedModel
.builder()
.channel(event.getChannel())

View File

@@ -14,7 +14,7 @@ import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.apache.commons.lang3.SerializationUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -58,9 +58,6 @@ public class MessageUpdatedListenerBean extends ListenerAdapter {
@Autowired
private MetricService metricService;
@Autowired
private CacheEntityService cacheEntityService;
private static final CounterMetric MESSAGE_UPDATED_COUNTER = CounterMetric
.builder()
.name(MESSAGE_METRIC)
@@ -69,10 +66,10 @@ public class MessageUpdatedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageUpdate(@Nonnull GuildMessageUpdateEvent event) {
public void onMessageUpdate(@Nonnull MessageUpdateEvent event) {
metricService.incrementCounter(MESSAGE_UPDATED_COUNTER);
Message message = event.getMessage();
messageCache.getMessageFromCache(message.getGuild().getIdLong(), message.getTextChannel().getIdLong(), event.getMessageIdLong()).thenAccept(cachedMessage -> {
messageCache.getMessageFromCache(message.getGuild().getIdLong(), message.getChannel().getIdLong(), event.getMessageIdLong()).thenAccept(cachedMessage -> {
try {
// we need to provide a copy of the object, so modifications here dont influence the async execution
// because we do modify it, as we are the one responsible for caching it
@@ -94,13 +91,13 @@ public class MessageUpdatedListenerBean extends ListenerAdapter {
}
@Transactional
public void executeListener(CachedMessage cachedMessage, GuildMessageUpdateEvent event) {
public void executeListener(CachedMessage cachedMessage, MessageUpdateEvent event) {
if(listenerList == null) return;
MessageUpdatedModel model = getModel(event, cachedMessage);
listenerList.forEach(messageUpdatedListener -> listenerService.executeFeatureAwareListener(messageUpdatedListener, model));
}
private MessageUpdatedModel getModel(GuildMessageUpdateEvent event, CachedMessage oldMessage) {
private MessageUpdatedModel getModel(MessageUpdateEvent event, CachedMessage oldMessage) {
return MessageUpdatedModel
.builder()
.after(event.getMessage())
@@ -108,7 +105,7 @@ public class MessageUpdatedListenerBean extends ListenerAdapter {
.build();
}
private void executeAsyncListeners(GuildMessageUpdateEvent event, CachedMessage oldMessage) {
private void executeAsyncListeners(MessageUpdateEvent event, CachedMessage oldMessage) {
if(asyncListenerList == null) return;
MessageUpdatedModel model = getModel(event, oldMessage);
asyncListenerList.forEach(messageTextUpdatedListener ->

View File

@@ -0,0 +1,94 @@
package dev.sheldan.abstracto.core.listener.sync.jda;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.listener.async.MessageContextCommandListener;
import dev.sheldan.abstracto.core.models.listener.interaction.MessageContextInteractionModel;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
public class MessageContextCommandListenerBean extends ListenerAdapter {
@Autowired(required = false)
private List<MessageContextCommandListener> listenerList;
@Autowired
@Qualifier("messageContextCommandExecutor")
private TaskExecutor messageContextCommandExecutor;
@Autowired
private MessageContextCommandListenerBean self;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private FeatureModeService featureModeService;
@Override
public void onMessageContextInteraction(@NotNull MessageContextInteractionEvent event) {
if(listenerList == null) return;
CompletableFuture.runAsync(() -> self.executeListenerLogic(event), messageContextCommandExecutor).exceptionally(throwable -> {
log.error("Failed to execute listener logic in async button event.", throwable);
return null;
});
}
@Transactional
public void executeListenerLogic(MessageContextInteractionEvent event) {
MessageContextInteractionModel model = MessageContextInteractionModel
.builder()
.event(event)
.build();
List<MessageContextCommandListener> validListener = filterFeatureAwareListener(listenerList, model);
Optional<MessageContextCommandListener> listenerOptional = findListener(validListener, model);
if(listenerOptional.isPresent()) {
MessageContextCommandListener listener = listenerOptional.get();
listener.execute(model);
} else {
log.info("No listener found for event {}", event.getClass().getSimpleName());
}
}
private Optional<MessageContextCommandListener> findListener(List<MessageContextCommandListener> featureAwareListeners, MessageContextInteractionModel model) {
return featureAwareListeners.stream().filter(contextListener -> contextListener.handlesEvent(model)).findFirst();
}
private List<MessageContextCommandListener> filterFeatureAwareListener(List<MessageContextCommandListener> featureAwareListeners, MessageContextInteractionModel model) {
return featureAwareListeners.stream().filter(trFeatureAwareListener -> {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(trFeatureAwareListener.getFeature());
if(!model.getEvent().isFromGuild()) {
return true;
}
if (!featureFlagService.isFeatureEnabled(feature, model.getServerId())) {
return false;
}
return featureModeService.necessaryFeatureModesMet(trFeatureAwareListener, model.getServerId());
}).collect(Collectors.toList());
}
public List<MessageContextCommandListener> getListenerList() {
return listenerList;
}
}

View File

@@ -9,7 +9,7 @@ import dev.sheldan.abstracto.core.models.listener.MessageDeletedModel;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.GuildMessageDeleteEvent;
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -47,7 +47,7 @@ public class MessageDeletedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageDelete(@Nonnull GuildMessageDeleteEvent event) {
public void onMessageDelete(@Nonnull MessageDeleteEvent event) {
metricService.incrementCounter(MESSAGE_DELETED_COUNTER);
if(listenerList == null) return;
Consumer<CachedMessage> cachedMessageConsumer = cachedMessage -> {

View File

@@ -9,7 +9,7 @@ import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.GuildMessageEmbedEvent;
import net.dv8tion.jda.api.events.message.MessageEmbedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@@ -48,13 +48,13 @@ public class MessageEmbeddedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageEmbed(@NotNull GuildMessageEmbedEvent event) {
public void onMessageEmbed(@NotNull MessageEmbedEvent event) {
if(listenerList == null) return;
GuildMessageEmbedEventModel model = buildModel(event);
listenerList.forEach(messageReceivedListener -> listenerService.executeFeatureAwareListener(messageReceivedListener, model));
}
private GuildMessageEmbedEventModel buildModel(GuildMessageEmbedEvent event) {
private GuildMessageEmbedEventModel buildModel(MessageEmbedEvent event) {
return GuildMessageEmbedEventModel
.builder()
.channelId(event.getChannel().getIdLong())

View File

@@ -12,7 +12,7 @@ import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -61,7 +61,7 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageReceived(@Nonnull GuildMessageReceivedEvent event) {
public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
metricService.incrementCounter(MESSAGE_RECEIVED_COUNTER);
messageCache.putMessageInCache(event.getMessage());
if(listenerList == null) return;
@@ -70,7 +70,7 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
}
private MessageReceivedModel getModel(GuildMessageReceivedEvent event) {
private MessageReceivedModel getModel(MessageReceivedEvent event) {
return MessageReceivedModel
.builder()
.message(event.getMessage())

View File

@@ -4,7 +4,8 @@ import dev.sheldan.abstracto.core.command.service.ExceptionService;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
import net.dv8tion.jda.api.entities.ChannelType;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -34,7 +35,8 @@ public class PrivateMessageReceivedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onPrivateMessageReceived(@Nonnull PrivateMessageReceivedEvent event) {
public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
if(!event.isFromType(ChannelType.PRIVATE)) return;
if(privateMessageReceivedListeners == null) return;
if(event.getAuthor().getId().equals(botService.getInstance().getSelfUser().getId())) {
return;
@@ -50,7 +52,7 @@ public class PrivateMessageReceivedListenerBean extends ListenerAdapter {
}
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualPrivateMessageReceivedListener(@Nonnull PrivateMessageReceivedEvent event, PrivateMessageReceivedListener messageReceivedListener) {
public void executeIndividualPrivateMessageReceivedListener(@Nonnull MessageReceivedEvent event, PrivateMessageReceivedListener messageReceivedListener) {
// no feature flag check, because we are in no server context
log.debug("Executing private message listener {} for member {}.", messageReceivedListener.getClass().getName(), event.getAuthor().getId());
messageReceivedListener.execute(event.getMessage());

View File

@@ -11,7 +11,7 @@ import dev.sheldan.abstracto.core.utils.BeanUtils;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -57,7 +57,7 @@ public class ReactionAddedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageReactionAdd(@Nonnull GuildMessageReactionAddEvent event) {
public void onMessageReactionAdd(@Nonnull MessageReactionAddEvent event) {
if(addedListenerList == null) return;
if(event.getUserIdLong() == botService.getInstance().getSelfUser().getIdLong()) {
return;
@@ -93,14 +93,14 @@ public class ReactionAddedListenerBean extends ListenerAdapter {
}
@Transactional
public void callAddedListeners(@Nonnull GuildMessageReactionAddEvent event, CachedMessage cachedMessage, CachedReactions reaction, Member member) {
public void callAddedListeners(@Nonnull MessageReactionAddEvent event, CachedMessage cachedMessage, CachedReactions reaction, Member member) {
ServerUser serverUser = ServerUser.builder().serverId(cachedMessage.getServerId()).userId(event.getUserIdLong()).build();
addReactionIfNotThere(cachedMessage, reaction, serverUser);
ReactionAddedModel model = getModel(event, cachedMessage, serverUser, member);
addedListenerList.forEach(reactedAddedListener -> listenerService.executeFeatureAwareListener(reactedAddedListener, model));
}
private ReactionAddedModel getModel(GuildMessageReactionAddEvent event, CachedMessage cachedMessage, ServerUser userReacting, Member member) {
private ReactionAddedModel getModel(MessageReactionAddEvent event, CachedMessage cachedMessage, ServerUser userReacting, Member member) {
return ReactionAddedModel
.builder()
.reaction(event.getReaction())

View File

@@ -7,7 +7,7 @@ import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionRemoveAllEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveAllEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -49,7 +49,7 @@ public class ReactionClearedListenerBean extends ListenerAdapter {
@Autowired
private ListenerService listenerService;
public void callClearListeners(@Nonnull GuildMessageReactionRemoveAllEvent event, CachedMessage cachedMessage) {
public void callClearListeners(@Nonnull MessageReactionRemoveAllEvent event, CachedMessage cachedMessage) {
if(clearedListenerList == null) return;
ReactionClearedModel model = getModel(event, cachedMessage);
clearedListenerList.forEach(reactionRemovedListener ->
@@ -57,7 +57,7 @@ public class ReactionClearedListenerBean extends ListenerAdapter {
);
}
private ReactionClearedModel getModel(GuildMessageReactionRemoveAllEvent event, CachedMessage message) {
private ReactionClearedModel getModel(MessageReactionRemoveAllEvent event, CachedMessage message) {
return ReactionClearedModel
.builder()
.message(message)
@@ -67,7 +67,7 @@ public class ReactionClearedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageReactionRemoveAll(@Nonnull GuildMessageReactionRemoveAllEvent event) {
public void onMessageReactionRemoveAll(@Nonnull MessageReactionRemoveAllEvent event) {
CompletableFuture<CachedMessage> asyncMessageFromCache = messageCache.getMessageFromCache(event.getGuild().getIdLong(), event.getChannel().getIdLong(), event.getMessageIdLong());
asyncMessageFromCache.thenAccept(cachedMessage -> {
cachedMessage.getReactions().clear();

View File

@@ -9,7 +9,7 @@ import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionRemoveEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -65,7 +65,7 @@ public class ReactionRemovedListenerBean extends ListenerAdapter {
@Override
@Transactional
public void onGuildMessageReactionRemove(@Nonnull GuildMessageReactionRemoveEvent event) {
public void onMessageReactionRemove(@Nonnull MessageReactionRemoveEvent event) {
if(reactionRemovedListeners == null) return;
if(event.getUserIdLong() == botService.getInstance().getSelfUser().getIdLong()) {
return;
@@ -86,14 +86,14 @@ public class ReactionRemovedListenerBean extends ListenerAdapter {
}
@Transactional
public void callRemoveListeners(@Nonnull GuildMessageReactionRemoveEvent event, CachedMessage cachedMessage, CachedReactions reaction) {
public void callRemoveListeners(@Nonnull MessageReactionRemoveEvent event, CachedMessage cachedMessage, CachedReactions reaction) {
ServerUser serverUser = ServerUser.builder().serverId(event.getGuild().getIdLong()).userId(event.getUserIdLong()).build();
removeReactionIfThere(cachedMessage, reaction, serverUser);
ReactionRemovedModel model = getModel(event, cachedMessage, serverUser);
reactionRemovedListeners.forEach(reactionRemovedListener -> listenerService.executeFeatureAwareListener(reactionRemovedListener, model));
}
private ReactionRemovedModel getModel(GuildMessageReactionRemoveEvent event, CachedMessage cachedMessage, ServerUser userRemoving) {
private ReactionRemovedModel getModel(MessageReactionRemoveEvent event, CachedMessage cachedMessage, ServerUser userRemoving) {
return ReactionRemovedModel
.builder()
.memberRemoving(event.getMember())

View File

@@ -1,6 +1,5 @@
package dev.sheldan.abstracto.core.listener.sync.jda;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.StartupServiceBean;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
@@ -24,9 +23,6 @@ public class ReadyListener extends ListenerAdapter {
@Autowired
private SchedulerService schedulerService;
@Autowired
private EventWaiter eventWaiter;
@Autowired
private BotService botService;
@@ -35,7 +31,6 @@ public class ReadyListener extends ListenerAdapter {
if(synchronize){
startup.synchronize();
}
botService.getInstance().addEventListener(eventWaiter);
schedulerService.startScheduler();
}
}

View File

@@ -15,7 +15,7 @@ import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@@ -66,21 +66,17 @@ public class SyncButtonClickedListenerBean extends ListenerAdapter {
private Gson gson;
@Override
public void onButtonClick(@NotNull ButtonClickEvent event) {
public void onButtonInteraction(@NotNull ButtonInteractionEvent event) {
if(listenerList == null) return;
if(event.getGuild() != null) {
event.deferEdit().queue();
CompletableFuture.runAsync(() -> self.executeListenerLogic(event), buttonClickedExecutor).exceptionally(throwable -> {
log.error("Failed to execute listener logic in async button event.", throwable);
return null;
});
} else {
log.warn("Received button clicked event outside of guild with id {}.", event.getComponentId());
}
event.deferEdit().queue();
CompletableFuture.runAsync(() -> self.executeListenerLogic(event), buttonClickedExecutor).exceptionally(throwable -> {
log.error("Failed to execute listener logic in async button event.", throwable);
return null;
});
}
@Transactional
public void executeListenerLogic(@NotNull ButtonClickEvent event) {
public void executeListenerLogic(@NotNull ButtonInteractionEvent event) {
ButtonClickedListenerModel model = null;
ButtonClickedListener listener = null;
try {
@@ -104,8 +100,12 @@ public class SyncButtonClickedListenerBean extends ListenerAdapter {
log.warn("No callback found for id {}.", event.getComponentId());
}
} catch (Exception exception) {
log.error("Button clicked listener failed with exception in server {} and channel {}.", event.getGuild().getIdLong(),
event.getGuildChannel().getIdLong(), exception);
if(event.isFromGuild()) {
log.error("Button clicked listener failed with exception in server {} and channel {}.", event.getGuild().getIdLong(),
event.getGuildChannel().getIdLong(), exception);
} else {
log.error("Button clicked listener failed with exception outside of a guild.", exception);
}
if(model != null && listener != null) {
InteractionResult result = InteractionResult.fromError("Failed to execute interaction.", exception);
if(postInteractionExecutions != null) {
@@ -125,6 +125,9 @@ public class SyncButtonClickedListenerBean extends ListenerAdapter {
private List<ButtonClickedListener> filterFeatureAwareListener(List<ButtonClickedListener> featureAwareListeners, ButtonClickedListenerModel model) {
return featureAwareListeners.stream().filter(trFeatureAwareListener -> {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(trFeatureAwareListener.getFeature());
if(!model.getEvent().isFromGuild()) {
return true;
}
if (!featureFlagService.isFeatureEnabled(feature, model.getServerId())) {
return false;
}
@@ -132,7 +135,7 @@ public class SyncButtonClickedListenerBean extends ListenerAdapter {
}).collect(Collectors.toList());
}
private ButtonClickedListenerModel getModel(ButtonClickEvent event, ComponentPayload componentPayload) throws ClassNotFoundException {
private ButtonClickedListenerModel getModel(ButtonInteractionEvent event, ComponentPayload componentPayload) throws ClassNotFoundException {
ButtonPayload payload = null;
if(componentPayload.getPayloadType() != null && componentPayload.getPayload() != null) {
payload = (ButtonPayload) gson.fromJson(componentPayload.getPayload(), Class.forName(componentPayload.getPayloadType()));

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.TextChannelCreatedModel;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.channel.text.TextChannelCreateEvent;
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -24,13 +24,13 @@ public class TextChannelCreatedListenerBean extends ListenerAdapter {
private ListenerService listenerService;
@Override
public void onTextChannelCreate(@Nonnull TextChannelCreateEvent event) {
public void onChannelCreate(@Nonnull ChannelCreateEvent event) {
if(listenerList == null) return;
TextChannelCreatedModel model = getModel(event);
listenerList.forEach(textChannelCreatedListener -> listenerService.executeFeatureAwareListener(textChannelCreatedListener, model));
}
private TextChannelCreatedModel getModel(TextChannelCreateEvent event) {
private TextChannelCreatedModel getModel(ChannelCreateEvent event) {
return TextChannelCreatedModel
.builder()
.channel(event.getChannel())

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.TextChannelDeletedModel;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.channel.text.TextChannelDeleteEvent;
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -24,13 +24,13 @@ public class TextChannelDeletedListenerBean extends ListenerAdapter {
private ListenerService listenerService;
@Override
public void onTextChannelDelete(@Nonnull TextChannelDeleteEvent event) {
public void onChannelDelete(@Nonnull ChannelDeleteEvent event) {
if(listenerList == null) return;
TextChannelDeletedModel model = getModel(event);
listenerList.forEach(textChannelCreatedListener -> listenerService.executeFeatureAwareListener(textChannelCreatedListener, model));
}
private TextChannelDeletedModel getModel(TextChannelDeleteEvent event) {
private TextChannelDeletedModel getModel(ChannelDeleteEvent event) {
return TextChannelDeletedModel
.builder()
.channel(event.getChannel())

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.core.service;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
@Component
public class ApplicationCommandServiceBean implements ApplicationCommandService {
@Override
public CompletableFuture<Void> deleteGuildCommand(Guild guild, Long commandId) {
return guild.deleteCommandById(commandId).submit();
}
}

View File

@@ -17,6 +17,7 @@ import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementServi
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -189,7 +190,7 @@ public class ChannelGroupServiceBean implements ChannelGroupService {
channelGroups.forEach(group -> {
List<ChannelGroupChannelModel> convertedChannels = new ArrayList<>();
group.getChannels().forEach(channel -> {
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
Optional<GuildMessageChannel> textChannelInGuild = channelService.getGuildMessageChannelFromAChannelOptional(channel);
ChannelGroupChannelModel convertedChannel = ChannelGroupChannelModel
.builder()
.channel(channel)

View File

@@ -10,12 +10,14 @@ import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementS
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FileService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.interactions.components.ActionComponent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.interactions.components.ItemComponent;
import net.dv8tion.jda.api.requests.restaction.MessageAction;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
@@ -28,6 +30,7 @@ import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static dev.sheldan.abstracto.core.config.MetricConstants.DISCORD_API_INTERACTION_METRIC;
import static dev.sheldan.abstracto.core.config.MetricConstants.INTERACTION_TYPE;
@@ -103,33 +106,18 @@ public class ChannelServiceBean implements ChannelService {
@Override
public CompletableFuture<Message> sendTextToAChannel(String text, AChannel channel) {
Guild guild = botService.getInstance().getGuildById(channel.getServer().getId());
if (guild != null) {
TextChannel textChannel = guild.getTextChannelById(channel.getId());
if(textChannel != null) {
return sendTextToChannel(text, textChannel);
} else {
log.error("Channel {} to post towards was not found in server {}", channel.getId(), channel.getServer().getId());
throw new ChannelNotInGuildException(channel.getId());
}
} else {
log.error("Guild {} was not found when trying to post a message", channel.getServer().getId());
throw new GuildNotFoundException(channel.getServer().getId());
}
GuildMessageChannel guildMessageChannel = getGuildMessageChannelFromAChannel(channel);
return sendTextToChannel(text, guildMessageChannel);
}
@Override
public CompletableFuture<Message> sendMessageToAChannel(Message message, AChannel channel) {
Optional<TextChannel> textChannelOpt = getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
if(textChannelOpt.isPresent()) {
TextChannel textChannel = textChannelOpt.get();
return sendMessageToChannel(message, textChannel);
}
throw new ChannelNotInGuildException(channel.getId());
GuildMessageChannel foundChannel = getMessageChannelFromServer(channel.getServer().getId(), channel.getId());
return sendMessageToChannel(message, foundChannel);
}
@Override
public CompletableFuture<Message> sendMessageToChannel(Message message, MessageChannel channel) {
public CompletableFuture<Message> sendMessageToChannel(Message message, GuildMessageChannel channel) {
log.debug("Sending message {} from channel {} and server {} to channel {}.",
message.getId(), message.getChannel().getId(), message.getGuild().getId(), channel.getId());
metricService.incrementCounter(MESSAGE_SEND_METRIC);
@@ -174,11 +162,8 @@ public class ChannelServiceBean implements ChannelService {
@Override
public List<CompletableFuture<Message>> sendMessageEmbedToSendToAChannel(MessageToSend messageToSend, AChannel channel) {
Optional<TextChannel> textChannelFromServer = getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
if(textChannelFromServer.isPresent()) {
return sendMessageToSendToChannel(messageToSend, textChannelFromServer.get());
}
throw new ChannelNotInGuildException(channel.getId());
GuildMessageChannel textChannelFromServer = getMessageChannelFromServer(channel.getServer().getId(), channel.getId());
return sendMessageToSendToChannel(messageToSend, textChannelFromServer);
}
@Override
@@ -188,7 +173,7 @@ public class ChannelServiceBean implements ChannelService {
@Override
public CompletableFuture<Message> retrieveMessageInChannel(Long serverId, Long channelId, Long messageId) {
TextChannel channel = getTextChannelFromServer(serverId, channelId);
MessageChannel channel = getMessageChannelFromServer(serverId, channelId);
return retrieveMessageInChannel(channel, messageId);
}
@@ -244,8 +229,7 @@ public class ChannelServiceBean implements ChannelService {
}
List<ActionRow> actionRows = messageToSend.getActionRows();
if(!actionRows.isEmpty() && textChannel instanceof GuildChannel) {
GuildChannel channel = (GuildChannel) textChannel;
if(!actionRows.isEmpty()) {
List<List<ActionRow>> groupedActionRows = ListUtils.partition(actionRows, ComponentService.MAX_BUTTONS_PER_ROW);
for (int i = 0; i < allMessageActions.size(); i++) {
allMessageActions.set(i, allMessageActions.get(i).setActionRows(groupedActionRows.get(i)));
@@ -254,14 +238,22 @@ public class ChannelServiceBean implements ChannelService {
// TODO maybe possible nicer
allMessageActions.add(textChannel.sendMessage(".").setActionRows(groupedActionRows.get(i)));
}
AServer server = serverManagementService.loadServer(channel.getGuild());
actionRows.forEach(components -> components.forEach(component -> {
String id = component.getId();
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
if(payload != null && payload.getPersistCallback()) {
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType());
AServer server = null;
if(textChannel instanceof GuildChannel) {
GuildChannel channel = (GuildChannel) textChannel;
server = serverManagementService.loadServer(channel.getGuild());
}
for (ActionRow components : actionRows) {
for (ItemComponent component : components) {
if (component instanceof ActionComponent) {
String id = ((ActionComponent) component).getId();
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
if (payload != null && payload.getPersistCallback()) {
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType());
}
}
}
}));
}
}
if(messageToSend.hasFileToSend()) {
@@ -292,10 +284,10 @@ public class ChannelServiceBean implements ChannelService {
@Override
public void editMessageInAChannel(MessageToSend messageToSend, AChannel channel, Long messageId) {
Optional<TextChannel> textChannelFromServer = getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
if(textChannelFromServer.isPresent()) {
TextChannel textChannel = textChannelFromServer.get();
editMessageInAChannel(messageToSend, textChannel, messageId);
Optional<GuildChannel> textChannelFromServer = getGuildChannelFromServerOptional(channel.getServer().getId(), channel.getId());
if(textChannelFromServer.isPresent() && textChannelFromServer.get() instanceof GuildMessageChannel) {
GuildMessageChannel messageChannel = (GuildMessageChannel) textChannelFromServer.get();
editMessageInAChannel(messageToSend, messageChannel, messageId);
} else {
throw new ChannelNotInGuildException(channel.getId());
}
@@ -406,8 +398,13 @@ public class ChannelServiceBean implements ChannelService {
@Override
@Transactional
public List<CompletableFuture<Message>> sendEmbedTemplateInTextChannelList(String templateKey, Object model, TextChannel channel) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(templateKey, model, channel.getGuild().getIdLong());
public List<CompletableFuture<Message>> sendEmbedTemplateInTextChannelList(String templateKey, Object model, MessageChannel channel) {
MessageToSend messageToSend;
if(channel instanceof GuildChannel) {
messageToSend = templateService.renderEmbedTemplate(templateKey, model, ((GuildChannel)channel).getGuild().getIdLong());
} else {
messageToSend = templateService.renderEmbedTemplate(templateKey, model);
}
return sendMessageToSendToChannel(messageToSend, channel);
}
@@ -419,8 +416,14 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public CompletableFuture<Message> sendTextTemplateInTextChannel(String templateKey, Object model, TextChannel channel) {
String text = templateService.renderTemplate(templateKey, model, channel.getGuild().getIdLong());
public CompletableFuture<Message> sendTextTemplateInTextChannel(String templateKey, Object model, MessageChannel channel) {
String text;
if(channel instanceof GuildChannel) {
text = templateService.renderTemplate(templateKey, model, ((GuildChannel)channel).getGuild().getIdLong());
} else {
text = templateService.renderTemplate(templateKey, model);
}
return sendTextToChannel(text, channel);
}
@@ -432,9 +435,14 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public RestAction<Void> deleteMessagesInChannel(TextChannel textChannel, List<Message> messages) {
public CompletableFuture<Void> deleteMessagesInChannel(MessageChannel messageChannel, List<Message> messages) {
metricService.incrementCounter(CHANNEL_MESSAGE_BULK_DELETE_METRIC);
return textChannel.deleteMessages(messages);
List<CompletableFuture<Void>> deleteFutures = messages
.stream()
.map(ISnowflake::getId)
.map(messageId -> messageChannel.deleteMessageById(messageId).submit())
.collect(Collectors.toList());
return new CompletableFutureList<>(deleteFutures).getMainFuture();
}
@Override
@@ -454,53 +462,115 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public Optional<TextChannel> getChannelFromAChannel(AChannel channel) {
return getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
public Optional<GuildChannel> getChannelFromAChannel(AChannel channel) {
return getGuildChannelFromServerOptional(channel.getServer().getId(), channel.getId());
}
@Override
public AChannel getFakeChannelFromTextChannel(TextChannel textChannel) {
AServer server = AServer
.builder()
.id(textChannel.getGuild().getIdLong())
.fake(true)
.build();
public Optional<GuildMessageChannel> getGuildMessageChannelFromAChannelOptional(AChannel channel) {
return getMessageChannelFromServerOptional(channel.getServer().getId(), channel.getId());
}
@Override
public GuildMessageChannel getGuildMessageChannelFromAChannel(AChannel channel) {
return getMessageChannelFromServer(channel.getServer().getId(), channel.getId());
}
@Override
public AChannel getFakeChannelFromTextChannel(MessageChannel messageChannel) {
AServer server = null;
if(messageChannel instanceof GuildChannel) {
server = AServer
.builder()
.id(((GuildChannel) messageChannel).getGuild().getIdLong())
.fake(true)
.build();
}
return AChannel
.builder()
.fake(true)
.id(textChannel.getIdLong())
.id(messageChannel.getIdLong())
.server(server)
.build();
}
@Override
public CompletableFuture<Message> sendSimpleTemplateToChannel(Long serverId, Long channelId, String template) {
TextChannel textChannel = getTextChannelFromServer(serverId, channelId);
return sendTextTemplateInTextChannel(template, new Object(), textChannel);
GuildMessageChannel foundChannel = getMessageChannelFromServer(serverId, channelId);
if(foundChannel != null) {
return sendTextTemplateInTextChannel(template, new Object(), foundChannel);
} else {
log.info("Channel {} in server {} not found.", channelId, serverId);
throw new IllegalArgumentException("Incorrect channel type.");
}
}
@Override
public CompletableFuture<MessageHistory> getHistoryOfChannel(TextChannel channel, Long startMessageId, Integer amount) {
public CompletableFuture<MessageHistory> getHistoryOfChannel(MessageChannel channel, Long startMessageId, Integer amount) {
return channel.getHistoryBefore(startMessageId, amount).submit();
}
@Override
public Optional<TextChannel> getTextChannelFromServerOptional(Guild guild, Long textChannelId) {
return Optional.ofNullable(guild.getTextChannelById(textChannelId));
public Optional<GuildChannel> getGuildChannelFromServerOptional(Guild guild, Long textChannelId) {
return Optional.ofNullable(guild.getGuildChannelById(textChannelId));
}
@Override
public TextChannel getTextChannelFromServer(Guild guild, Long textChannelId) {
return getTextChannelFromServerOptional(guild, textChannelId).orElseThrow(() -> new ChannelNotInGuildException(textChannelId));
public GuildMessageChannel getMessageChannelFromServer(Guild guild, Long textChannelId) {
GuildChannel foundChannel = getGuildChannelFromServerOptional(guild, textChannelId).orElseThrow(() -> new ChannelNotInGuildException(textChannelId));
if(foundChannel instanceof GuildMessageChannel) {
return (GuildMessageChannel) foundChannel;
}
log.info("Incorrect channel type of channel {} in guild {}: {}", textChannelId, guild.getId(), foundChannel.getType());
throw new IllegalArgumentException("Incorrect channel type found.");
}
@Override
public TextChannel getTextChannelFromServerNullable(Guild guild, Long textChannelId) {
return getTextChannelFromServerOptional(guild, textChannelId).orElse(null);
public GuildMessageChannel getMessageChannelFromServer(Long serverId, Long textChannelId) {
Optional<Guild> guildOptional = guildService.getGuildByIdOptional(serverId);
if(guildOptional.isPresent()) {
Guild guild = guildOptional.get();
return getMessageChannelFromServer(guild, textChannelId);
}
throw new GuildNotFoundException(serverId);
}
@Override
public Optional<TextChannel> getTextChannelFromServerOptional(Long serverId, Long textChannelId) {
public Optional<GuildMessageChannel> getMessageChannelFromServerOptional(Long serverId, Long textChannelId) {
Optional<GuildChannel> guildChannel = getGuildChannelFromServerOptional(serverId, textChannelId);
if(guildChannel.isPresent() && guildChannel.get() instanceof GuildMessageChannel) {
return Optional.of((GuildMessageChannel)guildChannel.get());
}
return Optional.empty();
}
@Override
public GuildMessageChannel getMessageChannelFromServerNullable(Guild guild, Long textChannelId) {
return getMessageChannelFromServer(guild, textChannelId);
}
@Override
public Optional<GuildChannel> getGuildChannelFromServerOptional(Long serverId, Long channelId) {
Optional<Guild> guildOptional = guildService.getGuildByIdOptional(serverId);
if(guildOptional.isPresent()) {
Guild guild = guildOptional.get();
return Optional.ofNullable(guild.getGuildChannelById(channelId));
}
throw new GuildNotFoundException(serverId);
}
@Override
public GuildChannel getGuildChannelFromServer(Long serverId, Long channelId) {
Optional<Guild> guildOptional = guildService.getGuildByIdOptional(serverId);
if(guildOptional.isPresent()) {
Guild guild = guildOptional.get();
return guild.getGuildChannelById(channelId);
}
throw new GuildNotFoundException(serverId);
}
@Override
public Optional<TextChannel> getTextChannelFromServerOptional(Long serverId, Long textChannelId) {
Optional<Guild> guildOptional = guildService.getGuildByIdOptional(serverId);
if(guildOptional.isPresent()) {
Guild guild = guildOptional.get();
@@ -509,10 +579,6 @@ public class ChannelServiceBean implements ChannelService {
throw new GuildNotFoundException(serverId);
}
@Override
public TextChannel getTextChannelFromServer(Long serverId, Long textChannelId) {
return getTextChannelFromServerOptional(serverId, textChannelId).orElseThrow(() -> new ChannelNotInGuildException(textChannelId));
}
@Override
public CompletableFuture<Void> setSlowModeInChannel(TextChannel textChannel, Integer seconds) {
@@ -521,17 +587,23 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public List<CompletableFuture<Message>> sendFileToChannel(String fileContent, String fileNameTemplate, String messageTemplate, Object model, TextChannel channel) {
public List<CompletableFuture<Message>> sendFileToChannel(String fileContent, String fileNameTemplate, String messageTemplate, Object model, MessageChannel channel) {
String fileName = templateService.renderTemplate(fileNameTemplate, model);
File tempFile = fileService.createTempFile(fileName);
try {
fileService.writeContentToFile(tempFile, fileContent);
long maxFileSize = channel.getGuild().getMaxFileSize();
// in this case, we cannot upload the file, so we need to fail
if(tempFile.length() > maxFileSize) {
throw new UploadFileTooLargeException(tempFile.length(), maxFileSize);
MessageToSend messageToSend;
if(channel instanceof GuildMessageChannel) {
GuildMessageChannel guildChannel = (GuildMessageChannel) channel;
long maxFileSize = guildChannel.getGuild().getMaxFileSize();
// in this case, we cannot upload the file, so we need to fail
if(tempFile.length() > maxFileSize) {
throw new UploadFileTooLargeException(tempFile.length(), maxFileSize);
}
messageToSend = templateService.renderEmbedTemplate(messageTemplate, model, guildChannel.getGuild().getIdLong());
} else {
messageToSend = templateService.renderEmbedTemplate(messageTemplate, model);
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(messageTemplate, model, channel.getGuild().getIdLong());
messageToSend.setFileToSend(tempFile);
return sendMessageToSendToChannel(messageToSend, channel);
} catch (IOException e) {
@@ -547,14 +619,16 @@ public class ChannelServiceBean implements ChannelService {
}
@Override
public List<CompletableFuture<Message>> sendFileToChannel(String fileContent, String fileName, TextChannel channel) {
public List<CompletableFuture<Message>> sendFileToChannel(String fileContent, String fileName, MessageChannel channel) {
File tempFile = fileService.createTempFile(fileName);
try {
fileService.writeContentToFile(tempFile, fileContent);
long maxFileSize = channel.getGuild().getMaxFileSize();
// in this case, we cannot upload the file, so we need to fail
if(tempFile.length() > maxFileSize) {
throw new UploadFileTooLargeException(tempFile.length(), maxFileSize);
if(channel instanceof GuildMessageChannel) {
long maxFileSize = ((GuildMessageChannel) channel).getGuild().getMaxFileSize();
// in this case, we cannot upload the file, so we need to fail
if(tempFile.length() > maxFileSize) {
throw new UploadFileTooLargeException(tempFile.length(), maxFileSize);
}
}
MessageToSend messageToSend = MessageToSend
.builder()

View File

@@ -2,16 +2,18 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.template.button.ButtonConfigModel;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.interactions.components.ActionComponent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Button;
import net.dv8tion.jda.api.interactions.components.ButtonStyle;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
import org.apache.commons.collections4.ListUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -35,7 +37,7 @@ public class ComponentServiceBean implements ComponentService {
}
@Override
public CompletableFuture<Message> addButtonToMessage(Long messageId, TextChannel textChannel, String buttonId, String description, String emoteMarkdown, ButtonStyle style) {
public CompletableFuture<Message> addButtonToMessage(Long messageId, GuildMessageChannel textChannel, String buttonId, String description, String emoteMarkdown, ButtonStyle style) {
return channelService.retrieveMessageInChannel(textChannel, messageId).thenCompose(message -> {
Button button = Button.of(style, buttonId, description);
if(emoteMarkdown != null) {
@@ -83,11 +85,13 @@ public class ComponentServiceBean implements ComponentService {
public CompletableFuture<Void> removeComponentWithId(Message message, String componentId, Boolean rearrange) {
List<ActionRow> actionRows = new ArrayList<>();
if(Boolean.TRUE.equals(rearrange)) {
List<net.dv8tion.jda.api.interactions.components.Component> components = new ArrayList<>();
List<net.dv8tion.jda.api.interactions.components.ActionComponent> components = new ArrayList<>();
message.getActionRows().forEach(row ->
row
.getComponents()
.stream()
.filter(ActionComponent.class::isInstance)
.map(ActionComponent.class::cast)
.filter(component -> component.getId() == null || !component.getId().equals(componentId))
.forEach(components::add));
actionRows = splitIntoActionRowsMax(components);
@@ -97,6 +101,8 @@ public class ComponentServiceBean implements ComponentService {
row
.getComponents()
.stream()
.filter(ActionComponent.class::isInstance)
.map(ActionComponent.class::cast)
.filter(component -> component.getId() == null || !component.getId().equals(componentId))
.collect(Collectors.toList())));
}
@@ -105,8 +111,8 @@ public class ComponentServiceBean implements ComponentService {
}
@Override
public List<ActionRow> splitIntoActionRowsMax(List<net.dv8tion.jda.api.interactions.components.Component> allComponents) {
List<List<net.dv8tion.jda.api.interactions.components.Component>> actionRows = ListUtils.partition(allComponents, MAX_BUTTONS_PER_ROW);
public List<ActionRow> splitIntoActionRowsMax(List<net.dv8tion.jda.api.interactions.components.ActionComponent> allComponents) {
List<List<net.dv8tion.jda.api.interactions.components.ActionComponent>> actionRows = ListUtils.partition(allComponents, MAX_BUTTONS_PER_ROW);
return actionRows.stream().map(ActionRow::of).collect(Collectors.toList());
}
@@ -118,18 +124,7 @@ public class ComponentServiceBean implements ComponentService {
private CompletableFuture<Void> setAllButtonStatesTo(Message message, Boolean disabled) {
List<ActionRow> actionRows = new ArrayList<>();
message.getActionRows().forEach(row -> {
List<net.dv8tion.jda.api.interactions.components.Component> newComponents = new ArrayList<>();
row.getComponents().forEach(component -> {
if(component.getType().equals(net.dv8tion.jda.api.interactions.components.Component.Type.BUTTON)) {
Button button = ((Button) component).withDisabled(disabled);
newComponents.add(button);
} else {
newComponents.add(component);
}
});
actionRows.add(ActionRow.of(newComponents));
});
message.getActionRows().forEach(row -> actionRows.add(row.withDisabled(disabled)));
return messageService.editMessageWithActionRows(message, actionRows);
}

View File

@@ -0,0 +1,36 @@
package dev.sheldan.abstracto.core.service;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
public class ContextCommandServiceBean implements ContextCommandService {
@Autowired
private ApplicationCommandService applicationCommandService;
@Override
public CompletableFuture<Command> upsertGuildMessageContextCommand(Guild guild, String name) {
return guild.upsertCommand(Commands.context(Command.Type.MESSAGE, name)).submit();
}
@Override
public CompletableFuture<Void> deleteGuildContextCommand(Guild guild, Long commandId) {
return applicationCommandService.deleteGuildCommand(guild, commandId);
}
@Override
public CompletableFuture<Void> deleteGuildContextCommandByName(Guild guild, String commandName) {
return guild.retrieveCommands().submit().thenCompose(commands -> {
Optional<Command> foundCommand = commands.stream().filter(command -> command.getType().equals(Command.Type.MESSAGE)).findAny();
return foundCommand.map(command -> guild.deleteCommandById(command.getIdLong()).submit())
.orElseGet(() -> CompletableFuture.completedFuture(null));
});
}
}

View File

@@ -7,6 +7,9 @@ import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.FeatureModeNotFoundException;
import dev.sheldan.abstracto.core.exception.FeatureNotFoundException;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import dev.sheldan.abstracto.core.listener.ListenerExecutionResult;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AServer;
@@ -29,6 +32,12 @@ public class FeatureConfigServiceBean implements FeatureConfigService {
@Autowired
private FeatureValidatorService featureValidatorService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private FeatureModeService featureModeService;
@Override
public List<String> getAllFeatures() {
return availableFeatures
@@ -148,4 +157,16 @@ public class FeatureConfigServiceBean implements FeatureConfigService {
.anyMatch(featureMode -> featureMode.getKey().equalsIgnoreCase(modeName))
);
}
@Override
public <T extends FeatureAwareListenerModel, R extends ListenerExecutionResult> boolean isFeatureAwareEnabled(FeatureAwareListener<T, R> listener, Long serverId) {
FeatureConfig feature = getFeatureDisplayForFeature(listener.getFeature());
if(serverId == null) {
return true;
}
if (!featureFlagService.isFeatureEnabled(feature, serverId)) {
return false;
}
return featureModeService.necessaryFeatureModesMet(listener, serverId);
}
}

View File

@@ -7,11 +7,14 @@ import dev.sheldan.abstracto.core.exception.FeatureNotFoundException;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.listener.FeatureActivationListenerModel;
import dev.sheldan.abstracto.core.models.listener.FeatureDeactivationListenerModel;
import dev.sheldan.abstracto.core.models.property.FeatureFlagProperty;
import dev.sheldan.abstracto.core.service.management.DefaultFeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import java.util.Optional;
@@ -34,6 +37,9 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
@Autowired
private DefaultFeatureFlagManagementService defaultFeatureFlagManagementService;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Override
public boolean isFeatureEnabled(FeatureConfig name, Long serverId) {
return getFeatureFlagValue(name.getFeature(), serverId);
@@ -56,6 +62,12 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
if(!featureConfigService.doesFeatureExist(name)) {
throw new FeatureNotFoundException(feature.getKey(), featureConfigService.getFeaturesAsList());
}
FeatureActivationListenerModel model = FeatureActivationListenerModel
.builder()
.featureName(feature.getKey())
.serverId(server.getId())
.build();
applicationEventPublisher.publishEvent(model);
updateFeatureFlag(feature, server, true);
}
@@ -71,6 +83,12 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
if(!featureConfigService.doesFeatureExist(name)) {
throw new FeatureNotFoundException(feature.getKey(), featureConfigService.getFeaturesAsList());
}
FeatureDeactivationListenerModel model = FeatureDeactivationListenerModel
.builder()
.featureName(feature.getKey())
.serverId(server.getId())
.build();
applicationEventPublisher.publishEvent(model);
updateFeatureFlag(feature, server, false);
}

View File

@@ -3,14 +3,16 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.command.service.ExceptionService;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.interactive.*;
import dev.sheldan.abstracto.core.interactive.setup.step.PostTargetSetupStep;
import dev.sheldan.abstracto.core.interactive.setup.step.SetupSummaryStep;
import dev.sheldan.abstracto.core.interactive.setup.step.SystemConfigSetupStep;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.template.commands.SetupCompletedNotificationModel;
import dev.sheldan.abstracto.core.models.template.commands.SetupInitialMessageModel;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -49,9 +51,6 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
@Autowired
private MemberService memberService;
@Autowired
private BotService botService;
@Autowired
private ExceptionService exceptionService;
@@ -59,68 +58,72 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
public CompletableFuture<Void> performFeatureSetup(FeatureConfig featureConfig, AServerChannelUserId user, Long initialMessageId) {
log.info("Performing setup of feature {} for user {} in channel {} in server {}.",
featureConfig.getFeature().getKey(), user.getUserId(), user.getChannelId(), user.getGuildId());
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelFromServerOptional(user.getGuildId(), user.getChannelId());
if (textChannelInGuild.isPresent()) {
Set<String> requiredSystemConfigKeys = new HashSet<>();
Set<PostTargetEnum> requiredPostTargets = new HashSet<>();
Set<SetupStep> customSetupSteps = new HashSet<>();
GuildMessageChannel messageChannelInGuild = channelService.getMessageChannelFromServer(user.getGuildId(), user.getChannelId());
Set<String> requiredSystemConfigKeys = new HashSet<>();
Set<PostTargetEnum> requiredPostTargets = new HashSet<>();
Set<SetupStep> customSetupSteps = new HashSet<>();
collectRequiredFeatureSteps(featureConfig, requiredSystemConfigKeys, requiredPostTargets, customSetupSteps, new HashSet<>());
collectRequiredFeatureSteps(featureConfig, requiredSystemConfigKeys, requiredPostTargets, customSetupSteps, new HashSet<>());
List<SetupExecution> steps = new ArrayList<>();
requiredSystemConfigKeys.forEach(s -> {
log.debug("Feature requires system config key {}.", s);
SetupExecution execution = SetupExecution
.builder()
.step(systemConfigSetupStep)
.parameter(SystemConfigStepParameter.builder().configKey(s).build())
.build();
steps.add(execution);
});
requiredPostTargets.forEach(postTargetEnum -> {
log.debug("Feature requires post target {}.", postTargetEnum.getKey());
SetupExecution execution = SetupExecution
.builder()
.step(postTargetSetupStep)
.parameter(PostTargetStepParameter.builder().postTargetKey(postTargetEnum.getKey()).build())
.build();
steps.add(execution);
});
customSetupSteps.forEach(setupStep -> {
log.debug("Feature requires custom setup step {}.", setupStep.getClass().getName());
SetupExecution execution = SetupExecution
.builder()
.step(setupStep)
.parameter(EmptySetupParameter.builder().build())
.build();
steps.add(execution);
});
for (int i = 0; i < steps.size(); i++) {
SetupExecution setupExecution = steps.get(i);
setupExecution.getParameter().setPreviousMessageId(initialMessageId);
if (i < steps.size() - 1) {
setupExecution.setNextStep(steps.get(i + 1));
}
}
SetupInitialMessageModel setupInitialMessageModel = SetupInitialMessageModel
List<SetupExecution> steps = new ArrayList<>();
requiredSystemConfigKeys.forEach(s -> {
log.debug("Feature requires system config key {}.", s);
SetupExecution execution = SetupExecution
.builder()
.featureConfig(featureConfig)
.step(systemConfigSetupStep)
.parameter(SystemConfigStepParameter.builder().configKey(s).build())
.build();
TextChannel textChannel = textChannelInGuild.get();
String text = templateService.renderTemplate(FEATURE_SETUP_INITIAL_MESSAGE_TEMPLATE_KEY, setupInitialMessageModel, user.getGuildId());
channelService.sendTextToChannel(text, textChannel);
ArrayList<DelayedActionConfig> delayedActionConfigs = new ArrayList<>();
featureConfig
.getAutoSetupSteps()
.forEach(autoDelayedAction -> delayedActionConfigs.add(autoDelayedAction.getDelayedActionConfig(user)));
return executeFeatureSetup(featureConfig, steps, user, delayedActionConfigs);
steps.add(execution);
});
requiredPostTargets.forEach(postTargetEnum -> {
log.debug("Feature requires post target {}.", postTargetEnum.getKey());
SetupExecution execution = SetupExecution
.builder()
.step(postTargetSetupStep)
.parameter(PostTargetStepParameter.builder().postTargetKey(postTargetEnum.getKey()).build())
.build();
steps.add(execution);
});
customSetupSteps.forEach(setupStep -> {
log.debug("Feature requires custom setup step {}.", setupStep.getClass().getName());
SetupExecution execution = SetupExecution
.builder()
.step(setupStep)
.parameter(EmptySetupParameter.builder().build())
.build();
steps.add(execution);
});
for (int i = 0; i < steps.size(); i++) {
SetupExecution setupExecution = steps.get(i);
setupExecution.getParameter().setPreviousMessageId(initialMessageId);
if (i < steps.size() - 1) {
setupExecution.setNextStep(steps.get(i + 1));
}
}
throw new ChannelNotInGuildException(user.getChannelId());
SetupInitialMessageModel setupInitialMessageModel = SetupInitialMessageModel
.builder()
.featureConfig(featureConfig)
.build();
String text = templateService.renderTemplate(FEATURE_SETUP_INITIAL_MESSAGE_TEMPLATE_KEY, setupInitialMessageModel, user.getGuildId());
channelService.sendTextToChannel(text, messageChannelInGuild);
ArrayList<DelayedActionConfigContainer> delayedActionConfigs = new ArrayList<>();
featureConfig
.getAutoSetupSteps()
.forEach(autoStep -> {
DelayedActionConfig autoDelayedAction = autoStep.getDelayedActionConfig(user);
DelayedActionConfigContainer container = DelayedActionConfigContainer
.builder()
.object(autoDelayedAction)
.type(autoDelayedAction.getClass())
.build();
delayedActionConfigs.add(container);
});
return executeFeatureSetup(featureConfig, steps, user, delayedActionConfigs);
}
@Override
public CompletableFuture<Void> executeFeatureSetup(FeatureConfig featureConfig, List<SetupExecution> steps, AServerChannelUserId user, List<DelayedActionConfig> delayedActionConfigs) {
public CompletableFuture<Void> executeFeatureSetup(FeatureConfig featureConfig, List<SetupExecution> steps, AServerChannelUserId user, List<DelayedActionConfigContainer> delayedActionConfigs) {
if (!steps.isEmpty()) {
SetupExecution nextStep = steps.get(0);
return executeStep(user, nextStep, delayedActionConfigs, featureConfig);
@@ -131,7 +134,7 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
}
}
private CompletableFuture<Void> executeStep(AServerChannelUserId aUserInAServer, SetupExecution execution, List<DelayedActionConfig> delayedActionConfigs, FeatureConfig featureConfig) {
private CompletableFuture<Void> executeStep(AServerChannelUserId aUserInAServer, SetupExecution execution, List<DelayedActionConfigContainer> delayedActionConfigs, FeatureConfig featureConfig) {
log.debug("Executing step {} in server {} in channel {} for user {}.", execution.getStep().getClass(), aUserInAServer.getGuildId(), aUserInAServer.getChannelId(), aUserInAServer.getUserId());
return execution.getStep().execute(aUserInAServer, execution.getParameter()).thenAccept(setpResult -> {
if (setpResult.getResult().equals(SetupStepResultType.SUCCESS)) {
@@ -158,9 +161,9 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
@Transactional
public void showExceptionMessage(Throwable throwable, AServerChannelUserId aServerChannelUserId) {
Optional<TextChannel> channelOptional = channelService.getTextChannelFromServerOptional(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
GuildMessageChannel messageChannelInGuild = channelService.getMessageChannelFromServer(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
memberService.getMemberInServerAsync(aServerChannelUserId.getGuildId(), aServerChannelUserId.getUserId()).thenAccept(member ->
channelOptional.ifPresent(textChannel -> exceptionService.reportExceptionToChannel(throwable, textChannel, member))
exceptionService.reportExceptionToChannel(throwable, messageChannelInGuild, member)
).exceptionally(innserThrowable -> {
log.error("Failed to report exception message for exception {} for user {} in channel {} in server {}.", throwable, aServerChannelUserId.getUserId(), aServerChannelUserId.getChannelId(), aServerChannelUserId.getGuildId(), innserThrowable);
return null;
@@ -168,43 +171,48 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
}
@Transactional
public void executePostSetupSteps(List<DelayedActionConfig> delayedActionConfigs, AServerChannelUserId user, Long initialMessage, FeatureConfig featureConfig) {
public void executePostSetupSteps(List<DelayedActionConfigContainer> delayedActionConfigs, AServerChannelUserId user, Long initialMessage, FeatureConfig featureConfig) {
SetupSummaryStepParameter parameter = SetupSummaryStepParameter
.builder()
.delayedActionList(delayedActionConfigs)
.featureConfig(featureConfig)
.previousMessageId(initialMessage)
.build();
setupSummaryStep.execute(user, parameter).thenAccept(setupStepResult -> self.notifyAboutCompletion(user, featureConfig, setupStepResult));
setupSummaryStep.execute(user, parameter)
.exceptionally(throwable -> {
showExceptionMessage(throwable.getCause(), user);
return null;
});
}
@Transactional
public void notifyAboutCompletion(AServerChannelUserId aServerChannelUserId, FeatureConfig featureConfig, SetupStepResult result) {
public void notifyAboutCompletion(AServerChannelUserId aServerChannelUserId, String featureKey, SetupStepResult result) {
log.debug("Notifying user {} in channel {} in server {} about completion of setup for feature {}.",
aServerChannelUserId.getUserId(), aServerChannelUserId.getChannelId(), aServerChannelUserId.getGuildId(), featureConfig.getFeature().getKey());
aServerChannelUserId.getUserId(), aServerChannelUserId.getChannelId(), aServerChannelUserId.getGuildId(), featureKey);
String templateKey;
if (result.getResult().equals(SetupStepResultType.CANCELLED)) {
templateKey = FEATURE_SETUP_CANCELLATION_NOTIFICATION_TEMPLATE;
} else {
templateKey = FEATURE_SETUP_COMPLETION_NOTIFICATION_TEMPLATE;
}
notifyUserWithTemplate(aServerChannelUserId, featureConfig, templateKey);
notifyUserWithTemplate(aServerChannelUserId, featureKey, templateKey);
}
private void notifyUserWithTemplate(AServerChannelUserId aServerChannelUserId, FeatureConfig featureConfig, String templateName) {
private void notifyUserWithTemplate(AServerChannelUserId aServerChannelUserId, String featureKey, String templateName) {
SetupCompletedNotificationModel model = SetupCompletedNotificationModel
.builder()
.featureConfig(featureConfig)
.featureKey(featureKey)
.build();
String text = templateService.renderTemplate(templateName, model, aServerChannelUserId.getGuildId());
Optional<TextChannel> textChannel = channelService.getTextChannelFromServerOptional(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
textChannel.ifPresent(channel -> channelService.sendTextToChannel(text, channel));
GuildMessageChannel messageChannelInGuild = channelService.getMessageChannelFromServer(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
channelService.sendTextToChannel(text, messageChannelInGuild);
}
@Transactional
public void notifyAboutCancellation(AServerChannelUserId aServerChannelUserId, FeatureConfig featureConfig) {
log.debug("Notifying user {} in channel {} in server {} about cancellation of setup for feature {}.",
aServerChannelUserId.getUserId(), aServerChannelUserId.getChannelId(), aServerChannelUserId.getGuildId(), featureConfig.getFeature().getKey());
notifyUserWithTemplate(aServerChannelUserId, featureConfig, FEATURE_SETUP_CANCELLATION_NOTIFICATION_TEMPLATE);
notifyUserWithTemplate(aServerChannelUserId, featureConfig.getFeature().getKey(), FEATURE_SETUP_CANCELLATION_NOTIFICATION_TEMPLATE);
}
private void collectRequiredFeatureSteps(FeatureConfig featureConfig, Set<String> requiredSystemConfigKeys,

View File

@@ -14,6 +14,7 @@ import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.interactions.Interaction;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.ActionComponent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.requests.restaction.WebhookMessageAction;
import org.springframework.beans.factory.annotation.Autowired;
@@ -86,10 +87,12 @@ public class InteractionServiceBean implements InteractionService {
AServer server = serverManagementService.loadServer(interactionHook.getInteraction().getGuild());
allMessageActions.set(0, allMessageActions.get(0).addActionRows(actionRows));
actionRows.forEach(components -> components.forEach(component -> {
String id = component.getId();
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
if(payload.getPersistCallback()) {
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType());
if(component instanceof ActionComponent) {
String id = ((ActionComponent)component).getId();
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
if(payload.getPersistCallback()) {
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType());
}
}
}));
}

View File

@@ -8,10 +8,7 @@ import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -35,15 +32,10 @@ public class MemberServiceBean implements MemberService {
@Override
public GuildChannelMember getServerChannelUser(Long serverId, Long channelId, Long userId) {
log.debug("Trying to retrieve member {}, channel {} in server {} from cache.", userId, channelId, serverId);
GuildChannel guildChannel = channelService.getGuildChannelFromServer(serverId, channelId);
Guild guild = guildService.getGuildById(serverId);
Optional<TextChannel> textChannelOptional = channelService.getTextChannelFromServerOptional(guild, channelId);
if(textChannelOptional.isPresent()) {
TextChannel textChannel = textChannelOptional.get();
Member member = guild.getMemberById(userId);
return GuildChannelMember.builder().guild(guild).textChannel(textChannel).member(member).build();
} else {
throw new ChannelNotInGuildException(channelId);
}
Member member = guild.getMemberById(userId);
return GuildChannelMember.builder().guild(guild).textChannel(guildChannel).member(member).build();
}
@Override
@@ -52,9 +44,9 @@ public class MemberServiceBean implements MemberService {
CompletableFuture<Member> memberFuture = getMemberInServerAsync(serverId, userId);
Guild guild = guildService.getGuildById(serverId);
TextChannel textChannel = channelService.getTextChannelFromServer(guild, channelId);
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(guild, channelId);
return memberFuture.thenApply(member ->
GuildChannelMember.builder().guild(guild).textChannel(textChannel).member(member).build()
GuildChannelMember.builder().guild(guild).textChannel(messageChannel).member(member).build()
);
}

View File

@@ -5,8 +5,9 @@ import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.MessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
@@ -75,10 +76,10 @@ public class MessageCacheBean implements MessageCache {
CompletableFuture<CachedMessage> future = new CompletableFuture<>();
Optional<Guild> guildOptional = guildService.getGuildByIdOptional(guildId);
if(guildOptional.isPresent()) {
Optional<TextChannel> textChannelByIdOptional = channelService.getTextChannelFromServerOptional(guildOptional.get(), textChannelId);
Optional<GuildMessageChannel> textChannelByIdOptional = channelService.getMessageChannelFromServerOptional(guildId, textChannelId);
if(textChannelByIdOptional.isPresent()) {
TextChannel textChannel = textChannelByIdOptional.get();
channelService.retrieveMessageInChannel(textChannel, messageId)
MessageChannel messageChannel = textChannelByIdOptional.get();
channelService.retrieveMessageInChannel(messageChannel, messageId)
.thenAccept(message ->
cacheEntityService.buildCachedMessageFromMessage(message)
.thenAccept(future::complete)

View File

@@ -76,7 +76,7 @@ public class MessageServiceBean implements MessageService {
@Override
public CompletableFuture<Void> deleteMessageInChannelInServer(Long serverId, Long channelId, Long messageId) {
metricService.incrementCounter(MESSAGE_DELETE_METRIC);
return channelService.getTextChannelFromServer(serverId, channelId).deleteMessageById(messageId).submit();
return channelService.getMessageChannelFromServer(serverId, channelId).deleteMessageById(messageId).submit();
}
@Override

View File

@@ -20,6 +20,7 @@ import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
@@ -73,7 +74,7 @@ public class PaginatorServiceBean implements PaginatorService {
private static final ReentrantLock lock = new ReentrantLock();
@Override
public CompletableFuture<Void> createPaginatorFromTemplate(String templateKey, Object model, TextChannel textChannel, Long userId) {
public CompletableFuture<Void> createPaginatorFromTemplate(String templateKey, Object model, GuildMessageChannel textChannel, Long userId) {
Long serverId = textChannel.getGuild().getIdLong();
String exitButtonId = componentService.generateComponentId(serverId);
String startButtonId = componentService.generateComponentId(serverId);

View File

@@ -14,9 +14,7 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.SnowflakeUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.apache.commons.collections4.SetUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,6 +22,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.security.auth.login.LoginException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -138,26 +137,66 @@ public class StartupServiceBean implements Startup {
Set<Long> existingRoleIds = SnowflakeUtils.getOwnItemsIds(existingRoles);
Set<Long> guildRoleIds = SnowflakeUtils.getSnowflakeIds(guildRoles);
Set<Long> newRoles = SetUtils.difference(guildRoleIds, existingRoleIds);
newRoles.forEach(aLong -> roleManagementService.createRole(aLong, existingAServer));
newRoles.forEach(roleId -> roleManagementService.createRole(roleId, existingAServer));
Set<Long> deletedRoles = SetUtils.difference(existingRoleIds, guildRoleIds);
deletedRoles.forEach(aLong -> roleManagementService.markDeleted(aLong));
deletedRoles.forEach(roleId -> roleManagementService.markDeleted(roleId));
}
private void synchronizeChannelsOf(Guild guild, AServer existingServer){
List<GuildChannel> available = guild.getChannels();
List<AChannel> knownChannels = existingServer.getChannels().stream().filter(aChannel -> !aChannel.getDeleted()).collect(Collectors.toList());
List<AChannel> knownChannels = existingServer
.getChannels()
.stream()
.filter(aChannel -> !aChannel.getDeleted())
.filter(aChannel -> !aChannel.getType().isThread())
.collect(Collectors.toList());
Set<Long> knownChannelsIds = SnowflakeUtils.getOwnItemsIds(knownChannels);
Set<Long> existingChannelsIds = SnowflakeUtils.getSnowflakeIds(available);
Set<Long> newChannels = SetUtils.difference(existingChannelsIds, knownChannelsIds);
newChannels.forEach(aLong -> {
GuildChannel channel1 = available.stream().filter(channel -> channel.getIdLong() == aLong).findFirst().get();
AChannelType type = AChannelType.getAChannelType(channel1.getType());
channelManagementService.createChannel(channel1.getIdLong(), type, existingServer);
newChannels.forEach(channelId -> {
GuildChannel existingChannel = available
.stream()
.filter(channel -> channel.getIdLong() == channelId)
.findFirst()
.get();
AChannelType type = AChannelType.getAChannelType(existingChannel.getType());
channelManagementService.createChannel(existingChannel.getIdLong(), type, existingServer);
});
Set<Long> noLongAvailable = SetUtils.difference(knownChannelsIds, existingChannelsIds);
noLongAvailable.forEach(aLong ->
channelManagementService.markAsDeleted(aLong)
noLongAvailable.forEach(channelId ->
channelManagementService.markAsDeleted(channelId)
);
List<ThreadChannel> availableThreads = new ArrayList<>();
List<AChannel> knownThreads = existingServer
.getChannels()
.stream()
.filter(aChannel -> !aChannel.getDeleted())
.filter(aChannel -> aChannel.getType().isThread())
.collect(Collectors.toList());
available.stream().forEach(guildChannel -> {
if(guildChannel instanceof IThreadContainer) {
IThreadContainer threadContainer = (IThreadContainer) guildChannel;
availableThreads.addAll(threadContainer.getThreadChannels());
}
});
Set<Long> knownThreadIds = SnowflakeUtils.getOwnItemsIds(knownThreads);
Set<Long> existingThreadsIds = SnowflakeUtils.getSnowflakeIds(availableThreads);
Set<Long> newThreads = SetUtils.difference(existingThreadsIds, knownThreadIds);
newThreads.forEach(threadId -> {
ThreadChannel existingThread = availableThreads
.stream()
.filter(channel -> channel.getIdLong() == threadId)
.findFirst()
.get();
IThreadContainer parentChannel = existingThread.getParentChannel();
AChannel parentChannelObj = channelManagementService.loadChannel(parentChannel);
AChannelType type = AChannelType.getAChannelType(existingThread.getType());
channelManagementService.createThread(existingThread.getIdLong(), type, existingServer, parentChannelObj);
});
Set<Long> noLongAvailableThreads = SetUtils.difference(knownThreadIds, existingThreadsIds);
noLongAvailableThreads.forEach(channelId ->
channelManagementService.markAsDeleted(channelId)
);
}
}

View File

@@ -10,6 +10,8 @@ import dev.sheldan.abstracto.core.models.listener.AChannelDeletedListenerModel;
import dev.sheldan.abstracto.core.repository.ChannelRepository;
import dev.sheldan.abstracto.core.service.LockService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Channel;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
@@ -41,8 +43,8 @@ public class ChannelManagementServiceBean implements ChannelManagementService {
}
@Override
public AChannel loadChannel(TextChannel textChannel) {
return loadChannel(textChannel.getIdLong());
public AChannel loadChannel(Channel guildChannel) {
return loadChannel(guildChannel.getIdLong());
}
@Override
@@ -67,6 +69,29 @@ public class ChannelManagementServiceBean implements ChannelManagementService {
}
}
@Override
public AChannel createThread(Long id, AChannelType type, AServer server, AChannel parentChannel) {
lockService.lockTable(TableLocks.CHANNELS);
if(!channelExists(id)) {
log.info("Creating channel {} with type {}", id, type);
AChannel build = AChannel
.builder()
.id(id)
.type(type)
.relatedChannel(parentChannel)
.server(server)
.deleted(false)
.build();
AChannel createdChannel = repository.save(build);
AChannelCreatedListenerModel model = getCreationModel(createdChannel);
eventPublisher.publishEvent(model);
return createdChannel;
} else {
Optional<AChannel> channelOptional = loadChannelOptional(id);
return channelOptional.orElse(null);
}
}
private AChannelCreatedListenerModel getCreationModel(AChannel channel) {
return AChannelCreatedListenerModel
.builder()

View File

@@ -1,13 +1,14 @@
package dev.sheldan.abstracto.core.service;
package dev.sheldan.abstracto.core.startup;
import dev.sheldan.abstracto.core.listener.AsyncStartupListener;
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
import dev.sheldan.abstracto.core.metric.service.MetricService;
import dev.sheldan.abstracto.core.service.BotService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DiscordStartupService implements AsyncStartupListener {
public class DiscordStartupListener implements AsyncStartupListener {
public static final String DISCORD_GATEWAY_PING = "discord.gateway.ping";
private static final CounterMetric DISCORD_GATE_WAY_PING_METRIC = CounterMetric

View File

@@ -0,0 +1,86 @@
package dev.sheldan.abstracto.core.startup;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.listener.AsyncStartupListener;
import dev.sheldan.abstracto.core.listener.async.MessageContextCommandListener;
import dev.sheldan.abstracto.core.listener.sync.jda.MessageContextCommandListenerBean;
import dev.sheldan.abstracto.core.service.*;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.ISnowflake;
import net.dv8tion.jda.api.interactions.commands.Command;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component
@Slf4j
public class MessageContextCommandListenerLoader implements AsyncStartupListener {
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private BotService botService;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private MessageContextCommandListenerBean listenerBean;
@Autowired
private ContextCommandService contextCommandService;
@Override
public void execute() {
List<MessageContextCommandListener> contextListeners = listenerBean.getListenerList();
if(contextListeners == null || contextListeners.isEmpty()) {
return;
}
JDA jda = botService.getInstance();
List<Guild> onlineGuilds = jda.getGuilds();
onlineGuilds.forEach(guild -> {
log.info("Updating commands for guild {}.", guild.getIdLong());
guild.retrieveCommands().queue(commands -> {
Map<String, Long> existingCommands = commands
.stream()
.filter(command -> command.getType().equals(Command.Type.MESSAGE))
.collect(Collectors.toMap(Command::getName, ISnowflake::getIdLong));
log.info("Loaded {} commands for guild {}.", commands.size(), guild.getIdLong());
contextListeners.forEach(listener -> {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(listener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, guild.getIdLong())) {
return;
}
if(!featureModeService.necessaryFeatureModesMet(listener, guild.getIdLong())) {
return;
}
log.info("Updating message context command {} in guild {}.", listener.getConfig().getName(), guild.getId());
if(existingCommands.containsKey(listener.getConfig().getName())) {
existingCommands.remove(listener.getConfig().getName());
contextCommandService.upsertGuildMessageContextCommand(guild, listener.getConfig().getName())
.thenAccept(command -> log.info("Updated message context command {} in guild {}.", listener.getConfig().getName(), guild.getId()));
}
});
log.info("Deleting {} message context commands in guild {}.", existingCommands.values().size(), guild.getIdLong());
existingCommands.forEach((commandName, commandId) ->
contextCommandService.deleteGuildContextCommand(guild, commandId)
.thenAccept(unused -> log.info("Deleted message context command {} with id {} in guild {}.", commandName, commandId, guild.getIdLong()))
.exceptionally(throwable -> {
log.warn("Failed to delete message context command {} with id {} in guild {}.", commandName, commandId, guild.getIdLong());
return null;
}));
},
throwable -> log.error("Failed to load commands for guild {}.", guild.getIdLong(), throwable));
});
}
}

View File

@@ -1,7 +1,7 @@
package dev.sheldan.abstracto.core.templating.model;
import com.google.gson.annotations.SerializedName;
import net.dv8tion.jda.api.interactions.components.ButtonStyle;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
public enum ButtonStyleConfig {
@SerializedName("primary")

View File

@@ -22,7 +22,7 @@ import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Button;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -1,6 +1,5 @@
abstracto.startup.synchronize=true
abstracto.eventWaiter.threads=3
server.port=8080
abstracto.allowedmention.everyone=false

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="tables/tables.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,23 @@
<?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="add-related-channel-column">
<addColumn tableName="channel" >
<column name="related_channel_id" type="BIGINT">
<constraints nullable="true" />
</column>
</addColumn>
<addForeignKeyConstraint baseColumnNames="related_channel_id" baseTableName="channel" constraintName="fk_channel_related_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="channel" validate="true"/>
<sql>
ALTER TABLE channel DROP CONSTRAINT check_channel_type;
ALTER TABLE channel ADD CONSTRAINT check_channel_type CHECK (type IN ('TEXT', 'DM', 'VOICE', 'NEWS', 'CATEGORY', 'NEWS_THREAD', 'PUBLIC_THREAD', 'PRIVATE_THREAD', 'STAGE', 'NEWS', 'UNKNOWN'));
</sql>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,12 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="remove-required-component-payload-server-id">
<dropNotNullConstraint columnName="server_id" tableName="component_payload"/>
</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="channel.xml" relativeToChangelogFile="true"/>
<include file="component_payload.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -23,4 +23,5 @@
<include file="1.3.9/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.10/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.13/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.0/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -20,7 +20,7 @@ import static dev.sheldan.abstracto.core.command.CommandConstants.COMMAND_CHANNE
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class ChannelGroupCommandServiceBeanTest {
public class ChannelGroupApplicationCommandServiceBeanTest {
@InjectMocks
private ChannelGroupCommandServiceBean testUnit;