diff --git a/Patchwork/build.gradle b/Patchwork/build.gradle index 6ce2389..fbf0c03 100644 --- a/Patchwork/build.gradle +++ b/Patchwork/build.gradle @@ -8,6 +8,9 @@ repositories { dependencies { library 'io.projectreactor:reactor-core:3.5.4' library 'io.github.classgraph:classgraph:4.8.162' + library 'com.electronwill.night-config:core:3.6.7' + library 'com.electronwill.night-config:toml:3.6.7' + library 'com.electronwill.night-config:json:3.6.7' api 'org.slf4j:slf4j-api:1.7.36' testImplementation platform('org.junit:junit-bom:5.9.1') @@ -19,7 +22,7 @@ bukkit { description = "Freedom Network Suite Core Module (API & Library)" } -var weight = 1 +def weight = 1 test { useJUnitPlatform() diff --git a/Patchwork/src/main/java/fns/patchwork/base/Registration.java b/Patchwork/src/main/java/fns/patchwork/base/Registration.java index ff81f7f..db491fa 100644 --- a/Patchwork/src/main/java/fns/patchwork/base/Registration.java +++ b/Patchwork/src/main/java/fns/patchwork/base/Registration.java @@ -23,12 +23,12 @@ package fns.patchwork.base; -import fns.patchwork.data.ConfigRegistry; -import fns.patchwork.data.EventRegistry; -import fns.patchwork.data.GroupRegistry; -import fns.patchwork.data.ModuleRegistry; -import fns.patchwork.data.ServiceTaskRegistry; -import fns.patchwork.data.UserRegistry; +import fns.patchwork.registry.ConfigRegistry; +import fns.patchwork.registry.EventRegistry; +import fns.patchwork.registry.GroupRegistry; +import fns.patchwork.registry.ModuleRegistry; +import fns.patchwork.registry.ServiceTaskRegistry; +import fns.patchwork.registry.UserRegistry; /** * This class is a holder for each registry in the data package. diff --git a/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java b/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java index 8e8c1fa..01162c6 100644 --- a/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java +++ b/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java @@ -43,6 +43,7 @@ import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; /** @@ -60,6 +61,8 @@ import org.jetbrains.annotations.NotNull; *
* This class is not meant to be used outside Patchwork. */ +@ApiStatus.Internal +@ApiStatus.NonExtendable public final class BukkitDelegate extends Command implements PluginIdentifiableCommand { private final JavaPlugin plugin; @@ -274,3 +277,4 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC return this.plugin; } } + diff --git a/Patchwork/src/main/java/fns/patchwork/command/annotation/Completions.java b/Patchwork/src/main/java/fns/patchwork/command/annotation/Completions.java index 54f2a30..20be942 100644 --- a/Patchwork/src/main/java/fns/patchwork/command/annotation/Completions.java +++ b/Patchwork/src/main/java/fns/patchwork/command/annotation/Completions.java @@ -27,6 +27,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.jetbrains.annotations.ApiStatus; /** * A marker interface which represents a holder for multiple {@link Completion} annotations. @@ -36,6 +37,7 @@ import java.lang.annotation.Target; */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@ApiStatus.Internal public @interface Completions { /** diff --git a/Patchwork/src/main/java/fns/patchwork/config/ConfigType.java b/Patchwork/src/main/java/fns/patchwork/config/ConfigType.java new file mode 100644 index 0000000..1a77e45 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/config/ConfigType.java @@ -0,0 +1,88 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.patchwork.config; + +import com.electronwill.nightconfig.core.Config; +import com.electronwill.nightconfig.core.ConfigFormat; +import com.electronwill.nightconfig.core.io.ConfigParser; +import com.electronwill.nightconfig.core.io.ConfigWriter; +import com.electronwill.nightconfig.json.FancyJsonWriter; +import com.electronwill.nightconfig.json.JsonFormat; +import com.electronwill.nightconfig.json.JsonParser; +import com.electronwill.nightconfig.json.MinimalJsonWriter; +import com.electronwill.nightconfig.toml.TomlFormat; +import com.electronwill.nightconfig.toml.TomlParser; +import com.electronwill.nightconfig.toml.TomlWriter; + +public enum ConfigType +{ + TOML(TomlFormat.instance(), + ".toml", + new TomlWriter(), + new TomlParser()), + JSON(JsonFormat.minimalInstance(), + ".json", + new MinimalJsonWriter(), + new JsonParser()), + JSON_FANCY(JsonFormat.fancyInstance(), + ".json", + new FancyJsonWriter(), + new JsonParser()); + + private final ConfigFormat format; + private final String fileExtension; + private final ConfigWriter writer; + private final ConfigParser parser; + + ConfigType(final ConfigFormat format, + final String fileExtension, + final ConfigWriter writer, + final ConfigParser parser) + { + this.format = format; + this.fileExtension = fileExtension; + this.writer = writer; + this.parser = parser; + } + + public ConfigFormat getFormat() + { + return this.format; + } + + public String getExtension() + { + return this.fileExtension; + } + + public ConfigWriter getWriter() + { + return this.writer; + } + + public ConfigParser getParser() + { + return this.parser; + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/config/Configuration.java b/Patchwork/src/main/java/fns/patchwork/config/Configuration.java index 3dfeec4..b7b4780 100644 --- a/Patchwork/src/main/java/fns/patchwork/config/Configuration.java +++ b/Patchwork/src/main/java/fns/patchwork/config/Configuration.java @@ -23,9 +23,8 @@ package fns.patchwork.config; -import fns.patchwork.api.Context; +import fns.patchwork.provider.Context; import fns.patchwork.provider.ContextProvider; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; import java.io.File; diff --git a/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java new file mode 100644 index 0000000..410e791 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java @@ -0,0 +1,224 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.patchwork.config; + +import com.electronwill.nightconfig.core.Config; +import com.electronwill.nightconfig.core.ConfigFormat; +import com.electronwill.nightconfig.core.UnmodifiableConfig; +import fns.patchwork.utils.FileUtils; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +public final class GenericConfiguration implements Configuration +{ + private final File configFile; + private final String fileName; + private final Config config; + private final ConfigType configType; + + public GenericConfiguration(@NotNull final ConfigType configType, + @Nullable final JavaPlugin plugin, + @NotNull final File dataFolder, + @NotNull final String fileName, + final boolean isConcurrent) throws IOException + { + if (!fileName.endsWith(configType.getExtension())) + throw new IllegalArgumentException("File name must end with " + configType.getExtension() + "!"); + + // Ternary just to piss off Allink :) + final Optional file = (plugin != null) ? + FileUtils.getOrCreateFileWithResource(dataFolder, fileName, plugin) : + FileUtils.getOrCreateFile(dataFolder, fileName); + + if (file.isEmpty()) + throw new FileNotFoundException(); + + this.configFile = file.get(); + this.fileName = fileName; + this.configType = configType; + + final ConfigFormat format = configType.getFormat(); + + // Another ternary just to piss off Allink :) + this.config = isConcurrent ? format.createConcurrentConfig() : format.createConfig(); + + this.load(); + } + + public GenericConfiguration(final ConfigType type, final File dataFolder, final String fileName) + throws IOException + { + this(type, null, dataFolder, fileName, false); + } + + public GenericConfiguration(final ConfigType type, final JavaPlugin plugin, final String fileName) + throws IOException + { + this(type, plugin, plugin.getDataFolder(), fileName, false); + } + + public GenericConfiguration(final ConfigType type, final File dataFolder, final String fileName, + final boolean isConcurrent) + throws IOException + { + this(type, null, dataFolder, fileName, isConcurrent); + } + + @Override + public void save() throws IOException + { + final File backup = new File(this.configFile.getParentFile(), this.fileName + ".bak"); + + if (backup.exists()) + Files.delete(backup.toPath()); + + Files.copy(this.configFile.toPath(), backup.toPath()); + + try (final FileWriter writer = new FileWriter(this.configFile)) + { + this.configType.getWriter().write(this.getConfig(), writer); + } + } + + @Override + public void load() throws IOException { + try (final FileReader reader = new FileReader(this.configFile)) { + this.config.clear(); + + final UnmodifiableConfig parsed = this.configType.getParser().parse(reader).unmodifiable(); + this.config.putAll(parsed); + } + } + + @Override + public String getFileName() + { + return fileName; + } + + @Override + public File getConfigurationFile() + { + return configFile; + } + + @Override + public String getString(final String path) + { + if (!(this.getConfig().get(path) instanceof String)) + throw new IllegalArgumentException(String.format("Value at path %s is not a string!", path)); + + return this.getConfig().get(path); + } + + @Override + public boolean getBoolean(String path) + { + if (!(this.getConfig().get(path) instanceof Boolean)) + throw new IllegalArgumentException(String.format("Value at path %s is not a boolean!", path)); + + return this.getConfig().get(path); + } + + @Override + @ApiStatus.Internal + public @Unmodifiable List getList(String path, Class clazz) + { + // TODO: Figure out how to parse lists with Night Config. + + return new ArrayList<>(); + } + + @Override + @ApiStatus.Internal + public @Unmodifiable List getStringList(String path) + { + // TODO: Figure out how to parse lists with Night Config. + + return new ArrayList<>(); + } + + @Override + public int getInt(String path) + { + return this.getConfig().getInt(path); + } + + @Override + public long getLong(String path) + { + return this.getConfig().getLong(path); + } + + @Override + public double getDouble(String path) + { + if (!(this.getConfig().get(path) instanceof Double)) + throw new IllegalArgumentException(String.format("Value at path %s is not a double!", path)); + + return this.getConfig().get(path); + } + + @Override + public Optional get(String path, Class clazz) + { + // I love ternary statements, sorry Allink :) + return clazz.isInstance(this.getConfig().get(path)) ? + Optional.of(clazz.cast(this.getConfig().get(path))) : + Optional.empty(); + } + + @Override + public T getOrDefault(String path, Class clazz, T fallback) + { + return this.get(path, clazz).orElse(fallback); + } + + @Override + public void set(final String path, final T value) { + this.config.set(path, value); + } + + private UnmodifiableConfig getConfig() + { + return config.unmodifiable(); + } + + public ConfigType getConfigType() + { + return configType; + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/event/EventBus.java b/Patchwork/src/main/java/fns/patchwork/event/EventBus.java index 9325702..cbda58d 100644 --- a/Patchwork/src/main/java/fns/patchwork/event/EventBus.java +++ b/Patchwork/src/main/java/fns/patchwork/event/EventBus.java @@ -23,7 +23,7 @@ package fns.patchwork.event; -import fns.patchwork.api.Context; +import fns.patchwork.provider.Context; import fns.patchwork.base.Patchwork; import fns.patchwork.service.Service; import java.util.HashSet; diff --git a/Patchwork/src/main/java/fns/patchwork/particle/Trail.java b/Patchwork/src/main/java/fns/patchwork/particle/Trail.java index 9d9f70b..62ceff3 100644 --- a/Patchwork/src/main/java/fns/patchwork/particle/Trail.java +++ b/Patchwork/src/main/java/fns/patchwork/particle/Trail.java @@ -23,7 +23,6 @@ package fns.patchwork.particle; -import fns.patchwork.api.Interpolator; import fns.patchwork.utils.InterpolationUtils; import java.util.Set; import java.util.UUID; @@ -101,7 +100,7 @@ public interface Trail * @see #getColor() * @see Particle * @see InterpolationUtils - * @see Interpolator + * @see InterpolationUtils.Interpolator */ @Nullable Set getColors(); diff --git a/Patchwork/src/main/java/fns/patchwork/api/Context.java b/Patchwork/src/main/java/fns/patchwork/provider/Context.java similarity index 99% rename from Patchwork/src/main/java/fns/patchwork/api/Context.java rename to Patchwork/src/main/java/fns/patchwork/provider/Context.java index 3369673..06757d0 100644 --- a/Patchwork/src/main/java/fns/patchwork/api/Context.java +++ b/Patchwork/src/main/java/fns/patchwork/provider/Context.java @@ -21,9 +21,8 @@ * SOFTWARE. */ -package fns.patchwork.api; +package fns.patchwork.provider; -import fns.patchwork.provider.ContextProvider; import java.util.function.Function; import net.kyori.adventure.text.Component; import org.bukkit.Location; diff --git a/Patchwork/src/main/java/fns/patchwork/data/ConfigRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/ConfigRegistry.java similarity index 98% rename from Patchwork/src/main/java/fns/patchwork/data/ConfigRegistry.java rename to Patchwork/src/main/java/fns/patchwork/registry/ConfigRegistry.java index da6f64a..d2aab53 100644 --- a/Patchwork/src/main/java/fns/patchwork/data/ConfigRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/ConfigRegistry.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.data; +package fns.patchwork.registry; import fns.patchwork.config.Configuration; import java.util.HashMap; diff --git a/Patchwork/src/main/java/fns/patchwork/data/EventRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/EventRegistry.java similarity index 98% rename from Patchwork/src/main/java/fns/patchwork/data/EventRegistry.java rename to Patchwork/src/main/java/fns/patchwork/registry/EventRegistry.java index 72ef4cb..443c61f 100644 --- a/Patchwork/src/main/java/fns/patchwork/data/EventRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/EventRegistry.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.data; +package fns.patchwork.registry; import fns.patchwork.event.FEvent; import fns.patchwork.provider.EventProvider; diff --git a/Patchwork/src/main/java/fns/patchwork/data/GroupRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/GroupRegistry.java similarity index 98% rename from Patchwork/src/main/java/fns/patchwork/data/GroupRegistry.java rename to Patchwork/src/main/java/fns/patchwork/registry/GroupRegistry.java index aaf3ab5..a2f5c1e 100644 --- a/Patchwork/src/main/java/fns/patchwork/data/GroupRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/GroupRegistry.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.data; +package fns.patchwork.registry; import fns.patchwork.permissible.Group; import java.util.ArrayList; diff --git a/Patchwork/src/main/java/fns/patchwork/data/ModuleRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/ModuleRegistry.java similarity index 98% rename from Patchwork/src/main/java/fns/patchwork/data/ModuleRegistry.java rename to Patchwork/src/main/java/fns/patchwork/registry/ModuleRegistry.java index c4b9f95..69873b6 100644 --- a/Patchwork/src/main/java/fns/patchwork/data/ModuleRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/ModuleRegistry.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.data; +package fns.patchwork.registry; import fns.patchwork.provider.ModuleProvider; import java.util.ArrayList; diff --git a/Patchwork/src/main/java/fns/patchwork/data/ServiceTaskRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java similarity index 99% rename from Patchwork/src/main/java/fns/patchwork/data/ServiceTaskRegistry.java rename to Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java index b9b7b5a..c346c0c 100644 --- a/Patchwork/src/main/java/fns/patchwork/data/ServiceTaskRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.data; +package fns.patchwork.registry; import fns.patchwork.service.Service; import fns.patchwork.service.ServiceSubscription; diff --git a/Patchwork/src/main/java/fns/patchwork/data/UserRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/UserRegistry.java similarity index 99% rename from Patchwork/src/main/java/fns/patchwork/data/UserRegistry.java rename to Patchwork/src/main/java/fns/patchwork/registry/UserRegistry.java index 10435e8..7daaefe 100644 --- a/Patchwork/src/main/java/fns/patchwork/data/UserRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/UserRegistry.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.data; +package fns.patchwork.registry; import fns.patchwork.user.User; import fns.patchwork.user.UserData; diff --git a/Patchwork/src/main/java/fns/patchwork/api/Serializable.java b/Patchwork/src/main/java/fns/patchwork/serializer/Serializable.java similarity index 98% rename from Patchwork/src/main/java/fns/patchwork/api/Serializable.java rename to Patchwork/src/main/java/fns/patchwork/serializer/Serializable.java index a8d33b3..cee964f 100644 --- a/Patchwork/src/main/java/fns/patchwork/api/Serializable.java +++ b/Patchwork/src/main/java/fns/patchwork/serializer/Serializable.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.api; +package fns.patchwork.serializer; /** * This interface represents a Serializable object. Objects which require custom serialization and cannot simply diff --git a/Patchwork/src/main/java/fns/patchwork/utils/FileUtils.java b/Patchwork/src/main/java/fns/patchwork/utils/FileUtils.java new file mode 100644 index 0000000..0f62b70 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/utils/FileUtils.java @@ -0,0 +1,129 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.patchwork.utils; + +import fns.patchwork.utils.logging.FNS4J; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public final class FileUtils +{ + @NonNls + private static final String CREATED_DIRECTORY = "Created directory "; + + private FileUtils() + { + throw new AssertionError(); + } + + public static String getExtension(@NotNull final File file) + { + return file.getName() + .substring(file.getName() + .lastIndexOf('.')); + } + + public static Optional getOrCreateDirectory(final File parentDirectory, final String directoryName) + { + if (parentDirectory.mkdirs()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + parentDirectory.getAbsolutePath()); + + final File directory = new File(parentDirectory, directoryName); + if (directory.mkdirs()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + directory.getAbsolutePath()); + + if (directory.exists()) + return Optional.of(directory); + + return Optional.empty(); + } + + public static Optional getOrCreateDirectory(final Path directoryPath) + { + Optional directory = Optional.empty(); + + if (directoryPath.toFile().mkdirs()) + directory = Optional.of(directoryPath.toFile()); + + if (directory.isPresent()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + directoryPath.toAbsolutePath()); + + return directory; + } + + public static Optional getOrCreateFile(final File parentDirectory, final String fileName) + { + if (parentDirectory.mkdirs()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + parentDirectory.getAbsolutePath()); + + final File file = new File(parentDirectory, fileName); + try + { + if (file.createNewFile()) + FNS4J.PATCHWORK.info("Created file " + file.getAbsolutePath()); + + return Optional.of(file); + } + catch (final IOException ex) + { + FNS4J.PATCHWORK.error("Failed to create file " + fileName + ": " + ex.getMessage()); + return Optional.empty(); + } + } + + public static Optional getOrCreateFileWithResource(final File parentDirectory, + final String fileName, + final JavaPlugin plugin) + { + if (parentDirectory.mkdirs()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + parentDirectory.getAbsolutePath()); + + final File file = new File(parentDirectory, fileName); + try + { + if (file.createNewFile()) + { + FNS4J.PATCHWORK.info("Created file " + file.getAbsolutePath()); + FNS4J.PATCHWORK.info( + "Copying default file from resources/" + fileName + " to " + file.getAbsolutePath()); + + plugin.saveResource(fileName, true); + FNS4J.PATCHWORK.info( + "Successfully copied default file from resources/" + fileName + " to " + file.getAbsolutePath()); + } + + return Optional.of(file); + } + catch (final IOException ex) + { + FNS4J.PATCHWORK.error("Failed to create file " + fileName + ": " + ex.getMessage()); + return Optional.empty(); + } + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/utils/InterpolationUtils.java b/Patchwork/src/main/java/fns/patchwork/utils/InterpolationUtils.java index c48ef15..7c7e46e 100644 --- a/Patchwork/src/main/java/fns/patchwork/utils/InterpolationUtils.java +++ b/Patchwork/src/main/java/fns/patchwork/utils/InterpolationUtils.java @@ -23,7 +23,6 @@ package fns.patchwork.utils; -import fns.patchwork.api.Interpolator; import java.util.LinkedHashSet; import java.util.Set; import net.kyori.adventure.text.format.NamedTextColor; @@ -155,4 +154,24 @@ public final class InterpolationUtils { return componentRGBGradient(length, from, to, InterpolationUtils::linear); } + + /** + * Interpolates a range of values and returns the results in a {@link Double} array. + *
+ * This is a functional interface, to allow for lambda expressions, but also for anonymous custom interpolation + * implementations. + */ + @FunctionalInterface + public static interface Interpolator + { + /** + * Interpolates a range of values and returns the results in a {@link Double} array. + * + * @param from The starting value. + * @param to The ending value. + * @param max The number of values to interpolate. + * @return The interpolated values. + */ + double[] interpolate(final double from, final double to, final int max); + } } diff --git a/Tyr/build.gradle b/Tyr/build.gradle index 8e258d1..4bd615f 100644 --- a/Tyr/build.gradle +++ b/Tyr/build.gradle @@ -9,6 +9,12 @@ repositories { mavenCentral() } +bukkit { + main = "fns.tyr.Tyr" + description = "SSH -> RCON Module for Freedom Network Suite" + depend = ["Patchwork", "Datura"] +} + dependencies { compileOnly project(":Patchwork") compileOnly project(":Datura") diff --git a/Veritas/build.gradle b/Veritas/build.gradle index 777e8e8..afed021 100644 --- a/Veritas/build.gradle +++ b/Veritas/build.gradle @@ -9,6 +9,12 @@ repositories { mavenCentral() } +bukkit { + main = "fns.veritas.Veritas" + description = "Discord Module for Freedom Network Suite" + depend = ["Patchwork", "Datura"] +} + dependencies { compileOnly project(":Patchwork") compileOnly project(":Datura") diff --git a/Veritas/src/main/java/fns/veritas/Aggregate.java b/Veritas/src/main/java/fns/veritas/Aggregate.java index 3d2df51..51afc09 100644 --- a/Veritas/src/main/java/fns/veritas/Aggregate.java +++ b/Veritas/src/main/java/fns/veritas/Aggregate.java @@ -24,31 +24,53 @@ package fns.veritas; import fns.patchwork.utils.logging.FNS4J; +import fns.veritas.bukkit.BukkitNative; +import fns.veritas.bukkit.ServerListener; import fns.veritas.client.BotClient; import fns.veritas.client.BotConfig; +import org.bukkit.Bukkit; public class Aggregate { - private final FNS4J logger; + private static final FNS4J logger = FNS4J.getLogger("Veritas"); private final BotClient bot; private final Veritas plugin; + private final BukkitNative bukkitNativeListener; + private final ServerListener serverListener; public Aggregate(final Veritas plugin) { this.plugin = plugin; - this.logger = FNS4J.getLogger(plugin.getName()); this.bot = new BotClient(new BotConfig(plugin)); + this.bukkitNativeListener = new BukkitNative(plugin); + this.serverListener = new ServerListener(plugin); + + Bukkit.getServer().getPluginManager().registerEvents(this.getBukkitNativeListener(), plugin); + this.getServerListener().minecraftChatBound().subscribe(); } - public FNS4J getLogger() { + public static FNS4J getLogger() + { return logger; } - public BotClient getBot() { + public ServerListener getServerListener() + { + return serverListener; + } + + public BukkitNative getBukkitNativeListener() + { + return bukkitNativeListener; + } + + public BotClient getBot() + { return bot; } - public Veritas getPlugin() { + public Veritas getPlugin() + { return plugin; } } diff --git a/Veritas/src/main/java/fns/veritas/Veritas.java b/Veritas/src/main/java/fns/veritas/Veritas.java index bd705e2..4616533 100644 --- a/Veritas/src/main/java/fns/veritas/Veritas.java +++ b/Veritas/src/main/java/fns/veritas/Veritas.java @@ -34,9 +34,8 @@ public class Veritas extends JavaPlugin { this.aggregate = new Aggregate(this); - getAggregate() - .getLogger() - .info("Veritas has been enabled!"); + Aggregate.getLogger() + .info("Veritas has been enabled!"); } public Aggregate getAggregate() diff --git a/Veritas/src/main/java/fns/veritas/bukkit/BukkitNative.java b/Veritas/src/main/java/fns/veritas/bukkit/BukkitNative.java index 287ce04..6d598d7 100644 --- a/Veritas/src/main/java/fns/veritas/bukkit/BukkitNative.java +++ b/Veritas/src/main/java/fns/veritas/bukkit/BukkitNative.java @@ -109,7 +109,7 @@ public class BukkitNative implements Listener if (!plugin.getServer().hasWhitelist() && bot != null) { plugin.getAggregate().getBot().messageChatChannel(player.getName() - + " \u00BB " + + " ยป " + message, true); } } diff --git a/Veritas/src/main/java/fns/veritas/bukkit/ServerListener.java b/Veritas/src/main/java/fns/veritas/bukkit/ServerListener.java index 1c9b2f3..6d9e113 100644 --- a/Veritas/src/main/java/fns/veritas/bukkit/ServerListener.java +++ b/Veritas/src/main/java/fns/veritas/bukkit/ServerListener.java @@ -27,6 +27,7 @@ import discord4j.core.event.domain.message.MessageCreateEvent; import discord4j.core.object.entity.Attachment; import discord4j.core.object.entity.Member; import discord4j.core.object.entity.Message; +import fns.veritas.Aggregate; import fns.veritas.Veritas; import fns.veritas.client.BotClient; import net.kyori.adventure.text.Component; @@ -35,6 +36,8 @@ import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import reactor.core.publisher.Mono; public class ServerListener { @@ -48,9 +51,9 @@ public class ServerListener this.bot = plugin.getAggregate().getBot(); } - public void minecraftChatBound() + public Mono minecraftChatBound() { - bot.getClient() + return bot.getClient() .getEventDispatcher() .on(MessageCreateEvent.class) .filter(m -> m.getMessage() @@ -62,8 +65,9 @@ public class ServerListener .orElseThrow(IllegalAccessError::new) .getId() .equals(plugin.getAggregate().getBot().getClient().getSelfId())) - .doOnError(plugin.getAggregate().getLogger()::error) - .subscribe(this::doMessageBodyDetails); + .doOnError(Aggregate.getLogger()::error) + .doOnNext(this::doMessageBodyDetails) + .then(); } private void doMessageBodyDetails(MessageCreateEvent m) @@ -83,6 +87,14 @@ public class ServerListener user = user.append(Component.text(member.getDisplayName().trim())); + final TextComponent message = builder(msg); + + Bukkit.broadcast(builder.append(prefix, user, message).build()); + } + + @NotNull + private TextComponent builder(Message msg) + { TextComponent message = Component.text(": ", NamedTextColor.DARK_GRAY) .append( Component.text(msg.getContent(), NamedTextColor.WHITE)); @@ -102,7 +114,6 @@ public class ServerListener .clickEvent(ClickEvent.openUrl(attachment.getUrl()))); } } - - Bukkit.broadcast(builder.append(prefix, user, message).build()); + return message; } } diff --git a/Veritas/src/main/java/fns/veritas/client/BotClient.java b/Veritas/src/main/java/fns/veritas/client/BotClient.java index 6de4540..46bc391 100644 --- a/Veritas/src/main/java/fns/veritas/client/BotClient.java +++ b/Veritas/src/main/java/fns/veritas/client/BotClient.java @@ -23,36 +23,40 @@ package fns.veritas.client; -import com.google.common.collect.ImmutableList; import discord4j.common.util.Snowflake; import discord4j.core.DiscordClientBuilder; import discord4j.core.GatewayDiscordClient; +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; import discord4j.core.object.entity.Guild; import discord4j.core.object.entity.Message; import discord4j.core.object.entity.channel.TextChannel; import discord4j.core.spec.MessageCreateSpec; +import fns.veritas.cmd.base.BotCommandHandler; +import java.util.List; import reactor.core.publisher.Mono; public class BotClient { private final GatewayDiscordClient client; private final BotConfig config; - private final ImmutableList DISCORD_SUBDOMAINS; + private final List subdomains; public BotClient(final BotConfig config) { this.config = config; - this.DISCORD_SUBDOMAINS = ImmutableList.of("discordapp.com", "discord.com", "discord.gg"); + this.subdomains = List.of("discordapp.com", "discord.com", "discord.gg"); + this.client = DiscordClientBuilder.create(config.getToken()) .build() .login() .block(); - } - public void validateConnection() - { if (client == null) throw new IllegalStateException(); + + final BotCommandHandler handler = new BotCommandHandler(client.getRestClient()); + + client.on(ChatInputInteractionEvent.class, handler::handle); } public String getBotId() @@ -87,14 +91,14 @@ public class BotClient public void messageChatChannel(String message, boolean system) { - String chat_channel_id = config.getChatChannelId().asString(); + String channelID = config.getChatChannelId().asString(); String sanitizedMessage = (system) ? message : sanitizeChatMessage(message); if (sanitizedMessage.isBlank()) return; - if (!chat_channel_id.isEmpty()) + if (!channelID.isEmpty()) { MessageCreateSpec spec = MessageCreateSpec.builder() .content(sanitizedMessage) @@ -124,7 +128,7 @@ public class BotClient return ""; } - for (String subdomain : DISCORD_SUBDOMAINS) + for (String subdomain : subdomains) { if (message.toLowerCase().contains(subdomain + "/invite")) { diff --git a/Veritas/src/main/java/fns/veritas/client/BotConfig.java b/Veritas/src/main/java/fns/veritas/client/BotConfig.java index 4ab90f0..08407ad 100644 --- a/Veritas/src/main/java/fns/veritas/client/BotConfig.java +++ b/Veritas/src/main/java/fns/veritas/client/BotConfig.java @@ -24,8 +24,8 @@ package fns.veritas.client; import discord4j.common.util.Snowflake; -import discord4j.discordjson.Id; import fns.patchwork.config.WrappedBukkitConfiguration; +import fns.veritas.Aggregate; import fns.veritas.Veritas; import java.io.File; import java.io.IOException; @@ -108,7 +108,7 @@ public class BotConfig } catch (IOException e) { - plugin.getAggregate().getLogger().error(e); + Aggregate.getLogger().error(e); } } } diff --git a/Veritas/src/main/java/fns/veritas/cmd/HelpCommand.java b/Veritas/src/main/java/fns/veritas/cmd/HelpCommand.java new file mode 100644 index 0000000..b3cc27a --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/cmd/HelpCommand.java @@ -0,0 +1,69 @@ +/* + * This file is part of Freedom-Network-Suite - https://github.com/AtlasMediaGroup/Freedom-Network-Suite + * Copyright (C) 2023 Total Freedom Server Network and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.veritas.cmd; + +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; +import fns.veritas.cmd.base.BotCommand; +import fns.veritas.messaging.Embed; +import fns.veritas.messaging.EmbedWrapper; +import java.util.ArrayList; +import java.util.List; +import reactor.core.publisher.Mono; + +public class HelpCommand implements BotCommand +{ + @Override + public String getName() + { + return "help"; + } + + @Override + public Mono handle(ChatInputInteractionEvent event) + { + final List content = new ArrayList<>(); + final EmbedWrapper e = new EmbedWrapper(); + + content.add(embedContent("help", + "Shows this message. \n" + + "Use /help info to see information about the server.", + false)); + content.add(embedContent("tps", + "Shows the server's current TPS.", + false)); + content.add(embedContent("list", + "Shows a list of all online players. \n" + + "Use /list staff to show online staff.", + false)); + e.quickEmbed("Command List:", + "A list of all currently supported commands", + content); + + return event.reply() + .withContent("Here is a list of all currently supported commands:") + .withEmbeds(e.getEmbeds()) + .withEphemeral(true) + .then(); + } +} diff --git a/Veritas/src/main/java/fns/veritas/cmd/ListCommand.java b/Veritas/src/main/java/fns/veritas/cmd/ListCommand.java new file mode 100644 index 0000000..b3df0d1 --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/cmd/ListCommand.java @@ -0,0 +1,102 @@ +/* + * This file is part of Freedom-Network-Suite - https://github.com/AtlasMediaGroup/Freedom-Network-Suite + * Copyright (C) 2023 Total Freedom Server Network and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.veritas.cmd; + +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; +import discord4j.core.object.command.ApplicationCommandInteractionOption; +import discord4j.core.object.command.ApplicationCommandInteractionOptionValue; +import fns.patchwork.kyori.PlainTextWrapper; +import fns.veritas.cmd.base.BotCommand; +import fns.veritas.messaging.Embed; +import fns.veritas.messaging.EmbedWrapper; +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Bukkit; +import reactor.core.publisher.Mono; + +public class ListCommand implements BotCommand +{ + @Override + public String getName() + { + return "list"; + } + + @Override + public Mono handle(final ChatInputInteractionEvent event) + { + final boolean showStaff = event.getOption("staff") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asBoolean) + .orElse(false); + + if (showStaff) + return staffList(event); + + final EmbedWrapper e = new EmbedWrapper(); + final List embeds = new ArrayList<>(); + + Bukkit.getOnlinePlayers() + .forEach(player -> + { + final String display = PlainTextWrapper.toPlainText(player.displayName()); + final String actual = PlainTextWrapper.toPlainText(player.name()); + + final Embed embed = new Embed(display, actual, false); + embeds.add(embed); + }); + + e.quickEmbed("Player List", "List of currently online players:", embeds); + + return event.reply() + .withEmbeds(e.getEmbeds()) + .withEphemeral(true) + .then(); + } + + private Mono staffList(final ChatInputInteractionEvent event) + { + final EmbedWrapper wrapper = new EmbedWrapper(); + final List embeds = new ArrayList<>(); + + Bukkit.getOnlinePlayers() + .stream() + .filter(player -> player.hasPermission("fns.marker.staff")) + .forEach(player -> + { + final String display = PlainTextWrapper.toPlainText(player.displayName()); + final String actual = PlainTextWrapper.toPlainText(player.name()); + + final Embed embed = new Embed(display, actual, false); + embeds.add(embed); + }); + + wrapper.quickEmbed("Staff List", "List of currently online staff members:", embeds); + + return event.reply() + .withEmbeds(wrapper.getEmbeds()) + .withEphemeral(true) + .then(); + } +} diff --git a/Veritas/src/main/java/fns/veritas/cmd/TpsCommand.java b/Veritas/src/main/java/fns/veritas/cmd/TpsCommand.java new file mode 100644 index 0000000..f6f27c7 --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/cmd/TpsCommand.java @@ -0,0 +1,64 @@ +/* + * This file is part of Freedom-Network-Suite - https://github.com/AtlasMediaGroup/Freedom-Network-Suite + * Copyright (C) 2023 Total Freedom Server Network and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.veritas.cmd; + +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; +import fns.veritas.cmd.base.BotCommand; +import fns.veritas.messaging.Embed; +import fns.veritas.messaging.EmbedWrapper; +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Bukkit; +import reactor.core.publisher.Mono; + +public class TpsCommand implements BotCommand +{ + @Override + public String getName() + { + return "tps"; + } + + @Override + public Mono handle(ChatInputInteractionEvent event) + { + final double[] tps = Bukkit.getServer().getTPS(); + final EmbedWrapper e = new EmbedWrapper(); + + final List embeds = new ArrayList<>(); + + embeds.add(embedContent("1 Minute:", String.valueOf(tps[0]), false)); + embeds.add(embedContent("5 Minutes:", String.valueOf(tps[1]), false)); + embeds.add(embedContent("15 Minutes:", String.valueOf(tps[2]), false)); + + e.quickEmbed("Server TPS:", + "Current TPS (1m, 5m, 15m)", + embeds); + + return event.reply() + .withEmbeds(e.getEmbeds()) + .withEphemeral(true) + .then(); + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/api/Interpolator.java b/Veritas/src/main/java/fns/veritas/cmd/base/BotCommand.java similarity index 65% rename from Patchwork/src/main/java/fns/patchwork/api/Interpolator.java rename to Veritas/src/main/java/fns/veritas/cmd/base/BotCommand.java index 19d888e..12fba41 100644 --- a/Patchwork/src/main/java/fns/patchwork/api/Interpolator.java +++ b/Veritas/src/main/java/fns/veritas/cmd/base/BotCommand.java @@ -21,24 +21,23 @@ * SOFTWARE. */ -package fns.patchwork.api; +package fns.veritas.cmd.base; -/** - * Interpolates a range of values and returns the results in a {@link Double} array. - *
- * This is a functional interface, to allow for lambda expressions, but also for anonymous custom interpolation - * implementations. - */ -@FunctionalInterface -public interface Interpolator +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; +import fns.patchwork.utils.container.Trio; +import fns.veritas.messaging.Embed; +import reactor.core.publisher.Mono; + +public interface BotCommand { - /** - * Interpolates a range of values and returns the results in a {@link Double} array. - * - * @param from The starting value. - * @param to The ending value. - * @param max The number of values to interpolate. - * @return The interpolated values. - */ - double[] interpolate(final double from, final double to, final int max); + String getName(); + + Mono handle(final ChatInputInteractionEvent event); + + default Embed embedContent(final String field, + final String value, + final boolean inline) + { + return new Embed(field, value, inline); + } } diff --git a/Veritas/src/main/java/fns/veritas/cmd/base/BotCommandHandler.java b/Veritas/src/main/java/fns/veritas/cmd/base/BotCommandHandler.java new file mode 100644 index 0000000..de9a636 --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/cmd/base/BotCommandHandler.java @@ -0,0 +1,139 @@ +/* + * This file is part of Freedom-Network-Suite - https://github.com/AtlasMediaGroup/Freedom-Network-Suite + * Copyright (C) 2023 Total Freedom Server Network and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.veritas.cmd.base; + +import discord4j.common.JacksonResources; +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; +import discord4j.discordjson.json.ApplicationCommandRequest; +import discord4j.rest.RestClient; +import discord4j.rest.service.ApplicationService; +import fns.patchwork.utils.logging.FNS4J; +import fns.veritas.Veritas; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public class BotCommandHandler +{ + private final List commands = new ArrayList<>(); + private final RestClient restClient; + + public BotCommandHandler(final RestClient restClient) + { + this.restClient = restClient; + } + + public void registerFromPluginDirectory(final Veritas plugin) throws IOException + { + final List jsonFiles = new ArrayList<>(); + final File commandsFolder = new File(plugin.getDataFolder(), "commands"); + if (!commandsFolder.exists() && commandsFolder.mkdirs()) + { + FNS4J.getLogger("Veritas").info("Created cmds folder. Copying default cmds..."); + plugin.saveResource("commands/", true); + } + + final File[] files = commandsFolder.listFiles(); + + if (files == null) + throw new IOException("Commands folder is empty or is not a valid directory!"); + + Stream.of(files) + .map(File::getName) + .filter(name -> name.endsWith(".json")) + .forEach(jsonFiles::add); + + final JacksonResources d4jMapper = JacksonResources.create(); + + final ApplicationService applicationService = restClient.getApplicationService(); + final long applicationId = Objects.requireNonNull(restClient.getApplicationId().block()); + + final List cmds = new ArrayList<>(); + for (final String json : getCommandsJson(plugin, jsonFiles)) + { + final ApplicationCommandRequest request = d4jMapper.getObjectMapper() + .readValue(json, ApplicationCommandRequest.class); + + cmds.add(request); + } + + applicationService.bulkOverwriteGlobalApplicationCommand(applicationId, cmds) + .doOnNext(cmd -> Bukkit.getLogger().info("Successfully registered Global Command " + + cmd.name())) + .doOnError(e -> Bukkit.getLogger().severe("Failed to register global cmds.\n" + + e.getMessage())) + .subscribe(); + } + + private @NotNull List getCommandsJson(final JavaPlugin plugin, final List fileNames) throws IOException + { + final String commandsFolderName = "commands/"; + final URL url = this.getClass().getClassLoader().getResource(commandsFolderName); + Objects.requireNonNull(url, commandsFolderName + " could not be found"); + + final List list = new ArrayList<>(); + for (final String file : fileNames) + { + final String resourceFileAsString = getResourceFileAsString(plugin, commandsFolderName + file); + list.add(Objects.requireNonNull(resourceFileAsString, "Command file not found: " + file)); + } + return list; + } + + private @Nullable String getResourceFileAsString(final JavaPlugin plugin, final String fileName) throws IOException + { + try (final InputStream resourceAsStream = plugin.getResource(fileName)) + { + if (resourceAsStream == null) + return null; + try (final InputStreamReader inputStreamReader = new InputStreamReader(resourceAsStream); + final BufferedReader reader = new BufferedReader(inputStreamReader)) + { + return reader.lines().collect(Collectors.joining(System.lineSeparator())); + } + } + } + + public Mono handle(final ChatInputInteractionEvent event) + { + return Flux.fromIterable(commands) + .filter(cmd -> cmd.getName().equals(event.getCommandName())) + .next() + .flatMap(cmd -> cmd.handle(event)); + } +} diff --git a/Veritas/src/main/java/fns/veritas/messaging/Embed.java b/Veritas/src/main/java/fns/veritas/messaging/Embed.java new file mode 100644 index 0000000..2248341 --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/messaging/Embed.java @@ -0,0 +1,28 @@ +/* + * This file is part of Freedom-Network-Suite - https://github.com/AtlasMediaGroup/Freedom-Network-Suite + * Copyright (C) 2023 Total Freedom Server Network and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.veritas.messaging; + +public record Embed(String fieldName, String value, boolean inline) +{ +} diff --git a/Veritas/src/main/java/fns/veritas/messaging/EmbedWrapper.java b/Veritas/src/main/java/fns/veritas/messaging/EmbedWrapper.java new file mode 100644 index 0000000..1fb5178 --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/messaging/EmbedWrapper.java @@ -0,0 +1,63 @@ +/* + * This file is part of Freedom-Network-Suite - https://github.com/AtlasMediaGroup/Freedom-Network-Suite + * Copyright (C) 2023 Total Freedom Server Network and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.veritas.messaging; + +import discord4j.core.spec.EmbedCreateSpec; +import java.util.ArrayList; +import java.util.List; + +public class EmbedWrapper +{ + private final List embeds = new ArrayList<>(); + + public List getEmbeds() + { + return embeds; + } + + public void addEmbed(final EmbedCreateSpec embed) + { + this.embeds.add(embed); + } + + public EmbedCreateSpec.Builder create() + { + return EmbedCreateSpec.builder(); + } + + public void quickEmbed(final String title, + final String description, + final List content) + { + final EmbedCreateSpec.Builder builder = create() + .title(title) + .description(description); + + content.forEach(t -> builder.addField(t.fieldName(), + t.value(), + t.inline())); + + addEmbed(builder.build()); + } +} diff --git a/Veritas/src/main/java/fns/veritas/messaging/SimpleMessageWrapper.java b/Veritas/src/main/java/fns/veritas/messaging/SimpleMessageWrapper.java new file mode 100644 index 0000000..6d94b1b --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/messaging/SimpleMessageWrapper.java @@ -0,0 +1,72 @@ +/* + * This file is part of Freedom-Network-Suite - https://github.com/AtlasMediaGroup/Freedom-Network-Suite + * Copyright (C) 2023 Total Freedom Server Network and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.veritas.messaging; + +import discord4j.core.object.component.LayoutComponent; +import discord4j.core.spec.MessageCreateFields; +import discord4j.core.spec.MessageCreateSpec; +import discord4j.rest.util.AllowedMentions; + +public class SimpleMessageWrapper +{ + private final MessageCreateSpec.Builder spec; + + public SimpleMessageWrapper() + { + this.spec = MessageCreateSpec.builder(); + } + + public void setContent(final String content) + { + this.spec.content(content); + } + + public void setEmbeds(final EmbedWrapper embed) + { + this.spec.addAllEmbeds(embed.getEmbeds()); + } + + public void setAttachments(final MessageCreateFields.File... files) + { + this.spec.addFiles(files); + } + + public void setSpoilerAttachments(final MessageCreateFields.FileSpoiler... files) + { + this.spec.addFileSpoilers(files); + } + + public void setAllowedMentions(final AllowedMentions allowedMentions) + { + this.spec.allowedMentions(allowedMentions); + } + + public void setLayoutComponents(final LayoutComponent... components) + { + for (final LayoutComponent component : components) + { + this.spec.addComponent(component); + } + } +} diff --git a/Veritas/src/main/resources/ExampleCommand.txt b/Veritas/src/main/resources/ExampleCommand.txt new file mode 100644 index 0000000..5df6392 --- /dev/null +++ b/Veritas/src/main/resources/ExampleCommand.txt @@ -0,0 +1,33 @@ +{ + "name": "", + "description": "", + "options": [ + { + "name": "", + "description": "", + "type": 3, + "required": true + } + ] +} + +# <-- Types --> # + +1 -> Sub Command +2 -> Sub Command Group +3 -> String +4 -> Integer +5 -> Boolean +6 -> User +7 -> Channel +8 -> Role +9 -> Mentionable +10 -> Number + +# <-- Choices --> # +(From the official documentation) +Choices can be defined on the STRING, INTEGER, and NUMBER option types. +Choices are preset values the user can pick when selecting the option that contains them. + +CAUTION +If you specify choices for an option, these are the only valid values a user may pick. \ No newline at end of file diff --git a/Veritas/src/main/resources/commands/help.json b/Veritas/src/main/resources/commands/help.json new file mode 100644 index 0000000..ad70352 --- /dev/null +++ b/Veritas/src/main/resources/commands/help.json @@ -0,0 +1,4 @@ +{ + "name": "help", + "description": "Shows a list of commands." +} \ No newline at end of file diff --git a/Veritas/src/main/resources/commands/list.json b/Veritas/src/main/resources/commands/list.json new file mode 100644 index 0000000..f73548e --- /dev/null +++ b/Veritas/src/main/resources/commands/list.json @@ -0,0 +1,12 @@ +{ + "name": "list", + "description": "List all players on the server.", + "options": [ + { + "type": 5, + "name": "staff", + "description": "Show only staff members currently online.", + "required": false + } + ] +} \ No newline at end of file diff --git a/Veritas/src/main/resources/commands/tps.json b/Veritas/src/main/resources/commands/tps.json new file mode 100644 index 0000000..c3ac8b1 --- /dev/null +++ b/Veritas/src/main/resources/commands/tps.json @@ -0,0 +1,4 @@ +{ + "name": "tps", + "description": "Shows the current server TPS." +} \ No newline at end of file