[AB-xxx] adding support for user installable apps to varying commands

This commit is contained in:
Sheldan
2024-04-02 23:48:27 +02:00
parent cd3378df32
commit 732535850b
98 changed files with 1184 additions and 773 deletions

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.condition.detail.AdminModeDetail;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.service.ServerService;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@@ -42,6 +43,9 @@ public class AdminModeCondition implements CommandCondition {
@Override
public ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
if(ContextUtils.isUserCommand(slashCommandInteractionEvent)) {
return ConditionResult.SUCCESS;
}
boolean adminModeActive = service.adminModeActive(slashCommandInteractionEvent.getGuild());
if(adminModeActive){
if(slashCommandInteractionEvent.getMember().hasPermission(Permission.ADMINISTRATOR)) {

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 dev.sheldan.abstracto.core.utils.ContextUtils;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -39,6 +40,9 @@ public class CommandCoolDownCondition implements CommandCondition {
@Override
public ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
if(ContextUtils.isUserCommand(slashCommandInteractionEvent)) {
return ConditionResult.SUCCESS;
}
commandCoolDownService.takeLock();
try {
CoolDownCheckResult result = commandCoolDownService.allowedToExecuteCommand(command, slashCommandInteractionEvent);

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.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
@@ -29,6 +30,9 @@ public class FeatureEnabledCondition implements CommandCondition {
@Override
public ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
if(ContextUtils.isUserCommand(slashCommandInteractionEvent)) {
return ConditionResult.SUCCESS;
}
Long serverId = slashCommandInteractionEvent.getGuild().getIdLong();
return evaluateFeatureCondition(command, serverId);
}

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 dev.sheldan.abstracto.core.utils.ContextUtils;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -50,6 +51,9 @@ public class FeatureModeCondition implements CommandCondition {
@Override
public ConditionResult shouldExecute(SlashCommandInteractionEvent slashCommandInteractionEvent, Command command) {
if(ContextUtils.isUserCommand(slashCommandInteractionEvent)) {
return ConditionResult.SUCCESS;
}
Long serverId = slashCommandInteractionEvent.getGuild().getIdLong();
return checkFeatureModeCondition(command, serverId);
}

View File

@@ -10,6 +10,7 @@ import dev.sheldan.abstracto.core.models.database.RoleImmunity;
import dev.sheldan.abstracto.core.service.RoleImmunityService;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
@@ -113,6 +114,9 @@ public class ImmuneUserCondition implements CommandCondition {
@Override
public CompletableFuture<ConditionResult> shouldExecuteAsync(SlashCommandInteractionEvent event, Command command) {
if(ContextUtils.isUserCommand(event)) {
return CompletableFuture.completedFuture(ConditionResult.SUCCESS);
}
CommandConfiguration commandConfig = command.getConfiguration();
if(commandConfig.getEffects().isEmpty()) {
return ConditionResult.fromAsyncSuccess();

View File

@@ -8,7 +8,9 @@ import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
@Getter @Builder @EqualsAndHashCode
@Getter
@Builder
@EqualsAndHashCode
public class CommandConfiguration {
private String name;
@@ -56,4 +58,8 @@ public class CommandConfiguration {
.enabled(false)
.build();
public boolean isUserInstallable() {
return slashCommandConfig != null && slashCommandConfig.isUserInstallable();
}
}

View File

@@ -39,6 +39,10 @@ public class Parameter implements Serializable {
private List<String> dependentFeatures = new ArrayList<>();
@Builder.Default
private List<String> choices = new ArrayList<>();
@Builder.Default
private Boolean useStrictParameters = false;
@Builder.Default
private Boolean supportsUserCommands = true;
public String getSlashCompatibleName() {
return name.toLowerCase(Locale.ROOT);

View File

@@ -0,0 +1,35 @@
package dev.sheldan.abstracto.core.command.config;
import lombok.Builder;
import lombok.Getter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Getter
@Builder
public class UserCommandConfig {
@Builder.Default
private Set<CommandContext> contexts = new HashSet<>(List.of(CommandContext.GUILD));
public static UserCommandConfig guildOnly() {
return UserCommandConfig
.builder()
.contexts(Set.of(CommandContext.GUILD))
.build();
}
public static UserCommandConfig all() {
return UserCommandConfig
.builder()
.contexts(Set.of(CommandContext.ALL))
.build();
}
public enum CommandContext {
BOT_DM, DM, GUILD, ALL
}
}

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.interaction.button.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -19,6 +20,6 @@ public class ButtonClickedListenerModel implements FeatureAwareListenerModel {
@Override
public Long getServerId() {
return event.isFromGuild() ? event.getGuild().getIdLong() : null;
return ContextUtils.hasGuild(event) ? event.getGuild().getIdLong() : null;
}
}

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.interaction.menu.listener;
import dev.sheldan.abstracto.core.interaction.menu.SelectMenuPayload;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -19,6 +20,6 @@ public class StringSelectMenuListenerModel implements FeatureAwareListenerModel
@Override
public Long getServerId() {
return event.isFromGuild() ? event.getGuild().getIdLong() : null;
return ContextUtils.hasGuild(event) ? event.getGuild().getIdLong() : null;
}
}

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.interaction.modal.listener;
import dev.sheldan.abstracto.core.interaction.modal.ModalPayload;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -18,6 +19,6 @@ public class ModalInteractionListenerModel implements FeatureAwareListenerModel
@Override
public Long getServerId() {
return event.isFromGuild() ? event.getGuild().getIdLong() : null;
return ContextUtils.hasGuild(event) ? event.getGuild().getIdLong() : null;
}
}

View File

@@ -1,9 +1,12 @@
package dev.sheldan.abstracto.core.interaction.slash;
import dev.sheldan.abstracto.core.command.config.UserCommandConfig;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import net.dv8tion.jda.api.interactions.commands.CommandInteractionPayload;
import org.apache.commons.lang3.StringUtils;
import java.util.Locale;
@@ -13,20 +16,29 @@ import java.util.Locale;
public class SlashCommandConfig {
private boolean enabled;
private String rootCommandName;
private String userRootCommandName;
private String groupName;
private String userGroupName;
private String commandName;
private String userCommandName;
@Builder.Default
private boolean userInstallable = false;
private UserCommandConfig userCommandConfig;
public boolean matchesInteraction(CommandInteractionPayload payload) {
if(getSlashCompatibleRootName() != null && payload.getName() != null && !getSlashCompatibleRootName().equals(payload.getName())) {
String rootNameToUse = ContextUtils.isUserCommand(payload) ? StringUtils.defaultString(getUserSlashCompatibleRootName(), getSlashCompatibleRootName()) : getSlashCompatibleRootName();
String groupNameToUse = ContextUtils.isUserCommand(payload) ? StringUtils.defaultString(getUserSlashCompatibleGroupName(), getSlashCompatibleGroupName()) : getSlashCompatibleGroupName();
String commandNameToUse = ContextUtils.isUserCommand(payload) ? StringUtils.defaultString(getUserSlashCompatibleCommandName(), getSlashCompatibleCommandName()) : getSlashCompatibleCommandName();
if(!StringUtils.equals(rootNameToUse, payload.getName())) {
return false;
}
if(getSlashCompatibleGroupName() != null && payload.getSubcommandGroup() != null && !getSlashCompatibleGroupName().equals(payload.getSubcommandGroup())) {
if(!StringUtils.equals(groupNameToUse, payload.getSubcommandGroup())) {
return false;
}
if(getSlashCompatibleCommandName() != null && payload.getSubcommandName() != null && !getSlashCompatibleCommandName().equals(payload.getSubcommandName())) {
if(!StringUtils.equals(commandNameToUse, payload.getSubcommandName())) {
return false;
}
return true;
}
@@ -41,4 +53,16 @@ public class SlashCommandConfig {
public String getSlashCompatibleCommandName() {
return commandName != null ? commandName.toLowerCase(Locale.ROOT) : null;
}
public String getUserSlashCompatibleRootName() {
return userRootCommandName != null ? userRootCommandName.toLowerCase(Locale.ROOT) : null;
}
public String getUserSlashCompatibleGroupName() {
return userGroupName != null ? userGroupName.toLowerCase(Locale.ROOT) : null;
}
public String getUserSlashCompatibleCommandName() {
return userCommandName != null ? userCommandName.toLowerCase(Locale.ROOT) : null;
}
}

View File

@@ -12,7 +12,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface SlashCommandService {
void convertCommandConfigToCommandData(CommandConfiguration commandConfiguration, List<Pair<List<CommandConfiguration>, SlashCommandData>> existingCommands, Long serverId);
void convertCommandConfigToCommandData(CommandConfiguration commandConfiguration, List<Pair<List<CommandConfiguration>, SlashCommandData>> existingCommands, Long serverId, boolean userCommandsOnly);
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);

View File

@@ -4,6 +4,7 @@ import lombok.Builder;
import lombok.Getter;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import java.util.ArrayList;
import java.util.List;
@Getter
@@ -11,4 +12,6 @@ import java.util.List;
public class SlashCommandOptionTypeMapping {
private Class type;
private List<OptionType> optionTypes;
@Builder.Default
private List<OptionType> strictTypes = new ArrayList<>();
}

View File

@@ -21,5 +21,6 @@ public interface SlashCommandParameterService {
Emoji loadEmoteFromString(String input, Guild guild);
List<OptionType> getTypesFromParameter(Parameter parameter);
List<OptionType> getTypesFromParameter(Class clazz);
List<OptionType> getTypesFromParameter(Class clazz, boolean strict);
String getFullQualifiedParameterName(String name, OptionType type);
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.models.listener.interaction;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import dev.sheldan.abstracto.core.utils.ContextUtils;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -14,6 +15,6 @@ public class MessageContextInteractionModel implements FeatureAwareListenerModel
@Override
public Long getServerId() {
return event.isFromGuild() ? event.getGuild().getIdLong() : null;
return ContextUtils.hasGuild(event) ? event.getGuild().getIdLong() : null;
}
}

View File

@@ -5,6 +5,7 @@ import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@Getter
@Setter
@@ -13,18 +14,7 @@ public class ChannelDisplay {
private String name;
private String channelMention;
public static ChannelDisplay fromChannel(TextChannel channel) {
if(channel == null) {
return null;
}
return ChannelDisplay
.builder()
.name(channel.getName())
.channelMention(channel.getAsMention())
.build();
}
public static ChannelDisplay fromChannel(GuildMessageChannel channel) {
public static ChannelDisplay fromChannel(MessageChannel channel) {
if(channel == null) {
return null;
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.models.template.display;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.utils.MemberUtils;
import lombok.Builder;
@@ -38,6 +39,14 @@ public class MemberDisplay {
.build();
}
public static MemberDisplay fromAUser(AUser aUser) {
return MemberDisplay
.builder()
.memberMention(MemberUtils.getUserAsMention(aUser.getId()))
.userId(aUser.getId())
.build();
}
public static MemberDisplay fromIds(Long serverId, Long userId) {
return MemberDisplay
.builder()

View File

@@ -11,6 +11,7 @@ import net.dv8tion.jda.api.entities.User;
@Builder
public class MemberNameDisplay {
private String userName;
private String displayName;
private String nickname;
private String discriminator;
private String userAvatarUrl;
@@ -25,6 +26,7 @@ public class MemberNameDisplay {
.memberAvatarUrl(member.getEffectiveAvatar().getUrl(4096))
.nickname(member.getNickname())
.userName(user.getName())
.displayName(user.getGlobalName())
.memberMention(member.getAsMention())
.userAvatarUrl(userAvatar)
.discriminator(user.getDiscriminator())

View File

@@ -1,47 +1,42 @@
package dev.sheldan.abstracto.core.utils;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.GuildChannelMember;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import net.dv8tion.jda.api.interactions.Interaction;
import net.dv8tion.jda.api.interactions.InteractionHook;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@Component
@Slf4j
public class ContextUtils {
@Autowired
private ChannelManagementService channelManagementService;
public static boolean isGuildKnown(Interaction interaction) {
return interaction.hasFullGuild();
}
@Autowired
private UserInServerManagementService userInServerManagementService;
public static boolean isGuildNotKnown(Interaction interaction) {
return !isGuildKnown(interaction);
}
@Autowired
private MemberService memberService;
public static boolean hasGuild(Interaction interaction) {
return interaction.getGuild() != null;
}
public <T extends UserInitiatedServerContext> UserInitiatedServerContext fromMessage(CachedMessage message, Class<T> clazz) {
Method m = null;
GuildChannelMember guildChannelMember = memberService.getServerChannelUser(message.getServerId(), message.getChannelId(), message.getAuthor().getAuthorId());
try {
m = clazz.getMethod("builder");
UserInitiatedServerContext.UserInitiatedServerContextBuilder<?, ?> builder = (UserInitiatedServerContext.UserInitiatedServerContextBuilder) m.invoke(null, null);
return builder
.member(guildChannelMember.getMember())
.guild(guildChannelMember.getGuild())
.messageChannel((GuildMessageChannel) guildChannelMember.getTextChannel())
.build();
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("Failed to execute builder method", e);
}
throw new AbstractoRunTimeException("Failed to create UserInitiatedServerContext from message");
public static boolean isUserCommandInGuild(Interaction interaction) {
return isUserCommand(interaction) && interaction.getGuild() != null && interaction.getGuild().isDetached();
}
public static boolean isUserCommand(Interaction interaction) {
return interaction.getIntegrationOwners().getUserIntegration() != null && interaction.getIntegrationOwners().getGuildIntegration() == null;
}
public static boolean isNotUserCommand(Interaction interaction) {
return !isUserCommand(interaction);
}
public static Long serverIdOrNull(Interaction interaction) {
return ContextUtils.isGuildKnown(interaction) ? interaction.getGuild().getIdLong() : null;
}
public static Long serverIdOrNull(InteractionHook hook) {
return ContextUtils.isGuildKnown(hook.getInteraction()) ? hook.getInteraction().getGuild().getIdLong() : null;
}
}

View File

@@ -7,6 +7,9 @@ public class MessageUtils {
}
public static String buildMessageUrl(Long serverId, Long channelId, Long messageId) {
if(serverId == null || channelId == null || messageId == null) {
return null;
}
return String.format("https://discord.com/channels/%s/%s/%s", serverId, channelId, messageId);
}
}