[AB-184] adding various metrics to the system, organizing imports, changing some transactional behaviour

adding okhttp metrics, split bot service into multiple services (guild, message), unified some places that services are used in order to interact with the api, and not directly the objects (this is to make it easier for metric to be accurate)
This commit is contained in:
Sheldan
2021-01-29 17:46:41 +01:00
parent 2a2a3aea70
commit b838678c15
362 changed files with 3045 additions and 1573 deletions

View File

@@ -139,7 +139,11 @@
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>metrics-interface</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

View File

@@ -6,20 +6,23 @@ import dev.sheldan.abstracto.core.command.config.ParameterValidator;
import dev.sheldan.abstracto.core.command.config.Parameters;
import dev.sheldan.abstracto.core.command.exception.CommandParameterValidationException;
import dev.sheldan.abstracto.core.command.exception.IncorrectParameterException;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.UnParsedCommandParameter;
import dev.sheldan.abstracto.core.command.handler.CommandParameterHandler;
import dev.sheldan.abstracto.core.command.handler.CommandParameterIterators;
import dev.sheldan.abstracto.core.command.service.CommandManager;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.ExceptionService;
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.core.command.execution.*;
import dev.sheldan.abstracto.core.command.execution.UnParsedCommandParameter;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import dev.sheldan.abstracto.core.service.EmoteService;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.service.management.*;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
@@ -28,9 +31,14 @@ import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -80,6 +88,14 @@ public class CommandReceivedHandler extends ListenerAdapter {
@Autowired
private List<CommandParameterHandler> parameterHandlers;
@Autowired
private MetricService metricService;
public static final String COMMAND_PROCESSED = "command.processed";
public static final String STATUS_TAG = "status";
public static final CounterMetric COMMANDS_PROCESSED_COUNTER = CounterMetric.builder().name(COMMAND_PROCESSED).tagList(Arrays.asList(MetricTag.getTag(STATUS_TAG, "processed"))).build();
public static final CounterMetric COMMANDS_WRONG_PARAMETER_COUNTER = CounterMetric.builder().name(COMMAND_PROCESSED).tagList(Arrays.asList(MetricTag.getTag(STATUS_TAG, "parameter.wrong"))).build();
@Override
@Transactional
public void onMessageReceived(MessageReceivedEvent event) {
@@ -89,7 +105,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
if(!commandManager.isCommand(event.getMessage())) {
return;
}
metricService.incrementCounter(COMMANDS_PROCESSED_COUNTER);
final Command foundCommand;
try {
String contentStripped = event.getMessage().getContentRaw();
@@ -134,9 +150,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
log.info("Executing async command {} for server {} in channel {} based on message {} by user {}.",
foundCommand.getConfiguration().getName(), commandContext.getGuild().getId(), commandContext.getChannel().getId(), commandContext.getMessage().getId(), commandContext.getAuthor().getId());
foundCommand.executeAsync(commandContext).thenAccept(result ->
executePostCommandListener(foundCommand, commandContext, result)
).exceptionally(throwable -> {
self.executeAsyncCommand(foundCommand, commandContext).exceptionally(throwable -> {
log.error("Asynchronous command {} failed.", foundCommand.getConfiguration().getName(), throwable);
UserInitiatedServerContext rebuildUserContext = buildTemplateParameter(event);
CommandContext rebuildContext = CommandContext.builder()
@@ -163,6 +177,13 @@ 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)
);
}
@Transactional
public void reportException(MessageReceivedEvent event, Command foundCommand, Throwable throwable, String s) {
UserInitiatedServerContext userInitiatedContext = buildTemplateParameter(event);
@@ -204,7 +225,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
}
}
@Transactional
@Transactional(isolation = Isolation.SERIALIZABLE)
public CommandResult executeCommand(Command foundCommand, CommandContext commandContext) {
log.info("Executing sync command {} for server {} in channel {} based on message {} by user {}.",
foundCommand.getConfiguration().getName(), commandContext.getGuild().getId(), commandContext.getChannel().getId(), commandContext.getMessage().getId(), commandContext.getAuthor().getId());
@@ -260,6 +281,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
throw abstractoRunTimeException;
} catch (Exception e) {
log.warn("Failed to parse parameter with exception.", e);
metricService.incrementCounter(COMMANDS_WRONG_PARAMETER_COUNTER);
throw new IncorrectParameterException(command, param.getName());
}
}
@@ -312,4 +334,10 @@ public class CommandReceivedHandler extends ListenerAdapter {
return CompletableFuture.completedFuture(parameters);
}
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(COMMANDS_PROCESSED_COUNTER, "Commands processed");
metricService.registerCounter(COMMANDS_WRONG_PARAMETER_COUNTER, "Commands with incorrect parameter");
}
}

View File

