Merge main

This commit is contained in:
Vankka 2023-09-13 21:12:39 +03:00
commit 02488472e5
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
30 changed files with 335 additions and 152 deletions

View File

@ -50,6 +50,7 @@ import com.discordsrv.common.debug.data.OnlineMode;
import com.discordsrv.common.messageforwarding.game.minecrafttodiscord.MinecraftToDiscordChatModule;
import com.discordsrv.common.plugin.PluginManager;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
@ -231,6 +232,13 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
return results;
}
@Override
protected void serverStarted() {
super.serverStarted();
server().getConsoleSender().sendMessage(ChatColor.BOLD + "asd" + ChatColor.RESET + " bbb " + ChatColor.UNDERLINE + "und");
}
@Override
protected void disable() {
super.disable();

View File

@ -23,6 +23,7 @@ import com.discordsrv.bukkit.config.main.BukkitRequiredLinkingConfig;
import com.discordsrv.bukkit.requiredlinking.BukkitRequiredLinkingModule;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.config.main.linking.ServerRequiredLinkingConfig;
import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import net.kyori.adventure.text.Component;
@ -141,7 +142,7 @@ public class BukkitRequiredLinkingListener implements Listener {
Consumer<String> disallow
) {
BukkitRequiredLinkingConfig config = discordSRV.config().requiredLinking;
if (!config.enabled || !config.action.equalsIgnoreCase("KICK")
if (!config.enabled || config.action != ServerRequiredLinkingConfig.Action.KICK
|| !eventType.equals(config.kick.event) || !priority.name().equals(config.kick.priority)) {
return;
}
@ -185,7 +186,7 @@ public class BukkitRequiredLinkingListener implements Listener {
}
BukkitRequiredLinkingConfig config = discordSRV.config().requiredLinking;
if (!config.enabled || !config.action.equalsIgnoreCase("FREEZE")) {
if (!config.enabled || config.action != ServerRequiredLinkingConfig.Action.FREEZE) {
return;
}

View File

@ -677,13 +677,13 @@ public abstract class AbstractDiscordSRV<
if (flags.contains(ReloadFlag.LINKED_ACCOUNT_PROVIDER)) {
LinkedAccountConfig linkedAccountConfig = config().linkedAccounts;
if (linkedAccountConfig != null && linkedAccountConfig.enabled) {
String provider = linkedAccountConfig.provider;
LinkedAccountConfig.Provider provider = linkedAccountConfig.provider;
boolean permitMinecraftAuth = connectionConfig().minecraftAuth.allow;
if (provider.equals("auto")) {
provider = permitMinecraftAuth && onlineMode().isOnline() ? "minecraftauth" : "storage";
if (provider == LinkedAccountConfig.Provider.AUTO) {
provider = permitMinecraftAuth && onlineMode().isOnline() ? LinkedAccountConfig.Provider.MINECRAFTAUTH : LinkedAccountConfig.Provider.STORAGE;
}
switch (provider) {
case "minecraftauth":
case MINECRAFTAUTH:
if (!permitMinecraftAuth) {
linkProvider = null;
logger().error("minecraftauth.me is disabled in the " + ConnectionConfig.FILE_NAME + ", "
@ -694,7 +694,7 @@ public abstract class AbstractDiscordSRV<
linkProvider = new MinecraftAuthenticationLinker(this);
logger().info("Using minecraftauth.me for linked accounts");
break;
case "storage":
case STORAGE:
linkProvider = new StorageLinker(this);
logger().info("Using storage for linked accounts");
break;

View File

@ -85,8 +85,8 @@ public class ChannelConfigHelper {
throws SerializationException {
MainConfigManager<?> configManager = discordSRV.configManager();
CommentedConfigurationNode defaultNode = CommentedConfigurationNode.root(configManager.nodeOptions());
CommentedConfigurationNode target = CommentedConfigurationNode.root(configManager.nodeOptions());
CommentedConfigurationNode defaultNode = CommentedConfigurationNode.root(configManager.nodeOptions(true));
CommentedConfigurationNode target = CommentedConfigurationNode.root(configManager.nodeOptions(true));
configManager.objectMapper()
.get((Class<BaseChannelConfig>) defaultConfig.getClass())

View File

@ -89,7 +89,7 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
boolean ephemeral = config.ephemeral;
event.asJDA().reply("Executing command `" + command + "`")
.setEphemeral(ephemeral)
.queue(ih -> new ExecutionContext(discordSRV, ih, config.getOutputMode(), ephemeral).run(command));
.queue(ih -> new ExecutionContext(discordSRV, ih, config.outputMode, ephemeral).run(command));
}
@Override
@ -214,7 +214,7 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
private void send() {
boolean ansi = outputMode == DiscordCommandConfig.OutputMode.ANSI;
boolean plainBlock = outputMode == DiscordCommandConfig.OutputMode.PLAIN_BLOCK;
boolean plainBlock = outputMode == DiscordCommandConfig.OutputMode.CODEBLOCK;
String prefix = ansi ? "```ansi\n" : (plainBlock ? "```\n" : "");
String suffix = ansi ? "```" : (plainBlock ? "```" : "");
@ -234,7 +234,7 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
discord = discordSRV.componentFactory().ansiSerializer().serialize(component);
break;
case PLAIN:
case PLAIN_BLOCK:
case CODEBLOCK:
discord = discordSRV.componentFactory().plainSerializer().serialize(component);
break;
}

View File

@ -0,0 +1,25 @@
package com.discordsrv.common.config.configurate.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* A config annotation that should be used to define config (comment) parts that should not be translated,
* remaining the same for all languages (for example, config option names referenced in comments, urls, etc.).
* <p>
* Replacements are {@code %i} where {@code i} start counting up from {@code 1}.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Constants {
String[] value();
/**
* Needs to go after {@link org.spongepowered.configurate.objectmapping.meta.Comment}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Comment {
String[] value();
}
}

View File

@ -19,13 +19,12 @@
package com.discordsrv.common.config.configurate.manager;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.configurate.manager.abstraction.TranslatedConfigManager;
import com.discordsrv.common.config.configurate.manager.loader.YamlConfigLoaderProvider;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.configurate.manager.abstraction.TranslatedConfigManager;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.lang.reflect.Field;
import java.nio.file.Path;
public abstract class ConnectionConfigManager<C extends ConnectionConfig>
@ -41,9 +40,8 @@ public abstract class ConnectionConfigManager<C extends ConnectionConfig>
}
@Override
public ConfigurationOptions configurationOptions(ObjectMapper.Factory objectMapper) {
return super.configurationOptions(objectMapper)
.header(ConnectionConfig.HEADER);
protected Field headerField() throws ReflectiveOperationException {
return ConnectionConfig.class.getField("HEADER");
}
@Override

View File

@ -19,13 +19,12 @@
package com.discordsrv.common.config.configurate.manager;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.configurate.manager.abstraction.TranslatedConfigManager;
import com.discordsrv.common.config.configurate.manager.loader.YamlConfigLoaderProvider;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.configurate.manager.abstraction.TranslatedConfigManager;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.lang.reflect.Field;
import java.nio.file.Path;
public abstract class MainConfigManager<C extends MainConfig>
@ -41,9 +40,8 @@ public abstract class MainConfigManager<C extends MainConfig>
}
@Override
public ConfigurationOptions configurationOptions(ObjectMapper.Factory objectMapper) {
return super.configurationOptions(objectMapper)
.header(MainConfig.HEADER);
protected Field headerField() throws ReflectiveOperationException {
return MainConfig.class.getField("HEADER");
}
@Override

View File

@ -22,18 +22,18 @@ import com.discordsrv.api.color.Color;
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.configurate.annotation.Constants;
import com.discordsrv.common.config.configurate.annotation.DefaultOnly;
import com.discordsrv.common.config.configurate.annotation.Order;
import com.discordsrv.common.config.configurate.fielddiscoverer.OrderedFieldDiscovererProxy;
import com.discordsrv.common.config.configurate.manager.loader.ConfigLoaderProvider;
import com.discordsrv.common.config.configurate.serializer.*;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.config.configurate.manager.loader.ConfigLoaderProvider;
import com.discordsrv.common.config.configurate.serializer.ColorSerializer;
import com.discordsrv.common.config.configurate.serializer.DiscordMessageEmbedSerializer;
import com.discordsrv.common.config.configurate.serializer.PatternSerializer;
import com.discordsrv.common.config.configurate.serializer.SendableDiscordMessageSerializer;
import com.discordsrv.common.exception.ConfigException;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.configurate.*;
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
@ -49,6 +49,7 @@ import org.spongepowered.configurate.util.NamingSchemes;
import org.spongepowered.configurate.yaml.ScalarStyle;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.nio.file.Path;
import java.util.Arrays;
@ -69,6 +70,7 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
};
private final Path filePath;
private final Logger logger;
private final ObjectMapper.Factory objectMapper;
private final ObjectMapper.Factory cleanObjectMapper;
private LT loader;
@ -76,12 +78,13 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
protected T configuration;
public ConfigurateConfigManager(DiscordSRV discordSRV) {
this(discordSRV.dataDirectory());
this(discordSRV.dataDirectory(), new NamedLogger(discordSRV, "CONFIG"));
}
protected ConfigurateConfigManager(Path dataDirectory) {
protected ConfigurateConfigManager(Path dataDirectory, Logger logger) {
this.filePath = dataDirectory.resolve(fileName());
this.objectMapper = objectMapperBuilder().build();
this.logger = logger;
this.objectMapper = objectMapperBuilder(true).build();
this.cleanObjectMapper = cleanObjectMapperBuilder().build();
}
@ -91,7 +94,7 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
public LT loader() {
if (loader == null) {
loader = createLoader(filePath(), nodeOptions());
loader = createLoader(filePath(), nodeOptions(true));
}
return loader;
}
@ -103,12 +106,48 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
protected abstract String fileName();
protected Field headerField() throws ReflectiveOperationException {
return null;
}
protected String header() {
try {
Field headerField = headerField();
return headerField != null ? (String) headerField.get(null) : null;
} catch (ReflectiveOperationException e) {
return null;
}
}
protected String[] headerConstants() {
try {
Field headerField = headerField();
if (headerField == null) {
return new String[0];
}
Constants constants = headerField.getAnnotation(Constants.class);
if (constants == null) {
return new String[0];
}
return constants.value();
} catch (ReflectiveOperationException e) {
return new String[0];
}
}
public IChannelConfig.Serializer getChannelConfigSerializer(ObjectMapper.Factory mapperFactory) {
return new IChannelConfig.Serializer(mapperFactory, BaseChannelConfig.class, ChannelConfig.class);
}
public ConfigurationOptions configurationOptions(ObjectMapper.Factory objectMapper) {
public ConfigurationOptions configurationOptions(ObjectMapper.Factory objectMapper, boolean headerSubstitutions) {
String header = header();
if (header != null && headerSubstitutions) {
header = doSubstitution(header, headerConstants());
}
return ConfigurationOptions.defaults()
.header(header)
.shouldCopyDefaults(false)
.implicitInitialization(false)
.serializers(builder -> {
@ -133,6 +172,8 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
}
});
builder.register(BaseChannelConfig.class, getChannelConfigSerializer(objectMapper));
//noinspection unchecked
builder.register((Class<Enum<?>>) (Object) Enum.class, new EnumSerializer(logger));
builder.register(Color.class, new ColorSerializer());
builder.register(Pattern.class, new PatternSerializer());
builder.register(DiscordMessageEmbed.Builder.class, new DiscordMessageEmbedSerializer(NAMING_SCHEME));
@ -149,16 +190,16 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
});
}
public ConfigurationOptions nodeOptions() {
return configurationOptions(objectMapper());
public ConfigurationOptions nodeOptions(boolean headerSubstitutions) {
return configurationOptions(objectMapper(), headerSubstitutions);
}
public ConfigurationOptions cleanNodeOptions() {
return configurationOptions(cleanObjectMapper());
return configurationOptions(cleanObjectMapper(), true);
}
@SuppressWarnings("unchecked")
public ObjectMapper.Factory.Builder commonObjectMapperBuilder() {
public ObjectMapper.Factory.Builder commonObjectMapperBuilder(boolean commentSubstitutions) {
Comparator<OrderedFieldDiscovererProxy.FieldCollectorData<Object, ?>> fieldOrder = Comparator.comparingInt(data -> {
Order order = data.annotations().getAnnotation(Order.class);
return order != null ? order.value() : 0;
@ -167,11 +208,50 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
return ObjectMapper.factoryBuilder()
.defaultNamingScheme(NAMING_SCHEME)
.addDiscoverer(new OrderedFieldDiscovererProxy<>((FieldDiscoverer<Object>) FieldDiscoverer.emptyConstructorObject(), fieldOrder))
.addDiscoverer(new OrderedFieldDiscovererProxy<>((FieldDiscoverer<Object>) FieldDiscoverer.record(), fieldOrder));
.addDiscoverer(new OrderedFieldDiscovererProxy<>((FieldDiscoverer<Object>) FieldDiscoverer.record(), fieldOrder))
.addProcessor(Constants.Comment.class, (data, fieldType) -> (value, destination) -> {
// This needs to go before comment processing.
if (commentSubstitutions && destination instanceof CommentedConfigurationNode) {
String comment = ((CommentedConfigurationNode) destination).comment();
if (comment != null) {
((CommentedConfigurationNode) destination).comment(
doSubstitution(comment, data.value())
);
}
}
})
.addProcessor(Constants.class, (data, fieldType) -> (value, destination) -> {
if (data == null || data.value().length == 0) {
return;
}
String optionValue = destination.getString();
if (optionValue == null) {
return;
}
try {
destination.set(
doSubstitution(
destination.getString(),
data.value()
)
);
} catch (SerializationException e) {
throw new RuntimeException(e);
}
});
}
public ObjectMapper.Factory.Builder objectMapperBuilder() {
return commonObjectMapperBuilder()
private static String doSubstitution(String input, String[] values) {
for (int i = 0; i < values.length; i++) {
input = input.replace("%" + (i + 1), values[i]);
}
return input;
}
public ObjectMapper.Factory.Builder objectMapperBuilder(boolean commentSubstitutions) {
return commonObjectMapperBuilder(commentSubstitutions)
.addProcessor(Comment.class, (data, fieldType) -> {
Processor<Object> processor = Processor.comments().make(data, fieldType);
@ -189,7 +269,7 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
}
protected ObjectMapper.Factory.Builder cleanObjectMapperBuilder() {
return commonObjectMapperBuilder()
return commonObjectMapperBuilder(true)
.addProcessor(DefaultOnly.class, (data, value) -> (value1, destination) -> {
String[] children = data.value();
boolean whitelist = data.whitelist();

View File

@ -25,9 +25,7 @@ import org.jetbrains.annotations.Nullable;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
@ -50,7 +48,7 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
}
protected TranslatedConfigManager(Path dataDirectory) {
super(dataDirectory);
super(dataDirectory, null);
this.discordSRV = null;
}
@ -66,12 +64,11 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
}
@Override
public ConfigurationOptions configurationOptions(ObjectMapper.Factory objectMapper) {
ConfigurationOptions options = super.configurationOptions(objectMapper);
protected String header() {
if (header != null) {
options = options.header(header);
return header;
}
return options;
return super.header();
}
@Override

View File

@ -0,0 +1,54 @@
package com.discordsrv.common.config.configurate.serializer;
import com.discordsrv.common.logging.Logger;
import io.leangen.geantyref.GenericTypeReflector;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class EnumSerializer implements TypeSerializer<Enum<?>> {
private final Logger logger;
public EnumSerializer(Logger logger) {
this.logger = logger;
}
@SuppressWarnings("unchecked") // Enum generic
@Override
public Enum<?> deserialize(Type type, ConfigurationNode node) throws SerializationException {
Class<? extends Enum<?>> theEnum = (Class<? extends Enum<?>>) GenericTypeReflector.erase(type).asSubclass(Enum.class);
String configValue = node.getString();
if (configValue == null) {
return null;
}
configValue = configValue.toLowerCase(Locale.ROOT);
List<String> values = new ArrayList<>();
for (Enum<?> constant : theEnum.getEnumConstants()) {
String lower = constant.name().toLowerCase(Locale.ROOT);
if (lower.equals(configValue)) {
return constant;
}
values.add(lower);
}
logger.error(
"Option \"" + node.key() + "\" "
+ "has invalid value: \"" + configValue + "\", "
+ "acceptable values: " + String.join(", ", values)
);
return null;
}
@Override
public void serialize(Type type, @Nullable Enum<?> obj, ConfigurationNode node) throws SerializationException {
node.raw(obj != null ? obj.name().toLowerCase(Locale.ROOT) : null);
}
}

View File

@ -18,10 +18,13 @@
package com.discordsrv.common.config.configurate.serializer;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
import org.spongepowered.configurate.yaml.ScalarStyle;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.lang.reflect.Type;
import java.util.regex.Pattern;
@ -31,11 +34,12 @@ public class PatternSerializer implements TypeSerializer<Pattern> {
@Override
public Pattern deserialize(Type type, ConfigurationNode node) {
String pattern = node != null ? node.getString() : null;
return pattern != null ? Pattern.compile(pattern) : null;
return StringUtils.isNotEmpty(pattern) ? Pattern.compile(pattern) : null;
}
@Override
public void serialize(Type type, @Nullable Pattern obj, ConfigurationNode node) throws SerializationException {
node.set(obj != null ? obj.pattern() : null);
node = node.hint(YamlConfigurationLoader.SCALAR_STYLE, ScalarStyle.DOUBLE_QUOTED);
node.raw(obj != null ? obj.pattern() : null);
}
}

View File

@ -1,5 +1,10 @@
package com.discordsrv.common.config.documentation;
public class DocumentationURLs {
public final class DocumentationURLs {
private DocumentationURLs() {}
public static final String CREATE_TOKEN = "https://docs.discordsrv.com/installation/initial-setup/#setting-up-the-bot";
public static final String ELT_FORMAT = "https://github.com/Vankka/EnhancedLegacyText/wiki/Format";
public static final String DISCORD_MARKDOWN = "https://support.discord.com/hc/en-us/articles/210298617";
}

View File

@ -13,7 +13,7 @@ public class AvatarProviderConfig {
@Untranslated(Untranslated.Type.VALUE)
@Comment("The template for URLs of player avatars\n" +
"This will be used for offical Java players only if auto-decide-avatar-url is set to true\n" +
"This will be used for official Java players only if auto-decide-avatar-url is set to true\n" +
"This will be used ALWAYS if auto-decide-avatar-url is set to false")
public String avatarUrlTemplate = "https://crafatar.com/avatars/%player_uuid_nodashes%.png?size=128&overlay#%player_texture%";
}

View File

@ -23,18 +23,7 @@ public class ConsoleConfig {
+ "- diff: A \"diff\" code block highlighting warnings and errors with different colors\n"
+ "- plain: Plain text code block\n"
+ "- plain_content: Plain text")
public String outputMode = "ansi";
public OutputMode getOutputMode() {
switch (outputMode.toLowerCase(Locale.ROOT)) {
default:
case "ansi": return OutputMode.ANSI;
case "log": return OutputMode.LOG;
case "diff": return OutputMode.DIFF;
case "plain": return OutputMode.PLAIN;
case "plain_content": return OutputMode.PLAIN_CONTENT;
}
}
public OutputMode outputMode = OutputMode.ANSI;
@Comment("Avoids sending new messages by editing the most recent message until it reaches it's maximum length")
public boolean useEditing = true;

View File

@ -1,5 +1,6 @@
package com.discordsrv.common.config.main;
import com.discordsrv.common.config.configurate.annotation.Constants;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ -13,7 +14,8 @@ public class DebugConfig {
@Comment("If debug messages should be logged into the config")
public boolean logToConsole = false;
@Comment("Additional levels to log\nExample value: {\"AWARD_LISTENER\":[\"TRACE\"]}")
@Comment("Additional levels to log\nExample value: %1")
@Constants.Comment("{\"AWARD_LISTENER\":[\"TRACE\"]}")
public Map<String, List<String>> additionalLevels = new HashMap<>();
}

View File

@ -1,5 +1,6 @@
package com.discordsrv.common.config.main;
import com.discordsrv.common.config.configurate.annotation.Constants;
import com.discordsrv.common.config.main.generic.GameCommandFilterConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ -7,7 +8,6 @@ import org.spongepowered.configurate.objectmapping.meta.Comment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@ConfigSerializable
public class DiscordCommandConfig {
@ -33,23 +33,13 @@ public class DiscordCommandConfig {
public boolean ephemeral = true;
@Comment("The mode for the command output, available options are:\n"
+ "- markdown: Regular Discord markdown\n"
+ "- ansi: A colored ansi code block\n"
+ "- plain: Plain text\n"
+ "- codeblock: Plain code block\n"
+ "- off: No command output")
public String outputMode = "markdown";
public OutputMode getOutputMode() {
switch (outputMode.toLowerCase(Locale.ROOT)) {
default:
case "markdown": return OutputMode.MARKDOWN;
case "ansi": return OutputMode.ANSI;
case "plain": return OutputMode.PLAIN;
case "codeblock": return OutputMode.PLAIN_BLOCK;
case "off": return OutputMode.OFF;
}
}
+ "- %1: Regular Discord markdown\n"
+ "- %2: A colored ansi code block\n"
+ "- %3: Plain text\n"
+ "- %4: Plain code block\n"
+ "- %5: No command output")
@Constants.Comment({"markdown", "ansi", "plain", "code_block", "off"})
public OutputMode outputMode = OutputMode.MARKDOWN;
@Comment("At least one condition has to match to allow execution")
public List<GameCommandFilterConfig> filters = new ArrayList<>();
@ -58,7 +48,8 @@ public class DiscordCommandConfig {
"Suggestions go through the server's main thread (on servers with a main thread) to ensure compatability.")
public boolean suggest = true;
@Comment("If suggestions should be filtered based on the \"filters\" option")
@Comment("If suggestions should be filtered based on the \"%1\" option")
@Constants.Comment("filters")
public boolean filterSuggestions = true;
}
@ -66,7 +57,7 @@ public class DiscordCommandConfig {
MARKDOWN,
ANSI,
PLAIN,
PLAIN_BLOCK,
CODEBLOCK,
OFF
}
}

View File

@ -18,18 +18,22 @@
package com.discordsrv.common.config.main;
import com.discordsrv.common.config.configurate.annotation.Constants;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ConfigSerializable
public class GameCommandConfig {
@Comment("If the /discord command should be set by DiscordSRV")
@Comment("If the %1 command should be set by DiscordSRV")
@Constants.Comment("/discord")
public boolean useDiscordCommand = true;
@Comment("If /link should be used as a alias for /discord link")
@Comment("If %1 should be used as a alias for %2")
@Constants.Comment({"/link", "/discord link"})
public boolean useLinkAlias = false;
@Comment("The Discord command response format (/discord), player placeholders may be used")
@Comment("The Discord command response format (%1), player placeholders may be used")
@Constants.Comment("/discord")
public String discordFormat = "[click:open_url:%discord_invite%][color:aqua][bold:on]Click here [color][bold][color:green]to join our Discord server!";
}

View File

@ -19,6 +19,7 @@
package com.discordsrv.common.config.main;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.configurate.annotation.Constants;
import com.discordsrv.common.groupsync.enums.GroupSyncDirection;
import com.discordsrv.common.groupsync.enums.GroupSyncSide;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ -42,16 +43,9 @@ public class GroupSyncConfig {
public Long roleId = 0L;
@Comment("The direction this group-role pair will synchronize in.\n"
+ "Valid options: BIDIRECTIONAL, MINECRAFT_TO_DISCORD, DISCORD_TO_MINECRAFT")
public String direction = GroupSyncDirection.BIDIRECTIONAL.name();
public GroupSyncDirection direction() {
try {
return GroupSyncDirection.valueOf(direction);
} catch (IllegalArgumentException ignored) {
return null;
}
}
+ "Valid options: %1, %2, %3")
@Constants.Comment({"bidirectional", "minecraft_to_discord", "discord_to_minecraft"})
public GroupSyncDirection direction = GroupSyncDirection.BIDIRECTIONAL;
@Comment("Timed resynchronization.\n"
+ "This is required if you're not using LuckPerms and want to use Minecraft to Discord synchronization")
@ -68,19 +62,13 @@ public class GroupSyncConfig {
}
@Comment("Decides which side takes priority when using timed synchronization or the resync command\n"
+ "Valid options: MINECRAFT, DISCORD")
public String tieBreaker = GroupSyncSide.MINECRAFT.name();
+ "Valid options: %1, %2")
@Constants.Comment({"minecraft", "discord"})
public GroupSyncSide tieBreaker = GroupSyncSide.MINECRAFT;
public GroupSyncSide tieBreaker() {
try {
return GroupSyncSide.valueOf(tieBreaker);
} catch (IllegalArgumentException ignored) {
return null;
}
}
@Comment("The LuckPerms \"server\" context value, used when adding, removing and checking the groups of players.\n"
+ "Make this blank (\"\") to use the current server's value, or \"global\" to not use the context")
@Comment("The LuckPerms \"%1\" context value, used when adding, removing and checking the groups of players.\n"
+ "Make this blank (\"\") to use the current server's value, or \"%2\" to not use the context")
@Constants.Comment({"server", "global"})
public String serverContext = "global";
public boolean isTheSameAs(PairConfig config) {
@ -90,7 +78,7 @@ public class GroupSyncConfig {
public boolean validate(DiscordSRV discordSRV) {
String label = "Group synchronization (" + groupName + ":" + Long.toUnsignedString(roleId) + ")";
boolean invalidTieBreaker, invalidDirection = false;
if ((invalidTieBreaker = (tieBreaker() == null)) || (invalidDirection = (direction == null))) {
if ((invalidTieBreaker = (tieBreaker == null)) || (invalidDirection = (direction == null))) {
if (invalidTieBreaker) {
discordSRV.logger().error(label + " has invalid tie-breaker: " + tieBreaker
+ ", should be one of " + Arrays.toString(GroupSyncSide.values()));
@ -100,10 +88,10 @@ public class GroupSyncConfig {
+ ", should be one of " + Arrays.toString(GroupSyncDirection.values()));
}
return false;
} else if (direction() != GroupSyncDirection.BIDIRECTIONAL) {
} else if (direction != GroupSyncDirection.BIDIRECTIONAL) {
boolean minecraft;
if ((direction() == GroupSyncDirection.MINECRAFT_TO_DISCORD) != (minecraft = (tieBreaker() == GroupSyncSide.MINECRAFT))) {
String opposite = (minecraft ? GroupSyncSide.DISCORD : GroupSyncSide.MINECRAFT).name();
if ((direction == GroupSyncDirection.MINECRAFT_TO_DISCORD) != (minecraft = (tieBreaker == GroupSyncSide.MINECRAFT))) {
GroupSyncSide opposite = (minecraft ? GroupSyncSide.DISCORD : GroupSyncSide.MINECRAFT);
discordSRV.logger().warning(label + " with direction "
+ direction + " with tie-breaker "
+ tieBreaker + " (should be " + opposite + ")");
@ -116,7 +104,7 @@ public class GroupSyncConfig {
@Override
public String toString() {
String arrow;
switch (direction()) {
switch (direction) {
default:
case BIDIRECTIONAL:
arrow = "<->";

View File

@ -20,9 +20,11 @@ package com.discordsrv.common.config.main;
import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.common.config.Config;
import com.discordsrv.common.config.configurate.annotation.Constants;
import com.discordsrv.common.config.configurate.annotation.DefaultOnly;
import com.discordsrv.common.config.configurate.annotation.Order;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.documentation.DocumentationURLs;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
import com.discordsrv.common.config.main.linking.LinkedAccountConfig;
@ -36,12 +38,13 @@ public abstract class MainConfig implements Config {
public static final String FILE_NAME = "config.yaml";
@Constants({DocumentationURLs.ELT_FORMAT, DocumentationURLs.DISCORD_MARKDOWN})
public static final String HEADER = String.join("\n", Arrays.asList(
"Welcome to the DiscordSRV configuration file",
"",
"Looking for the \"BotToken\" option? It has been moved into the " + ConnectionConfig.FILE_NAME,
"Need help with the format for Minecraft messages? https://github.com/Vankka/EnhancedLegacyText/wiki/Format",
"Need help with Discord markdown? https://support.discord.com/hc/en-us/articles/210298617"
"Need help with the format for Minecraft messages? %1",
"Need help with Discord markdown? %2"
));
@Override
@ -60,10 +63,11 @@ public abstract class MainConfig implements Config {
@DefaultOnly(ChannelConfig.DEFAULT_KEY)
@Comment("Channels configuration\n\n"
+ "This is where everything related to in-game chat channels is configured.\n"
+ "The key of this option is the in-game channel name (the default keys are \"global\" and \"default\")\n"
+ "channel-ids and threads can be configured for all channels except \"default\"\n"
+ "\"default\" is a special section which has the default values for all channels unless they are specified (overridden) under the channel's own section\n"
+ "So if you don't specify a certain option under a channel's own section, the option will take its value from the \"default\" section")
+ "The key of this option is the in-game channel name (the default keys are \"%1\" and \"%2\")\n"
+ "%3 and %4 can be configured for all channels except \"%2\"\n"
+ "\"%2\" is a special section which has the default values for all channels unless they are specified (overridden) under the channel's own section\n"
+ "So if you don't specify a certain option under a channel's own section, the option will take its value from the \"%2\" section")
@Constants.Comment({GameChannel.DEFAULT_NAME, ChannelConfig.DEFAULT_KEY, "channel-ids", "threads"})
public Map<String, BaseChannelConfig> channels = new LinkedHashMap<String, BaseChannelConfig>() {{
put(GameChannel.DEFAULT_NAME, createDefaultChannel());
put(ChannelConfig.DEFAULT_KEY, createDefaultBaseChannel());
@ -85,13 +89,15 @@ 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()));
@Comment("Configuration for the %discord_invite% placeholder. The below options will be attempted in the order they are in")
@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();
public MessagesMainConfig messages = new MessagesMainConfig();
@Order(10) // To go below required linking config @ 5
@Comment("Configuration for the %player_avatar_url% placeholder")
@Comment("Configuration for the %1 placeholder")
@Constants.Comment("%player_avatar_url%")
public AvatarProviderConfig avatarProvider = new AvatarProviderConfig();
public abstract PluginIntegrationConfig integrations();

View File

@ -18,6 +18,7 @@
package com.discordsrv.common.config.main.channels;
import com.discordsrv.common.config.configurate.annotation.Constants;
import com.discordsrv.common.config.configurate.annotation.Untranslated;
import com.discordsrv.common.config.main.generic.DiscordIgnoresConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ -33,25 +34,23 @@ public class DiscordToMinecraftChatConfig {
public boolean enabled = true;
@Comment("The Discord to Minecraft message format for regular users and bots")
@Untranslated(Untranslated.Type.VALUE)
public String format = "[[color:#5865F2]Discord[color]] [hover:show_text:Username: @%user_tag%\nRoles: %user_roles:', '|text:'[color:gray][italics:on]None[color][italics]'%]%user_color%%user_effective_server_name%[color][hover]%message_reply% » %message%%message_attachments%";
@Comment("The Discord to Minecraft message format for webhook messages (if enabled)")
@Untranslated(Untranslated.Type.VALUE)
public String webhookFormat = "[[color:#5865F2]Discord[color]] [hover:show_text:Bot message]%user_effective_name%[hover] » %message%%message_attachments%";
@Comment("Format for a single attachment in the %message_attachments% placeholder")
@Untranslated(Untranslated.Type.VALUE)
public String attachmentFormat = " [hover:show_text:Open %file_name% in browser][click:open_url:%file_url%][color:green][[color:white]%file_name%[color:green]][color][click][hover]";
@Comment("Format for the %message_reply% placeholder, when the message is a reply to another message")
@Untranslated(Untranslated.Type.VALUE)
public String replyFormat = " [hover:show_text:%message%][click:open_url:%message_jump_url%]replying to %user_color|text:''%%user_effective_server_name|user_effective_name%[color][click][hover]";
// TODO: more info on regex pairs (String#replaceAll)
@Comment("Regex filters for Discord message contents (this is the %message% part of the \"format\" option)")
@Untranslated(Untranslated.Type.VALUE)
public Map<Pattern, String> contentRegexFilters = new LinkedHashMap<>();
public Map<Pattern, String> contentRegexFilters = new LinkedHashMap<Pattern, String>() {{
put(Pattern.compile("\\n{2,}"), "\n");
}};
@Comment("Users, bots, roles and webhooks to ignore")
public DiscordIgnoresConfig ignores = new DiscordIgnoresConfig();

View File

@ -21,6 +21,7 @@ package com.discordsrv.common.config.main.channels;
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
import com.discordsrv.api.event.events.message.receive.game.JoinMessageReceiveEvent;
import com.discordsrv.common.config.configurate.annotation.Constants;
import com.discordsrv.common.config.configurate.annotation.Untranslated;
import com.discordsrv.common.config.main.generic.IMessageConfig;
import org.jetbrains.annotations.Nullable;
@ -68,8 +69,18 @@ public class JoinMessageConfig implements IMessageConfig {
@ConfigSerializable
public static class FirstJoin implements IMessageConfig {
@Comment("How first join should behave:\n- enabled (uses the format below)\n- disabled (first join messages are disabled)\n- use_regular (uses the format above)")
public String firstJoinPreference = "enabled";
@Comment("How first join should behave:\n"
+ "- %1 (uses the format below)\n"
+ "- %2 (first join messages are disabled)\n"
+ "- %3 (uses the format above)")
@Constants.Comment({"enabled", "disabled", "use_regular"})
public Preference preference = Preference.ENABLED;
public enum Preference {
ENABLED,
DISABLED,
USE_REGULAR
}
@Untranslated(Untranslated.Type.VALUE)
public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
@ -81,12 +92,12 @@ public class JoinMessageConfig implements IMessageConfig {
);
public boolean isRegular() {
return "use_regular".equalsIgnoreCase(firstJoinPreference);
return preference == Preference.USE_REGULAR;
}
@Override
public boolean enabled() {
return "enabled".equalsIgnoreCase(firstJoinPreference);
return preference == Preference.ENABLED;
}
@Override

View File

@ -1,5 +1,6 @@
package com.discordsrv.common.config.main.generic;
import com.discordsrv.common.config.configurate.annotation.Constants;
import org.apache.commons.lang3.StringUtils;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ -17,7 +18,8 @@ public class DestinationConfig {
public List<Long> channelIds = new ArrayList<>();
@Setting("threads")
@Comment("The threads that this in-game channel will forward to in Discord (this can be used instead of or with the channel-ids option)")
@Comment("The threads that this in-game channel will forward to in Discord (this can be used instead of or with the %1 option)")
@Constants.Comment("channel-ids")
public List<ThreadConfig> threads = new ArrayList<>(Collections.singletonList(new ThreadConfig()));
@ConfigSerializable

View File

@ -18,6 +18,7 @@
package com.discordsrv.common.config.main.linking;
import com.discordsrv.common.config.configurate.annotation.Constants;
import com.discordsrv.common.config.connection.ConnectionConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ -30,8 +31,15 @@ public class LinkedAccountConfig {
@Comment("The linked account provider\n"
+ "\n"
+ " - auto: Uses \"minecraftauth\" if the " + ConnectionConfig.FILE_NAME + " permits it and the server is in online mode, otherwise \"storage\"\n"
+ " - minecraftauth: Uses minecraftauth.me as the linked account provider\n"
+ " - storage: Use the configured database for linked accounts")
public String provider = "auto";
+ " - auto: Uses \"%3\" if the %1 permits it and the server is in online mode, otherwise \"%4\"\n"
+ " - %3: Uses %2 as the linked account provider\n"
+ " - %4: Use the configured database for linked accounts")
@Constants.Comment({ConnectionConfig.FILE_NAME, "minecraftauth.me", "minecraftauth", "storage"})
public Provider provider = Provider.AUTO;
public enum Provider {
AUTO,
MINECRAFTAUTH,
STORAGE
}
}

View File

@ -26,8 +26,13 @@ import org.spongepowered.configurate.objectmapping.meta.Setting;
@ConfigSerializable
public class ServerRequiredLinkingConfig extends RequiredLinkingConfig {
@Comment("How the player should be blocked from joining the server.\nAvailable options: KICK, FREEZE")
public String action = "KICK";
@Comment("How the player should be blocked from joining the server.\nAvailable options: kick, freeze")
public Action action = Action.KICK;
public enum Action {
KICK,
FREEZE
}
@Setting(nodeFromParent = true)
@Order(10)

View File

@ -64,7 +64,7 @@ public class SingleConsoleHandler {
}
ConsoleConfig.Appender appenderConfig = config.appender;
ConsoleConfig.OutputMode outputMode = appenderConfig.getOutputMode();
ConsoleConfig.OutputMode outputMode = appenderConfig.outputMode;
Queue<LogMessage> currentBuffer = new LinkedBlockingQueue<>();
LogEntry entry;

View File

@ -149,8 +149,8 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
for (Map.Entry<GroupSyncConfig.PairConfig, Future<?>> entry : pairs.entrySet()) {
GroupSyncConfig.PairConfig pair = entry.getKey();
builder.append("\n- ").append(pair)
.append(" (tie-breaker: ").append(pair.tieBreaker())
.append(", direction: ").append(pair.direction())
.append(" (tie-breaker: ").append(pair.tieBreaker)
.append(", direction: ").append(pair.direction)
.append(", server context: ").append(pair.serverContext).append(")");
if (entry.getValue() != null) {
builder.append(" [Timed]");
@ -343,8 +343,8 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
return;
}
GroupSyncSide side = pair.tieBreaker();
GroupSyncDirection direction = pair.direction();
GroupSyncSide side = pair.tieBreaker;
GroupSyncDirection direction = pair.direction;
CompletableFuture<Void> future;
GroupSyncResult result;
if (hasRole) {
@ -473,7 +473,7 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
Map<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> futures = new LinkedHashMap<>();
for (GroupSyncConfig.PairConfig pair : pairs) {
GroupSyncDirection direction = pair.direction();
GroupSyncDirection direction = pair.direction;
if (direction == GroupSyncDirection.MINECRAFT_TO_DISCORD) {
// Not going Discord -> Minecraft
futures.put(pair, CompletableFuture.completedFuture(GroupSyncResult.WRONG_DIRECTION));
@ -543,7 +543,7 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
PermissionDataProvider.Groups permissionProvider = getPermissionProvider();
Map<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> futures = new LinkedHashMap<>();
for (GroupSyncConfig.PairConfig pair : pairs) {
GroupSyncDirection direction = pair.direction();
GroupSyncDirection direction = pair.direction;
if (direction == GroupSyncDirection.DISCORD_TO_MINECRAFT) {
// Not going Minecraft -> Discord
futures.put(pair, CompletableFuture.completedFuture(GroupSyncResult.WRONG_DIRECTION));

View File

@ -52,9 +52,14 @@ import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.regex.Pattern;
public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
// Filter for ASCII control characters which have no use being displayed, but might be misinterpreted somewhere
// Notably this excludes, 0x09 HT (\t), 0x0A LF (\n), 0x0B VT (\v) and 0x0D CR (\r) (which may be used for text formatting)
private static final Pattern ASCII_CONTROL_FILTER = Pattern.compile("[\\u0000-\\u0008\\u000C\\u000E-\\u001F\\u007F]");
private final Map<String, MessageSend> sends = new ConcurrentHashMap<>();
public DiscordChatMessageModule(DiscordSRV discordSRV) {
@ -182,6 +187,7 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
}
Placeholders message = new Placeholders(event.getContent());
message.replaceAll(ASCII_CONTROL_FILTER, "");
chatConfig.contentRegexFilters.forEach(message::replaceAll);
Component messageComponent = DiscordSRVMinecraftRenderer.getWithContext(guild, chatConfig, () ->

View File

@ -24,6 +24,7 @@ import com.discordsrv.common.config.Config;
import com.discordsrv.common.config.configurate.annotation.Untranslated;
import com.discordsrv.common.config.configurate.manager.abstraction.ConfigurateConfigManager;
import com.discordsrv.common.config.configurate.manager.abstraction.TranslatedConfigManager;
import com.discordsrv.common.logging.backend.impl.JavaLoggerImpl;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
@ -82,16 +83,16 @@ public final class DiscordSRVTranslation {
String fileIdentifier = config.getFileName();
ConfigurationNode commentSection = node.node(fileIdentifier + "_comments");
String header = configManager.nodeOptions().header();
String header = configManager.nodeOptions(false).header();
if (header != null) {
commentSection.node("$header").set(header);
}
ObjectMapper.Factory mapperFactory = configManager.objectMapperBuilder()
ObjectMapper.Factory mapperFactory = configManager.objectMapperBuilder(false)
.addProcessor(Untranslated.class, untranslatedProcessorFactory)
.build();
TranslationConfigManagerProxy<?> configManagerProxy = new TranslationConfigManagerProxy<>(DATA_DIRECTORY, mapperFactory, configManager);
TranslationConfigManagerProxy<?> configManagerProxy = new TranslationConfigManagerProxy<>(DATA_DIRECTORY, JavaLoggerImpl.getRoot(), mapperFactory, configManager);
CommentedConfigurationNode configurationNode = configManagerProxy.getDefaultNode(mapperFactory);
convertCommentsToOptions(configurationNode, commentSection);

View File

@ -20,6 +20,7 @@ package com.discordsrv.config;
import com.discordsrv.common.config.configurate.manager.abstraction.ConfigurateConfigManager;
import com.discordsrv.common.config.configurate.manager.loader.YamlConfigLoaderProvider;
import com.discordsrv.common.logging.Logger;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
@ -32,8 +33,8 @@ public class TranslationConfigManagerProxy<C>
private final ObjectMapper.Factory objectMapper;
private final ConfigurateConfigManager<C, ?> configManager;
public TranslationConfigManagerProxy(Path dataDirectory, ObjectMapper.Factory objectMapper, ConfigurateConfigManager<C, ?> configManager) {
super(dataDirectory);
public TranslationConfigManagerProxy(Path dataDirectory, Logger logger, ObjectMapper.Factory objectMapper, ConfigurateConfigManager<C, ?> configManager) {
super(dataDirectory, logger);
this.objectMapper = objectMapper;
this.configManager = configManager;
}