[AB-365] introducing slash commands for a selection of commands

adding method for pinning a message
moving suggestion to correct deployment
This commit is contained in:
Sheldan
2022-05-17 00:39:06 +02:00
parent 1913bc930d
commit 1d6de3f1e8
286 changed files with 8021 additions and 3065 deletions

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.FeatureAware;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import java.util.concurrent.CompletableFuture;
@@ -11,5 +12,6 @@ public interface Command extends FeatureAware {
default CommandResult execute(CommandContext commandContext) {return CommandResult.fromSuccess();}
default CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {return CompletableFuture.completedFuture(CommandResult.fromSuccess());}
default CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) { return CompletableFuture.completedFuture(CommandResult.fromSuccess());}
CommandConfiguration getConfiguration();
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.core.command;
public class CoreSlashCommandNames {
public static final String UTILITY = "utility";
public static final String CONFIG = "config";
public static final String FEATURE = "feature";
public static final String CHANNELS = "channels";
public static final String POST_TARGET = "posttarget";
public static final String PROFANITY = "profanity";
public static final String INFO = "info";
public static final String INTERNAL = "internal";
}

View File

@@ -35,11 +35,13 @@ public abstract class AbstractConditionableCommand implements ConditionalCommand
@Autowired
private CommandCoolDownCondition coolDownCondition;
@Autowired
private MessageCommandCondition messageCommandCondition;
@Override
public List<CommandCondition> getConditions() {
return new ArrayList<>(Arrays.asList(adminModeCondition, featureEnabledCondition, commandDisabledCondition,
commandDisallowedCondition, featureModeCondition, coolDownCondition));
commandDisallowedCondition, featureModeCondition, coolDownCondition, messageCommandCondition));
}
protected void checkParameters(CommandContext context) {

View File

@@ -6,6 +6,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.service.ServerService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -21,11 +22,49 @@ public class AdminModeCondition implements CommandCondition {
boolean adminModeActive = service.adminModeActive(context.getGuild());
if(adminModeActive){
if(context.getAuthor().hasPermission(Permission.ADMINISTRATOR)) {
return ConditionResult.builder().result(true).build();
return ConditionResult
.builder()
.result(true)
.build();
} else {
return ConditionResult.builder().result(false).conditionDetail(new AdminModeDetail()).build();
return ConditionResult
.builder()
.result(false)
.conditionDetail(new AdminModeDetail())
.build();
}
}
return ConditionResult.builder().result(true).build();
return ConditionResult
.builder()
.result(true)
.build();
}
@Override
public ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
boolean adminModeActive = service.adminModeActive(slashCommandInteractionEvent.getGuild());
if(adminModeActive){
if(slashCommandInteractionEvent.getMember().hasPermission(Permission.ADMINISTRATOR)) {
return ConditionResult
.builder()
.result(true)
.build();
} else {
return ConditionResult
.builder()
.result(false)
.conditionDetail(new AdminModeDetail())
.build();
}
}
return ConditionResult
.builder()
.result(true)
.build();
}
@Override
public boolean supportsSlashCommands() {
return true;
}
}

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.command.condition;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import java.util.concurrent.CompletableFuture;
@@ -10,10 +11,22 @@ public interface CommandCondition {
default ConditionResult shouldExecute(CommandContext commandContext, Command command) {
return ConditionResult.fromSuccess();
}
default ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
return ConditionResult.fromSuccess();
}
default boolean isAsync() {
return false;
}
default boolean supportsSlashCommands() {
return false;
}
default CompletableFuture<ConditionResult> shouldExecuteAsync(CommandContext commandContext, Command command) {
return CompletableFuture.completedFuture(ConditionResult.fromSuccess());
}
default CompletableFuture<ConditionResult> shouldExecuteAsync(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
return CompletableFuture.completedFuture(ConditionResult.fromSuccess());
}
}

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.command.condition.detail.CommandCoolDownDetail
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CoolDownCheckResult;
import dev.sheldan.abstracto.core.command.service.CommandCoolDownService;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -19,13 +20,47 @@ public class CommandCoolDownCondition implements CommandCondition {
commandCoolDownService.takeLock();
try {
CoolDownCheckResult result = commandCoolDownService.allowedToExecuteCommand(command, commandContext);
if(result.getCanExecute()) {
return ConditionResult.builder().result(true).build();
if (result.getCanExecute()) {
return ConditionResult
.builder()
.result(true)
.build();
} else {
return ConditionResult.builder().result(false).conditionDetail(new CommandCoolDownDetail(result)).build();
return ConditionResult
.builder()
.result(false)
.conditionDetail(new CommandCoolDownDetail(result))
.build();
}
} finally {
commandCoolDownService.releaseLock();
}
}
@Override
public ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
commandCoolDownService.takeLock();
try {
CoolDownCheckResult result = commandCoolDownService.allowedToExecuteCommand(command, slashCommandInteractionEvent);
if (result.getCanExecute()) {
return ConditionResult
.builder()
.result(true)
.build();
} else {
return ConditionResult
.builder()
.result(false)
.conditionDetail(new CommandCoolDownDetail(result))
.build();
}
} finally {
commandCoolDownService.releaseLock();
}
}
@Override
public boolean supportsSlashCommands() {
return true;
}
}