@@ -30,7 +30,7 @@ public class AUserInAServerParameterHandlerImpl implements AUserInAServerParamet
AUserInAServer actualInstance;
if (throwable == null) {
Member member = (Member) o;
actualInstance = userInServerManagementService.loadUser(member);
actualInstance = userInServerManagementService.loadOrCreateUser(member);
} else {
Long userId = Long.parseLong(input);
actualInstance = userInServerManagementService.loadAUserInAServerOptional(context.getGuild().getIdLong(), userId).orElseThrow(() -> new UserInServerNotFoundException(0L));

View File

@@ -8,7 +8,7 @@ import dev.sheldan.abstracto.core.command.models.condition.GenericConditionModel
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.core.models.GuildChannelMember;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.ReactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -18,7 +18,7 @@ public class ConditionPostExecution implements PostCommandExecution {
public static final String GENERIC_COMMAND_EXCEPTION_MODEL_KEY = "generic_condition_notification";
@Autowired
private MessageService messageService;
private ReactionService reactionService;
@Autowired
private ChannelService channelService;
@@ -26,7 +26,7 @@ public class ConditionPostExecution implements PostCommandExecution {
@Override
public void execute(CommandContext commandContext, CommandResult commandResult, Command command) {
if(commandResult.getResult().equals(ResultState.CONDITION) && commandResult.getConditionResult() != null && !commandResult.getConditionResult().isResult() && commandResult.getConditionResult().getConditionDetail() != null) {
messageService.addReactionToMessage(WARN_REACTION_EMOTE, commandContext.getGuild().getIdLong(), commandContext.getMessage());
reactionService.addReactionToMessage(WARN_REACTION_EMOTE, commandContext.getGuild().getIdLong(), commandContext.getMessage());
GenericConditionModel conditionModel = GenericConditionModel
.builder()
.conditionDetail(commandResult.getConditionResult().getConditionDetail())

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.core.command.post;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.service.ExceptionService;
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ResultState;
import dev.sheldan.abstracto.core.command.service.ExceptionService;
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -1,11 +1,12 @@
package dev.sheldan.abstracto.core.command.post;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ResultState;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ReactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -15,18 +16,21 @@ public class ReactionPostExecution implements PostCommandExecution {
public static final String WARN_REACTION_EMOTE = "warnReaction";
public static final String SUCCESS_REACTION_EMOTE = "successReaction";
@Autowired
private MessageService messageService;
private ReactionService reactionService;
@Autowired
private ChannelService channelService;
@Override
public void execute(CommandContext commandContext, CommandResult commandResult, Command command) {
ResultState result = commandResult.getResult();
if(result.equals(ResultState.ERROR) || result.equals(ResultState.REPORTED_ERROR)) {
messageService.addReactionToMessage(WARN_REACTION_EMOTE, commandContext.getGuild().getIdLong(), commandContext.getMessage());
reactionService.addReactionToMessage(WARN_REACTION_EMOTE, commandContext.getGuild().getIdLong(), commandContext.getMessage());
if(commandResult.getMessage() != null && commandResult.getThrowable() == null){
commandContext.getChannel().sendMessage(commandResult.getMessage()).queue();
channelService.sendTextToChannel(commandResult.getMessage(), commandContext.getChannel());
}
} else if(result.equals(ResultState.SUCCESSFUL) && command.getConfiguration().isCausesReaction()) {
messageService.addReactionToMessage(SUCCESS_REACTION_EMOTE, commandContext.getGuild().getIdLong(), commandContext.getMessage());
reactionService.addReactionToMessage(SUCCESS_REACTION_EMOTE, commandContext.getGuild().getIdLong(), commandContext.getMessage());
}
}

View File

@@ -5,20 +5,26 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ResultState;
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.core.service.MessageService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class SelfDestructPostExecution implements PostCommandExecution {
@Autowired
private MessageService messageService;
@Override
public void execute(CommandContext commandContext, CommandResult commandResult, Command command) {
if(commandResult.getResult().equals(ResultState.SELF_DESTRUCT)) {
Message message = commandContext.getMessage();
log.trace("Command {} is of type self destruct. Deleting message {} in channel {} in server {}.",
command.getConfiguration().getName(), message.getId(), message.getChannel().getId(), message.getGuild().getId());
message.delete().queue();
messageService.deleteMessage(message);
}
}
}

View File

@@ -1,12 +1,14 @@
package dev.sheldan.abstracto.core.command.service;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.config.ModuleInterface;
import dev.sheldan.abstracto.core.command.exception.CommandNotFoundException;
import dev.sheldan.abstracto.core.command.CommandReceivedHandler;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.ModuleInterface;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.exception.CommandNotFoundException;
import dev.sheldan.abstracto.core.command.exception.InsufficientParametersException;
import dev.sheldan.abstracto.core.command.execution.UnParsedCommandParameter;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService;
import net.dv8tion.jda.api.entities.Message;
@@ -30,6 +32,9 @@ public class CommandManager implements CommandRegistry {
@Autowired
private DefaultConfigManagementService defaultConfigManagementService;
@Autowired
private MetricService metricService;
@Override
public Command findCommandByParameters(String name, UnParsedCommandParameter unParsedCommandParameter) {
Optional<Command> commandOptional = commands.stream().filter((Command o )-> {
@@ -46,6 +51,7 @@ public class CommandManager implements CommandRegistry {
boolean hasRemainderParameter = commandConfiguration.getParameters().stream().anyMatch(Parameter::isRemainder);
if(unParsedCommandParameter.getParameters().size() < commandConfiguration.getNecessaryParameterCount()) {
String nextParameterName = commandConfiguration.getParameters().get(commandConfiguration.getNecessaryParameterCount() - 1).getName();
metricService.incrementCounter(CommandReceivedHandler.COMMANDS_WRONG_PARAMETER_COUNTER);
throw new InsufficientParametersException(o, nextParameterName);
}
parameterFit = paramCountFits || hasRemainderParameter;

View File

@@ -94,7 +94,7 @@ public class ExceptionServiceBean implements ExceptionService {
}
private GenericExceptionModel buildCommandModel(Throwable throwable, CommandContext context) {
FullUserInServer fullUser = FullUserInServer.builder().member(context.getAuthor()).aUserInAServer(userInServerManagementService.loadUser(context.getAuthor())).build();
FullUserInServer fullUser = FullUserInServer.builder().member(context.getAuthor()).aUserInAServer(userInServerManagementService.loadUserOptional(context.getGuild().getIdLong(), context.getAuthor().getIdLong()).orElse(null)).build();
return GenericExceptionModel
.builder()
.user(fullUser)
@@ -103,7 +103,7 @@ public class ExceptionServiceBean implements ExceptionService {
}
private GenericExceptionModel buildMemberContext(Throwable throwable, Member member) {
AUserInAServer userInAServer = userInServerManagementService.loadUser(member);
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member);
FullUserInServer fullUser = FullUserInServer
.builder()
.aUserInAServer(userInAServer)
@@ -117,7 +117,7 @@ public class ExceptionServiceBean implements ExceptionService {
}
private GenericExceptionModel buildPrivateMessageReceivedModel(Throwable throwable, User user) {
AUser aUser = userManagementService.loadUser(user.getIdLong());
AUser aUser = userManagementService.loadOrCreateUser(user.getIdLong());
FullUser fullUser = FullUser
.builder()
.user(user)

View File

@@ -6,7 +6,7 @@ import dev.sheldan.abstracto.core.command.config.SingleLevelPackedModule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
@Service

View File

@@ -15,6 +15,7 @@ public class CommandInServerManagementServiceBean implements CommandInServerMana
@Autowired
private CommandInServerRepository repository;
@Override
public ACommandInAServer crateCommandInServer(ACommand command, AServer server) {
ACommandInAServer commandInAServer = ACommandInAServer
@@ -40,6 +41,6 @@ public class CommandInServerManagementServiceBean implements CommandInServerMana
@Override
public ACommandInAServer getCommandForServer(ACommand command, Long serverId) {
return null;
return repository.findByServerReference_IdAndCommandReference(serverId, command);
}
}

View File

@@ -3,11 +3,11 @@ package dev.sheldan.abstracto.core.commands.channels;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.service.ChannelGroupService;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -1,7 +1,7 @@
package dev.sheldan.abstracto.core.commands.channels;
import dev.sheldan.abstracto.core.command.config.ModuleInterface;
import dev.sheldan.abstracto.core.command.config.ModuleInfo;
import dev.sheldan.abstracto.core.command.config.ModuleInterface;
import org.springframework.stereotype.Component;
@Component

View File

@@ -3,11 +3,11 @@ package dev.sheldan.abstracto.core.commands.channels;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.ChannelGroupType;
import dev.sheldan.abstracto.core.service.ChannelGroupService;
import dev.sheldan.abstracto.core.service.management.ChannelGroupTypeManagementService;

View File

@@ -3,11 +3,11 @@ package dev.sheldan.abstracto.core.commands.channels;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.service.ChannelGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -4,11 +4,11 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.service.ChannelGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -4,11 +4,11 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.service.ChannelGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -3,20 +3,20 @@ package dev.sheldan.abstracto.core.commands.channels;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.models.template.commands.ChannelGroupChannelModel;
import dev.sheldan.abstracto.core.models.template.commands.ChannelGroupModel;
import dev.sheldan.abstracto.core.models.template.commands.ListChannelGroupsModel;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
@@ -58,7 +58,7 @@ public class ListChannelGroups extends AbstractConditionableCommand {
channelGroups.forEach(group -> {
List<ChannelGroupChannelModel> convertedChannels = new ArrayList<>();
group.getChannels().forEach(channel -> {
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelInGuild(channel.getServer().getId(), channel.getId());
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
if(textChannelInGuild.isPresent()) {
ChannelGroupChannelModel convertedChannel = ChannelGroupChannelModel
.builder()

View File

@@ -4,9 +4,11 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.*;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.exception.PostTargetNotValidException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.PostTarget;

View File

@@ -3,11 +3,11 @@ package dev.sheldan.abstracto.core.commands.channels;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.service.ChannelGroupService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.service.CacheServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -5,11 +5,11 @@ import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.ParameterValidator;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.config.validator.MaxStringLengthValidator;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
@@ -13,7 +14,6 @@ import dev.sheldan.abstracto.core.command.service.management.CommandManagementSe
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
@@ -13,7 +14,6 @@ import dev.sheldan.abstracto.core.command.service.management.CommandManagementSe
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;

View File

@@ -4,14 +4,14 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.template.commands.EnableModel;
import dev.sheldan.abstracto.core.service.ChannelService;

View File

@@ -4,14 +4,14 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.template.commands.EnableModel;

View File

@@ -3,12 +3,12 @@ package dev.sheldan.abstracto.core.commands.config.features;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.converter.FeatureFlagConverter;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AServer;

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
@@ -13,7 +14,6 @@ import dev.sheldan.abstracto.core.command.service.management.CommandManagementSe
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
@@ -13,7 +14,6 @@ import dev.sheldan.abstracto.core.command.service.management.CommandManagementSe
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
@@ -13,7 +14,6 @@ import dev.sheldan.abstracto.core.command.service.management.CommandManagementSe
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;

View File

@@ -1,8 +1,11 @@
package dev.sheldan.abstracto.core.commands.help;
import dev.sheldan.abstracto.core.command.*;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.execution.*;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
import dev.sheldan.abstracto.core.command.models.database.ACommandInAServer;
import dev.sheldan.abstracto.core.command.service.CommandRegistry;
@@ -11,7 +14,9 @@ import dev.sheldan.abstracto.core.command.service.ModuleRegistry;
import dev.sheldan.abstracto.core.command.service.management.CommandInServerManagementService;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.models.template.commands.help.HelpCommandDetailsModel;
import dev.sheldan.abstracto.core.models.template.commands.help.HelpModuleDetailsModel;
import dev.sheldan.abstracto.core.models.template.commands.help.HelpModuleOverviewModel;
@@ -24,7 +29,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -59,14 +66,49 @@ public class Help implements Command {
@Autowired
private CommandService commandService;
@Autowired
private MetricService metricService;
public static final String HELP_COMMAND_EXECUTED_METRIC = "help.executions";
public static final String CATEGORY = "category";
private static final CounterMetric HELP_COMMAND_NO_PARAMETER_METRIC =
CounterMetric
.builder()
.name(HELP_COMMAND_EXECUTED_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(CATEGORY, "no.parmeter")))
.build();
private static final CounterMetric HELP_COMMAND_MODULE_METRIC =
CounterMetric
.builder()
.name(HELP_COMMAND_EXECUTED_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(CATEGORY, "module")))
.build();
private static final CounterMetric HELP_COMMAND_COMMAND_METRIC =
CounterMetric
.builder()
.name(HELP_COMMAND_EXECUTED_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(CATEGORY, "command")))
.build();
private static final CounterMetric HELP_COMMAND_WRONG_PARAM_METRIC =
CounterMetric
.builder()
.name(HELP_COMMAND_EXECUTED_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(CATEGORY, "wrong.argument")))
.build();
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
if(parameters.isEmpty()) {
metricService.incrementCounter(HELP_COMMAND_NO_PARAMETER_METRIC);
return displayHelpOverview(commandContext);
} else {
String parameter = (String) parameters.get(0);
if(moduleService.moduleExists(parameter)){
metricService.incrementCounter(HELP_COMMAND_MODULE_METRIC);
ModuleInterface moduleInterface = moduleService.getModuleByName(parameter);
log.trace("Displaying help for module {}.", moduleInterface.getInfo().getName());
SingleLevelPackedModule module = moduleService.getPackedModule(moduleInterface);
@@ -86,6 +128,7 @@ public class Help implements Command {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(aVoid -> CommandResult.fromIgnored());
} else if(commandRegistry.commandExists(parameter)) {
metricService.incrementCounter(HELP_COMMAND_COMMAND_METRIC);
Command command = commandRegistry.getCommandByName(parameter);
log.trace("Displaying help for command {}.", command.getConfiguration().getName());
ACommand aCommand = commandManagementService.findCommandByName(parameter);
@@ -102,6 +145,7 @@ public class Help implements Command {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(aVoid -> CommandResult.fromIgnored());
} else {
metricService.incrementCounter(HELP_COMMAND_WRONG_PARAM_METRIC);
return displayHelpOverview(commandContext);
}
}
@@ -142,4 +186,12 @@ public class Help implements Command {
public FeatureEnum getFeature() {
return CoreFeatures.CORE_FEATURE;
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(HELP_COMMAND_NO_PARAMETER_METRIC, "Help command executions without argument.");
metricService.registerCounter(HELP_COMMAND_MODULE_METRIC, "Help command executions with a module as argument.");
metricService.registerCounter(HELP_COMMAND_COMMAND_METRIC, "Help command executions with a command as argument.");
metricService.registerCounter(HELP_COMMAND_WRONG_PARAM_METRIC, "Help command executions with a parameter not matching.");
}
}

View File

@@ -1,7 +1,7 @@
package dev.sheldan.abstracto.core.commands.help;
import dev.sheldan.abstracto.core.command.config.ModuleInterface;
import dev.sheldan.abstracto.core.command.config.ModuleInfo;
import dev.sheldan.abstracto.core.command.config.ModuleInterface;
import org.springframework.stereotype.Component;
@Component

View File

@@ -2,14 +2,15 @@ package dev.sheldan.abstracto.core.commands.utility;
import dev.sheldan.abstracto.core.command.UtilityModuleInterface;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.template.commands.EchoModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -25,6 +26,9 @@ public class Echo extends AbstractConditionableCommand {
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Override
public CommandResult execute(CommandContext commandContext) {
StringBuilder sb = new StringBuilder();
@@ -32,7 +36,8 @@ public class Echo extends AbstractConditionableCommand {
sb.append(o.toString())
);
EchoModel model = EchoModel.builder().text(sb.toString()).build();
commandContext.getChannel().sendMessage(templateService.renderTemplate(TEMPLATE_NAME, model)).queue();
String textToSend = templateService.renderTemplate(TEMPLATE_NAME, model);
channelService.sendTextToChannel(textToSend, commandContext.getChannel());
return CommandResult.fromIgnored();
}

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.UtilityModuleInterface;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.template.commands.PingModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MessageService;

View File

@@ -4,13 +4,13 @@ import dev.sheldan.abstracto.core.command.UtilityModuleInterface;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.ParameterValidator;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.config.validator.MaxStringLengthValidator;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.service.EmoteService;
import dev.sheldan.abstracto.core.service.management.EmoteManagementService;

View File

@@ -4,6 +4,7 @@ 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.metrics.OkHttpMetrics;
import dev.sheldan.abstracto.core.service.BotService;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +28,9 @@ public class CoreConfig {
@Value("${abstracto.eventWaiter.threads}")
private Integer threadCount;
@Autowired
private OkHttpMetrics okHttpMetrics;
@Bean
public Gson gson() {
return new GsonBuilder()
@@ -44,7 +48,7 @@ public class CoreConfig {
@Bean
public OkHttpClient client() {
return new OkHttpClient();
return new OkHttpClient.Builder().addInterceptor(okHttpMetrics).build();
}
@Bean

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.core.config;
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class MetricConstants {
public static final String DISCORD_API_INTERACTION_METRIC = "discord.api.interactions";
public static final String INTERACTION_TYPE = "action";
}

View File

@@ -1,6 +1,9 @@
package dev.sheldan.abstracto.core.config;
import com.google.gson.*;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import java.time.Instant;

View File

@@ -5,7 +5,6 @@ import com.jagrosh.jdautilities.menu.ButtonMenu;
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.service.BotService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.EmoteService;
import dev.sheldan.abstracto.core.service.MessageService;
@@ -42,9 +41,6 @@ public class InteractiveServiceBean implements InteractiveService {
@Autowired
private EmoteService emoteService;
@Autowired
private BotService botService;
@Override
public void createMessageWithResponse(String messageText, AUserInAServer responder, AChannel channel, Long messageId, Consumer<MessageReceivedEvent> action, Runnable finalAction) {
channelService.sendTextToAChannel(messageText, channel);
@@ -90,14 +86,14 @@ public class InteractiveServiceBean implements InteractiveService {
}
})
.build();
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelInGuild(serverId, channel.getId());
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelFromServerOptional(serverId, channel.getId());
textChannelInGuild.ifPresent(menu::display);
}
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 = botService.getEmote(serverId, emoteOrFakeEmote);
Optional<Emote> emote = emoteService.getEmote(serverId, emoteOrFakeEmote);
emote.ifPresent(emote1 -> {
builder.addChoice(emote1);
actions.put(emote1.getId(), consumer);

View File

@@ -5,7 +5,7 @@ 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.template.commands.SetupPostTargetMessageModel;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
@@ -47,7 +47,7 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
private TemplateService templateService;
@Autowired
private BotService botService;
private ChannelService channelService;
@Autowired
private PostTargetManagement postTargetManagement;
@@ -58,7 +58,7 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
TextChannel currentTextChannel;
if(postTargetManagement.postTargetExists(postTargetStepParameter.getPostTargetKey(), user.getGuildId())) {
PostTarget postTarget = postTargetManagement.getPostTarget(postTargetStepParameter.getPostTargetKey(), user.getGuildId());
currentTextChannel = botService.getTextChannelFromServerOptional(user.getGuildId(), postTarget.getChannelReference().getId()).orElse(null);
currentTextChannel = channelService.getTextChannelFromServerOptional(user.getGuildId(), postTarget.getChannelReference().getId()).orElse(null);
} else {
currentTextChannel = null;
}
@@ -70,7 +70,7 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
String messageText = templateService.renderTemplate(FEATURE_SETUP_POST_TARGET_MESSAGE_TEMPLATE_KEY, model);
AChannel channel = channelManagementService.loadChannel(user.getChannelId());
CompletableFuture<SetupStepResult> future = new CompletableFuture<>();
AUserInAServer aUserInAServer = userInServerManagementService.loadUser(user.getGuildId(), user.getUserId());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(user.getGuildId(), user.getUserId());
Runnable finalAction = super.getTimeoutRunnable(user.getGuildId(), user.getChannelId());
log.trace("Executing setup for post target {} in server {} for user {}.", postTargetStepParameter.getPostTargetKey(), user.getGuildId(), user.getUserId());
Consumer<MessageReceivedEvent> configAction = (MessageReceivedEvent event) -> {

View File

@@ -49,7 +49,7 @@ public class SetupSummaryStep extends AbstractConfigSetupStep {
String messageToSend = templateService.renderTemplate(FEATURE_SETUP_CONFIRMATION_TEMPLATE_KEY, model);
AChannel channel = channelManagementService.loadChannel(user.getChannelId());
CompletableFuture<SetupStepResult> future = new CompletableFuture<>();
AUserInAServer aUserInAServer = userInServerManagementService.loadUser(user.getGuildId(), user.getUserId());
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) -> {

View File

@@ -61,7 +61,7 @@ public class SystemConfigSetupStep extends AbstractConfigSetupStep {
String messageText = templateService.renderTemplate(SETUP_SYSTEM_CONFIG_MESSAGE_TEMPLATE_KEY, model);
AChannel channel = channelManagementService.loadChannel(user.getChannelId());
CompletableFuture<SetupStepResult> future = new CompletableFuture<>();
AUserInAServer aUserInAServer = userInServerManagementService.loadUser(user.getGuildId(), user.getUserId());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(user.getGuildId(), user.getUserId());
log.trace("Executing setup for system config {} in server {} for user {}.", systemConfigStepParameter.getConfigKey(), user.getGuildId(), user.getUserId());
Runnable finalAction = super.getTimeoutRunnable(user.getGuildId(), user.getChannelId());

View File

@@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -62,7 +63,7 @@ public class AsyncEmoteCreatedListenerBean extends ListenerAdapter {
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeCreatedListener(AsyncEmoteCreatedListener listener, CachedEmote createDdEmote, Long serverId) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(listener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, serverId)) {

View File

@@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -47,6 +48,7 @@ public class AsyncEmoteDeletedListenerBean extends ListenerAdapter {
private CacheEntityService cacheEntityService;
@Override
@Transactional
public void onEmoteRemoved(@NotNull EmoteRemovedEvent event) {
if(deletedListeners == null) return;
CachedEmote cachedEmote = cacheEntityService.getCachedEmoteFromEmote(event.getEmote(), event.getGuild());
@@ -62,7 +64,7 @@ public class AsyncEmoteDeletedListenerBean extends ListenerAdapter {
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeDeletedListener(AsyncEmoteDeletedListener listener, CachedEmote deletedEmote, Long serverId) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(listener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, serverId)) {

View File

@@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -47,6 +48,7 @@ public class AsyncEmoteUpdatedListenerBean extends ListenerAdapter {
private CacheEntityService cacheEntityService;
@Override
@Transactional
public void onEmoteUpdateName(@NotNull EmoteUpdateNameEvent event) {
if(updatedListeners == null) return;
CachedEmote cachedEmote = cacheEntityService.getCachedEmoteFromEmote(event.getEmote(), event.getGuild());
@@ -61,7 +63,7 @@ public class AsyncEmoteUpdatedListenerBean extends ListenerAdapter {
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeUpdatedListener(AsyncEmoteUpdatedListener listener, CachedEmote updatedEmote, String oldName, String newName, Long serverId) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(listener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, serverId)) {

View File

@@ -12,6 +12,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -61,7 +62,7 @@ public class AsyncJoinListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualJoinListener(AsyncJoinListener joinListener, ServerUser serverUser) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(joinListener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, serverUser.getServerId())) {

View File

@@ -12,6 +12,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -58,7 +59,7 @@ public class AsyncLeaveListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualLeaveListener(ServerUser user, AsyncLeaveListener leaveListener) {
log.trace("Executing leave listener {} for member {} in guild {}.", leaveListener.getClass().getName(), user.getUserId(), user.getServerId());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(leaveListener.getFeature());

View File

@@ -17,6 +17,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -84,7 +85,7 @@ public class AsyncMessageDeletedListenerBean extends ListenerAdapter {
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualMessageDeletedListener(CachedMessage cachedMessage, AsyncMessageDeletedListener messageDeletedListener) {
log.trace("Executing message deleted listener {} for message {} in guild {}.", messageDeletedListener.getClass().getName(), cachedMessage.getMessageId(), cachedMessage.getMessageId());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(messageDeletedListener.getFeature());

View File

@@ -52,6 +52,7 @@ public class AsyncMessageEmbeddedListenerBean extends ListenerAdapter {
private AsyncMessageEmbeddedListenerBean self;
@Override
@Transactional
public void onGuildMessageEmbed(@NotNull GuildMessageEmbedEvent event) {
if(listenerList == null) return;
GuildMessageEmbedEventModel model = buildModel(event);

View File

@@ -14,6 +14,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -50,6 +51,7 @@ public class AsyncMessageReceivedListenerBean extends ListenerAdapter {
private AsyncMessageReceivedListenerBean self;
@Override
@Transactional
public void onGuildMessageReceived(@Nonnull GuildMessageReceivedEvent event) {
if(listenerList == null) return;
messageCache.putMessageInCache(event.getMessage()).thenAccept(message -> {
@@ -63,7 +65,7 @@ public class AsyncMessageReceivedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualGuildMessageReceivedListener(CachedMessage cachedMessage, AsyncMessageReceivedListener messageReceivedListener) {
try {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(messageReceivedListener.getFeature());

View File

@@ -14,6 +14,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -44,11 +45,12 @@ public class AsyncMessageUpdatedListenerBean extends ListenerAdapter {
private TaskExecutor messageUpdatedExecutor;
@Override
@Transactional
public void onGuildMessageUpdate(GuildMessageUpdateEvent event) {
if(listener == null) return;
Message message = event.getMessage();
messageCache.getMessageFromCache(message.getGuild().getIdLong(), message.getTextChannel().getIdLong(), event.getMessageIdLong())
.thenAcceptBoth(messageCache.putMessageInCache(message), (oldCache, newCache) -> self.executeListener(newCache, oldCache))
.thenAcceptBoth(messageCache.putMessageInCache(message), (oldCache, newCache) -> self.executeListeners(newCache, oldCache))
.exceptionally(throwable -> {
log.error("Message retrieval {} from cache failed. ", event.getMessage().getId(), throwable);
return null;
@@ -56,7 +58,7 @@ public class AsyncMessageUpdatedListenerBean extends ListenerAdapter {
}
@Transactional
public void executeListener(CachedMessage updatedMessage, CachedMessage oldMessage) {
public void executeListeners(CachedMessage updatedMessage, CachedMessage oldMessage) {
listener.forEach(messageTextUpdatedListener ->
CompletableFuture
.runAsync(() -> self.executeIndividualMessageUpdatedListener(updatedMessage, oldMessage, messageTextUpdatedListener), messageUpdatedExecutor)
@@ -67,7 +69,7 @@ public class AsyncMessageUpdatedListenerBean extends ListenerAdapter {
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualMessageUpdatedListener(CachedMessage updatedMessage, CachedMessage cachedMessage, AsyncMessageTextUpdatedListener messageTextUpdatedListener) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(messageTextUpdatedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, cachedMessage.getServerId())) {

View File

@@ -11,6 +11,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -42,6 +43,7 @@ public class AsyncPrivateMessageReceivedListenerBean extends ListenerAdapter {
private TaskExecutor privateMessageReceivedExecutor;
@Override
@Transactional
public void onPrivateMessageReceived(@Nonnull PrivateMessageReceivedEvent event) {
if(privateMessageReceivedListeners == null) return;
if(event.getAuthor().getId().equals(botService.getInstance().getSelfUser().getId())) {
@@ -65,7 +67,7 @@ public class AsyncPrivateMessageReceivedListenerBean extends ListenerAdapter {
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualPrivateMessageReceivedListener(CachedMessage cachedMessage, AsyncPrivateMessageReceivedListener messageReceivedListener) {
log.trace("Executing private message listener {} for member {}.", messageReceivedListener.getClass().getName(), cachedMessage.getAuthor().getAuthorId());
messageReceivedListener.execute(cachedMessage);

View File

@@ -13,6 +13,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -108,7 +109,7 @@ public class AsyncReactionAddedListenerBean extends ListenerAdapter {
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualReactionAddedListener(@Nonnull CachedReactions reaction, CachedMessage cachedMessage, ServerUser serverUser, AsyncReactionAddedListener reactedAddedListener) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(reactedAddedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, serverUser.getServerId())) {

View File

@@ -12,6 +12,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -59,7 +60,7 @@ public class AsyncReactionClearedListenerBean extends ListenerAdapter {
if(clearedListenerList == null) return;
clearedListenerList.forEach(reactionRemovedListener ->
CompletableFuture.runAsync(() ->
self.callConcreteListener(cachedMessage, reactionRemovedListener)
self.executeIndividualListener(cachedMessage, reactionRemovedListener)
, reactionClearedExecutor)
.exceptionally(throwable -> {
log.error("Async reaction cleared listener {} failed with exception.", reactionRemovedListener, throwable);
@@ -68,8 +69,8 @@ public class AsyncReactionClearedListenerBean extends ListenerAdapter {
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void callConcreteListener(CachedMessage cachedMessage, AsyncReactionClearedListener reactionRemovedListener) {
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualListener(CachedMessage cachedMessage, AsyncReactionClearedListener reactionRemovedListener) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(reactionRemovedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, cachedMessage.getServerId())) {
return;

View File

@@ -14,6 +14,7 @@ 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.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -109,7 +110,7 @@ public class AsyncReactionRemovedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualReactionRemovedListener(@Nonnull CachedReactions reaction, CachedMessage cachedMessage, ServerUser userRemoving, AsyncReactionRemovedListener reactionRemovedListener) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(reactionRemovedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, cachedMessage.getServerId())) {

View File

@@ -1,8 +1,8 @@
package dev.sheldan.abstracto.core.listener.sync.jda;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.models.database.AChannelType;
import dev.sheldan.abstracto.core.models.database.AServer;
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;
@@ -11,6 +11,7 @@ import net.dv8tion.jda.api.events.channel.text.TextChannelDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
@@ -28,14 +29,14 @@ public class ChannelListener extends ListenerAdapter {
// TODO move this to a separate sync listener, which implements a a generic channel listener, in order to provide
// the channel listener for other implementations
@Override
@Transactional
@Transactional(isolation = Isolation.SERIALIZABLE)
public void onTextChannelDelete(@Nonnull TextChannelDeleteEvent event) {
log.info("Handling channel delete event. Marking channel {} as deleted in server {}", event.getChannel().getIdLong(), event.getGuild().getIdLong());
channelManagementService.markAsDeleted(event.getChannel().getIdLong());
}
@Override
@Transactional
@Transactional(isolation = Isolation.SERIALIZABLE)
public void onTextChannelCreate(@Nonnull TextChannelCreateEvent event) {
log.info("Handling channel created event. Creating channel {} in server {}", event.getChannel().getIdLong(), event.getGuild().getIdLong());
AServer serverObject = serverManagementService.loadOrCreate(event.getGuild().getIdLong());

View File

@@ -13,6 +13,7 @@ import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -55,7 +56,7 @@ public class EmoteCreatedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeCreatedListener(EmoteCreatedListener listener, Emote createDdEmote) {
listener.emoteCreated(createDdEmote);
}

View File

@@ -13,6 +13,7 @@ import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -54,7 +55,7 @@ public class EmoteDeletedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeDeletedListener(EmoteDeletedListener listener, Emote createDdEmote) {
listener.emoteDeleted(createDdEmote);
}

View File

@@ -13,6 +13,7 @@ import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -40,6 +41,7 @@ public class EmoteUpdatedListenerBean extends ListenerAdapter {
private EmoteUpdatedListenerBean self;
@Override
@Transactional
public void onEmoteUpdateName(@NotNull EmoteUpdateNameEvent event) {
if(updatedListeners == null) return;
updatedListeners.forEach(emoteUpdatedListener -> {
@@ -54,7 +56,7 @@ public class EmoteUpdatedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeUpdatedListener(EmoteUpdatedListener listener, Emote updatedEmote, String oldName, String newName) {
listener.emoteUpdated(updatedEmote, oldName, newName);
}

View File

@@ -11,6 +11,7 @@ import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -47,7 +48,7 @@ public class JoinListenerBean extends ListenerAdapter {
return;
}
try {
AUserInAServer aUserInAServer = userInServerManagementService.loadUser(event.getMember());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(event.getMember());
self.executeIndividualJoinListener(event, joinListener, aUserInAServer);
} catch (Exception e) {
log.error("Listener {} failed with exception:", joinListener.getClass().getName(), e);
@@ -55,7 +56,7 @@ public class JoinListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualJoinListener(@Nonnull GuildMemberJoinEvent event, JoinListener joinListener, AUserInAServer aUserInAServer) {
log.trace("Executing join listener {} for member {} in guild {}.", joinListener.getClass().getName(), event.getMember().getId(), event.getGuild().getId());
joinListener.execute(event.getMember(), event.getGuild(), aUserInAServer);

View File

@@ -10,6 +10,7 @@ import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -50,7 +51,7 @@ public class LeaveListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualLeaveListener(@Nonnull GuildMemberRemoveEvent event, LeaveListener leaveListener) {
log.trace("Executing leave listener {} for member {} in guild {}.", leaveListener.getClass().getName(), event.getMember().getId(), event.getGuild().getId());
leaveListener.execute(event.getMember(), event.getGuild());

View File

@@ -3,13 +3,13 @@ package dev.sheldan.abstracto.core.listener.sync.jda;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.models.AServerAChannelAUser;
import dev.sheldan.abstracto.core.models.GuildChannelMember;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
@@ -19,14 +19,19 @@ import net.dv8tion.jda.api.events.message.guild.GuildMessageDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import static dev.sheldan.abstracto.core.listener.sync.jda.MessageReceivedListenerBean.ACTION;
import static dev.sheldan.abstracto.core.listener.sync.jda.MessageReceivedListenerBean.MESSAGE_METRIC;
@Component
@Slf4j
public class MessageDeletedListenerBean extends ListenerAdapter {
@@ -55,11 +60,27 @@ public class MessageDeletedListenerBean extends ListenerAdapter {
private ChannelManagementService channelManagementService;
@Autowired
private BotService botService;
private GuildService guildService;
@Autowired
private ChannelService channelService;
@Autowired
private MemberService memberService;
@Autowired
private MetricService metricService;
private static final CounterMetric MESSAGE_DELETED_COUNTER =
CounterMetric
.builder().name(MESSAGE_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(ACTION, "deleted")))
.build();
@Override
@Transactional
public void onGuildMessageDelete(@Nonnull GuildMessageDeleteEvent event) {
metricService.incrementCounter(MESSAGE_DELETED_COUNTER);
if(listener == null) return;
Consumer<CachedMessage> cachedMessageConsumer = cachedMessage -> self.executeListener(cachedMessage);
messageCache.getMessageFromCache(event.getGuild().getIdLong(), event.getChannel().getIdLong(), event.getMessageIdLong())
@@ -77,14 +98,14 @@ public class MessageDeletedListenerBean extends ListenerAdapter {
.builder()
.guild(serverManagementService.loadOrCreate(cachedMessage.getServerId()))
.channel(channelManagementService.loadChannel(cachedMessage.getChannelId()))
.aUserInAServer(userInServerManagementService.loadUser(cachedMessage.getServerId(), cachedMessage.getAuthor().getAuthorId()))
.aUserInAServer(userInServerManagementService.loadOrCreateUser(cachedMessage.getServerId(), cachedMessage.getAuthor().getAuthorId()))
.build();
botService.getMemberInServerAsync(cachedMessage.getServerId(), cachedMessage.getAuthor().getAuthorId()).thenAccept(member -> {
memberService.getMemberInServerAsync(cachedMessage.getServerId(), cachedMessage.getAuthor().getAuthorId()).thenAccept(member -> {
GuildChannelMember authorMember = GuildChannelMember
.builder()
.guild(botService.getGuildById(cachedMessage.getServerId()))
.textChannel(botService.getTextChannelFromServerOptional(cachedMessage.getServerId(), cachedMessage.getChannelId()).orElseThrow(() -> new ChannelNotInGuildException(cachedMessage.getChannelId())))
.member(botService.getMemberInServer(cachedMessage.getServerId(), cachedMessage.getAuthor().getAuthorId()))
.guild(guildService.getGuildById(cachedMessage.getServerId()))
.textChannel(channelService.getTextChannelFromServerOptional(cachedMessage.getServerId(), cachedMessage.getChannelId()).orElseThrow(() -> new ChannelNotInGuildException(cachedMessage.getChannelId())))
.member(memberService.getMemberInServer(cachedMessage.getServerId(), cachedMessage.getAuthor().getAuthorId()))
.build();
listener.forEach(messageDeletedListener -> {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(messageDeletedListener.getFeature());
@@ -104,7 +125,7 @@ public class MessageDeletedListenerBean extends ListenerAdapter {
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualMessageDeletedListener(CachedMessage cachedMessage, AServerAChannelAUser authorUser, GuildChannelMember authorMember, MessageDeletedListener messageDeletedListener) {
log.trace("Executing message deleted listener {} for message {} in guild {}.", messageDeletedListener.getClass().getName(), cachedMessage.getMessageId(), cachedMessage.getMessageId());
messageDeletedListener.execute(cachedMessage, authorUser, authorMember);
@@ -112,6 +133,7 @@ public class MessageDeletedListenerBean extends ListenerAdapter {
@PostConstruct
public void postConstruct() {
metricService.registerCounter(MESSAGE_DELETED_COUNTER, "Messages deleted");
BeanUtils.sortPrioritizedListeners(listener);
}
}

View File

@@ -47,6 +47,7 @@ public class MessageEmbeddedListenerBean extends ListenerAdapter {
private MessageEmbeddedListenerBean self;
@Override
@Transactional
public void onGuildMessageEmbed(@NotNull GuildMessageEmbedEvent event) {
if(listenerList == null) return;
GuildMessageEmbedEventModel model = buildModel(event);

View File

@@ -3,6 +3,9 @@ package dev.sheldan.abstracto.core.listener.sync.jda;
import dev.sheldan.abstracto.core.command.service.ExceptionService;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.execution.result.MessageReceivedListenerResult;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
@@ -13,11 +16,13 @@ import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
@Component
@@ -45,8 +50,17 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
@Autowired
private MessageReceivedListenerBean self;
@Autowired
private MetricService metricService;
public static final String MESSAGE_METRIC = "message";
public static final String ACTION = "action";
private static final CounterMetric MESSAGE_RECEIVED_COUNTER = CounterMetric.builder().name(MESSAGE_METRIC).tagList(Arrays.asList(MetricTag.getTag(ACTION, "received"))).build();
@Override
@Transactional
public void onGuildMessageReceived(@Nonnull GuildMessageReceivedEvent event) {
metricService.incrementCounter(MESSAGE_RECEIVED_COUNTER);
messageCache.putMessageInCache(event.getMessage());
if(listenerList == null) return;
for (MessageReceivedListener messageReceivedListener : listenerList) {
@@ -66,13 +80,14 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public MessageReceivedListenerResult executeIndividualGuildMessageReceivedListener(@Nonnull GuildMessageReceivedEvent event, MessageReceivedListener messageReceivedListener) {
return messageReceivedListener.execute(event.getMessage());
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(MESSAGE_RECEIVED_COUNTER, "Message received");
BeanUtils.sortPrioritizedListeners(listenerList);
}
}

View File

@@ -2,6 +2,9 @@ package dev.sheldan.abstracto.core.listener.sync.jda;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
@@ -13,13 +16,18 @@ import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
import static dev.sheldan.abstracto.core.listener.sync.jda.MessageReceivedListenerBean.ACTION;
import static dev.sheldan.abstracto.core.listener.sync.jda.MessageReceivedListenerBean.MESSAGE_METRIC;
@Component
@Slf4j
public class MessageUpdatedListenerBean extends ListenerAdapter {
@@ -39,8 +47,15 @@ public class MessageUpdatedListenerBean extends ListenerAdapter {
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private MetricService metricService;
private static final CounterMetric MESSAGE_UPDATED_COUNTER = CounterMetric.builder().name(MESSAGE_METRIC).tagList(Arrays.asList(MetricTag.getTag(ACTION, "updated"))).build();
@Override
@Transactional
public void onGuildMessageUpdate(@Nonnull GuildMessageUpdateEvent event) {
metricService.incrementCounter(MESSAGE_UPDATED_COUNTER);
if(listener == null) return;
Message message = event.getMessage();
messageCache.getMessageFromCache(message.getGuild().getIdLong(), message.getTextChannel().getIdLong(), event.getMessageIdLong()).thenAccept(cachedMessage -> {
@@ -68,13 +83,14 @@ public class MessageUpdatedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualMessageUpdatedListener(Message message, CachedMessage cachedMessage, MessageTextUpdatedListener messageTextUpdatedListener) {
messageTextUpdatedListener.execute(cachedMessage, message);
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(MESSAGE_UPDATED_COUNTER, "Messages updated");
BeanUtils.sortPrioritizedListeners(listener);
}
}

View File

@@ -8,6 +8,7 @@ import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -32,6 +33,7 @@ public class PrivateMessageReceivedListenerBean extends ListenerAdapter {
private PrivateMessageReceivedListenerBean self;
@Override
@Transactional
public void onPrivateMessageReceived(@Nonnull PrivateMessageReceivedEvent event) {
if(privateMessageReceivedListeners == null) return;
if(event.getAuthor().getId().equals(botService.getInstance().getSelfUser().getId())) {
@@ -47,7 +49,7 @@ public class PrivateMessageReceivedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualPrivateMessageReceivedListener(@Nonnull PrivateMessageReceivedEvent event, PrivateMessageReceivedListener messageReceivedListener) {
// no feature flag check, because we are in no server context
log.trace("Executing private message listener {} for member {}.", messageReceivedListener.getClass().getName(), event.getAuthor().getId());

View File

@@ -12,6 +12,7 @@ import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEve
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -106,7 +107,7 @@ public class ReactionAddedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualReactionAddedListener(@Nonnull GuildMessageReactionAddEvent event, CachedMessage cachedMessage, ServerUser serverUser, ReactionAddedListener reactedAddedListener) {
reactedAddedListener.executeReactionAdded(cachedMessage, event, serverUser);
}

View File

@@ -9,8 +9,10 @@ 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.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -50,20 +52,24 @@ public class ReactionClearedListenerBean extends ListenerAdapter {
@Autowired
private EmoteService emoteService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void callClearListeners(@Nonnull GuildMessageReactionRemoveAllEvent event, CachedMessage cachedMessage) {
if(clearedListenerList == null) return;
clearedListenerList.forEach(reactionRemovedListener -> {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(reactionRemovedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return;
}
try {
reactionRemovedListener.executeReactionCleared(cachedMessage);
} catch (AbstractoRunTimeException e) {
log.warn(String.format("Failed to execute reaction clear listener %s.", reactionRemovedListener.getClass().getName()), e);
}
});
clearedListenerList.forEach(reactionRemovedListener ->
self.executeIndividualListener(event, cachedMessage, reactionRemovedListener)
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualListener(@NotNull GuildMessageReactionRemoveAllEvent event, CachedMessage cachedMessage, ReactionClearedListener reactionRemovedListener) {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(reactionRemovedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return;
}
try {
reactionRemovedListener.executeReactionCleared(cachedMessage);
} catch (AbstractoRunTimeException e) {
log.warn(String.format("Failed to execute reaction clear listener %s.", reactionRemovedListener.getClass().getName()), e);
}
}
@Override

View File

@@ -13,6 +13,7 @@ import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionRemove
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -103,7 +104,7 @@ public class ReactionRemovedListenerBean extends ListenerAdapter {
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executeIndividualReactionRemovedListener(@Nonnull GuildMessageReactionRemoveEvent event, CachedMessage cachedMessage, ServerUser serverUser, ReactionRemovedListener reactionRemovedListener) {
reactionRemovedListener.executeReactionRemoved(cachedMessage, event, serverUser);
}

View File

@@ -10,6 +10,7 @@ import net.dv8tion.jda.api.events.role.RoleDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
@@ -28,7 +29,7 @@ public class RoleListener extends ListenerAdapter {
private ServerManagementService serverManagementService;
@Override
@Transactional
@Transactional(isolation = Isolation.SERIALIZABLE)
public void onRoleCreate(@Nonnull RoleCreateEvent event) {
log.info("Creating role {} in server {}.", event.getRole().getId(), event.getGuild().getId());
AServer server = serverManagementService.loadOrCreate(event.getGuild().getIdLong());
@@ -36,7 +37,7 @@ public class RoleListener extends ListenerAdapter {
}
@Override
@Transactional
@Transactional(isolation = Isolation.SERIALIZABLE)
public void onRoleDelete(@Nonnull RoleDeleteEvent event) {
log.info("Marking role {} as deleted in server {}.", event.getRole().getId(), event.getGuild().getId());
AServer server = serverManagementService.loadOrCreate(event.getGuild().getIdLong());

View File

@@ -8,6 +8,7 @@ import net.dv8tion.jda.api.events.guild.GuildJoinEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -36,7 +37,7 @@ public class ServerJoinListener extends ListenerAdapter {
configListeners.forEach(serverConfigListener -> self.executingIndividualServerConfigListener(server, serverConfigListener));
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void executingIndividualServerConfigListener(AServer server, ServerConfigListener serverConfigListener) {
log.trace("Executing server config listener for server {}.", server.getId());
serverConfigListener.updateServerConfig(server);

View File

@@ -0,0 +1,130 @@
package dev.sheldan.abstracto.core.metrics;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
@Component
@Slf4j
public class OkHttpMetrics implements Interceptor {
@Autowired
private MetricService metricService;
public static final String MODERATION_PURGE_METRIC = "okhttp.request";
public static final String HTTP_CODE = "http.code";
private static final CounterMetric OKTTHP_200_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "200")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric OKTTHP_201_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "201")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric OKTTHP_204_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "204")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric OKTTHP_202_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "202")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric OKTTHP_401_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "401")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric OKTTHP_403_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "403")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric OKTTHP_404_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "404")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric OKTTHP_429_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "429")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final CounterMetric OKTTHP_500_RESPONSE =
CounterMetric
.builder()
.tagList(Arrays.asList(MetricTag.getTag(HTTP_CODE, "500")))
.name(MODERATION_PURGE_METRIC)
.build();
private static final HashMap<Integer, CounterMetric> METRICS = new HashMap<>();
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
incrementCounter(response.code());
return response;
}
private void incrementCounter(Integer code) {
if(METRICS.containsKey(code)) {
metricService.incrementCounter(METRICS.get(code));
} else {
log.warn("Unmapped response code from server {}.", code);
}
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(OKTTHP_200_RESPONSE, "Amount of HTTP 200 responses in okhttp");
metricService.registerCounter(OKTTHP_201_RESPONSE, "Amount of HTTP 201 responses in okhttp");
metricService.registerCounter(OKTTHP_202_RESPONSE, "Amount of HTTP 202 responses in okhttp");
metricService.registerCounter(OKTTHP_204_RESPONSE, "Amount of HTTP 204 responses in okhttp");
metricService.registerCounter(OKTTHP_401_RESPONSE, "Amount of HTTP 401 responses in okhttp");
metricService.registerCounter(OKTTHP_403_RESPONSE, "Amount of HTTP 403 responses in okhttp");
metricService.registerCounter(OKTTHP_404_RESPONSE, "Amount of HTTP 404 responses in okhttp");
metricService.registerCounter(OKTTHP_429_RESPONSE, "Amount of HTTP 429 responses in okhttp");
metricService.registerCounter(OKTTHP_500_RESPONSE, "Amount of HTTP 500 responses in okhttp");
METRICS.put(200, OKTTHP_200_RESPONSE);
METRICS.put(201, OKTTHP_201_RESPONSE);
METRICS.put(202, OKTTHP_202_RESPONSE);
METRICS.put(204, OKTTHP_204_RESPONSE);
METRICS.put(401, OKTTHP_401_RESPONSE);
METRICS.put(403, OKTTHP_403_RESPONSE);
METRICS.put(404, OKTTHP_404_RESPONSE);
METRICS.put(429, OKTTHP_429_RESPONSE);
METRICS.put(500, OKTTHP_500_RESPONSE);
}
}

View File

@@ -5,7 +5,10 @@ import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity

View File

@@ -1,24 +1,17 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.models.GuildChannelMember;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.metrics.OkHttpMetrics;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
import net.dv8tion.jda.internal.utils.IOUtil;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.security.auth.login.LoginException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static net.dv8tion.jda.api.requests.GatewayIntent.*;
@@ -28,6 +21,9 @@ public class BotServiceBean implements BotService {
private JDA instance;
@Autowired
private OkHttpMetrics okHttpMetrics;
@Override
public void login() throws LoginException {
JDABuilder builder = JDABuilder.create(System.getenv("TOKEN"), GatewayIntent.GUILD_MEMBERS, GUILD_VOICE_STATES,
@@ -35,7 +31,10 @@ public class BotServiceBean implements BotService {
GUILD_MESSAGE_REACTIONS, DIRECT_MESSAGE_REACTIONS, DIRECT_MESSAGES, GUILD_PRESENCES);
builder.setBulkDeleteSplittingEnabled(false);
builder.setMemberCachePolicy(MemberCachePolicy.ALL);
builder.setMemberCachePolicy(MemberCachePolicy.DEFAULT);
OkHttpClient.Builder defaultBuilder = IOUtil.newHttpClientBuilder();
defaultBuilder.addInterceptor(okHttpMetrics);
builder.setHttpClientBuilder(defaultBuilder);
this.instance = builder.build();
}
@@ -45,201 +44,5 @@ public class BotServiceBean implements BotService {
return instance;
}
@Override
public GuildChannelMember getServerChannelUser(Long serverId, Long channelId, Long userId) {
log.trace("Trying to retrieve member {}, channel {} in server {} from cache.", userId, channelId, serverId);
Guild guild = getGuildById(serverId);
Optional<TextChannel> textChannelOptional = this.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);
}
}
@Override
public CompletableFuture<GuildChannelMember> getServerChannelUserAsync(Long serverId, Long channelId, Long userId) {
log.trace("Trying to retrieve member {}, channel {} in server {} async.", userId, channelId, serverId);
CompletableFuture<Member> memberFuture = getMemberInServerAsync(serverId, userId);
Guild guild = getGuildById(serverId);
TextChannel textChannel = this.getTextChannelFromServer(guild, channelId);
return memberFuture.thenApply(member ->
GuildChannelMember.builder().guild(guild).textChannel(textChannel).member(member).build()
);
}
@Override
public Member getMemberInServer(Long serverId, Long memberId) {
log.trace("Retrieving member {} in server {} from cache.", memberId, serverId);
Guild guildById = instance.getGuildById(serverId);
if(guildById != null) {
return guildById.getMemberById(memberId);
} else {
throw new GuildNotFoundException(serverId);
}
}
@Override
public CompletableFuture<Member> getMemberInServerAsync(Long serverId, Long memberId) {
log.trace("Retrieving member {} in server {} from cache.", memberId, serverId);
Guild guildById = instance.getGuildById(serverId);
if(guildById != null) {
return guildById.retrieveMemberById(memberId).submit();
} else {
throw new GuildNotFoundException(serverId);
}
}
@Override
public CompletableFuture<Member> retrieveMemberInServer(ServerUser serverUser) {
return getMemberInServerAsync(serverUser.getServerId(), serverUser.getUserId());
}
@Override
public CompletableFuture<User> retrieveUserById(Long userId) {
return instance.retrieveUserById(userId).submit();
}
@Override
public boolean isUserInGuild(AUserInAServer aUserInAServer) {
Guild guildById = instance.getGuildById(aUserInAServer.getServerReference().getId());
if(guildById != null) {
return isUserInGuild(guildById, aUserInAServer);
} else {
throw new GuildNotFoundException(aUserInAServer.getServerReference().getId());
}
}
@Override
public boolean isUserInGuild(Guild guild, AUserInAServer aUserInAServer) {
return guild.getMemberById(aUserInAServer.getUserReference().getId()) != null;
}
@Override
public Member getMemberInServer(AUserInAServer aUserInAServer) {
return getMemberInServer(aUserInAServer.getServerReference().getId(), aUserInAServer.getUserReference().getId());
}
@Override
public CompletableFuture<Member> getMemberInServerAsync(AUserInAServer aUserInAServer) {
return getMemberInServerAsync(aUserInAServer.getServerReference().getId(), aUserInAServer.getUserReference().getId());
}
@Override
public Member getMemberInServer(AServer server, AUser member) {
return getMemberInServer(server.getId(), member.getId());
}
@Override
public CompletableFuture<Void> deleteMessage(Long serverId, Long channelId, Long messageId) {
Optional<TextChannel> textChannelOptional = getTextChannelFromServerOptional(serverId, channelId);
if(textChannelOptional.isPresent()) {
TextChannel textChannel = textChannelOptional.get();
return textChannel.deleteMessageById(messageId).submit();
} else {
log.warn("Could not find channel {} in guild {} to delete message {} in.", channelId, serverId, messageId);
}
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Void> deleteMessage(Long channelId, Long messageId) {
TextChannel textChannel = getInstance().getTextChannelById(channelId);
if(textChannel != null) {
return textChannel.deleteMessageById(messageId).submit();
} else {
log.warn("Could not find channel {} to delete message {} in.", channelId, messageId);
}
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Member> forceReloadMember(Member member) {
return member.getGuild().retrieveMember(member.getUser()).submit();
}
@Override
public Optional<Emote> getEmote(Long serverId, AEmote emote) {
if(Boolean.FALSE.equals(emote.getCustom())) {
return Optional.empty();
}
Optional<Guild> guildById = getGuildByIdOptional(serverId);
if(guildById.isPresent()) {
Guild guild = guildById.get();
Emote emoteById = guild.getEmoteById(emote.getEmoteId());
return Optional.ofNullable(emoteById);
}
throw new GuildNotFoundException(serverId);
}
@Override
public Optional<Emote> getEmote(AEmote emote) {
if(Boolean.FALSE.equals(emote.getCustom())) {
return Optional.empty();
}
return Optional.ofNullable(instance.getEmoteById(emote.getEmoteId()));
}
@Override
public Optional<TextChannel> getTextChannelFromServerOptional(Guild guild, Long textChannelId) {
return Optional.ofNullable(guild.getTextChannelById(textChannelId));
}
@Override
public TextChannel getTextChannelFromServer(Guild guild, Long textChannelId) {
return getTextChannelFromServerOptional(guild, textChannelId).orElseThrow(() -> new ChannelNotInGuildException(textChannelId));
}
@Override
public TextChannel getTextChannelFromServerNullable(Guild guild, Long textChannelId) {
return getTextChannelFromServerOptional(guild, textChannelId).orElse(null);
}
@Override
public Optional<TextChannel> getTextChannelFromServerOptional(Long serverId, Long textChannelId) {
Optional<Guild> guildOptional = getGuildByIdOptional(serverId);
if(guildOptional.isPresent()) {
Guild guild = guildOptional.get();
return Optional.ofNullable(guild.getTextChannelById(textChannelId));
}
throw new GuildNotFoundException(serverId);
}
@Override
public TextChannel getTextChannelFromServer(Long serverId, Long textChannelId) {
return getTextChannelFromServerOptional(serverId, textChannelId).orElseThrow(() -> new ChannelNotInGuildException(textChannelId));
}
@Override
public Optional<Guild> getGuildByIdOptional(Long serverId) {
return Optional.ofNullable(instance.getGuildById(serverId));
}
@Override
public Guild getGuildById(Long serverId) {
Guild guildById = instance.getGuildById(serverId);
if(guildById == null) {
throw new GuildNotFoundException(serverId);
}
return guildById;
}
@Override
public CompletableFuture<Guild> retrieveGuildById(Long serverId) {
return null;
}
@Override
public Member getBotInGuild(AServer server) {
Guild guild = getGuildById(server.getId());
return guild.getSelfMember();
}
@Override
public CompletableFuture<User> getUserViaId(Long userId) {
return instance.retrieveUserById(userId).submit();
}
}

View File

@@ -3,34 +3,80 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.CategoryNotFoundException;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.requests.restaction.MessageAction;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.config.MetricConstants.DISCORD_API_INTERACTION_METRIC;
import static dev.sheldan.abstracto.core.config.MetricConstants.INTERACTION_TYPE;
import static dev.sheldan.abstracto.core.service.MessageServiceBean.*;
@Slf4j
@Component
public class ChannelServiceBean implements ChannelService {
@Autowired
private GuildService guildService;
@Autowired
private BotService botService;
@Autowired
private TemplateService templateService;
@Autowired
private MetricService metricService;
public static final CounterMetric CHANNEL_CREATE_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "channel.create")))
.build();
public static final CounterMetric CHANNEL_DELETE_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "channel.delete")))
.build();
public static final CounterMetric CHANNEL_MESSAGE_BULK_DELETE_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "channel.message.bulk.delete")))
.build();
public static final CounterMetric CHANNEL_RETRIEVE_HISTORY = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "channel.load.history")))
.build();
public static final CounterMetric CHANNEL_CHANGE_SLOW_MODE = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "channel.slowmode")))
.build();
@Override
public void sendTextToAChannelNotAsync(String text, AChannel channel) {
sendTextToAChannel(text, channel);
@@ -60,7 +106,7 @@ public class ChannelServiceBean implements ChannelService {
@Override
public CompletableFuture<Message> sendMessageToAChannel(Message message, AChannel channel) {
Optional<TextChannel> textChannelOpt = botService.getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
Optional<TextChannel> textChannelOpt = getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
if(textChannelOpt.isPresent()) {
TextChannel textChannel = textChannelOpt.get();
return sendMessageToChannel(message, textChannel);
@@ -72,12 +118,14 @@ public class ChannelServiceBean implements ChannelService {
public CompletableFuture<Message> sendMessageToChannel(Message message, MessageChannel channel) {
log.trace("Sending message {} from channel {} and server {} to channel {}.",
message.getId(), message.getChannel().getId(), message.getGuild().getId(), channel.getId());
metricService.incrementCounter(MESSAGE_SEND_METRIC);
return channel.sendMessage(message).submit();
}
@Override
public CompletableFuture<Message> sendTextToChannel(String text, MessageChannel channel) {
log.trace("Sending text to channel {}.", channel.getId());
metricService.incrementCounter(MESSAGE_SEND_METRIC);
return channel.sendMessage(text).submit();
}
@@ -106,12 +154,13 @@ public class ChannelServiceBean implements ChannelService {
@Override
public MessageAction sendEmbedToChannelInComplete(MessageEmbed embed, MessageChannel channel) {
metricService.incrementCounter(MESSAGE_SEND_METRIC);
return channel.sendMessage(embed);
}
@Override
public List<CompletableFuture<Message>> sendMessageToSendToAChannel(MessageToSend messageToSend, AChannel channel) {
Optional<TextChannel> textChannelFromServer = botService.getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
Optional<TextChannel> textChannelFromServer = getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
if(textChannelFromServer.isPresent()) {
return sendMessageToSendToChannel(messageToSend, textChannelFromServer.get());
}
@@ -123,6 +172,18 @@ public class ChannelServiceBean implements ChannelService {
return sendEmbedToAChannel(messageToSend.getEmbeds().get(embedIndex), channel);
}
@Override
public CompletableFuture<Message> retrieveMessageInChannel(Long serverId, Long channelId, Long messageId) {
TextChannel channel = getTextChannelFromServer(serverId, channelId);
return retrieveMessageInChannel(channel, messageId);
}
@Override
public CompletableFuture<Message> retrieveMessageInChannel(MessageChannel channel, Long messageId) {
metricService.incrementCounter(MESSAGE_LOAD_METRIC);
return channel.retrieveMessageById(messageId).submit();
}
@Override
public List<CompletableFuture<Message>> sendMessageToSendToChannel(MessageToSend messageToSend, MessageChannel textChannel) {
String messageText = messageToSend.getMessage();
@@ -130,10 +191,12 @@ public class ChannelServiceBean implements ChannelService {
MessageAction firstMessageAction = null;
List<MessageAction> allMessageActions = new ArrayList<>();
if(!StringUtils.isBlank(messageText)) {
metricService.incrementCounter(MESSAGE_SEND_METRIC);
firstMessageAction = textChannel.sendMessage(messageText);
}
if(!messageToSend.getEmbeds().isEmpty()) {
if(firstMessageAction != null) {
metricService.incrementCounter(MESSAGE_SEND_METRIC);
firstMessageAction.embed(messageToSend.getEmbeds().get(0));
} else {
firstMessageAction = textChannel.sendMessage(messageToSend.getEmbeds().get(0));
@@ -142,6 +205,8 @@ public class ChannelServiceBean implements ChannelService {
}
if(messageToSend.hasFileToSend()) {
if(firstMessageAction != null) {
// in case there has not been a message, we need to increment it
metricService.incrementCounter(MESSAGE_SEND_METRIC);
firstMessageAction.addFile(messageToSend.getFileToSend());
} else {
firstMessageAction = textChannel.sendFile(messageToSend.getFileToSend());
@@ -154,14 +219,9 @@ public class ChannelServiceBean implements ChannelService {
return futures;
}
@Override
public Optional<TextChannel> getTextChannelInGuild(Long serverId, Long channelId) {
return botService.getTextChannelFromServerOptional(serverId, channelId);
}
@Override
public void editMessageInAChannel(MessageToSend messageToSend, AChannel channel, Long messageId) {
Optional<TextChannel> textChannelFromServer = botService.getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
Optional<TextChannel> textChannelFromServer = getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
if(textChannelFromServer.isPresent()) {
TextChannel textChannel = textChannelFromServer.get();
editMessageInAChannel(messageToSend, textChannel, messageId);
@@ -193,26 +253,35 @@ public class ChannelServiceBean implements ChannelService {
throw new IllegalArgumentException("Message to send did not contain anything to send.");
}
}
metricService.incrementCounter(MESSAGE_EDIT_METRIC);
return messageAction.submit();
}
@Override
public CompletableFuture<Message> editEmbedMessageInAChannel(MessageEmbed embedToSend, MessageChannel channel, Long messageId) {
metricService.incrementCounter(MESSAGE_EDIT_METRIC);
return channel.editMessageById(messageId, embedToSend).submit();
}
@Override
public CompletableFuture<Message> editTextMessageInAChannel(String text, MessageChannel channel, Long messageId) {
metricService.incrementCounter(MESSAGE_EDIT_METRIC);
return channel.editMessageById(messageId, text).submit();
}
@Override
public CompletableFuture<Message> editTextMessageInAChannel(String text, MessageEmbed messageEmbed, MessageChannel channel, Long messageId) {
metricService.incrementCounter(MESSAGE_EDIT_METRIC);
return channel.editMessageById(messageId, text).embed(messageEmbed).submit();
}
@Override
public List<CompletableFuture<Message>> editMessagesInAChannelFuture(MessageToSend messageToSend, MessageChannel channel, List<Long> messageIds) {
List<CompletableFuture<Message>> futures = new ArrayList<>();
futures.add(editMessageInAChannelFuture(messageToSend, channel ,messageIds.get(0)));
for (int i = 1; i < messageIds.size(); i++) {
Long messageIdToUpdate = messageIds.get(i);
futures.add(channel.editMessageById(messageIdToUpdate, messageToSend.getEmbeds().get(i)).submit());
futures.add(editEmbedMessageInAChannel(messageToSend.getEmbeds().get(i), channel, messageIdToUpdate));
}
return futures;
}
@@ -224,11 +293,11 @@ public class ChannelServiceBean implements ChannelService {
@Override
public CompletableFuture<Message> removeFieldFromMessage(MessageChannel channel, Long messageId, Integer index, Integer embedIndex) {
return channel.retrieveMessageById(messageId).submit().thenCompose(message -> {
return retrieveMessageInChannel(channel, messageId).thenCompose(message -> {
EmbedBuilder embedBuilder = new EmbedBuilder(message.getEmbeds().get(embedIndex));
embedBuilder.getFields().remove(index.intValue());
log.trace("Removing field with index {} from message {}.", index, messageId);
return channel.editMessageById(messageId, embedBuilder.build()).submit();
return editEmbedMessageInAChannel(embedBuilder.build(), channel, messageId);
});
}
@@ -242,6 +311,7 @@ public class ChannelServiceBean implements ChannelService {
TextChannel textChannelById = botService.getInstance().getTextChannelById(channelId);
if(textChannelById != null) {
log.info("Deleting channel {} on server {}.", channelId, serverId);
metricService.incrementCounter(CHANNEL_DELETE_METRIC);
return textChannelById.delete().submit();
}
throw new ChannelNotInGuildException(channelId);
@@ -260,13 +330,20 @@ public class ChannelServiceBean implements ChannelService {
return sendTextToChannel(text, channel);
}
@Override
public RestAction<Void> deleteMessagesInChannel(TextChannel textChannel, List<Message> messages) {
metricService.incrementCounter(CHANNEL_MESSAGE_BULK_DELETE_METRIC);
return textChannel.deleteMessages(messages);
}
@Override
public CompletableFuture<TextChannel> createTextChannel(String name, AServer server, Long categoryId) {
Optional<Guild> guildById = botService.getGuildByIdOptional(server.getId());
Optional<Guild> guildById = guildService.getGuildByIdOptional(server.getId());
if(guildById.isPresent()) {
Guild guild = guildById.get();
Category categoryById = guild.getCategoryById(categoryId);
if(categoryById != null) {
metricService.incrementCounter(CHANNEL_CREATE_METRIC);
log.info("Creating channel on server {} in category {}.", server.getId(), categoryById);
return categoryById.createTextChannel(name).submit();
}
@@ -277,7 +354,7 @@ public class ChannelServiceBean implements ChannelService {
@Override
public Optional<TextChannel> getChannelFromAChannel(AChannel channel) {
return botService.getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
return getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId());
}
@Override
@@ -297,12 +374,58 @@ public class ChannelServiceBean implements ChannelService {
@Override
public CompletableFuture<Message> sendSimpleTemplateToChannel(Long serverId, Long channelId, String template) {
TextChannel textChannel = getTextChannel(serverId, channelId);
TextChannel textChannel = getTextChannelFromServer(serverId, channelId);
return sendTextTemplateInChannel(template, new Object(), textChannel);
}
@Override
public TextChannel getTextChannel(Long serverId, Long channelId) {
return botService.getTextChannelFromServer(serverId, channelId);
public CompletableFuture<MessageHistory> getHistoryOfChannel(TextChannel 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));
}
@Override
public TextChannel getTextChannelFromServer(Guild guild, Long textChannelId) {
return getTextChannelFromServerOptional(guild, textChannelId).orElseThrow(() -> new ChannelNotInGuildException(textChannelId));
}
@Override
public TextChannel getTextChannelFromServerNullable(Guild guild, Long textChannelId) {
return getTextChannelFromServerOptional(guild, textChannelId).orElse(null);
}
@Override
public Optional<TextChannel> getTextChannelFromServerOptional(Long serverId, Long textChannelId) {
Optional<Guild> guildOptional = guildService.getGuildByIdOptional(serverId);
if(guildOptional.isPresent()) {
Guild guild = guildOptional.get();
return Optional.ofNullable(guild.getTextChannelById(textChannelId));
}
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) {
metricService.incrementCounter(CHANNEL_CHANGE_SLOW_MODE);
return textChannel.getManager().setSlowmode(seconds).submit();
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(CHANNEL_CREATE_METRIC, "Amount of channels created");
metricService.registerCounter(CHANNEL_DELETE_METRIC, "Amount of channels deleted");
metricService.registerCounter(CHANNEL_MESSAGE_BULK_DELETE_METRIC, "Amount of channel bulk message delete");
metricService.registerCounter(CHANNEL_RETRIEVE_HISTORY, "Amount of retrievals of message history in a channel");
metricService.registerCounter(CHANNEL_CHANGE_SLOW_MODE, "Amount of times slowmode has been changed");
}
}

View File

@@ -1,8 +1,8 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.ConfigurationKeyNotFoundException;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
import dev.sheldan.abstracto.core.models.database.AConfig;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.EmoteNotDefinedException;
import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.models.cache.CachedEmote;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.cache.CachedReactions;
@@ -26,6 +27,9 @@ public class EmoteServiceBean implements EmoteService {
@Autowired
private BotService botService;
@Autowired
private GuildService guildService;
@Autowired
private EmoteManagementService emoteManagementService;
@@ -58,7 +62,7 @@ public class EmoteServiceBean implements EmoteService {
@Override
public String getEmoteAsMention(AEmote emote, Long serverId, String defaultText) {
if(emote != null && emote.getCustom()) {
Optional<Emote> emoteOptional = botService.getEmote(serverId, emote);
Optional<Emote> emoteOptional = getEmote(serverId, emote);
if (emoteOptional.isPresent()) {
return emoteOptional.get().getAsMention();
} else {
@@ -183,9 +187,30 @@ public class EmoteServiceBean implements EmoteService {
if(!cachedEmote.getCustom()) {
throw new IllegalArgumentException("Given Emote was not a custom emote.");
}
return botService.retrieveGuildById(cachedEmote.getServerId()).thenCompose(guild ->
guild.retrieveEmoteById(cachedEmote.getEmoteId()).submit().thenApply(listedEmote -> listedEmote)
);
Guild guild = guildService.getGuildById(cachedEmote.getServerId());
return guild.retrieveEmoteById(cachedEmote.getEmoteId()).submit().thenApply(listedEmote -> listedEmote);
}
@Override
public Optional<Emote> getEmote(Long serverId, AEmote emote) {
if(Boolean.FALSE.equals(emote.getCustom())) {
return Optional.empty();
}
Optional<Guild> guildById = guildService.getGuildByIdOptional(serverId);
if(guildById.isPresent()) {
Guild guild = guildById.get();
Emote emoteById = guild.getEmoteById(emote.getEmoteId());
return Optional.ofNullable(emoteById);
}
throw new GuildNotFoundException(serverId);
}
@Override
public Optional<Emote> getEmote(AEmote emote) {
if(Boolean.FALSE.equals(emote.getCustom())) {
return Optional.empty();
}
return Optional.ofNullable(botService.getInstance().getEmoteById(emote.getEmoteId()));
}
}

View File

@@ -1,12 +1,12 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.FeatureAware;
import dev.sheldan.abstracto.core.command.exception.IncorrectFeatureModeException;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.exception.FeatureModeNotFoundException;
import dev.sheldan.abstracto.core.FeatureAware;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.models.template.commands.FeatureModeDisplay;
import dev.sheldan.abstracto.core.service.management.DefaultFeatureModeManagement;

View File

@@ -1,9 +1,8 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.command.service.ExceptionService;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.interactive.*;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.template.commands.SetupCompletedNotificationModel;
@@ -48,6 +47,9 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
@Autowired
private TemplateService templateService;
@Autowired
private MemberService memberService;
@Autowired
private BotService botService;
@@ -58,7 +60,7 @@ 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.getTextChannelInGuild(user.getGuildId(), user.getChannelId());
Optional<TextChannel> textChannelInGuild = channelService.getTextChannelFromServerOptional(user.getGuildId(), user.getChannelId());
if(textChannelInGuild.isPresent()) {
List<String> requiredSystemConfigKeys = featureConfig.getRequiredSystemConfigKeys();
List<SetupExecution> steps = new ArrayList<>();
@@ -142,8 +144,8 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
@Transactional
public void showExceptionMessage(Throwable throwable, AServerChannelUserId aServerChannelUserId) {
Optional<TextChannel> channelOptional = botService.getTextChannelFromServerOptional(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
botService.getMemberInServerAsync(aServerChannelUserId.getGuildId(), aServerChannelUserId.getUserId()).thenAccept(member ->
Optional<TextChannel> channelOptional = channelService.getTextChannelFromServerOptional(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
memberService.getMemberInServerAsync(aServerChannelUserId.getGuildId(), aServerChannelUserId.getUserId()).thenAccept(member ->
channelOptional.ifPresent(textChannel -> exceptionService.reportExceptionToChannel(throwable, textChannel, 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);
@@ -174,7 +176,7 @@ public class FeatureSetupServiceBean implements FeatureSetupService {
.featureConfig(featureConfig)
.build();
String text = templateService.renderTemplate(templateName, model);
Optional<TextChannel> textChannel = channelService.getTextChannelInGuild(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
Optional<TextChannel> textChannel = channelService.getTextChannelFromServerOptional(aServerChannelUserId.getGuildId(), aServerChannelUserId.getChannelId());
textChannel.ifPresent(channel -> channelService.sendTextToChannel(text, channel));
}

View File

@@ -37,6 +37,9 @@ public class FeatureValidationServiceBean implements FeatureValidatorService {
@Autowired
private BotService botService;
@Autowired
private MemberService memberService;
@Override
public void checkPostTarget(PostTargetEnum name, AServer server, FeatureValidationResult featureValidationResult) {
if(!postTargetManagement.postTargetExists(name.getKey(), server)) {
@@ -73,7 +76,7 @@ public class FeatureValidationServiceBean implements FeatureValidatorService {
if(emoteById == null) {
rejectEmote(emoteKey, featureValidationResult);
} else {
Member selfMember = botService.getMemberInServer(server.getId(), botService.getInstance().getSelfUser().getIdLong());
Member selfMember = memberService.getMemberInServer(server.getId(), botService.getInstance().getSelfUser().getIdLong());
if(!emoteById.canInteract(selfMember)) {
rejectEmote(emoteKey, featureValidationResult);
}

View File

@@ -0,0 +1,29 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class GuildServiceBean implements GuildService {
@Autowired
private BotService botService;
@Override
public Optional<Guild> getGuildByIdOptional(Long serverId) {
return Optional.ofNullable(botService.getInstance().getGuildById(serverId));
}
@Override
public Guild getGuildById(Long serverId) {
Guild guildById = botService.getInstance().getGuildById(serverId);
if(guildById == null) {
throw new GuildNotFoundException(serverId);
}
return guildById;
}
}

View File

@@ -0,0 +1,137 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.models.GuildChannelMember;
import dev.sheldan.abstracto.core.models.ServerUser;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class MemberServiceBean implements MemberService {
@Autowired
private GuildService guildService;
@Autowired
private BotService botService;
@Autowired
private ChannelService channelService;
@Override
public GuildChannelMember getServerChannelUser(Long serverId, Long channelId, Long userId) {
log.trace("Trying to retrieve member {}, channel {} in server {} from cache.", userId, channelId, serverId);
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);
}
}
@Override
public CompletableFuture<GuildChannelMember> getServerChannelUserAsync(Long serverId, Long channelId, Long userId) {
log.trace("Trying to retrieve member {}, channel {} in server {} async.", userId, channelId, serverId);
CompletableFuture<Member> memberFuture = getMemberInServerAsync(serverId, userId);
Guild guild = guildService.getGuildById(serverId);
TextChannel textChannel = channelService.getTextChannelFromServer(guild, channelId);
return memberFuture.thenApply(member ->
GuildChannelMember.builder().guild(guild).textChannel(textChannel).member(member).build()
);
}
@Override
public Member getMemberInServer(Long serverId, Long memberId) {
log.trace("Retrieving member {} in server {} from cache.", memberId, serverId);
Guild guildById = guildService.getGuildById(serverId);
if(guildById != null) {
return guildById.getMemberById(memberId);
} else {
throw new GuildNotFoundException(serverId);
}
}
@Override
public CompletableFuture<Member> getMemberInServerAsync(Long serverId, Long memberId) {
log.trace("Retrieving member {} in server {} from cache.", memberId, serverId);
Guild guildById = guildService.getGuildById(serverId);
if(guildById != null) {
return guildById.retrieveMemberById(memberId).submit();
} else {
throw new GuildNotFoundException(serverId);
}
}
@Override
public CompletableFuture<Member> retrieveMemberInServer(ServerUser serverUser) {
return getMemberInServerAsync(serverUser.getServerId(), serverUser.getUserId());
}
@Override
public CompletableFuture<User> retrieveUserById(Long userId) {
return botService.getInstance().retrieveUserById(userId).submit();
}
@Override
public boolean isUserInGuild(AUserInAServer aUserInAServer) {
Guild guildById = guildService.getGuildById(aUserInAServer.getServerReference().getId());
if(guildById != null) {
return isUserInGuild(guildById, aUserInAServer);
} else {
throw new GuildNotFoundException(aUserInAServer.getServerReference().getId());
}
}
@Override
public boolean isUserInGuild(Guild guild, AUserInAServer aUserInAServer) {
return guild.getMemberById(aUserInAServer.getUserReference().getId()) != null;
}
@Override
public Member getMemberInServer(AUserInAServer aUserInAServer) {
return getMemberInServer(aUserInAServer.getServerReference().getId(), aUserInAServer.getUserReference().getId());
}
@Override
public CompletableFuture<Member> getMemberInServerAsync(AUserInAServer aUserInAServer) {
return getMemberInServerAsync(aUserInAServer.getServerReference().getId(), aUserInAServer.getUserReference().getId());
}
@Override
public Member getMemberInServer(AServer server, AUser member) {
return getMemberInServer(server.getId(), member.getId());
}
@Override
public CompletableFuture<Member> forceReloadMember(Member member) {
return member.getGuild().retrieveMember(member.getUser()).submit();
}
@Override
public Member getBotInGuild(AServer server) {
Guild guild = guildService.getGuildById(server.getId());
return guild.getSelfMember();
}
@Override
public CompletableFuture<User> getUserViaId(Long userId) {
return botService.getInstance().retrieveUserById(userId).submit();
}
}

View File

@@ -4,7 +4,9 @@ import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
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.*;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CachePut;
@@ -21,7 +23,10 @@ import java.util.concurrent.CompletableFuture;
public class MessageCacheBean implements MessageCache {
@Autowired
private BotService botService;
private GuildService guildService;
@Autowired
private ChannelService channelService;
@Autowired
private EmoteService emoteService;
@@ -66,13 +71,13 @@ public class MessageCacheBean implements MessageCache {
@Override
public CompletableFuture<CachedMessage> loadMessage(Long guildId, Long textChannelId, Long messageId) {
CompletableFuture<CachedMessage> future = new CompletableFuture<>();
Optional<Guild> guildOptional = botService.getGuildByIdOptional(guildId);
Optional<Guild> guildOptional = guildService.getGuildByIdOptional(guildId);
if(guildOptional.isPresent()) {
Optional<TextChannel> textChannelByIdOptional = botService.getTextChannelFromServerOptional(guildOptional.get(), textChannelId);
Optional<TextChannel> textChannelByIdOptional = channelService.getTextChannelFromServerOptional(guildOptional.get(), textChannelId);
if(textChannelByIdOptional.isPresent()) {
TextChannel textChannel = textChannelByIdOptional.get();
textChannel.retrieveMessageById(messageId).queue(message ->
channelService.retrieveMessageInChannel(textChannel, messageId)
.thenAccept(message ->
cacheEntityService.buildCachedMessageFromMessage(message)
.thenAccept(future::complete)
.exceptionally(throwable -> {
@@ -80,8 +85,7 @@ public class MessageCacheBean implements MessageCache {
future.completeExceptionally(throwable);
return null;
})
);
);
} else {
log.error("Not able to load message {} in channel {} in guild {}. Text channel not found.", messageId, textChannelId, guildId);
future.completeExceptionally(new ChannelNotInGuildException(textChannelId));

View File

@@ -1,253 +1,73 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.ConfiguredEmoteNotUsableException;
import dev.sheldan.abstracto.core.exception.EmoteNotInServerException;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.EmoteManagementService;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.MessageAction;
import org.jetbrains.annotations.NotNull;
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.List;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.config.MetricConstants.DISCORD_API_INTERACTION_METRIC;
import static dev.sheldan.abstracto.core.config.MetricConstants.INTERACTION_TYPE;
@Component
@Slf4j
public class MessageServiceBean implements MessageService {
@Autowired
private BotService botService;
@Autowired
private EmoteManagementService emoteManagementService;
@Autowired
private EmoteService emoteService;
@Autowired
private ChannelService channelService;
@Autowired
private MessageServiceBean self;
@Autowired
private TemplateService templateService;
@Override
public void addReactionToMessage(String emoteKey, Long serverId, Message message) {
addReactionToMessageWithFuture(emoteKey, serverId, message);
}
@Autowired
private MemberService memberService;
@Override
public void addDefaultReactionToMessage(String unicode, Message message) {
addDefaultReactionToMessageAsync(unicode, message);
}
@Autowired
private MetricService metricService;
@Override
public CompletableFuture<Void> addDefaultReactionToMessageAsync(String unicode, Message message) {
return message.addReaction(unicode).submit();
}
public static final CounterMetric MESSAGE_SEND_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "message.send")))
.build();
@Override
public CompletableFuture<Void> addDefaultReactionToMessageAsync(String unicode, Long serverId, Long channelId, Long messageId) {
TextChannel channel = botService.getTextChannelFromServer(serverId, channelId);
return channel.retrieveMessageById(messageId).submit()
.thenCompose(message -> self.addDefaultReactionToMessageAsync(unicode, message));
}
public static final CounterMetric MESSAGE_EDIT_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "message.edit")))
.build();
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(String emoteKey, Long serverId, Message message) {
Guild guild = botService.getGuildById(serverId);
return addReactionToMessageWithFuture(emoteKey, guild, message);
}
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(String emoteKey, Guild guild, Message message) {
AEmote emote = emoteService.getEmoteOrDefaultEmote(emoteKey, guild.getIdLong());
return addReactionToMessageWithFuture(emote, guild, message);
}
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(AEmote emote, Long serverId, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
return addReactionToMessageWithFuture(emote.getEmoteId(), serverId, message);
} else {
return message.addReaction(emote.getEmoteKey()).submit();
}
}
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(AEmote emote, Guild guild, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById != null) {
log.trace("Adding custom emote {} as reaction to message {}.", emoteById.getId(), message.getId());
return message.addReaction(emoteById).submit();
} else {
log.error("Emote with key {} and id {} for guild {} was not found.", emote.getName() , emote.getEmoteId(), guild.getId());
throw new ConfiguredEmoteNotUsableException(emote);
}
} else {
log.trace("Adding default emote {} as reaction to message {}.", emote.getEmoteKey(), message.getId());
return message.addReaction(emote.getEmoteKey()).submit();
}
}
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(Long emoteId, Long serverId, Message message) {
Emote emoteById = botService.getInstance().getEmoteById(emoteId);
if(emoteById == null) {
throw new EmoteNotInServerException(emoteId);
}
return message.addReaction(emoteById).submit();
}
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(String emoteKey, Long serverId, Long channelId, Long messageId) {
TextChannel channel = botService.getTextChannelFromServer(serverId, channelId);
return channel.retrieveMessageById(messageId).submit()
.thenCompose(message -> self.addReactionToMessageWithFuture(emoteKey, serverId, message));
}
@Override
public CompletableFuture<Void> removeReactionFromMessageWithFuture(AEmote emote, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById == null) {
throw new EmoteNotInServerException(emote.getEmoteId());
}
log.trace("Removing single custom reaction for emote {} on message {}.", emoteById.getId(), message.getId());
return message.removeReaction(emoteById).submit();
} else {
log.trace("Removing single default emote {} reaction from message {}.", emote.getEmoteKey(), message.getId());
return message.removeReaction(emote.getEmoteKey()).submit();
}
}
@Override
public CompletableFuture<Void> clearReactionFromMessageWithFuture(AEmote emote, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById == null) {
throw new EmoteNotInServerException(emote.getEmoteId());
}
log.trace("Clearing reactions for custom emote {} on message {}.", emoteById.getId(), message.getId());
return message.clearReactions(emoteById).submit();
} else {
log.trace("Clearing reactions for default emote {} on message {}.", emote.getEmoteKey(), message.getId());
return message.clearReactions(emote.getEmoteKey()).submit();
}
}
@Override
public CompletableFuture<Void> removeReactionFromMessageWithFuture(Integer emoteId, Message message) {
AEmote emote = emoteManagementService.loadEmote(emoteId);
return removeReactionFromMessageWithFuture(emote, message);
}
@Override
public CompletableFuture<Void> clearReactionFromMessageWithFuture(Integer emoteId, Message message) {
AEmote emote = emoteManagementService.loadEmote(emoteId);
return clearReactionFromMessageWithFuture(emote, message);
}
@Override
public CompletableFuture<Void> removeReactionFromMessageWithFuture(AEmote emote, Long serverId, Long channelId, Long messageId) {
TextChannel channel = botService.getTextChannelFromServer(serverId, channelId);
Integer emoteId = emote.getId();
return channel.retrieveMessageById(messageId).submit()
.thenCompose(message -> self.removeReactionFromMessageWithFuture(emoteId, message));
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(AEmote emote, Long serverId, Long channelId, Long messageId, Long userId) {
Guild guild = botService.getGuildById(serverId);
Integer emoteId = emote.getId();
TextChannel textChannel = botService.getTextChannelFromServer(serverId, channelId);
CompletableFuture<Member> memberFuture = guild.retrieveMemberById(userId).submit();
CompletableFuture<Message> messageFuture = textChannel.retrieveMessageById(messageId).submit();
return CompletableFuture.allOf(memberFuture, messageFuture).thenCompose(aVoid ->
memberFuture.thenCompose(member ->
self.removeReactionOfUserFromMessageWithFuture(emoteId, messageFuture.join(), memberFuture.join())
)
);
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(AEmote emote, Long serverId, Long channelId, Long messageId, Member member) {
TextChannel channel = botService.getTextChannelFromServer(serverId, channelId);
Integer emoteId = emote.getId();
return channel.retrieveMessageById(messageId).submit()
.thenCompose(message -> self.removeReactionOfUserFromMessageWithFuture(emoteId, message, member));
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(AEmote emote, Message message, Member member) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById == null) {
throw new EmoteNotInServerException(emote.getEmoteId());
}
log.trace("Removing reaction for custom emote {} from user {} on message {}.", emoteById.getId(), member.getId(), member.getId());
return message.removeReaction(emoteById, member.getUser()).submit();
} else {
log.trace("Removing reaction for default emote {} from user {} on message {}.", emote.getEmoteKey(), member.getId(), member.getId());
return message.removeReaction(emote.getEmoteKey(), member.getUser()).submit();
}
}
@Override
@Transactional
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(Integer emoteId, Message message, Member member) {
AEmote emote = emoteManagementService.loadEmote(emoteId);
return removeReactionOfUserFromMessageWithFuture(emote, message, member);
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(AEmote emote, Message message, Long userId) {
Integer emoteId = emote.getId();
return message.getGuild().retrieveMemberById(userId).submit().thenCompose(member ->
self.removeReactionOfUserFromMessageWithFuture(emoteId, message, member)
);
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(Integer emoteId, Message message, Long userId) {
return message.getGuild().retrieveMemberById(userId).submit().thenCompose(member ->
self.removeReactionOfUserFromMessageWithFuture(emoteId, message, member)
);
}
@Override
public CompletableFuture<Void> clearReactionFromMessageWithFuture(AEmote emote, Long serverId, Long channelId, Long messageId) {
TextChannel channel = botService.getTextChannelFromServer(serverId, channelId);
Integer emoteId = emote.getId();
return channel.retrieveMessageById(messageId).submit()
.thenCompose(message1 -> clearReactionFromMessageWithFuture(emoteId, message1));
}
@Override
public List<CompletableFuture<Void>> addReactionsToMessageWithFuture(List<String> emoteKeys, Long serverId, Message message) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
emoteKeys.forEach(s -> futures.add(addReactionToMessageWithFuture(s, serverId, message)));
return futures;
}
public static final CounterMetric MESSAGE_LOAD_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "message.load")))
.build();
public static final CounterMetric MESSAGE_DELETE_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "message.delete")))
.build();
@Override
public CompletableFuture<Void> deleteMessageInChannelInServer(Long serverId, Long channelId, Long messageId) {
return botService.deleteMessage(serverId, channelId, messageId);
metricService.incrementCounter(MESSAGE_DELETE_METRIC);
return channelService.getTextChannelFromServer(serverId, channelId).deleteMessageById(messageId).submit();
}
@Override
@@ -277,7 +97,7 @@ public class MessageServiceBean implements MessageService {
@Override
public CompletableFuture<Message> sendMessageToUser(AUserInAServer userInAServer, String text) {
return botService.getMemberInServerAsync(userInAServer).thenCompose(member ->
return memberService.getMemberInServerAsync(userInAServer).thenCompose(member ->
sendMessageToUser(member.getUser(), text)
);
}
@@ -285,7 +105,7 @@ public class MessageServiceBean implements MessageService {
@Override
public CompletableFuture<Message> sendSimpleTemplateToUser(Long userId, String templateKey) {
String text = templateService.renderSimpleTemplate(templateKey);
return botService.getUserViaId(userId)
return memberService.getUserViaId(userId)
.thenCompose(this::openPrivateChannelForUser)
.thenCompose(o -> channelService.sendTextToChannel(text, o));
}
@@ -343,6 +163,32 @@ public class MessageServiceBean implements MessageService {
@Override
public CompletableFuture<Message> loadMessage(Long serverId, Long channelId, Long messageId) {
return channelService.getTextChannel(serverId, channelId).retrieveMessageById(messageId).submit();
return channelService.retrieveMessageInChannel(serverId, channelId, messageId);
}
@Override
public MessageAction editMessage(Message message, MessageEmbed messageEmbed) {
metricService.incrementCounter(MESSAGE_EDIT_METRIC);
return message.editMessage(messageEmbed);
}
@Override
public MessageAction editMessage(Message message, String text, MessageEmbed messageEmbed) {
metricService.incrementCounter(MESSAGE_EDIT_METRIC);
return message.editMessage(text).embed(messageEmbed);
}
@Override
public AuditableRestAction<Void> deleteMessage(Message message) {
metricService.incrementCounter(MESSAGE_DELETE_METRIC);
return message.delete();
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(MESSAGE_SEND_METRIC, "Messages send to discord");
metricService.registerCounter(MESSAGE_EDIT_METRIC, "Messages edited in discord");
metricService.registerCounter(MESSAGE_LOAD_METRIC, "Messages loaded from discord");
metricService.registerCounter(MESSAGE_DELETE_METRIC, "Messages deleted from discord");
}
}

View File

@@ -25,6 +25,8 @@ public class PaginatorServiceBean implements PaginatorService {
@Autowired
private Gson gson;
@Autowired
private MessageService messageService;
@Override
public Paginator createPaginatorFromTemplate(String templateKey, Object model, EventWaiter waiter) {
@@ -42,7 +44,7 @@ public class PaginatorServiceBean implements PaginatorService {
.setEventWaiter(waiter)
.waitOnSinglePage(true)
.setTimeout(ObjectUtils.defaultIfNull(configuration.getTimeoutSeconds(), 120L), TimeUnit.SECONDS)
.setFinalAction(message -> message.delete().queue())
.setFinalAction(message -> messageService.deleteMessage(message))
.build();
}

View File

@@ -2,11 +2,14 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.exception.*;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.exception.PostTargetNotFoundException;
import dev.sheldan.abstracto.core.exception.PostTargetNotValidException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.core.service.management.DefaultPostTargetManagementService;
import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
@@ -45,6 +48,9 @@ public class PostTargetServiceBean implements PostTargetService {
@Autowired
private DefaultPostTargetManagementService defaultPostTargetManagementService;
@Autowired
private MessageService messageService;
@Override
public CompletableFuture<Message> sendTextInPostTarget(String text, PostTarget target) {
log.trace("Sending text to post target {}.", target.getName());
@@ -55,7 +61,7 @@ public class PostTargetServiceBean implements PostTargetService {
public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTarget target) {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
log.trace("Sending message embed to post target {}.", target.getName());
return textChannelForPostTarget.sendMessage(embed).submit();
return channelService.sendEmbedToChannel(embed, textChannelForPostTarget);
}
private TextChannel getTextChannelForPostTarget(PostTarget target) {
@@ -127,10 +133,10 @@ public class PostTargetServiceBean implements PostTargetService {
String messageText = message.getMessage();
if(StringUtils.isBlank(messageText)) {
log.trace("Editing embeds of message {} in post target {}.", messageId, target.getName());
return Arrays.asList(textChannelForPostTarget.editMessageById(messageId, message.getEmbeds().get(0)).submit());
return Arrays.asList(channelService.editEmbedMessageInAChannel(message.getEmbeds().get(0), textChannelForPostTarget, messageId));
} else {
log.trace("Editing message text and potentially text for message {} in post target {}.", messageId, target.getName());
return Arrays.asList(textChannelForPostTarget.editMessageById(messageId, messageText).embed(message.getEmbeds().get(0)).submit());
return Arrays.asList(channelService.editTextMessageInAChannel(messageText, message.getEmbeds().get(0), textChannelForPostTarget, messageId));
}
}
@@ -141,48 +147,39 @@ public class PostTargetServiceBean implements PostTargetService {
CompletableFuture<Message> messageEditFuture = new CompletableFuture<>();
futures.add(messageEditFuture);
if(StringUtils.isBlank(messageToSend.getMessage().trim())) {
textChannelForPostTarget
.retrieveMessageById(messageId)
.queue(
existingMessage -> {
log.trace("Editing existing message {} when upserting message embeds in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
existingMessage
.editMessage(messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
},
throwable -> {
log.trace("Creating new message when upserting message embeds for message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
messageEditFuture.completeExceptionally(innerThrowable);
return null;
});
});
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
log.trace("Editing existing message {} when upserting message embeds in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageService.editMessage(message, messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
}).exceptionally(throwable -> {
log.trace("Creating new message when upserting message embeds for message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
messageEditFuture.completeExceptionally(innerThrowable);
return null;
});
return null;
});
} else {
textChannelForPostTarget
.retrieveMessageById(messageId)
.queue(
existingMessage -> {
log.trace("Editing existing message {} when upserting message in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
existingMessage
.editMessage(messageToSend.getMessage())
.embed(messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
},
throwable -> {
log.trace("Creating new message when trying to upsert a message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
messageEditFuture.completeExceptionally(innerThrowable);
return null;
});
});
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
log.trace("Editing existing message {} when upserting message in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageService.editMessage(message, messageToSend.getMessage(), messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
}).exceptionally(throwable -> {
log.trace("Creating new message when trying to upsert a message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
messageEditFuture.completeExceptionally(innerThrowable);
return null;
});
return null;
});
}
return futures;

View File

@@ -1,14 +1,31 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.ConfiguredEmoteNotUsableException;
import dev.sheldan.abstracto.core.exception.EmoteNotInServerException;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.cache.CachedReaction;
import net.dv8tion.jda.api.entities.Message;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.service.management.EmoteManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.core.config.MetricConstants.DISCORD_API_INTERACTION_METRIC;
import static dev.sheldan.abstracto.core.config.MetricConstants.INTERACTION_TYPE;
@Component
@Slf4j
public class ReactionServiceBean implements ReactionService {
@Autowired
@@ -17,9 +34,45 @@ public class ReactionServiceBean implements ReactionService {
@Autowired
private EmoteService emoteService;
@Autowired
private MemberService memberService;
@Autowired
private GuildService guildService;
@Autowired
private BotService botService;
@Autowired
private ChannelService channelService;
@Autowired
private EmoteManagementService emoteManagementService;
@Autowired
private ReactionService self;
@Autowired
private MetricService metricService;
public static final CounterMetric REACTION_ADDED_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "reaction.added")))
.build();
public static final CounterMetric REACTION_REMOVED_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "reaction.removed")))
.build();
public static final CounterMetric REACTION_CLEARED_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "reaction.cleared")))
.build();
@Override
public CompletableFuture<Void> removeReactionFromMessage(CachedReaction reaction, CachedMessage cachedMessage) {
return messageService.loadMessageFromCachedMessage(cachedMessage).thenCompose(message -> removeReactionFromMessage(reaction, message));
@@ -27,14 +80,257 @@ public class ReactionServiceBean implements ReactionService {
@Override
public CompletableFuture<Void> removeReactionFromMessage(CachedReaction reaction, Message message) {
return botService.retrieveUserById(reaction.getUser().getUserId()).thenCompose(user -> {
return memberService.retrieveUserById(reaction.getUser().getUserId()).thenCompose(user -> {
if(reaction.getEmote().getCustom()) {
return emoteService.getEmoteFromCachedEmote(reaction.getEmote()).thenCompose(emote ->
message.removeReaction(emote, user).submit()
removeReaction(message, emote, user)
);
} else {
return message.removeReaction(reaction.getEmote().getEmoteName(), user).submit();
return removeReaction(message, reaction.getEmote().getEmoteName(), user);
}
});
}
@Override
public void addReactionToMessage(String emoteKey, Long serverId, Message message) {
addReactionToMessageAsync(emoteKey, serverId, message);
}
@Override
public void addDefaultReactionToMessage(String unicode, Message message) {
addDefaultReactionToMessageAsync(unicode, message);
}
@Override
public CompletableFuture<Void> addDefaultReactionToMessageAsync(String unicode, Message message) {
metricService.incrementCounter(REACTION_ADDED_METRIC);
return message.addReaction(unicode).submit();
}
@Override
public CompletableFuture<Void> addDefaultReactionToMessageAsync(String unicode, Long serverId, Long channelId, Long messageId) {
return channelService.retrieveMessageInChannel(serverId, channelId, messageId)
.thenCompose(message -> self.addDefaultReactionToMessageAsync(unicode, message));
}
@Override
public CompletableFuture<Void> addReactionToMessageAsync(String emoteKey, Long serverId, Message message) {
Guild guild = guildService.getGuildById(serverId);
return addReactionToMessageAsync(emoteKey, guild, message);
}
@Override
public CompletableFuture<Void> addReactionToMessageAsync(String emoteKey, Guild guild, Message message) {
AEmote emote = emoteService.getEmoteOrDefaultEmote(emoteKey, guild.getIdLong());
return addReactionToMessageAsync(emote, guild, message);
}
@Override
public CompletableFuture<Void> addReactionToMessageAsync(AEmote emote, Long serverId, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
return addReactionToMessageAsync(emote.getEmoteId(), serverId, message);
} else {
return addDefaultReactionToMessageAsync(emote.getEmoteKey(), message);
}
}
@Override
public CompletableFuture<Void> addReactionToMessageAsync(AEmote emote, Guild guild, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById != null) {
log.trace("Adding custom emote {} as reaction to message {}.", emoteById.getId(), message.getId());
return addReactionToMessageAsync(emoteById, message);
} else {
log.error("Emote with key {} and id {} for guild {} was not found.", emote.getName() , emote.getEmoteId(), guild.getId());
throw new ConfiguredEmoteNotUsableException(emote);
}
} else {
log.trace("Adding default emote {} as reaction to message {}.", emote.getEmoteKey(), message.getId());
return addDefaultReactionToMessageAsync(emote.getEmoteKey(), message);
}
}
@Override
public CompletableFuture<Void> addReactionToMessageAsync(Emote emote, Message message) {
metricService.incrementCounter(REACTION_ADDED_METRIC);
return message.addReaction(emote).submit();
}
@Override
public CompletableFuture<Void> addReactionToMessageAsync(Long emoteId, Long serverId, Message message) {
Emote emoteById = botService.getInstance().getEmoteById(emoteId);
if(emoteById == null) {
throw new EmoteNotInServerException(emoteId);
}
return addReactionToMessageAsync(emoteById, message);
}
@Override
public CompletableFuture<Void> addReactionToMessageAsync(String emoteKey, Long serverId, Long channelId, Long messageId) {
return channelService.retrieveMessageInChannel(serverId, channelId, messageId)
.thenCompose(message -> self.addReactionToMessageAsync(emoteKey, serverId, message));
}
@Override
public CompletableFuture<Void> removeReactionFromMessageWithFuture(AEmote emote, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById == null) {
throw new EmoteNotInServerException(emote.getEmoteId());
}
log.trace("Removing single custom reaction for emote {} on message {}.", emoteById.getId(), message.getId());
return removeReaction(message, emoteById);
} else {
log.trace("Removing single default emote {} reaction from message {}.", emote.getEmoteKey(), message.getId());
return removeReaction(message, emote.getEmoteKey());
}
}
@Override
public CompletableFuture<Void> removeReaction(Message message, String key) {
metricService.incrementCounter(REACTION_REMOVED_METRIC);
return message.removeReaction(key).submit();
}
@Override
public CompletableFuture<Void> removeReaction(Message message, String key, User user) {
metricService.incrementCounter(REACTION_REMOVED_METRIC);
return message.removeReaction(key, user).submit();
}
@Override
public CompletableFuture<Void> removeReaction(Message message, Emote emoteById) {
metricService.incrementCounter(REACTION_REMOVED_METRIC);
return message.removeReaction(emoteById).submit();
}
@Override
public CompletableFuture<Void> removeReaction(Message message, Emote emoteById, User user) {
metricService.incrementCounter(REACTION_REMOVED_METRIC);
return message.removeReaction(emoteById, user).submit();
}
@Override
public CompletableFuture<Void> clearReactionFromMessageWithFuture(AEmote emote, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById == null) {
throw new EmoteNotInServerException(emote.getEmoteId());
}
log.trace("Clearing reactions for custom emote {} on message {}.", emoteById.getId(), message.getId());
return clearReaction(message, emoteById);
} else {
log.trace("Clearing reactions for default emote {} on message {}.", emote.getEmoteKey(), message.getId());
return clearReaction(message, emote.getEmoteKey());
}
}
public CompletableFuture<Void> clearReaction(Message message, String key) {
metricService.incrementCounter(REACTION_CLEARED_METRIC);
return message.clearReactions(key).submit();
}
public CompletableFuture<Void> clearReaction(Message message, Emote emoteById) {
metricService.incrementCounter(REACTION_CLEARED_METRIC);
return message.clearReactions(emoteById).submit();
}
@Override
public CompletableFuture<Void> removeReactionFromMessageWithFuture(Integer emoteId, Message message) {
AEmote emote = emoteManagementService.loadEmote(emoteId);
return removeReactionFromMessageWithFuture(emote, message);
}
@Override
public CompletableFuture<Void> clearReactionFromMessageWithFuture(Integer emoteId, Message message) {
AEmote emote = emoteManagementService.loadEmote(emoteId);
return clearReactionFromMessageWithFuture(emote, message);
}
@Override
public CompletableFuture<Void> removeReactionFromMessageWithFuture(AEmote emote, Long serverId, Long channelId, Long messageId) {
Integer emoteId = emote.getId();
return channelService.retrieveMessageInChannel(serverId, channelId, messageId)
.thenCompose(message -> self.removeReactionFromMessageWithFuture(emoteId, message));
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(AEmote emote, Long serverId, Long channelId, Long messageId, Long userId) {
Guild guild = guildService.getGuildById(serverId);
Integer emoteId = emote.getId();
CompletableFuture<Member> memberFuture = guild.retrieveMemberById(userId).submit();
CompletableFuture<Message> messageFuture = channelService.retrieveMessageInChannel(serverId, channelId, messageId);
return CompletableFuture.allOf(memberFuture, messageFuture).thenCompose(aVoid ->
memberFuture.thenCompose(member ->
self.removeReactionOfUserFromMessageWithFuture(emoteId, messageFuture.join(), memberFuture.join())
)
);
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(AEmote emote, Long serverId, Long channelId, Long messageId, Member member) {
Integer emoteId = emote.getId();
return channelService.retrieveMessageInChannel(serverId, channelId, messageId)
.thenCompose(message -> self.removeReactionOfUserFromMessageWithFuture(emoteId, message, member));
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(AEmote emote, Message message, Member member) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById == null) {
throw new EmoteNotInServerException(emote.getEmoteId());
}
log.trace("Removing reaction for custom emote {} from user {} on message {}.", emoteById.getId(), member.getId(), member.getId());
return removeReaction(message, emoteById, member.getUser());
} else {
log.trace("Removing reaction for default emote {} from user {} on message {}.", emote.getEmoteKey(), member.getId(), member.getId());
return removeReaction(message, emote.getEmoteKey(), member.getUser());
}
}
@Override
@Transactional
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(Integer emoteId, Message message, Member member) {
AEmote emote = emoteManagementService.loadEmote(emoteId);
return removeReactionOfUserFromMessageWithFuture(emote, message, member);
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(AEmote emote, Message message, Long userId) {
Integer emoteId = emote.getId();
return message.getGuild().retrieveMemberById(userId).submit().thenCompose(member ->
self.removeReactionOfUserFromMessageWithFuture(emoteId, message, member)
);
}
@Override
public CompletableFuture<Void> removeReactionOfUserFromMessageWithFuture(Integer emoteId, Message message, Long userId) {
return message.getGuild().retrieveMemberById(userId).submit().thenCompose(member ->
self.removeReactionOfUserFromMessageWithFuture(emoteId, message, member)
);
}
@Override
public CompletableFuture<Void> clearReactionFromMessageWithFuture(AEmote emote, Long serverId, Long channelId, Long messageId) {
Integer emoteId = emote.getId();
return channelService.retrieveMessageInChannel(serverId, channelId, messageId)
.thenCompose(message1 -> clearReactionFromMessageWithFuture(emoteId, message1));
}
@Override
public List<CompletableFuture<Void>> addReactionsToMessageWithFuture(List<String> emoteKeys, Long serverId, Message message) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
emoteKeys.forEach(s -> futures.add(addReactionToMessageAsync(s, serverId, message)));
return futures;
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(REACTION_ADDED_METRIC, "Reactions added");
metricService.registerCounter(REACTION_REMOVED_METRIC, "Reactions removed");
metricService.registerCounter(REACTION_CLEARED_METRIC, "Reactions cleared");
}
}

View File

@@ -4,6 +4,9 @@ import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.exception.RoleDeletedException;
import dev.sheldan.abstracto.core.exception.RoleNotFoundInDBException;
import dev.sheldan.abstracto.core.exception.RoleNotFoundInGuildException;
import dev.sheldan.abstracto.core.metrics.service.CounterMetric;
import dev.sheldan.abstracto.core.metrics.service.MetricService;
import dev.sheldan.abstracto.core.metrics.service.MetricTag;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
@@ -15,24 +18,45 @@ import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
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;
@Component
@Slf4j
public class RoleServiceBean implements RoleService {
@Autowired
private BotService botService;
private GuildService guildService;
@Autowired
private RoleManagementService roleManagementService;
@Autowired
private MetricService metricService;
public static final CounterMetric ROLE_ASSIGNED_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "role.assigned")))
.build();
public static final CounterMetric ROLE_REMOVED_METRIC = CounterMetric
.builder()
.name(DISCORD_API_INTERACTION_METRIC)
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "role.removed")))
.build();
@Override
public void addRoleToUser(AUserInAServer aUserInAServer, ARole role) {
Optional<Guild> guildById = botService.getGuildByIdOptional(aUserInAServer.getServerReference().getId());
Optional<Guild> guildById = guildService.getGuildByIdOptional(aUserInAServer.getServerReference().getId());
if(guildById.isPresent()) {
addRoleToUser(guildById.get(), role, aUserInAServer.getUserReference().getId());
} else {
@@ -42,9 +66,9 @@ public class RoleServiceBean implements RoleService {
@Override
public CompletableFuture<Void> addRoleToUserFuture(AUserInAServer aUserInAServer, ARole role) {
Optional<Guild> guildById = botService.getGuildByIdOptional(aUserInAServer.getServerReference().getId());
Optional<Guild> guildById = guildService.getGuildByIdOptional(aUserInAServer.getServerReference().getId());
if(guildById.isPresent()) {
return addRoleToUserFuture(guildById.get(), role, aUserInAServer.getUserReference().getId());
return addRoleToUserFuture(guildById.get(), aUserInAServer.getUserReference().getId(), role);
} else {
throw new GuildNotFoundException(aUserInAServer.getServerReference().getId());
}
@@ -56,7 +80,7 @@ public class RoleServiceBean implements RoleService {
if(role == null) {
throw new RoleNotFoundInGuildException(roleId, member.getGuild().getIdLong());
}
return member.getGuild().addRoleToMember(member, role).submit();
return addRoleToUser(member.getGuild(), member.getIdLong(), role);
}
@@ -90,7 +114,7 @@ public class RoleServiceBean implements RoleService {
return member.getGuild().removeRoleFromMember(member, role).submit();
}
private CompletableFuture<Void> addRoleToUserFuture(Guild guild, ARole role, Long userId) {
private CompletableFuture<Void> addRoleToUserFuture(Guild guild, Long userId, ARole role) {
if(role.getDeleted()) {
log.warn("Not possible to add role to user. Role {} was marked as deleted.", role.getId());
throw new RoleDeletedException(role);
@@ -98,15 +122,27 @@ public class RoleServiceBean implements RoleService {
Role roleById = guild.getRoleById(role.getId());
if(roleById != null) {
log.info("Adding role {} to user {} in server {}.", role.getId(), userId, guild.getId());
return guild.addRoleToMember(userId, roleById).submit();
return addRoleToUser(guild, userId, roleById);
} else {
throw new RoleNotFoundInGuildException(role.getId(), guild.getIdLong());
}
}
@Override
public CompletableFuture<Void> addRoleToUser(Guild guild, Long userId, Role roleById) {
metricService.incrementCounter(ROLE_ASSIGNED_METRIC);
return guild.addRoleToMember(userId, roleById).submit();
}
@Override
public CompletableFuture<Void> removeRoleFromUser(Guild guild, Long userId, Role roleById) {
metricService.incrementCounter(ROLE_REMOVED_METRIC);
return guild.removeRoleFromMember(userId, roleById).submit();
}
private void addRoleToUser(Guild guild, ARole role, Long userId) {
addRoleToUserFuture(guild, role, userId);
addRoleToUserFuture(guild, userId, role);
}
private CompletableFuture<Void> removeRoleFromUserFuture(Guild guild, ARole role, Long userId) {
@@ -117,7 +153,7 @@ public class RoleServiceBean implements RoleService {
Role roleById = guild.getRoleById(role.getId());
if(roleById != null) {
log.info("Removing role {} from user {} in server {}.", role.getId(), userId, guild.getId());
return guild.removeRoleFromMember(userId, roleById).submit();
return removeRoleFromUser(guild, userId, roleById);
} else {
throw new RoleNotFoundInGuildException(role.getId(), guild.getIdLong());
}
@@ -130,7 +166,7 @@ public class RoleServiceBean implements RoleService {
@Override
public void removeRoleFromUser(AUserInAServer aUserInAServer, ARole role) {
Optional<Guild> guildById = botService.getGuildByIdOptional(aUserInAServer.getServerReference().getId());
Optional<Guild> guildById = guildService.getGuildByIdOptional(aUserInAServer.getServerReference().getId());
if(guildById.isPresent()) {
removeRoleFromUser(guildById.get(), role, aUserInAServer.getUserReference().getId());
} else {
@@ -140,7 +176,7 @@ public class RoleServiceBean implements RoleService {
@Override
public CompletableFuture<Void> removeRoleFromUserFuture(AUserInAServer aUserInAServer, ARole role) {
Optional<Guild> guildById = botService.getGuildByIdOptional(aUserInAServer.getServerReference().getId());
Optional<Guild> guildById = guildService.getGuildByIdOptional(aUserInAServer.getServerReference().getId());
if(guildById.isPresent()) {
return removeRoleFromUserFuture(guildById.get(), role, aUserInAServer.getUserReference().getId());
} else {
@@ -166,7 +202,7 @@ public class RoleServiceBean implements RoleService {
log.warn("Trying to load role {} which is marked as deleted.", role.getId());
throw new RoleDeletedException(role);
}
Optional<Guild> guildById = botService.getGuildByIdOptional(role.getServer().getId());
Optional<Guild> guildById = guildService.getGuildByIdOptional(role.getServer().getId());
if(guildById.isPresent()) {
log.trace("Loading role {} from server {}.", role.getId(), role.getServer().getId());
return guildById.get().getRoleById(role.getId());
@@ -227,4 +263,10 @@ public class RoleServiceBean implements RoleService {
.server(server)
.build();
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(ROLE_ASSIGNED_METRIC, "Amount of roles assigned");
metricService.registerCounter(ROLE_REMOVED_METRIC, "Amount of roles removed");
}
}

View File

@@ -1,14 +1,14 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.listener.sync.entity.ServerConfigListener;
import dev.sheldan.abstracto.core.utils.SnowflakeUtils;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AChannelType;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
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;
@@ -18,9 +18,10 @@ import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.apache.commons.collections4.SetUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import javax.security.auth.login.LoginException;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -55,7 +56,7 @@ public class StartupServiceBean implements Startup {
}
@Override
@Transactional
@Transactional(isolation = Isolation.SERIALIZABLE)
public void synchronize() {
log.info("Synchronizing servers.");
synchronizeServers();

View File

@@ -21,6 +21,9 @@ public class UndoActionServiceBean implements UndoActionService {
@Autowired
private ChannelService channelService;
@Autowired
private MessageService messageService;
@Override
public void performActions(List<UndoActionInstance> actionsToPerform) {
performActionsFuture(actionsToPerform);
@@ -41,12 +44,12 @@ public class UndoActionServiceBean implements UndoActionService {
log.info("Deleting channel {} in guild {} because of an UNDO action.", ids.get(1), ids.get(0));
futures.add(deleteChannel(ids.get(0), ids.get(1)));
} else if(action.equals(UndoAction.DELETE_MESSAGE)) {
if(ids.size() != 2) {
if(ids.size() != 3) {
log.error("Not the correct amount of ids provided for the message deletion undo action.");
return;
}
log.info("Deleting message {} in channel {} because of an UNDO action.", ids.get(1), ids.get(0));
futures.add(botService.deleteMessage(ids.get(0), ids.get(1)));
log.info("Deleting message {} in channel {} in server {} because of an UNDO action.", ids.get(2), ids.get(1), ids.get(0));
futures.add(messageService.deleteMessageInChannelInServer(ids.get(0), ids.get(1), ids.get(2)));
}
});
return FutureUtils.toSingleFutureGeneric(futures);

View File

@@ -5,8 +5,8 @@ import dev.sheldan.abstracto.core.exception.ServerChannelConflictException;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.repository.PostTargetRepository;
import dev.sheldan.abstracto.core.service.PostTargetService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

View File

@@ -67,6 +67,10 @@ public class ServerManagementServiceBean implements ServerManagementService {
@Override
public AUserInAServer addUserToServer(Long serverId, Long userId) {
log.info("Adding user {} to server {}", userId, serverId);
// we need to reload the user, because the user already got persisted within this transaction (if the user didnt exist)
// but it seems the current transaction is not aware that it is already persisted, it tries to create again
// but fails with integrity constraint violation
userManagementService.loadOrCreateUser(userId);
AUser user = userManagementService.loadUser(userId);
AServer serverReference = loadServer(serverId);
AUserInAServer aUserInAServer = AUserInAServer.builder().serverReference(serverReference).userReference(user).build();

View File

@@ -9,7 +9,10 @@ import dev.sheldan.abstracto.core.repository.UserInServerRepository;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@@ -27,9 +30,11 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
@Autowired
private UserManagementService userManagementService;
@Autowired
private UserInServerManagementServiceBean self;
@Override
public AUserInAServer loadUser(Long serverId, Long userId) {
public AUserInAServer loadOrCreateUser(Long serverId, Long userId) {
if(userInServerRepository.existsByServerReference_IdAndUserReference_Id(serverId, userId)) {
return userInServerRepository.findByServerReference_IdAndUserReference_Id(serverId, userId).orElseThrow(() -> new UserInServerNotFoundException(0L));
} else {
@@ -38,8 +43,13 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
}
@Override
public AUserInAServer loadUser(ServerUser serverUser) {
return loadUser(serverUser.getServerId(), serverUser.getUserId());
public AUserInAServer onlyLoadUser(Long serverId, Long userId) {
return userInServerRepository.findByServerReference_IdAndUserReference_Id(serverId, userId).orElseThrow(() -> new UserInServerNotFoundException(0L));
}
@Override
public AUserInAServer loadOrCreateUser(ServerUser serverUser) {
return loadOrCreateUser(serverUser.getServerId(), serverUser.getUserId());
}
@Override
@@ -48,7 +58,7 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
}
@Override
public AUserInAServer loadUser(AServer server, AUser user) {
public AUserInAServer loadOrCreateUser(AServer server, AUser user) {
if(userInServerRepository.existsByServerReferenceAndUserReference(server, user)) {
return userInServerRepository.findByServerReferenceAndUserReference(server, user).orElseThrow(() -> new UserInServerNotFoundException(0L));
} else {
@@ -57,8 +67,8 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
}
@Override
public AUserInAServer loadUser(Member member) {
return this.loadUser(member.getGuild().getIdLong(), member.getIdLong());
public AUserInAServer loadOrCreateUser(Member member) {
return this.loadOrCreateUser(member.getGuild().getIdLong(), member.getIdLong());
}
@Override
@@ -67,7 +77,7 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
}
@Override
public AUserInAServer loadUser(Long userInServerId) {
public AUserInAServer loadOrCreateUser(Long userInServerId) {
return loadUserOptional(userInServerId).orElseThrow(() -> new UserInServerNotFoundException(userInServerId));
}
@@ -77,8 +87,21 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
}
@Override
public AUserInAServer createUserInServer(Long guildId, Long userId) {
log.info("Creating user {} in server {}.", userId, guildId);
public AUserInAServer createUserInServer(Long serverId, Long userId) {
log.info("Creating user {} in server {}.", userId, serverId);
AUserInAServer aUserInAServer;
try {
// TODO there seems to be an issue, of trying to create the user a second time
aUserInAServer = self.tryToCreateAUserInAServer(serverId, userId);
} catch (DataIntegrityViolationException ex) {
log.info("Concurrency exception creating user - retrieving.");
aUserInAServer = onlyLoadUser(serverId, userId);
}
return aUserInAServer;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public AUserInAServer tryToCreateAUserInAServer(Long guildId, Long userId) {
AUserInAServer aUserInAServer = serverManagementService.addUserToServer(guildId, userId);
userInServerRepository.save(aUserInAServer);
return aUserInAServer;
@@ -86,13 +109,13 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
@Override
public List<AUserInAServer> getUserInAllServers(Long userId) {
AUser user = userManagementService.loadUser(userId);
AUser user = userManagementService.loadOrCreateUser(userId);
return userInServerRepository.findByUserReference(user);
}
@Override
public Optional<AUserInAServer> loadAUserInAServerOptional(Long serverId, Long userId) {
AUser user = userManagementService.loadUser(userId);
AUser user = userManagementService.loadOrCreateUser(userId);
AServer server = serverManagementService.loadServer(serverId);
return userInServerRepository.findByServerReferenceAndUserReference(server, user);
}

View File

@@ -5,7 +5,10 @@ import dev.sheldan.abstracto.core.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@@ -16,6 +19,9 @@ public class UserManagementServiceBean implements UserManagementService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserManagementServiceBean self;
@Override
public AUser createUser(Member member) {
return createUser(member.getIdLong());
@@ -23,14 +29,30 @@ public class UserManagementServiceBean implements UserManagementService {
@Override
public AUser createUser(Long userId) {
AUser user;
try {
user = self.tryCreateUser(userId);
} catch (DataIntegrityViolationException ex) {
log.info("Concurrency exception creating user - retrieving.");
user = loadUser(userId);
}
return user;
}
@Override
public AUser loadUser(Long userId) {
return userRepository.getOne(userId);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public AUser tryCreateUser(Long userId) {
AUser aUser = AUser.builder().id(userId).build();
log.info("Creating user {}.", userId);
userRepository.save(aUser);
return aUser;
}
@Override
public AUser loadUser(Long userId) {
public AUser loadOrCreateUser(Long userId) {
Optional<AUser> optional = loadUserOptional(userId);
return optional.orElseGet(() -> this.createUser(userId));
}

View File

@@ -1,3 +1,4 @@
abstracto.startup.synchronize=true
abstracto.eventWaiter.threads=10
server.port=8080

View File

@@ -1,6 +1,8 @@
package dev.sheldan.abstracto.core.command.handler;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.requests.RestAction;
import org.junit.Assert;
import org.junit.Test;

View File

@@ -12,7 +12,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class CommandServiceBeanTest {

View File

@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)

View File

@@ -15,7 +15,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class FeatureFlagListenerTest {