mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-21 11:45:25 +01:00
Add custom commands
This commit is contained in:
parent
b8f8b7d585
commit
7d4755240f
@ -39,6 +39,13 @@ import java.util.concurrent.CompletableFuture;
|
||||
*/
|
||||
public interface DiscordAPI {
|
||||
|
||||
/**
|
||||
* Gets a Discord channel by id, the provided entity should not be stored for long periods of time.
|
||||
* @param id the id for the channel
|
||||
* @return the channel
|
||||
*/
|
||||
DiscordChannel getChannelById(long id);
|
||||
|
||||
/**
|
||||
* Gets a Discord message channel by id, the provided entity should not be stored for long periods of time.
|
||||
* @param id the id for the message channel
|
||||
@ -95,7 +102,6 @@ public interface DiscordAPI {
|
||||
@Nullable
|
||||
DiscordStageChannel getStageChannelById(long id);
|
||||
|
||||
|
||||
/**
|
||||
* Gets a Discord thread channel by id from the cache, the provided entity should not be stored for long periods of time.
|
||||
* @param id the id for the thread channel
|
||||
|
@ -55,9 +55,9 @@ public class DiscordCommand implements JDAEntity<CommandData> {
|
||||
* @see DiscordChatInputInteractionEvent
|
||||
*/
|
||||
public static ChatInputBuilder chatInput(
|
||||
ComponentIdentifier id,
|
||||
@org.intellij.lang.annotations.Pattern(CHAT_INPUT_NAME_REGEX) String name,
|
||||
String description
|
||||
@NotNull ComponentIdentifier id,
|
||||
@NotNull @org.intellij.lang.annotations.Pattern(CHAT_INPUT_NAME_REGEX) String name,
|
||||
@NotNull String description
|
||||
) {
|
||||
if (!CHAT_INPUT_NAME_PATTERN.matcher(name).matches()) {
|
||||
throw new IllegalArgumentException("Name must be alphanumeric (dashes allowed), 1 and 32 characters");
|
||||
@ -74,8 +74,8 @@ public class DiscordCommand implements JDAEntity<CommandData> {
|
||||
* @see DiscordUserContextInteractionEvent
|
||||
*/
|
||||
public static Builder<DiscordUserContextInteractionEvent> user(
|
||||
ComponentIdentifier id,
|
||||
@org.intellij.lang.annotations.Pattern(".{1,32}") String name
|
||||
@NotNull ComponentIdentifier id,
|
||||
@NotNull @org.intellij.lang.annotations.Pattern(".{1,32}") String name
|
||||
) {
|
||||
return new Builder<>(id, CommandType.USER, name);
|
||||
}
|
||||
@ -89,8 +89,8 @@ public class DiscordCommand implements JDAEntity<CommandData> {
|
||||
* @see DiscordMessageContextInteractionEvent
|
||||
*/
|
||||
public static Builder<DiscordMessageContextInteractionEvent> message(
|
||||
ComponentIdentifier id,
|
||||
@org.intellij.lang.annotations.Pattern(".{1,32}") String name
|
||||
@NotNull ComponentIdentifier id,
|
||||
@NotNull @org.intellij.lang.annotations.Pattern(".{1,32}") String name
|
||||
) {
|
||||
return new Builder<>(id, CommandType.MESSAGE, name);
|
||||
}
|
||||
|
@ -373,10 +373,18 @@ public interface SendableDiscordMessage {
|
||||
@NotNull
|
||||
Formatter addContext(Object... context);
|
||||
|
||||
default Formatter addPlaceholder(String placeholder, Object replacement, String reLookup) {
|
||||
return addContext(new SinglePlaceholder(placeholder, replacement, reLookup));
|
||||
}
|
||||
|
||||
default Formatter addPlaceholder(String placeholder, Object replacement) {
|
||||
return addContext(new SinglePlaceholder(placeholder, replacement));
|
||||
}
|
||||
|
||||
default Formatter addPlaceholder(String placeholder, Supplier<Object> replacementSupplier, String reLookup) {
|
||||
return addContext(new SinglePlaceholder(placeholder, replacementSupplier, reLookup));
|
||||
}
|
||||
|
||||
default Formatter addPlaceholder(String placeholder, Supplier<Object> replacementSupplier) {
|
||||
return addContext(new SinglePlaceholder(placeholder, replacementSupplier));
|
||||
}
|
||||
|
@ -23,9 +23,12 @@
|
||||
|
||||
package com.discordsrv.api.events.discord.interaction.command;
|
||||
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.discord.entity.interaction.DiscordInteractionHook;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
@ -39,7 +42,10 @@ import java.util.concurrent.CompletableFuture;
|
||||
public abstract class AbstractCommandInteractionEvent<E extends GenericCommandInteractionEvent>
|
||||
extends AbstractDeferrableInteractionEvent<E> {
|
||||
|
||||
private final DiscordSRVApi discordSRV;
|
||||
|
||||
public AbstractCommandInteractionEvent(
|
||||
DiscordSRVApi discordSRV,
|
||||
E jdaEvent,
|
||||
ComponentIdentifier identifier,
|
||||
DiscordUser user,
|
||||
@ -48,6 +54,7 @@ public abstract class AbstractCommandInteractionEvent<E extends GenericCommandIn
|
||||
DiscordInteractionHook interaction
|
||||
) {
|
||||
super(jdaEvent, identifier, user, member, channel, interaction);
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
public abstract CompletableFuture<DiscordInteractionHook> reply(SendableDiscordMessage message, boolean ephemeral);
|
||||
@ -57,8 +64,65 @@ public abstract class AbstractCommandInteractionEvent<E extends GenericCommandIn
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getOption(String name) {
|
||||
public String getOptionAsString(String name) {
|
||||
OptionMapping mapping = jdaEvent.getOption(name);
|
||||
return mapping != null ? mapping.getAsString() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DiscordUser getOptionAsUser(String name) {
|
||||
OptionMapping mapping = jdaEvent.getOption(name);
|
||||
if (mapping == null) {
|
||||
return null;
|
||||
}
|
||||
long id = mapping.getAsLong();
|
||||
return discordSRV.discordAPI().getUserById(id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DiscordRole getOptionAsRole(String name) {
|
||||
OptionMapping mapping = jdaEvent.getOption(name);
|
||||
if (mapping == null) {
|
||||
return null;
|
||||
}
|
||||
long id = mapping.getAsLong();
|
||||
return discordSRV.discordAPI().getRoleById(id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DiscordChannel getOptionAsChannel(String name) {
|
||||
OptionMapping mapping = jdaEvent.getOption(name);
|
||||
if (mapping == null) {
|
||||
return null;
|
||||
}
|
||||
long id = mapping.getAsLong();
|
||||
return discordSRV.discordAPI().getChannelById(id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getOptionAsLong(String name) {
|
||||
OptionMapping mapping = jdaEvent.getOption(name);
|
||||
if (mapping == null) {
|
||||
return null;
|
||||
}
|
||||
return mapping.getAsLong();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Double getOptionAsDouble(String name) {
|
||||
OptionMapping mapping = jdaEvent.getOption(name);
|
||||
if (mapping == null) {
|
||||
return null;
|
||||
}
|
||||
return mapping.getAsDouble();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean getOptionAsBoolean(String name) {
|
||||
OptionMapping mapping = jdaEvent.getOption(name);
|
||||
if (mapping == null) {
|
||||
return null;
|
||||
}
|
||||
return mapping.getAsBoolean();
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
package com.discordsrv.api.events.discord.interaction.command;
|
||||
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
@ -33,6 +34,7 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve
|
||||
public abstract class DiscordChatInputInteractionEvent extends AbstractCommandInteractionEvent<SlashCommandInteractionEvent> {
|
||||
|
||||
public DiscordChatInputInteractionEvent(
|
||||
DiscordSRVApi discordSRV,
|
||||
SlashCommandInteractionEvent jdaEvent,
|
||||
ComponentIdentifier identifier,
|
||||
DiscordUser user,
|
||||
@ -40,6 +42,6 @@ public abstract class DiscordChatInputInteractionEvent extends AbstractCommandIn
|
||||
DiscordMessageChannel channel,
|
||||
DiscordInteractionHook interaction
|
||||
) {
|
||||
super(jdaEvent, identifier, user, member, channel, interaction);
|
||||
super(discordSRV, jdaEvent, identifier, user, member, channel, interaction);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
package com.discordsrv.api.events.discord.interaction.command;
|
||||
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
@ -34,6 +35,7 @@ public abstract class DiscordMessageContextInteractionEvent
|
||||
extends AbstractCommandInteractionEvent<MessageContextInteractionEvent> {
|
||||
|
||||
public DiscordMessageContextInteractionEvent(
|
||||
DiscordSRVApi discordSRV,
|
||||
MessageContextInteractionEvent jdaEvent,
|
||||
ComponentIdentifier identifier,
|
||||
DiscordUser user,
|
||||
@ -41,6 +43,6 @@ public abstract class DiscordMessageContextInteractionEvent
|
||||
DiscordMessageChannel channel,
|
||||
DiscordInteractionHook interaction
|
||||
) {
|
||||
super(jdaEvent, identifier, user, member, channel, interaction);
|
||||
super(discordSRV, jdaEvent, identifier, user, member, channel, interaction);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
package com.discordsrv.api.events.discord.interaction.command;
|
||||
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
@ -33,6 +34,7 @@ import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEven
|
||||
public abstract class DiscordUserContextInteractionEvent extends AbstractCommandInteractionEvent<UserContextInteractionEvent> {
|
||||
|
||||
public DiscordUserContextInteractionEvent(
|
||||
DiscordSRVApi discordSRV,
|
||||
UserContextInteractionEvent jdaEvent,
|
||||
ComponentIdentifier identifier,
|
||||
DiscordUser user,
|
||||
@ -40,6 +42,6 @@ public abstract class DiscordUserContextInteractionEvent extends AbstractCommand
|
||||
DiscordMessageChannel channel,
|
||||
DiscordInteractionHook interaction
|
||||
) {
|
||||
super(jdaEvent, identifier, user, member, channel, interaction);
|
||||
super(discordSRV, jdaEvent, identifier, user, member, channel, interaction);
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ package com.discordsrv.api.placeholder.provider;
|
||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ -33,26 +34,43 @@ public class SinglePlaceholder implements PlaceholderProvider {
|
||||
|
||||
private final String matchPlaceholder;
|
||||
private final Supplier<Object> resultProvider;
|
||||
private final String reLookup;
|
||||
|
||||
public SinglePlaceholder(String placeholder, Object result) {
|
||||
this(placeholder, () -> result);
|
||||
this(placeholder, result, null);
|
||||
}
|
||||
|
||||
public SinglePlaceholder(String placeholder, Object result, String reLookup) {
|
||||
this(placeholder, () -> result, reLookup);
|
||||
}
|
||||
|
||||
public SinglePlaceholder(String placeholder, Supplier<Object> resultProvider) {
|
||||
this(placeholder, resultProvider, null);
|
||||
}
|
||||
|
||||
public SinglePlaceholder(String placeholder, Supplier<Object> resultProvider, String reLookup) {
|
||||
this.matchPlaceholder = placeholder;
|
||||
this.resultProvider = resultProvider;
|
||||
this.reLookup = reLookup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PlaceholderLookupResult lookup(@NotNull String placeholder, @NotNull Set<Object> context) {
|
||||
if (!placeholder.equals(matchPlaceholder)) {
|
||||
if (!(reLookup != null ? placeholder.startsWith(matchPlaceholder) : placeholder.equals(matchPlaceholder))) {
|
||||
return PlaceholderLookupResult.UNKNOWN_PLACEHOLDER;
|
||||
}
|
||||
|
||||
try {
|
||||
return PlaceholderLookupResult.success(
|
||||
resultProvider.get()
|
||||
);
|
||||
Object result = resultProvider.get();
|
||||
if (reLookup == null) {
|
||||
return PlaceholderLookupResult.success(result);
|
||||
}
|
||||
|
||||
String newPlaceholder = reLookup + (placeholder.substring(matchPlaceholder.length()));
|
||||
Set<Object> newContext = new LinkedHashSet<>();
|
||||
newContext.add(result);
|
||||
newContext.addAll(context);
|
||||
return PlaceholderLookupResult.newLookup(newPlaceholder, newContext);
|
||||
} catch (Throwable t) {
|
||||
return PlaceholderLookupResult.lookupFailed(t);
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ import com.discordsrv.common.feature.channel.ChannelLockingModule;
|
||||
import com.discordsrv.common.feature.channel.TimedUpdaterModule;
|
||||
import com.discordsrv.common.feature.channel.global.GlobalChannelLookupModule;
|
||||
import com.discordsrv.common.feature.console.ConsoleModule;
|
||||
import com.discordsrv.common.feature.customcommands.CustomCommandModule;
|
||||
import com.discordsrv.common.feature.debug.data.VersionInfo;
|
||||
import com.discordsrv.common.feature.groupsync.GroupSyncModule;
|
||||
import com.discordsrv.common.feature.linking.LinkProvider;
|
||||
@ -595,6 +596,7 @@ public abstract class AbstractDiscordSRV<
|
||||
registerModule(LinkingModule::new);
|
||||
registerModule(PresenceUpdaterModule::new);
|
||||
registerModule(MentionGameRenderingModule::new);
|
||||
registerModule(CustomCommandModule::new);
|
||||
|
||||
// Integrations
|
||||
registerIntegration("com.discordsrv.common.integration.LuckPermsIntegration");
|
||||
|
@ -95,7 +95,7 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
|
||||
return;
|
||||
}
|
||||
|
||||
String command = event.getOption("command");
|
||||
String command = event.getOptionAsString("command");
|
||||
if (command == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ import java.util.stream.Collectors;
|
||||
public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurationLoader<CommentedConfigurationNode>>
|
||||
implements ConfigManager<T>, ConfigLoaderProvider<LT> {
|
||||
|
||||
public static final ThreadLocal<Boolean> CLEAN_MAPPER = ThreadLocal.withInitial(() -> false);
|
||||
public static final ThreadLocal<Boolean> DEFAULT_CONFIG = ThreadLocal.withInitial(() -> false);
|
||||
private static final ThreadLocal<Boolean> SAVE_OR_LOAD = ThreadLocal.withInitial(() -> false);
|
||||
|
||||
public static NamingScheme NAMING_SCHEME = in -> {
|
||||
@ -353,19 +353,19 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
|
||||
/**
|
||||
* Gets the default config given the default object from {@link #createConfiguration()}
|
||||
* @param defaultConfig the object
|
||||
* @param cleanMapper if options that are marked with {@link DefaultOnly} or serializers that make use of {@link #CLEAN_MAPPER} should be excluded from the node
|
||||
* @param cleanMapper if options that are marked with {@link DefaultOnly} or serializers that make use of {@link #DEFAULT_CONFIG} should be excluded from the node
|
||||
* @return the node with the values from the object
|
||||
* @throws SerializationException if serialization fails
|
||||
*/
|
||||
private CommentedConfigurationNode getDefault(T defaultConfig, boolean cleanMapper) throws SerializationException {
|
||||
try {
|
||||
if (cleanMapper) {
|
||||
CLEAN_MAPPER.set(true);
|
||||
DEFAULT_CONFIG.set(true);
|
||||
}
|
||||
return getDefault(defaultConfig, cleanMapper ? cleanObjectMapper() : objectMapper());
|
||||
} finally {
|
||||
if (cleanMapper) {
|
||||
CLEAN_MAPPER.set(false);
|
||||
DEFAULT_CONFIG.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +46,9 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
|
||||
|
||||
@Override
|
||||
public DiscordMessageEmbed.Builder deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
||||
if (ConfigurateConfigManager.CLEAN_MAPPER.get()) {
|
||||
return null;
|
||||
Object raw = node.raw();
|
||||
if (raw instanceof DiscordMessageEmbed.Builder) {
|
||||
return (DiscordMessageEmbed.Builder) raw;
|
||||
}
|
||||
if (!node.node(map("Enabled")).getBoolean(node.node(map("Enable")).getBoolean(true))) {
|
||||
return null;
|
||||
@ -90,10 +91,14 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
|
||||
@Override
|
||||
public void serialize(Type type, DiscordMessageEmbed.@Nullable Builder obj, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
if (obj == null || ConfigurateConfigManager.CLEAN_MAPPER.get()) {
|
||||
if (obj == null) {
|
||||
node.set(null);
|
||||
return;
|
||||
}
|
||||
if (ConfigurateConfigManager.DEFAULT_CONFIG.get()) {
|
||||
node.raw(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
node.node(map("Color")).set(obj.getColor());
|
||||
|
||||
@ -134,6 +139,11 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
|
||||
|
||||
@Override
|
||||
public DiscordMessageEmbed.Field deserialize(Type type, ConfigurationNode node) {
|
||||
Object raw = node.raw();
|
||||
if (raw instanceof DiscordMessageEmbed.Field) {
|
||||
return (DiscordMessageEmbed.Field) raw;
|
||||
}
|
||||
|
||||
// v1 compat
|
||||
String footerString = node.getString();
|
||||
if (footerString != null) {
|
||||
@ -165,6 +175,10 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
|
||||
node.set(null);
|
||||
return;
|
||||
}
|
||||
if (ConfigurateConfigManager.DEFAULT_CONFIG.get()) {
|
||||
node.raw(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
node.node(map("Title")).set(obj.getTitle());
|
||||
node.node(map("Value")).set(obj.getValue());
|
||||
|
@ -47,8 +47,13 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
||||
@Override
|
||||
public SendableDiscordMessage.Builder deserialize(Type type, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
Object raw = node.raw();
|
||||
if (raw instanceof SendableDiscordMessage.Builder) {
|
||||
return (SendableDiscordMessage.Builder) raw;
|
||||
}
|
||||
|
||||
String contentOnly = node.getString();
|
||||
if (contentOnly != null || ConfigurateConfigManager.CLEAN_MAPPER.get()) {
|
||||
if (contentOnly != null) {
|
||||
return SendableDiscordMessage.builder()
|
||||
.setContent(contentOnly);
|
||||
}
|
||||
@ -82,10 +87,14 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
||||
@Override
|
||||
public void serialize(Type type, SendableDiscordMessage.@Nullable Builder obj, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
if (obj == null || ConfigurateConfigManager.CLEAN_MAPPER.get()) {
|
||||
if (obj == null) {
|
||||
node.set(null);
|
||||
return;
|
||||
}
|
||||
if (ConfigurateConfigManager.DEFAULT_CONFIG.get()) {
|
||||
node.raw(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
String webhookUsername = obj.getWebhookUsername();
|
||||
if (webhookUsername != null) {
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main;
|
||||
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandOption;
|
||||
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigSerializable
|
||||
public class CustomCommandConfig {
|
||||
|
||||
public static CustomCommandConfig defaultIp() {
|
||||
CustomCommandConfig config = new CustomCommandConfig();
|
||||
config.command = "ip";
|
||||
config.description = "Get the Minecraft server ip";
|
||||
config.ephemeral = true;
|
||||
config.response = SendableDiscordMessage.builder().setContent("`yourserveripchange.me`");
|
||||
return config;
|
||||
}
|
||||
|
||||
public static CustomCommandConfig defaultHelloWorld() {
|
||||
CustomCommandConfig config = new CustomCommandConfig();
|
||||
config.command = "hello";
|
||||
config.description = "Greet a user";
|
||||
config.options.add(new OptionConfig());
|
||||
config.response = SendableDiscordMessage.builder()
|
||||
.addEmbed(DiscordMessageEmbed.builder().setDescription("Hello %option_target_user_name%").build());
|
||||
return config;
|
||||
}
|
||||
|
||||
public String command = "";
|
||||
public String description = "";
|
||||
public boolean ephemeral = false;
|
||||
|
||||
public List<OptionConfig> options = new ArrayList<>();
|
||||
|
||||
public SendableDiscordMessage.Builder response = SendableDiscordMessage.builder().setContent("test");
|
||||
|
||||
@ConfigSerializable
|
||||
public static class OptionConfig {
|
||||
|
||||
public CommandOption.Type type = CommandOption.Type.USER;
|
||||
public String name = "target_user";
|
||||
public String description = "The user to greet";
|
||||
public boolean required = true;
|
||||
|
||||
}
|
||||
}
|
@ -104,6 +104,11 @@ public abstract class MainConfig implements Config {
|
||||
@Comment("Options for console channel(s) and/or thread(s)")
|
||||
public List<ConsoleConfig> console = new ArrayList<>(Collections.singleton(new ConsoleConfig()));
|
||||
|
||||
public List<CustomCommandConfig> customCommands = new ArrayList<>(Arrays.asList(
|
||||
CustomCommandConfig.defaultIp(),
|
||||
CustomCommandConfig.defaultHelloWorld()
|
||||
));
|
||||
|
||||
@Comment("Configuration for the %1 placeholder. The below options will be attempted in the order they are in")
|
||||
@Constants.Comment("%discord_invite%")
|
||||
public DiscordInviteConfig invite = new DiscordInviteConfig();
|
||||
|
@ -19,7 +19,6 @@
|
||||
package com.discordsrv.common.config.main.channels;
|
||||
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.configurate.annotation.DefaultOnly;
|
||||
import com.discordsrv.common.config.configurate.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.configurate.manager.abstraction.ConfigurateConfigManager;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
@ -40,7 +39,6 @@ public class MinecraftToDiscordChatConfig implements IMessageConfig {
|
||||
public Boolean enabled = true;
|
||||
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
@DefaultOnly
|
||||
public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
|
||||
.setWebhookUsername("%player_meta_prefix|player_prefix%%player_display_name|player_name%%player_meta_suffix|player_suffix%")
|
||||
.setWebhookAvatarUrl("%player_avatar_url%")
|
||||
|
@ -113,6 +113,16 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return CompletableFutureUtil.failed(new NotReadyException());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordChannel getChannelById(long id) {
|
||||
DiscordForumChannel forumChannel = getForumChannelById(id);
|
||||
if (forumChannel != null) {
|
||||
return forumChannel;
|
||||
}
|
||||
|
||||
return getMessageChannelById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscordMessageChannel getMessageChannelById(long id) {
|
||||
DiscordTextChannel textChannel = getTextChannelById(id);
|
||||
@ -130,6 +140,11 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return voiceChannel;
|
||||
}
|
||||
|
||||
DiscordStageChannel stageChannel = getStageChannelById(id);
|
||||
if (stageChannel != null) {
|
||||
return stageChannel;
|
||||
}
|
||||
|
||||
DiscordNewsChannel newsChannel = getNewsChannelById(id);
|
||||
if (newsChannel != null) {
|
||||
return newsChannel;
|
||||
@ -153,12 +168,14 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return getTextChannel((TextChannel) jda);
|
||||
} else if (jda instanceof ThreadChannel) {
|
||||
return getThreadChannel((ThreadChannel) jda);
|
||||
} else if (jda instanceof PrivateChannel) {
|
||||
return getDirectMessageChannel((PrivateChannel) jda);
|
||||
} else if (jda instanceof NewsChannel) {
|
||||
return getNewsChannel((NewsChannel) jda);
|
||||
} else if (jda instanceof VoiceChannel) {
|
||||
return getVoiceChannel((VoiceChannel) jda);
|
||||
} else if (jda instanceof StageChannel) {
|
||||
return getStageChannel((StageChannel) jda);
|
||||
} else if (jda instanceof NewsChannel) {
|
||||
return getNewsChannel((NewsChannel) jda);
|
||||
} else if (jda instanceof PrivateChannel) {
|
||||
return getDirectMessageChannel((PrivateChannel) jda);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unmappable MessageChannel type: " + jda.getClass().getName());
|
||||
}
|
||||
@ -189,7 +206,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
|
||||
@Override
|
||||
public @Nullable DiscordNewsChannel getNewsChannelById(long id) {
|
||||
return null;
|
||||
return mapJDAEntity(jda -> jda.getNewsChannelById(id), this::getNewsChannel);
|
||||
}
|
||||
|
||||
public DiscordNewsChannelImpl getNewsChannel(NewsChannel jda) {
|
||||
|
@ -43,7 +43,7 @@ public class DiscordChatInputInteractionEventImpl extends DiscordChatInputIntera
|
||||
DiscordUser user,
|
||||
DiscordGuildMember member,
|
||||
DiscordMessageChannel channel, DiscordInteractionHook interaction) {
|
||||
super(jdaEvent, identifier, user, member, channel, interaction);
|
||||
super(discordSRV, jdaEvent, identifier, user, member, channel, interaction);
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ public class DiscordMessageContextInteractionEventImpl extends DiscordMessageCon
|
||||
DiscordUser user,
|
||||
DiscordGuildMember member,
|
||||
DiscordMessageChannel channel, DiscordInteractionHook interaction) {
|
||||
super(jdaEvent, identifier, user, member, channel, interaction);
|
||||
super(discordSRV, jdaEvent, identifier, user, member, channel, interaction);
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ public class DiscordUserContextInteractionEventImpl extends DiscordUserContextIn
|
||||
DiscordUser user,
|
||||
DiscordGuildMember member,
|
||||
DiscordMessageChannel channel, DiscordInteractionHook interaction) {
|
||||
super(jdaEvent, identifier, user, member, channel, interaction);
|
||||
super(discordSRV, jdaEvent, identifier, user, member, channel, interaction);
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.feature.customcommands;
|
||||
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandOption;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.CustomCommandConfig;
|
||||
import com.discordsrv.common.core.logging.NamedLogger;
|
||||
import com.discordsrv.common.core.module.type.AbstractModule;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.intellij.lang.annotations.Subst;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class CustomCommandModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
private final List<DiscordCommand> registeredCommands = new ArrayList<>();
|
||||
|
||||
public CustomCommandModule(DiscordSRV discordSRV) {
|
||||
super(discordSRV, new NamedLogger(discordSRV, "CUSTOM_COMMANDS"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEnableBeforeReady() {
|
||||
return discordSRV.config() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(Consumer<DiscordSRVApi.ReloadResult> resultConsumer) {
|
||||
List<CustomCommandConfig> configs = discordSRV.config().customCommands;
|
||||
|
||||
List<LayerCommand> layeredCommands = new ArrayList<>();
|
||||
int i = 0;
|
||||
for (CustomCommandConfig config : configs) {
|
||||
List<String> commandParts = Arrays.asList(config.command.split(" "));
|
||||
int parts = commandParts.size();
|
||||
if (parts > 3) {
|
||||
logger().error("Invalid command (" + config.command + "), too many parts: " + parts);
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isEmpty(config.description)) {
|
||||
logger().error("Invalid command (" + config.command + "): empty description");
|
||||
continue;
|
||||
}
|
||||
|
||||
String prefixOrMainCommand = String.join(" ", commandParts.subList(0, Math.max(parts - 1, 1)));
|
||||
if (parts > 2) {
|
||||
String group = commandParts.get(0);
|
||||
if (layeredCommands.stream().anyMatch(command -> command.getPrefix().equals(group))) {
|
||||
logger().error("Cannot use sub command group, sub command already being used: " + group); // TODO: better error
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@Subst("ip")
|
||||
String name = commandParts.get(parts - 1);
|
||||
DiscordCommand.ChatInputBuilder commandBuilder = DiscordCommand.chatInput(
|
||||
ComponentIdentifier.of("DiscordSRV", "custom-command-" + (++i)),
|
||||
name,
|
||||
config.description
|
||||
);
|
||||
|
||||
for (CustomCommandConfig.OptionConfig option : config.options) {
|
||||
if (StringUtils.isEmpty(option.description)) {
|
||||
logger().error("Invalid command option (" + option.name + " of " + config.command + "): empty description");
|
||||
continue;
|
||||
}
|
||||
commandBuilder.addOption(
|
||||
CommandOption.builder(option.type, option.name, option.description)
|
||||
.setRequired(option.required)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
commandBuilder.setEventHandler(event -> {
|
||||
SendableDiscordMessage.Formatter formatter = config.response.toFormatter();
|
||||
|
||||
for (CustomCommandConfig.OptionConfig option : config.options) {
|
||||
String optionName = option.name;
|
||||
|
||||
Object context;
|
||||
String reLookup = null;
|
||||
switch (option.type) {
|
||||
case CHANNEL:
|
||||
context = event.getOptionAsChannel(optionName);
|
||||
reLookup = "channel";
|
||||
break;
|
||||
case USER:
|
||||
context = event.getOptionAsUser(optionName);
|
||||
reLookup = "user";
|
||||
break;
|
||||
case ROLE:
|
||||
context = event.getOptionAsRole(optionName);
|
||||
reLookup = "role";
|
||||
break;
|
||||
case MENTIONABLE:
|
||||
Long id = event.getOptionAsLong(optionName);
|
||||
if (id == null) {
|
||||
context = event.getOptionAsString(optionName);
|
||||
break;
|
||||
}
|
||||
|
||||
DiscordUser user = discordSRV.discordAPI().getUserById(id);
|
||||
if (user != null) {
|
||||
context = user;
|
||||
reLookup = "user";
|
||||
break;
|
||||
}
|
||||
|
||||
DiscordRole role = discordSRV.discordAPI().getRoleById(id);
|
||||
if (role != null) {
|
||||
context = role;
|
||||
reLookup = "role";
|
||||
break;
|
||||
}
|
||||
|
||||
DiscordChannel channel = discordSRV.discordAPI().getChannelById(id);
|
||||
if (channel != null) {
|
||||
context = channel;
|
||||
reLookup = "channel";
|
||||
break;
|
||||
}
|
||||
|
||||
context = event.getOptionAsString(optionName);
|
||||
break;
|
||||
default:
|
||||
context = event.getOptionAsString(optionName);
|
||||
break;
|
||||
}
|
||||
|
||||
formatter = formatter.addPlaceholder("option_" + optionName, context, reLookup);
|
||||
}
|
||||
|
||||
SendableDiscordMessage message = formatter.applyPlaceholderService().build();
|
||||
event.reply(message, config.ephemeral).whenComplete((ih, t) -> {
|
||||
if (t != null) {
|
||||
logger().debug("Failed to reply to custom command: " + config.command, t);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
DiscordCommand command = commandBuilder.build();
|
||||
|
||||
LayerCommand foundLayer = layeredCommands.stream()
|
||||
.filter(cmd -> cmd.getLayer() == parts)
|
||||
.filter(cmd -> cmd.getPrefix().equals(prefixOrMainCommand))
|
||||
.findAny().orElse(null);
|
||||
if (foundLayer != null) {
|
||||
if (parts == 1) {
|
||||
logger().error("Duplicate main command: " + commandParts.get(0));
|
||||
continue;
|
||||
}
|
||||
|
||||
foundLayer.getCommands().add(command);
|
||||
continue;
|
||||
}
|
||||
|
||||
layeredCommands.add(new LayerCommand(
|
||||
prefixOrMainCommand,
|
||||
parts,
|
||||
new ArrayList<>(Collections.singleton(command))
|
||||
));
|
||||
}
|
||||
|
||||
List<DiscordCommand> commandsToRegister = new ArrayList<>();
|
||||
for (LayerCommand layeredCommand : layeredCommands) {
|
||||
commandsToRegister.addAll(layeredCommand.getCommands());
|
||||
}
|
||||
|
||||
for (DiscordCommand command : registeredCommands) {
|
||||
discordSRV.discordAPI().unregisterCommand(command);
|
||||
}
|
||||
registeredCommands.clear();
|
||||
registeredCommands.addAll(commandsToRegister);
|
||||
|
||||
for (DiscordCommand command : commandsToRegister) {
|
||||
DiscordCommand.RegistrationResult registrationResult = discordSRV.discordAPI().registerCommand(command);
|
||||
logger().debug("Registration of " + command.getName() + ": " + registrationResult.name());
|
||||
}
|
||||
}
|
||||
|
||||
public static class LayerCommand {
|
||||
|
||||
private final String prefix;
|
||||
private final int layer;
|
||||
private final List<DiscordCommand> commands;
|
||||
|
||||
public LayerCommand(String prefix, int layer, List<DiscordCommand> commands) {
|
||||
this.prefix = prefix;
|
||||
this.layer = layer;
|
||||
this.commands = commands;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public int getLayer() {
|
||||
return layer;
|
||||
}
|
||||
|
||||
public List<DiscordCommand> getCommands() {
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user