View File

@@ -36,17 +36,27 @@ public class CommandDisallowedCondition implements CommandCondition {
ACommand aCommand = commandService.findCommandByName(command.getConfiguration().getName());
ACommandInAServer commandForServer = commandInServerManagementService.getCommandForServer(aCommand, context.getUserInitiatedContext().getGuild().getIdLong());
if(Boolean.FALSE.equals(commandForServer.getRestricted())) {
return ConditionResult.builder().result(true).build();
return ConditionResult
.builder()
.result(true)
.build();
}
for (ARole role : commandForServer.getAllowedRoles()) {
Member author = context.getAuthor();
if (roleService.memberHasRole(author, role)) {
log.debug("Member {} is able to execute restricted command {}, because of role {}.", author.getIdLong(), aCommand.getName(), role.getId());
return ConditionResult.builder().result(true).build();
return ConditionResult
.builder()
.result(true)
.build();
}
}
List<Role> allowedRoles = roleService.getRolesFromGuild(commandForServer.getAllowedRoles());
InsufficientPermissionConditionDetail exception = new InsufficientPermissionConditionDetail(allowedRoles);
return ConditionResult.builder().result(false).conditionDetail(exception).build();
return ConditionResult
.builder()
.result(false)
.conditionDetail(exception)
.build();
}
}

View File

@@ -13,6 +13,8 @@ public class ConditionResult {
private boolean result;
private String reason;
private ConditionDetail conditionDetail;
@Builder.Default
private boolean reportResult = true;
public static final ConditionResult SUCCESS = ConditionResult.builder().result(true).build();

View File

@@ -7,6 +7,7 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -22,16 +23,39 @@ public class FeatureEnabledCondition implements CommandCondition {
@Override
public ConditionResult shouldExecute(CommandContext context, Command command) {
Long serverId = context.getGuild().getIdLong();
return evaluateFeatureCondition(command, serverId);
}
@Override
public ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
Long serverId = slashCommandInteractionEvent.getGuild().getIdLong();
return evaluateFeatureCondition(command, serverId);
}
private ConditionResult evaluateFeatureCondition(Command command, Long serverId) {
FeatureDefinition feature = command.getFeature();
boolean featureFlagValue = true;
boolean featureFlagValue;
if(feature != null) {
featureFlagValue = featureFlagService.getFeatureFlagValue(feature, context.getGuild().getIdLong());
featureFlagValue = featureFlagService.getFeatureFlagValue(feature, serverId);
if(!featureFlagValue) {
log.debug("Feature {} is disabled, disallows command {} to be executed in guild {}.", feature.getKey(), command.getConfiguration().getName(), context.getGuild().getId());
log.debug("Feature {} is disabled, disallows command {} to be executed in guild {}.", feature.getKey(), command.getConfiguration().getName(), serverId);
FeatureDisabledConditionDetail exception = new FeatureDisabledConditionDetail(featureConfigService.getFeatureDisplayForFeature(command.getFeature()));
return ConditionResult.builder().result(false).conditionDetail(exception).build();
return ConditionResult
.builder()
.result(false)
.conditionDetail(exception)
.build();
}
}
return ConditionResult.builder().result(true).build();
return ConditionResult
.builder()
.result(true)
.build();
}
@Override
public boolean supportsSlashCommands() {
return true;
}
}

View File

@@ -6,6 +6,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -17,12 +18,20 @@ public class FeatureModeCondition implements CommandCondition {
@Override
public ConditionResult shouldExecute(CommandContext context, Command command) {
Long serverId = context.getUserInitiatedContext().getGuild().getIdLong();
return checkFeatureModeCondition(command, serverId);
}
private ConditionResult checkFeatureModeCondition(Command command, Long serverId) {
if(!command.getFeatureModeLimitations().isEmpty()){
FeatureDefinition feature = command.getFeature();
if(feature != null) {
for (FeatureMode featureModeLimitation : command.getFeatureModeLimitations()) {
if(modeService.featureModeActive(feature, context.getUserInitiatedContext().getGuild().getIdLong(), featureModeLimitation)) {
return ConditionResult.builder().result(true).build();
if(modeService.featureModeActive(feature, serverId, featureModeLimitation)) {
return ConditionResult
.builder()
.result(true)
.build();
}
}
return ConditionResult
@@ -33,6 +42,20 @@ public class FeatureModeCondition implements CommandCondition {
}
}
return ConditionResult.builder().result(true).build();
return ConditionResult
.builder()
.result(true)
.build();
}
@Override
public ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
Long serverId = slashCommandInteractionEvent.getGuild().getIdLong();
return checkFeatureModeCondition(command, serverId);
}
@Override
public boolean supportsSlashCommands() {
return true;
}
}

View File

@@ -5,8 +5,7 @@ import dev.sheldan.abstracto.core.command.condition.detail.ImmuneUserConditionDe
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.EffectConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.service.management.CommandInServerManagementService;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.RoleImmunity;
import dev.sheldan.abstracto.core.service.RoleImmunityService;
import dev.sheldan.abstracto.core.service.RoleService;
@@ -14,6 +13,8 @@ import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -27,12 +28,6 @@ import java.util.stream.Collectors;
@Slf4j
public class ImmuneUserCondition implements CommandCondition {
@Autowired
private CommandInServerManagementService commandInServerManagementService;
@Autowired
private CommandManagementService commandService;
@Autowired
private RoleService roleService;
@@ -42,6 +37,9 @@ public class ImmuneUserCondition implements CommandCondition {
@Autowired
private ImmuneUserCondition self;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Override
public CompletableFuture<ConditionResult> shouldExecuteAsync(CommandContext commandContext, Command command) {
CommandConfiguration commandConfig = command.getConfiguration();
@@ -102,16 +100,7 @@ public class ImmuneUserCondition implements CommandCondition {
User user = (User) parameter;
member = memberMap.get(user.getIdLong());
}
if(member != null) {
Optional<RoleImmunity> immunityOptional = roleImmunityService.getRoleImmunity(member, effectConfig.getEffectKey());
if (immunityOptional.isPresent()) {
RoleImmunity immunity = immunityOptional.get();
ImmuneUserConditionDetail conditionDetail = new ImmuneUserConditionDetail(roleService.getRoleFromGuild(immunity.getRole()),
effectConfig.getEffectKey());
resultFuture.complete(ConditionResult.fromFailure(conditionDetail));
return;
}
} else {
if(member != null && checkRoleImmunity(resultFuture, effectConfig, member)) {
return;
}
} else {
@@ -122,6 +111,91 @@ public class ImmuneUserCondition implements CommandCondition {
resultFuture.complete(ConditionResult.fromSuccess());
}
@Override
public CompletableFuture<ConditionResult> shouldExecuteAsync(SlashCommandInteractionEvent event, Command command) {
CommandConfiguration commandConfig = command.getConfiguration();
if(commandConfig.getEffects().isEmpty()) {
return ConditionResult.fromAsyncSuccess();
}
List<CompletableFuture<Member>> futures = new ArrayList<>();
for (EffectConfig effectConfig : commandConfig.getEffects()) {
String parameterName = effectConfig.getParameterName();
if (slashCommandParameterService.hasCommandOptionWithFullType(parameterName, event, OptionType.USER)) {
if (slashCommandParameterService.hasCommandOption(parameterName, event, User.class, User.class)) {
User user = slashCommandParameterService.getCommandOption(parameterName, event, User.class);
futures.add(event.getGuild().retrieveMember(user).submit());
} else if (slashCommandParameterService.hasCommandOption(parameterName, event, Member.class, Member.class)) {
Member member = slashCommandParameterService.getCommandOption(parameterName, event, Member.class);
futures.add(CompletableFuture.completedFuture(member));
}
} else {
log.info("Not found parameter {} in slash command event in guild {} in channel {} from user {}.",
parameterName, event.getGuild().getIdLong(), event.getChannel().getIdLong(), event.getUser().getIdLong());
}
}
if(!futures.isEmpty()) {
CompletableFuture<ConditionResult> resultFuture = new CompletableFuture<>();
CompletableFutureList<Member> futureList = new CompletableFutureList<>(futures);
futureList.getMainFuture().whenComplete((unused, throwable) -> {
if(throwable != null) {
log.warn("Future for user immune condition failed. Continuing processing.", throwable);
}
Map<Long, Member> memberMap = futureList
.getObjects()
.stream()
.collect(Collectors.toMap(Member::getIdLong, Function.identity()));
self.checkConditions(commandConfig, event, resultFuture, memberMap);
}).exceptionally(throwable -> {
resultFuture.completeExceptionally(throwable);
return null;
});
return resultFuture;
} else {
return ConditionResult.fromAsyncSuccess();
}
}
@Transactional
public void checkConditions(CommandConfiguration commandConfig, SlashCommandInteractionEvent event, CompletableFuture<ConditionResult> resultFuture, Map<Long, Member> memberMap) {
for (EffectConfig effectConfig : commandConfig.getEffects()) {
String parameterName = effectConfig.getParameterName();
if (slashCommandParameterService.hasCommandOptionWithFullType(parameterName, event, OptionType.USER)) {
Member member = null;
if (slashCommandParameterService.hasCommandOption(parameterName, event, User.class, User.class)) {
User user = slashCommandParameterService.getCommandOption(parameterName, event, User.class);
member = memberMap.get(user.getIdLong());
} else if (slashCommandParameterService.hasCommandOption(parameterName, event, Member.class, Member.class)) {
member = slashCommandParameterService.getCommandOption(parameterName, event, Member.class);
}
if(member != null && checkRoleImmunity(resultFuture, effectConfig, member)) {
return;
}
} else {
log.info("Not found parameter {} in slash command event in guild {} in channel {} from user {}.",
parameterName, event.getGuild().getIdLong(), event.getChannel().getIdLong(), event.getUser().getIdLong());
}
}
resultFuture.complete(ConditionResult.fromSuccess());
}
private boolean checkRoleImmunity(CompletableFuture<ConditionResult> resultFuture, EffectConfig effectConfig, Member member) {
Optional<RoleImmunity> immunityOptional = roleImmunityService.getRoleImmunity(member, effectConfig.getEffectKey());
if (immunityOptional.isPresent()) {
RoleImmunity immunity = immunityOptional.get();
ImmuneUserConditionDetail conditionDetail = new ImmuneUserConditionDetail(roleService.getRoleFromGuild(immunity.getRole()),
effectConfig.getEffectKey());
resultFuture.complete(ConditionResult.fromFailure(conditionDetail));
return true;
}
return false;
}
@Override
public boolean supportsSlashCommands() {
return true;
}
@Override
public boolean isAsync() {
return true;

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.core.command.condition;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import org.springframework.stereotype.Component;
@Component
public class MessageCommandCondition implements CommandCondition {
@Override
public ConditionResult shouldExecute(CommandContext commandContext, Command command) {
if(command.getConfiguration().isSupportsMessageCommand()) {
return ConditionResult.builder().result(true).build();
} else {
return ConditionResult.builder().result(false).reportResult(false).build();
}
}
}

View File

@@ -1,12 +1,13 @@
package dev.sheldan.abstracto.core.command.config;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
@Getter @Builder
@Getter @Builder @EqualsAndHashCode
public class CommandConfiguration {
private String name;
@@ -37,8 +38,17 @@ public class CommandConfiguration {
@Builder.Default
private List<EffectConfig> effects = new ArrayList<>();
@Builder.Default
private boolean supportsMessageCommand = true;
private CommandCoolDownConfig coolDownConfig;
@Builder.Default
private SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(false)
.build();
public int getNecessaryParameterCount(){
return (int) parameters.stream().filter(parameter -> !parameter.isOptional()).count();
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.command.config;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@@ -9,6 +10,7 @@ import java.time.Duration;
@Getter
@Setter
@Builder
@EqualsAndHashCode
public class CommandCoolDownConfig {
private Duration serverCoolDown;
private Duration channelCoolDown;

View File

@@ -1,13 +1,16 @@
package dev.sheldan.abstracto.core.command.config;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
@EqualsAndHashCode
public class EffectConfig {
private Integer position;
private String effectKey;
private String parameterName;
}

View File

@@ -1,18 +1,17 @@
package dev.sheldan.abstracto.core.command.config;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
@Getter
@Setter
@Builder
@EqualsAndHashCode
public class Parameter implements Serializable {
private String name;
private Class type;
@@ -26,9 +25,17 @@ public class Parameter implements Serializable {
@Builder.Default
private Boolean templated = false;
@Builder.Default
private Boolean slashCommandOnly = false;
@Builder.Default
private Integer listSize = 0;
@Builder.Default
private List<ParameterValidator> validators = new ArrayList<>();
@Builder.Default
private Map<String, Object> additionalInfo = new HashMap<>();
public String getSlashCompatibleName() {
return name.toLowerCase(Locale.ROOT);
}
public static final String ADDITIONAL_TYPES_KEY = "ADDITIONAL_TYPES";
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.abstracto.core.command.config;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import net.dv8tion.jda.api.interactions.commands.CommandInteractionPayload;
import java.util.Locale;
@Getter
@Builder
@EqualsAndHashCode
public class SlashCommandConfig {
private boolean enabled;
private String rootCommandName;
private String groupName;
private String commandName;
public boolean matchesInteraction(CommandInteractionPayload payload) {
if(getSlashCompatibleRootName() != null && payload.getName() != null && !getSlashCompatibleRootName().equals(payload.getName())) {
return false;
}
if(getSlashCompatibleGroupName() != null && payload.getSubcommandGroup() != null && !getSlashCompatibleGroupName().equals(payload.getSubcommandGroup())) {
return false;
}
if(getSlashCompatibleCommandName() != null && payload.getSubcommandName() != null && !getSlashCompatibleCommandName().equals(payload.getSubcommandName())) {
return false;
}
return true;
}
public String getSlashCompatibleRootName() {
return rootCommandName != null ? rootCommandName.toLowerCase(Locale.ROOT) : null;
}
public String getSlashCompatibleGroupName() {
return groupName != null ? groupName.toLowerCase(Locale.ROOT) : null;
}
public String getSlashCompatibleCommandName() {
return commandName != null ? commandName.toLowerCase(Locale.ROOT) : null;
}
}

View File

@@ -11,7 +11,7 @@ public class ChannelNotInChannelGroupException extends AbstractoRunTimeException
private final ChannelNotInChannelGroupExceptionModel model;
public ChannelNotInChannelGroupException(AChannel channel, AChannelGroup group) {
super("Channel is already part of channel group");
super("Channel is not part of channel group");
this.model = ChannelNotInChannelGroupExceptionModel.builder().channel(channel).channelGroup(group).build();
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.core.command.exception;
import dev.sheldan.abstracto.core.command.model.exception.SlashCommandParameterMissingModel;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
public class SlashCommandParameterMissingException extends AbstractoRunTimeException implements Templatable {
private final SlashCommandParameterMissingModel model;
public SlashCommandParameterMissingException(String parameterName) {
this.model = SlashCommandParameterMissingModel
.builder()
.parameterName(parameterName)
.build();
}
@Override
public String getTemplateName() {
return "slash_command_parameter_missing_exception";
}
@Override
public Object getTemplateModel() {
return model;
}
}

View File

@@ -6,6 +6,8 @@ import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.CompletableFuture;
@Getter
@Setter
@Builder
@@ -42,4 +44,8 @@ public class CommandResult {
public static CommandResult fromCondition(ConditionResult result) {
return CommandResult.builder().conditionResult(result).result(ResultState.CONDITION).build();
}
public static CompletableFuture<CommandResult> commandResultFutureSuccessful() {
return CompletableFuture.completedFuture(fromSuccess());
}
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.core.command.handler.provided;
import dev.sheldan.abstracto.core.command.handler.CommandParameterHandler;
public interface InstantParameterHandler extends CommandParameterHandler {
}

View File

@@ -48,6 +48,9 @@ public class ACommandInAServer implements Serializable {
@Column(name = "restricted", nullable = false)
private Boolean restricted = false;
@Column(name = "slash_command_id")
private Long slashCommandId;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.core.command.model.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
@Builder
public class SlashCommandParameterMissingModel implements Serializable {
private String parameterName;
}

View File

@@ -7,6 +7,7 @@ import dev.sheldan.abstracto.core.command.model.database.ACommand;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.ServerIdChannelId;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import java.time.Duration;
@@ -21,6 +22,7 @@ public interface CommandCoolDownService {
*/
void releaseLock();
CoolDownCheckResult allowedToExecuteCommand(Command command, CommandContext context);
CoolDownCheckResult allowedToExecuteCommand(Command command, SlashCommandInteractionEvent slashCommandInteractionEvent);
Duration getServerCoolDownForCommand(Command command, Long serverId);
Duration getServerCoolDownForCommand(ACommand aCommand, Command command, Long serverId);
Duration getChannelGroupCoolDownForCommand(Command command, ServerIdChannelId serverIdChannelId);
@@ -34,6 +36,7 @@ public interface CommandCoolDownService {
void addMemberCoolDown(Command command, AServerChannelUserId context);
void addMemberCoolDown(Command command, AServerChannelUserId context, boolean takeLock);
void updateCoolDowns(Command command, CommandContext context);
void updateCoolDowns(Command command, SlashCommandInteractionEvent event);
void setCoolDownConfigForChannelGroup(AChannelGroup aChannelGroup, Duration groupCoolDown, Duration memberCoolDown);
void clearCoolDownsForServer(Long serverId);
}

View File

@@ -11,6 +11,7 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import java.util.concurrent.CompletableFuture;
@@ -25,6 +26,7 @@ public interface CommandService {
void disAllowCommandForRole(ACommand aCommand, ARole role);
void disAllowFeatureForRole(FeatureDefinition featureDefinition, ARole role);
CompletableFuture<ConditionResult> isCommandExecutable(Command command, CommandContext commandContext);
CompletableFuture<ConditionResult> isCommandExecutable(Command command, SlashCommandInteractionEvent slashCommandInteractionEvent);
UnParsedCommandParameter getUnParsedCommandParameter(String messageContent, Message message);
CompletableFuture<Parameters> getParametersForCommand(String commandName, Message messageContainingContent);
Parameter cloneParameter(Parameter parameter);

View File

@@ -7,11 +7,13 @@ import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
public interface ExceptionService {
CommandResult reportExceptionToContext(Throwable exception, CommandContext context, Command command);
void reportExceptionToInteraction(Throwable exception, ButtonClickedListenerModel interActionContext, ButtonClickedListener executedListener);
void reportSlashException(Throwable exception, SlashCommandInteractionEvent event, Command command);
void reportExceptionToGuildMessageReceivedContext(Throwable exception, MessageReceivedEvent event);
void reportExceptionToPrivateMessageReceivedContext(Throwable exception, MessageReceivedEvent event);
void reportExceptionToChannel(Throwable exception, MessageChannel channel, Member member);

View File

@@ -3,7 +3,10 @@ package dev.sheldan.abstracto.core.command.service;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
public interface PostCommandExecution {
void execute(CommandContext commandContext, CommandResult commandResult, Command command);
default void executeSlash(SlashCommandInteractionEvent interaction, CommandResult commandResult, Command command) {}
default boolean supportsSlash() {return false;}
}

View File

@@ -4,9 +4,14 @@ import dev.sheldan.abstracto.core.command.model.database.ACommand;
import dev.sheldan.abstracto.core.command.model.database.ACommandInAServer;
import dev.sheldan.abstracto.core.models.database.AServer;
import java.util.List;
public interface CommandInServerManagementService {
ACommandInAServer createCommandInServer(ACommand command, AServer server);
ACommandInAServer createCommandInServer(ACommand command, AServer server, Long commandId);
boolean doesCommandExistInServer(ACommand command, AServer server);
ACommandInAServer getCommandForServer(ACommand command, AServer server);
ACommandInAServer getCommandForServer(ACommand command, Long serverId);
ACommandInAServer getCommandForServer(Long commandInServerId);
List<ACommandInAServer> getCommandsForServer(List<Long> commandInServerId);
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.abstracto.core.command.slash;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import org.springframework.data.util.Pair;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface SlashCommandService {
void convertCommandConfigToCommandData(CommandConfiguration commandConfiguration, List<Pair<List<CommandConfiguration>, SlashCommandData>> existingCommands);
CompletableFuture<List<Command>> updateGuildSlashCommand(Guild guild, List<Pair<List<CommandConfiguration>, SlashCommandData>> commandData);
CompletableFuture<Void> deleteGuildSlashCommands(Guild guild, List<Long> slashCommandId, List<Long> commandInServerIdsToUnset);
CompletableFuture<Void> addGuildSlashCommands(Guild guild, List<Pair<List<CommandConfiguration>, SlashCommandData>> commandData);
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.core.command.slash.parameter;
import lombok.Builder;
import lombok.Getter;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import java.util.List;
@Getter
@Builder
public class SlashCommandOptionTypeMapping {
private Class type;
private List<OptionType> optionTypes;
}

View File

@@ -0,0 +1,22 @@
package dev.sheldan.abstracto.core.command.slash.parameter;
import dev.sheldan.abstracto.core.models.database.AEmote;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import java.util.List;
public interface SlashCommandParameterService {
<T, Z> Z getCommandOption(String name, SlashCommandInteractionEvent event, Class<T> parameterType, Class<Z> slashParameterType);
<T, Z> boolean hasCommandOption(String name, SlashCommandInteractionEvent event, Class<T> parameterType, Class<Z> slashParameterType);
<T> T getCommandOption(String name, SlashCommandInteractionEvent event, Class<T> parameterType);
Object getCommandOption(String name, SlashCommandInteractionEvent event);
Boolean hasCommandOption(String name, SlashCommandInteractionEvent event);
Boolean hasCommandOptionWithFullType(String name, SlashCommandInteractionEvent event, OptionType optionType);
AEmote loadAEmoteFromString(String input, SlashCommandInteractionEvent event);
Emoji loadEmoteFromString(String input, SlashCommandInteractionEvent event);
List<OptionType> getTypesFromParameter(Class clazz);
String getFullQualifiedParameterName(String name, OptionType type);
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.core.command.slash.parameter.provider;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandOptionTypeMapping;
public interface SlashCommandParameterProvider {
SlashCommandOptionTypeMapping getOptionMapping();
}

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.interaction;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -10,4 +11,9 @@ import java.util.concurrent.CompletableFuture;
public interface InteractionService {
List<CompletableFuture<Message>> sendMessageToInteraction(MessageToSend messageToSend, InteractionHook interactionHook);
List<CompletableFuture<Message>> sendMessageToInteraction(String templateKey, Object model, InteractionHook interactionHook);
CompletableFuture<InteractionHook> replyEmbed(String templateKey, Object model, IReplyCallback callback);
CompletableFuture<InteractionHook> replyEmbed(String templateKey, IReplyCallback callback);
CompletableFuture<Message> editOriginal(MessageToSend messageToSend, InteractionHook interactionHook);
CompletableFuture<InteractionHook> replyMessageToSend(MessageToSend messageToSend, IReplyCallback callback);
CompletableFuture<InteractionHook> replyMessage(String templateKey, Object model, IReplyCallback callback);
}

View File

@@ -19,6 +19,7 @@ public class FeatureValidationResult implements Templatable {
private Boolean validationResult;
@Builder.Default
private List<ValidationErrorModel> validationErrorModels = new ArrayList<>();
private String validationText;
public static FeatureValidationResult validationSuccessful(FeatureConfig featureConfig) {
return FeatureValidationResult

View File

@@ -1,15 +1,15 @@
package dev.sheldan.abstracto.core.models.template.commands;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Getter
@Setter
@SuperBuilder
public class FeatureSwitchModel extends UserInitiatedServerContext {
@Builder
public class FeatureSwitchModel {
private List<String> features;
private String validationText;
}

View File

@@ -1,16 +1,15 @@
package dev.sheldan.abstracto.core.models.template.commands;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Getter
@SuperBuilder
@Builder
@Setter
public class FeaturesModel extends UserInitiatedServerContext {
public class FeaturesModel {
private List<FeatureFlagDisplay> features;
private List<DefaultFeatureFlagDisplay> defaultFeatures;
}

View File

@@ -1,16 +1,15 @@
package dev.sheldan.abstracto.core.models.template.commands;
import dev.sheldan.abstracto.core.models.context.SlimUserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.time.Instant;
@Getter
@Setter
@SuperBuilder
public class GetCustomTemplateModel extends SlimUserInitiatedServerContext {
@Builder
public class GetCustomTemplateModel {
private String templateKey;
private String templateContent;
private Instant lastModified;

View File

@@ -1,16 +1,15 @@
package dev.sheldan.abstracto.core.models.template.commands;
import dev.sheldan.abstracto.core.models.context.SlimUserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.time.Instant;
@Getter
@Setter
@SuperBuilder
public class GetTemplateModel extends SlimUserInitiatedServerContext {
@Builder
public class GetTemplateModel {
private String templateKey;
private String templateContent;
private Instant lastModified;

View File

@@ -1,15 +1,14 @@
package dev.sheldan.abstracto.core.models.template.commands;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Getter
@Setter
@SuperBuilder
public class ListChannelGroupsModel extends UserInitiatedServerContext {
@Builder
public class ListChannelGroupsModel {
private List<ChannelGroupModel> groups;
}

View File

@@ -1,15 +1,14 @@
package dev.sheldan.abstracto.core.models.template.commands;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Getter
@Setter
@SuperBuilder
public class PostTargetDisplayModel extends UserInitiatedServerContext {
@Builder
public class PostTargetDisplayModel {
private List<PostTargetModelEntry> postTargets;
}

View File

@@ -1,16 +1,15 @@
package dev.sheldan.abstracto.core.models.template.commands;
import dev.sheldan.abstracto.core.models.context.SlimUserInitiatedServerContext;
import dev.sheldan.abstracto.core.models.database.ProfanityGroup;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Getter
@Setter
@SuperBuilder
public class ProfanityConfigModel extends SlimUserInitiatedServerContext {
@Builder
public class ProfanityConfigModel {
private List<ProfanityGroup> profanityGroups;
}

View File

@@ -2,18 +2,17 @@ package dev.sheldan.abstracto.core.models.template.commands.help;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.CommandCoolDownConfig;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Role;
import java.util.List;
@Getter
@Setter
@SuperBuilder
public class HelpCommandDetailsModel extends UserInitiatedServerContext {
@Builder
public class HelpCommandDetailsModel {
private CommandConfiguration command;
private List<String> serverSpecificAliases;
private String usage;

View File

@@ -2,17 +2,16 @@ package dev.sheldan.abstracto.core.models.template.commands.help;
import dev.sheldan.abstracto.core.command.config.ModuleDefinition;
import dev.sheldan.abstracto.core.command.config.SingleLevelPackedModule;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Getter
@Setter
@SuperBuilder
public class HelpModuleDetailsModel extends UserInitiatedServerContext {
@Builder
public class HelpModuleDetailsModel {
private SingleLevelPackedModule module;
private List<ModuleDefinition> subModules;
}

View File

@@ -1,16 +1,15 @@
package dev.sheldan.abstracto.core.models.template.commands.help;
import dev.sheldan.abstracto.core.command.config.ModuleDefinition;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Getter
@Setter
@SuperBuilder
public class HelpModuleOverviewModel extends UserInitiatedServerContext {
@Builder
public class HelpModuleOverviewModel {
private List<ModuleDefinition> modules;
}

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.database.ChannelGroupType;
import dev.sheldan.abstracto.core.models.template.commands.ChannelGroupModel;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.entities.TextChannel;
import java.util.List;
@@ -11,7 +12,7 @@ import java.util.List;
public interface ChannelGroupService {
AChannelGroup createChannelGroup(String name, Long serverId, ChannelGroupType channelGroupType);
void deleteChannelGroup(String name, Long serverId);
void addChannelToChannelGroup(String channelGroupName, TextChannel textChannel);
void addChannelToChannelGroup(String channelGroupName, GuildChannel textChannel);
void addChannelToChannelGroup(String channelGroupName, Long channelId, Long serverId);
void addChannelToChannelGroup(String channelGroupName, AChannel channel);
void removeChannelFromChannelGroup(String channelGroupName, TextChannel textChannel);

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.models.cache.CachedEmote;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.cache.CachedReactions;
import dev.sheldan.abstracto.core.models.database.AEmote;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.entities.Emote;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.MessageReaction;
@@ -26,6 +27,7 @@ public interface EmoteService {
boolean compareCachedEmoteWithAEmote(CachedEmote a, AEmote b);
AEmote getFakeEmote(Object object);
AEmote getFakeEmoteFromEmote(Emote emote);
AEmote getFakeEmoteFromEmoji(Emoji emoji);
boolean emoteIsFromGuild(Emote emote, Guild guild);
CompletableFuture<Emote> getEmoteFromCachedEmote(CachedEmote cachedEmote);

View File

@@ -44,4 +44,5 @@ public interface MessageService {
CompletableFuture<Void> deleteMessage(Message message);
CompletableFuture<Void> editMessageWithActionRows(Message message, List<ActionRow> rows);
CompletableFuture<Message> editMessageWithActionRowsMessage(Message message, List<ActionRow> rows);
CompletableFuture<Void> pinMessage(Message message);
}

View File

@@ -2,9 +2,11 @@ package dev.sheldan.abstracto.core.service;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
import java.util.concurrent.CompletableFuture;
public interface PaginatorService {
CompletableFuture<Void> createPaginatorFromTemplate(String templateKey, Object model, GuildMessageChannel textChannel, Long userId);
CompletableFuture<Void> createPaginatorFromTemplate(String templateKey, Object model, IReplyCallback callback);
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.core.templating.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.utils.AttachmentOption;
import java.io.File;
import java.util.List;
@Getter
@Setter
@Builder
public class AttachedFile {
private File file;
private String fileName;
private List<AttachmentOption> options;
}

View File

@@ -7,7 +7,6 @@ import lombok.Setter;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -33,7 +32,7 @@ public class MessageToSend {
/**
* The file handle to send attached to the message.
*/
private File fileToSend;
private List<AttachedFile> attachedFiles;
private MessageConfig messageConfig;
private Long referencedMessageId;
@Builder.Default
@@ -44,8 +43,8 @@ public class MessageToSend {
@Builder.Default
private Boolean ephemeral = false;
public boolean hasFileToSend() {
return fileToSend != null;
public boolean hasFilesToSend() {
return attachedFiles != null && !attachedFiles.isEmpty();
}
@Getter

View File

@@ -0,0 +1,33 @@
package dev.sheldan.abstracto.core.utils;
import dev.sheldan.abstracto.core.models.SnowFlake;
import net.dv8tion.jda.api.entities.ISnowflake;
import java.time.Instant;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class SnowflakeUtils {
private SnowflakeUtils() {
}
public static Set<Long> getOwnItemsIds(List<? extends SnowFlake> elements){
return elements.stream().map(SnowFlake::getId).collect(Collectors.toSet());
}
public static Set<Long> getSnowflakeIds(List<? extends ISnowflake> elements){
return elements.stream().map(ISnowflake::getIdLong).collect(Collectors.toSet());
}
// just so we can use it for reference, this is not a valid snowflake
public static Long createSnowFlake(Instant pointInTime) {
return (pointInTime.toEpochMilli() - 1420070400000L) << 22;
}
public static Long createSnowFlake() {
return createSnowFlake(Instant.now());
}
}

View File

@@ -51,6 +51,9 @@ public class FeatureModeConditionTest {
@Test
public void testNoLimitations() {
when(command.getFeatureModeLimitations()).thenReturn(new ArrayList<>());
when(userInitiatedServerContext.getGuild()).thenReturn(server);
when(server.getIdLong()).thenReturn(SERVER_ID);
when(commandContext.getUserInitiatedContext()).thenReturn(userInitiatedServerContext);
CommandTestUtilities.checkSuccessfulCondition(testUnit.shouldExecute(commandContext, command));
}