From c71ab845b9e9098c15cd55528f6220427624619d Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Mon, 28 Aug 2023 01:53:48 -0500 Subject: [PATCH 1/7] Fully implement Discord bot --- Patchwork/build.gradle | 2 + .../java/fns/patchwork/base/Registration.java | 12 +- .../fns/patchwork/config/Configuration.java | 3 +- .../config/WrappedTomlConfiguration.java | 184 ++++++++++++++++++ .../java/fns/patchwork/event/EventBus.java | 2 +- .../java/fns/patchwork/particle/Trail.java | 3 +- .../patchwork/{api => provider}/Context.java | 3 +- .../{data => registry}/ConfigRegistry.java | 2 +- .../{data => registry}/EventRegistry.java | 2 +- .../{data => registry}/GroupRegistry.java | 2 +- .../{data => registry}/ModuleRegistry.java | 2 +- .../ServiceTaskRegistry.java | 2 +- .../{data => registry}/UserRegistry.java | 2 +- .../{api => serializer}/Serializable.java | 2 +- .../patchwork/utils/InterpolationUtils.java | 21 +- .../src/main/java/fns/veritas/Aggregate.java | 32 ++- .../src/main/java/fns/veritas/Veritas.java | 5 +- .../java/fns/veritas/bukkit/BukkitNative.java | 2 +- .../fns/veritas/bukkit/ServerListener.java | 23 ++- .../java/fns/veritas/client/BotClient.java | 22 ++- .../java/fns/veritas/client/BotConfig.java | 4 +- .../java/fns/veritas/cmd/HelpCommand.java | 69 +++++++ .../java/fns/veritas/cmd/ListCommand.java | 102 ++++++++++ .../main/java/fns/veritas/cmd/TpsCommand.java | 64 ++++++ .../java/fns/veritas/cmd/base/BotCommand.java | 35 ++-- .../veritas/cmd/base/BotCommandHandler.java | 139 +++++++++++++ .../java/fns/veritas/messaging/Embed.java | 28 +++ .../fns/veritas/messaging/EmbedWrapper.java | 63 ++++++ .../messaging/SimpleMessageWrapper.java | 72 +++++++ Veritas/src/main/resources/ExampleCommand.txt | 33 ++++ Veritas/src/main/resources/commands/help.json | 4 + Veritas/src/main/resources/commands/list.json | 12 ++ Veritas/src/main/resources/commands/tps.json | 4 + 33 files changed, 892 insertions(+), 65 deletions(-) create mode 100644 Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java rename Patchwork/src/main/java/fns/patchwork/{api => provider}/Context.java (99%) rename Patchwork/src/main/java/fns/patchwork/{data => registry}/ConfigRegistry.java (98%) rename Patchwork/src/main/java/fns/patchwork/{data => registry}/EventRegistry.java (98%) rename Patchwork/src/main/java/fns/patchwork/{data => registry}/GroupRegistry.java (98%) rename Patchwork/src/main/java/fns/patchwork/{data => registry}/ModuleRegistry.java (98%) rename Patchwork/src/main/java/fns/patchwork/{data => registry}/ServiceTaskRegistry.java (99%) rename Patchwork/src/main/java/fns/patchwork/{data => registry}/UserRegistry.java (99%) rename Patchwork/src/main/java/fns/patchwork/{api => serializer}/Serializable.java (98%) create mode 100644 Veritas/src/main/java/fns/veritas/cmd/HelpCommand.java create mode 100644 Veritas/src/main/java/fns/veritas/cmd/ListCommand.java create mode 100644 Veritas/src/main/java/fns/veritas/cmd/TpsCommand.java rename Patchwork/src/main/java/fns/patchwork/api/Interpolator.java => Veritas/src/main/java/fns/veritas/cmd/base/BotCommand.java (65%) create mode 100644 Veritas/src/main/java/fns/veritas/cmd/base/BotCommandHandler.java create mode 100644 Veritas/src/main/java/fns/veritas/messaging/Embed.java create mode 100644 Veritas/src/main/java/fns/veritas/messaging/EmbedWrapper.java create mode 100644 Veritas/src/main/java/fns/veritas/messaging/SimpleMessageWrapper.java create mode 100644 Veritas/src/main/resources/ExampleCommand.txt create mode 100644 Veritas/src/main/resources/commands/help.json create mode 100644 Veritas/src/main/resources/commands/list.json create mode 100644 Veritas/src/main/resources/commands/tps.json diff --git a/Patchwork/build.gradle b/Patchwork/build.gradle index 6ce2389..5b8f357 100644 --- a/Patchwork/build.gradle +++ b/Patchwork/build.gradle @@ -8,6 +8,8 @@ repositories { dependencies { library 'io.projectreactor:reactor-core:3.5.4' library 'io.github.classgraph:classgraph:4.8.162' + library 'org.tomlj:tomlj:1.1.0' + library 'com.google.code.gson:gson:2.8.9' api 'org.slf4j:slf4j-api:1.7.36' testImplementation platform('org.junit:junit-bom:5.9.1') diff --git a/Patchwork/src/main/java/fns/patchwork/base/Registration.java b/Patchwork/src/main/java/fns/patchwork/base/Registration.java index b619965..fc159dc 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/config/Configuration.java b/Patchwork/src/main/java/fns/patchwork/config/Configuration.java index 4bcb347..a5cc16f 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/WrappedTomlConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java new file mode 100644 index 0000000..585ed4e --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java @@ -0,0 +1,184 @@ +/* + * 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.patchwork.config; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Unmodifiable; +import org.tomlj.Toml; +import org.tomlj.TomlParseResult; + +// TODO: Finish implementation +public class WrappedTomlConfiguration implements Configuration +{ + private final Map previousValues = new HashMap<>(); + private final TomlParseResult toml; + private final File file; + + + public WrappedTomlConfiguration(final JavaPlugin plugin, final File file) throws IOException + { + if (!file.exists() && file.createNewFile()) + { + plugin.saveResource(file.getName(), true); + } + + this.toml = Toml.parse(Path.of(file.toURI())); + this.file = file; + } + + @Override + public void save() throws IOException + { + // Create a backup file + final File backup = new File(this.file.getParentFile(), this.file.getName() + ".bak"); + if (backup.exists() && !Files.deleteIfExists(Path.of(backup.toURI()))) + { + throw new IOException("Failed to delete existing backup file: " + backup.getName()); + } + + // Serialize the current configuration to a temporary file + final File tempFile = new File(this.file.getParentFile(), this.file.getName() + ".temp"); + try (FileWriter tempFileWriter = new FileWriter(tempFile)) + { + // Convert the updated TomlTable to TOML format and write it to the temporary file + String tomlString = this.toml.toToml(); + tempFileWriter.write(tomlString); + } + + // Compare the new configuration with the previous one + TomlParseResult newToml = Toml.parse(Path.of(tempFile.toURI())); + for (Map.Entry entry : newToml.toMap().entrySet()) + { + String key = entry.getKey(); + Object newValue = entry.getValue(); + Object oldValue = previousValues.get(key); + + if (oldValue == null || !oldValue.equals(newValue)) + { + // Value has changed, update it + this.toml.toMap().replace(key, newValue); + previousValues.put(key, newValue); + } + } + + // Save the updated configuration to the original file + try (FileWriter fileWriter = new FileWriter(this.file)) + { + // Convert the updated TomlTable to TOML format and write it to the original file + String tomlString = this.toml.toToml(); + fileWriter.write(tomlString); + } + + // Delete the temporary file and the backup file + Files.delete(Path.of(tempFile.toURI())); + Files.delete(Path.of(backup.toURI())); + } + + @Override + public void load() throws IOException + { + // TODO: Implement + } + + @Override + public String getFileName() + { + return null; + } + + @Override + public File getConfigurationFile() + { + return null; + } + + @Override + public String getString(String path) + { + return null; + } + + @Override + public boolean getBoolean(String path) + { + return false; + } + + @Override + public @Unmodifiable List getList(String path, Class clazz) + { + return null; + } + + @Override + public @Unmodifiable List getStringList(String path) + { + return null; + } + + @Override + public int getInt(String path) + { + return 0; + } + + @Override + public long getLong(String path) + { + return 0; + } + + @Override + public double getDouble(String path) + { + return 0; + } + + @Override + public void set(String path, T value) + { + // TODO: Implement + } + + @Override + public Optional get(String path, Class clazz) + { + return Optional.empty(); + } + + @Override + public T getOrDefault(String path, Class clazz, T fallback) + { + return null; + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/event/EventBus.java b/Patchwork/src/main/java/fns/patchwork/event/EventBus.java index 7a57ac4..c6065f9 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 6e6ca6b..e98d250 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 2d34a6f..450f846 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 185091f..a8b237a 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 85deaca..e886209 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 02b7168..f9afbc4 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 2449ff0..a085228 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 8814146..9a88d83 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 571ca52..1cdc92c 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 902e36b..b458c78 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/InterpolationUtils.java b/Patchwork/src/main/java/fns/patchwork/utils/InterpolationUtils.java index 2ba4ebf..4573f33 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 static Set standardComponentGradient(final int length, final T { 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/Veritas/src/main/java/fns/veritas/Aggregate.java b/Veritas/src/main/java/fns/veritas/Aggregate.java index d2371c9..935d951 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 16b3949..4af1cfe 100644 --- a/Veritas/src/main/java/fns/veritas/Veritas.java +++ b/Veritas/src/main/java/fns/veritas/Veritas.java @@ -34,9 +34,8 @@ public void onEnable() { 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 3863616..b25a779 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 void onAsyncPlayerChat(final AsyncChatEvent event) 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 f2a2635..90ce95e 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.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.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 ServerListener(final Veritas plugin) 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 void minecraftChatBound() .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 @@ private void doMessageBodyDetails(MessageCreateEvent m) 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 @@ private void doMessageBodyDetails(MessageCreateEvent m) .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 178feaf..806a8b4 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 String getInviteLink() 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 @@ private String sanitizeChatMessage(String message) 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 3b57fa6..c675137 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 @@ private Function f0(final Veritas plugin) } 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 df105d5..5825479 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 From e71c167d5e3d5eadcfc8f0881d97d52710f601eb Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Mon, 28 Aug 2023 18:17:12 -0500 Subject: [PATCH 2/7] Fix plugin-yml plugin errors & switch to night-config # Changes: - Migrate from com.google.gson:gson and com.tomlj:tomlj over to com.electronwill.night-config:core, toml, and json - Add the appropriate bukkit tags required by the minecrell/plugin-yml gradle plugin to Veritas and Tyr. --- Patchwork/build.gradle | 7 ++++--- Tyr/build.gradle | 6 ++++++ Veritas/build.gradle | 6 ++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Patchwork/build.gradle b/Patchwork/build.gradle index 5b8f357..fbf0c03 100644 --- a/Patchwork/build.gradle +++ b/Patchwork/build.gradle @@ -8,8 +8,9 @@ repositories { dependencies { library 'io.projectreactor:reactor-core:3.5.4' library 'io.github.classgraph:classgraph:4.8.162' - library 'org.tomlj:tomlj:1.1.0' - library 'com.google.code.gson:gson:2.8.9' + 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') @@ -21,7 +22,7 @@ bukkit { description = "Freedom Network Suite Core Module (API & Library)" } -var weight = 1 +def weight = 1 test { useJUnitPlatform() 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") From 26f4e0746bc19eb070c3c558599ede707f609f42 Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Mon, 28 Aug 2023 21:52:40 -0500 Subject: [PATCH 3/7] Replace TOML wrapper with generic support # Changes: - Removed specific TOML wrapper in favor of GenericConfiguration. - Added ConfigType enum to define configuration formats, parsers, and writers for TOML and JSON. - Created FileUtils class containing useful file and directory creation methods - Added @ApiStatus.Internal to both the BukkitDelegate class and Completions annotation to specify that they should not be used externally. --- .../fns/patchwork/command/BukkitDelegate.java | 4 + .../command/annotation/Completions.java | 2 + .../java/fns/patchwork/config/ConfigType.java | 88 +++++++ .../config/GenericConfiguration.java | 224 ++++++++++++++++++ .../config/WrappedTomlConfiguration.java | 184 -------------- .../java/fns/patchwork/utils/FileUtils.java | 129 ++++++++++ 6 files changed, 447 insertions(+), 184 deletions(-) create mode 100644 Patchwork/src/main/java/fns/patchwork/config/ConfigType.java create mode 100644 Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java delete mode 100644 Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java create mode 100644 Patchwork/src/main/java/fns/patchwork/utils/FileUtils.java 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.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 @@ *
* 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 List tabComplete(final CommandSender sender, final String alias, 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.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 @@ */ @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/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/config/WrappedTomlConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java deleted file mode 100644 index 585ed4e..0000000 --- a/Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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.patchwork.config; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.Unmodifiable; -import org.tomlj.Toml; -import org.tomlj.TomlParseResult; - -// TODO: Finish implementation -public class WrappedTomlConfiguration implements Configuration -{ - private final Map previousValues = new HashMap<>(); - private final TomlParseResult toml; - private final File file; - - - public WrappedTomlConfiguration(final JavaPlugin plugin, final File file) throws IOException - { - if (!file.exists() && file.createNewFile()) - { - plugin.saveResource(file.getName(), true); - } - - this.toml = Toml.parse(Path.of(file.toURI())); - this.file = file; - } - - @Override - public void save() throws IOException - { - // Create a backup file - final File backup = new File(this.file.getParentFile(), this.file.getName() + ".bak"); - if (backup.exists() && !Files.deleteIfExists(Path.of(backup.toURI()))) - { - throw new IOException("Failed to delete existing backup file: " + backup.getName()); - } - - // Serialize the current configuration to a temporary file - final File tempFile = new File(this.file.getParentFile(), this.file.getName() + ".temp"); - try (FileWriter tempFileWriter = new FileWriter(tempFile)) - { - // Convert the updated TomlTable to TOML format and write it to the temporary file - String tomlString = this.toml.toToml(); - tempFileWriter.write(tomlString); - } - - // Compare the new configuration with the previous one - TomlParseResult newToml = Toml.parse(Path.of(tempFile.toURI())); - for (Map.Entry entry : newToml.toMap().entrySet()) - { - String key = entry.getKey(); - Object newValue = entry.getValue(); - Object oldValue = previousValues.get(key); - - if (oldValue == null || !oldValue.equals(newValue)) - { - // Value has changed, update it - this.toml.toMap().replace(key, newValue); - previousValues.put(key, newValue); - } - } - - // Save the updated configuration to the original file - try (FileWriter fileWriter = new FileWriter(this.file)) - { - // Convert the updated TomlTable to TOML format and write it to the original file - String tomlString = this.toml.toToml(); - fileWriter.write(tomlString); - } - - // Delete the temporary file and the backup file - Files.delete(Path.of(tempFile.toURI())); - Files.delete(Path.of(backup.toURI())); - } - - @Override - public void load() throws IOException - { - // TODO: Implement - } - - @Override - public String getFileName() - { - return null; - } - - @Override - public File getConfigurationFile() - { - return null; - } - - @Override - public String getString(String path) - { - return null; - } - - @Override - public boolean getBoolean(String path) - { - return false; - } - - @Override - public @Unmodifiable List getList(String path, Class clazz) - { - return null; - } - - @Override - public @Unmodifiable List getStringList(String path) - { - return null; - } - - @Override - public int getInt(String path) - { - return 0; - } - - @Override - public long getLong(String path) - { - return 0; - } - - @Override - public double getDouble(String path) - { - return 0; - } - - @Override - public void set(String path, T value) - { - // TODO: Implement - } - - @Override - public Optional get(String path, Class clazz) - { - return Optional.empty(); - } - - @Override - public T getOrDefault(String path, Class clazz, T fallback) - { - return null; - } -} 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(); + } + } +} From 4681fc9596a61883a596ce6a9d63be38891965f9 Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Wed, 30 Aug 2023 20:49:22 -0500 Subject: [PATCH 4/7] Minor tweaks to GenericConfig & ContextProvider # Changes: - Changed Configuration#getList(String, Class) to Configuration#getCollection(String, Class) - Renamed GenericConfiguration -> GenericConfig - Implemented semantics for GenericConfig#getCollection and GenericConfig#getStringList - Adjusted return value of ContextProvider#fromString to return Optional instead of @Nullable T - Adjusted classes which used previous API methods to use the newly updated ones. --- .../fns/patchwork/command/BukkitDelegate.java | 88 ++++++++------ .../fns/patchwork/config/Configuration.java | 3 +- ...cConfiguration.java => GenericConfig.java} | 112 +++++++++++++----- .../config/WrappedBukkitConfiguration.java | 4 +- .../patchwork/provider/ContextProvider.java | 14 +-- .../src/main/java/fns/veritas/Aggregate.java | 20 +++- .../java/fns/veritas/client/BotConfig.java | 10 +- 7 files changed, 170 insertions(+), 81 deletions(-) rename Patchwork/src/main/java/fns/patchwork/config/{GenericConfiguration.java => GenericConfig.java} (59%) diff --git a/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java b/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java index 01162c6..46efcf9 100644 --- a/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java +++ b/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Set; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; @@ -156,50 +157,19 @@ private void processSubCommands(final @NotNull String @NotNull [] args, if (argTypes.length > args.length) return; - final Object[] objects = new Object[argTypes.length + 1]; - - for (int i = 0; i < argTypes.length; i++) - { - final Class argType = argTypes[i]; - final String arg = args[i]; + final Player p = (sender instanceof Player player) ? player : null; - if (argType.equals(String.class)) - { - if (i == argTypes.length - 1) - { - final String[] reasonArgs = Arrays.copyOfRange(args, i, args.length - 1); - final String reason = String.join(" ", reasonArgs); - objects[i] = reason; - } - else - { - continue; - } - } + final Object[] objects = new Object[argTypes.length + 1]; - if (argType.equals(Location.class)) - { - final String[] locationArgs = Arrays.copyOfRange(args, i, i + 3); - final String location = String.join(" ", locationArgs); - objects[i] = location; - } + parseArguments(args, provider, argTypes, objects); - final Object obj = provider.fromString(arg, argType); - if (obj == null) - { - FNS4J.getLogger("Datura") - .error("Failed to parse argument " + arg + " for type " + argType.getName()); - return; - } - objects[i] = obj; - } try { if (noConsole) { command.getSubcommands() .get(node) - .invoke(command, (Player) sender, objects); + .invoke(command, p, objects); } else { @@ -215,11 +185,55 @@ private void processSubCommands(final @NotNull String @NotNull [] args, } } + private void parseArguments(@NotNull String @NotNull [] args, + ContextProvider provider, + Class[] argTypes, + Object[] objects) + { + for (int i = 0; i < argTypes.length; i++) + { + final Class argType = argTypes[i]; + String arg = args[i]; + + boolean wasResolved = false; + + if (argType.equals(String.class) && (i == argTypes.length - 1)) + { + final String[] reasonArgs = Arrays.copyOfRange(args, i, args.length - 1); + final String reason = String.join(" ", reasonArgs); + objects[i] = reason; + wasResolved = true; + } + + if (argType.equals(Location.class)) + { + final String[] locationArgs = Arrays.copyOfRange(args, i, i + 3); + arg = String.join(" ", locationArgs); + } + + if (!wasResolved) + { + final Optional obj = provider.fromString(arg, argType); + if (obj.isEmpty()) + { + FNS4J.getLogger("Datura") + .error("Failed to parse argument " + arg + " for type " + argType.getName()); + continue; + } + objects[i] = obj.get(); + } + } + } + @Override - public List tabComplete(final CommandSender sender, final String alias, final String[] args) + public @NotNull List tabComplete(final @NotNull CommandSender sender, final @NotNull String alias, + final String[] args) { - final Set completions = command.getCompletions(); final List results = new ArrayList<>(); + final Set completions = command.getCompletions(); + + if (completions == null || completions.isEmpty()) + return results; if (args.length == 0) { diff --git a/Patchwork/src/main/java/fns/patchwork/config/Configuration.java b/Patchwork/src/main/java/fns/patchwork/config/Configuration.java index b7b4780..19ef8c2 100644 --- a/Patchwork/src/main/java/fns/patchwork/config/Configuration.java +++ b/Patchwork/src/main/java/fns/patchwork/config/Configuration.java @@ -25,6 +25,7 @@ import fns.patchwork.provider.Context; import fns.patchwork.provider.ContextProvider; +import java.util.Collection; import org.jetbrains.annotations.Unmodifiable; import java.io.File; @@ -87,7 +88,7 @@ public interface Configuration * @param clazz The class of the type. * @return The List object. */ - @Unmodifiable List getList(String path, Class clazz); + @Unmodifiable Collection getCollection(String path, Class clazz); /** * Gets a List object from the associated path. The List that is returned will be the String values which are stored diff --git a/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/GenericConfig.java similarity index 59% rename from Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java rename to Patchwork/src/main/java/fns/patchwork/config/GenericConfig.java index 410e791..507eb8f 100644 --- a/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java +++ b/Patchwork/src/main/java/fns/patchwork/config/GenericConfig.java @@ -26,7 +26,9 @@ import com.electronwill.nightconfig.core.Config; import com.electronwill.nightconfig.core.ConfigFormat; import com.electronwill.nightconfig.core.UnmodifiableConfig; +import fns.patchwork.provider.ContextProvider; import fns.patchwork.utils.FileUtils; +import fns.patchwork.utils.logging.FNS4J; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; @@ -34,26 +36,28 @@ import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; 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 +public final class GenericConfig implements Configuration { + private static final ContextProvider PROVIDER = new ContextProvider(); 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 + public GenericConfig(@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() + "!"); @@ -78,20 +82,20 @@ public GenericConfiguration(@NotNull final ConfigType configType, this.load(); } - public GenericConfiguration(final ConfigType type, final File dataFolder, final String fileName) + public GenericConfig(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) + public GenericConfig(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) + public GenericConfig(final ConfigType type, final File dataFolder, final String fileName, + final boolean isConcurrent) throws IOException { this(type, null, dataFolder, fileName, isConcurrent); @@ -114,8 +118,10 @@ public void save() throws IOException } @Override - public void load() throws IOException { - try (final FileReader reader = new FileReader(this.configFile)) { + 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(); @@ -145,7 +151,7 @@ public String getString(final String path) } @Override - public boolean getBoolean(String path) + public boolean getBoolean(final String path) { if (!(this.getConfig().get(path) instanceof Boolean)) throw new IllegalArgumentException(String.format("Value at path %s is not a boolean!", path)); @@ -153,22 +159,70 @@ public boolean getBoolean(String path) return this.getConfig().get(path); } + + /* + * I am pretty sure that this works, but not really. + * This is sort of a shot in the dark because Night Config did specify that they support collections + * in TOML and JSON files, but there is no specific get method for objects that are not primitives. + * Additionally, not all primitives are natively supported. + */ @Override - @ApiStatus.Internal - public @Unmodifiable List getList(String path, Class clazz) + public @Unmodifiable Collection getCollection(String path, Class clazz) { - // TODO: Figure out how to parse lists with Night Config. - - return new ArrayList<>(); + if (!(this.getConfig().get(path) instanceof Collection)) + throw new IllegalArgumentException(String.format("Value at path %s is not a collection!", path)); + + final Collection collection = this.getConfig().get(path); + final Collection collected = new ArrayList<>(); + + collection.stream() + .map(obj -> + { + final Optional optional; + + if (obj instanceof String string) + optional = PROVIDER.fromString(string, clazz); + else if (clazz.isInstance(obj)) + optional = Optional.of(clazz.cast(obj)); + else + optional = Optional.empty(); + + return optional; + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toCollection(() -> collected)); + + return collected; } + /* + * I am pretty sure that this works, but not really. + * This is sort of a shot in the dark because Night Config did specify that they support collections + * in TOML and JSON files, but there is no specific get method for objects that are not primitives. + * Additionally, not all primitives are natively supported. + */ @Override - @ApiStatus.Internal public @Unmodifiable List getStringList(String path) { - // TODO: Figure out how to parse lists with Night Config. + if (!(this.getConfig().get(path) instanceof Collection c)) + throw new IllegalArgumentException(String.format("Value at path %s is not a collection!", path)); + + final Collection collection = this.getConfig().get(path); + final List list = new ArrayList<>(); - return new ArrayList<>(); + if (c.isEmpty() || !(c.toArray()[0] instanceof String)) + { + FNS4J.PATCHWORK.warn(String.format("Collection at path %s is empty or does not contain strings!", path)); + FNS4J.PATCHWORK.warn("Returning empty list!"); + return list; + } + + collection.stream() + .map(String.class::cast) + .collect(Collectors.toCollection(() -> list)); + + return list; } @Override @@ -195,20 +249,22 @@ public double getDouble(String 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(); + return this.getConfig() + .getOptional(path) + .filter(clazz::isInstance) + .map(clazz::cast); } @Override public T getOrDefault(String path, Class clazz, T fallback) { - return this.get(path, clazz).orElse(fallback); + return this.get(path, clazz) + .orElse(fallback); } @Override - public void set(final String path, final T value) { + public void set(final String path, final T value) + { this.config.set(path, value); } diff --git a/Patchwork/src/main/java/fns/patchwork/config/WrappedBukkitConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/WrappedBukkitConfiguration.java index f50e146..1baf944 100644 --- a/Patchwork/src/main/java/fns/patchwork/config/WrappedBukkitConfiguration.java +++ b/Patchwork/src/main/java/fns/patchwork/config/WrappedBukkitConfiguration.java @@ -94,7 +94,7 @@ public boolean getBoolean(String path) } @Override - public List getList(String path, Class clazz) + public List getCollection(String path, Class clazz) { return this.contextProvider.getList(this.getStringList(path), clazz); } @@ -132,7 +132,7 @@ public void set(String path, T value) @Override public Optional get(String path, Class clazz) { - return Optional.ofNullable(this.contextProvider.fromString(path, clazz)); + return this.contextProvider.fromString(path, clazz); } @Override diff --git a/Patchwork/src/main/java/fns/patchwork/provider/ContextProvider.java b/Patchwork/src/main/java/fns/patchwork/provider/ContextProvider.java index 9bb86f3..476a3a6 100644 --- a/Patchwork/src/main/java/fns/patchwork/provider/ContextProvider.java +++ b/Patchwork/src/main/java/fns/patchwork/provider/ContextProvider.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; @@ -60,7 +61,7 @@ */ public class ContextProvider { - public T fromString(final String string, final Class clazz) + public Optional fromString(final String string, final Class clazz) { return Stream.of(toBoolean(string, clazz), toLong(string, clazz), @@ -74,9 +75,9 @@ public T fromString(final String string, final Class clazz) toLocation(string, clazz), toComponent(string, clazz)) .filter(Objects::nonNull) - .findFirst() + .filter(clazz::isInstance) .map(clazz::cast) - .orElse(null); + .findFirst(); } private @Nullable Boolean toBoolean(final String string, final Class clazz) @@ -227,10 +228,9 @@ private OfflinePlayer toOfflinePlayer(final String string, final Class clazz) public @NotNull List<@Nullable T> getList(final List resolvable, final Class clazz) { final List resolved = new ArrayList<>(); - for (final String entry : resolvable) - { - resolved.add(this.fromString(entry, clazz)); - } + + resolvable.forEach(entry -> this.fromString(entry, clazz).ifPresent(resolved::add)); + return resolved; } } diff --git a/Veritas/src/main/java/fns/veritas/Aggregate.java b/Veritas/src/main/java/fns/veritas/Aggregate.java index 51afc09..c01a1a7 100644 --- a/Veritas/src/main/java/fns/veritas/Aggregate.java +++ b/Veritas/src/main/java/fns/veritas/Aggregate.java @@ -28,6 +28,7 @@ import fns.veritas.bukkit.ServerListener; import fns.veritas.client.BotClient; import fns.veritas.client.BotConfig; +import java.io.IOException; import org.bukkit.Bukkit; public class Aggregate @@ -40,13 +41,30 @@ public class Aggregate public Aggregate(final Veritas plugin) { + BotClient bot1; this.plugin = plugin; - this.bot = new BotClient(new BotConfig(plugin)); + + try + { + bot1 = new BotClient(new BotConfig(plugin)); + } + catch (IOException ex) + { + getLogger().error("Failed to load bot config! Shutting down..."); + getLogger().error(ex); + this.bot = null; + this.serverListener = null; + this.bukkitNativeListener = null; + Bukkit.getPluginManager().disablePlugin(plugin); + return; + } + this.bukkitNativeListener = new BukkitNative(plugin); this.serverListener = new ServerListener(plugin); Bukkit.getServer().getPluginManager().registerEvents(this.getBukkitNativeListener(), plugin); this.getServerListener().minecraftChatBound().subscribe(); + this.bot = bot1; } public static FNS4J getLogger() diff --git a/Veritas/src/main/java/fns/veritas/client/BotConfig.java b/Veritas/src/main/java/fns/veritas/client/BotConfig.java index 08407ad..d2e1783 100644 --- a/Veritas/src/main/java/fns/veritas/client/BotConfig.java +++ b/Veritas/src/main/java/fns/veritas/client/BotConfig.java @@ -24,7 +24,8 @@ package fns.veritas.client; import discord4j.common.util.Snowflake; -import fns.patchwork.config.WrappedBukkitConfiguration; +import fns.patchwork.config.ConfigType; +import fns.patchwork.config.GenericConfig; import fns.veritas.Aggregate; import fns.veritas.Veritas; import java.io.File; @@ -41,12 +42,11 @@ public class BotConfig public static final String GUILD_ID = "guild_id"; @NonNls private static final String BOT_TOKEN = "bot_token"; - private final WrappedBukkitConfiguration config; + private final GenericConfig config; - public BotConfig(final Veritas plugin) + public BotConfig(final Veritas plugin) throws IOException { - this.config = new WrappedBukkitConfiguration(f0(plugin), - new File(plugin.getDataFolder(), "config.yml")); + this.config = new GenericConfig(ConfigType.TOML, plugin, "config.toml"); } public String getToken() From 85cc1f7ae0caafe1b5544ba75de70da805e1e546 Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Mon, 4 Sep 2023 22:57:36 -0500 Subject: [PATCH 5/7] Bot Command Implementation # Changes: - Added /ban [duration] command. Bans a user on the server for the specified reason and duration (in minutes). If no duration is specified, the default is 5 minutes. - Added /kick command. Kicks a player on the server for the specified reason. - Added /whisper command. Send a private message to a player on the server. --- .../patchwork/kyori/MiniMessageWrapper.java | 16 +-- .../src/main/java/fns/veritas/Aggregate.java | 25 ++++- .../fns/veritas/bukkit/ServerListener.java | 30 +++--- .../java/fns/veritas/client/BotClient.java | 60 ++++++------ .../java/fns/veritas/client/BotConfig.java | 39 +++++--- .../main/java/fns/veritas/cmd/BanCommand.java | 98 +++++++++++++++++++ .../java/fns/veritas/cmd/KickCommand.java | 90 +++++++++++++++++ .../java/fns/veritas/cmd/WhisperCommand.java | 75 ++++++++++++++ .../messaging/SimpleMessageWrapper.java | 72 -------------- Veritas/src/main/resources/commands/ban.json | 24 +++++ Veritas/src/main/resources/commands/kick.json | 18 ++++ .../src/main/resources/commands/whisper.json | 18 ++++ Veritas/src/main/resources/config.toml | 10 ++ 13 files changed, 434 insertions(+), 141 deletions(-) create mode 100644 Veritas/src/main/java/fns/veritas/cmd/BanCommand.java create mode 100644 Veritas/src/main/java/fns/veritas/cmd/KickCommand.java create mode 100644 Veritas/src/main/java/fns/veritas/cmd/WhisperCommand.java delete mode 100644 Veritas/src/main/java/fns/veritas/messaging/SimpleMessageWrapper.java create mode 100644 Veritas/src/main/resources/commands/ban.json create mode 100644 Veritas/src/main/resources/commands/kick.json create mode 100644 Veritas/src/main/resources/commands/whisper.json create mode 100644 Veritas/src/main/resources/config.toml diff --git a/Patchwork/src/main/java/fns/patchwork/kyori/MiniMessageWrapper.java b/Patchwork/src/main/java/fns/patchwork/kyori/MiniMessageWrapper.java index c814ab9..fcb292e 100644 --- a/Patchwork/src/main/java/fns/patchwork/kyori/MiniMessageWrapper.java +++ b/Patchwork/src/main/java/fns/patchwork/kyori/MiniMessageWrapper.java @@ -37,14 +37,14 @@ public class MiniMessageWrapper private static final MiniMessage unsafe = MiniMessage.miniMessage(); private static final MiniMessage safe = MiniMessage.builder() .tags(TagResolver.resolver( - StandardTags.color(), - StandardTags.rainbow(), - StandardTags.gradient(), - StandardTags.newline(), - StandardTags.decorations(TextDecoration.ITALIC), - StandardTags.decorations(TextDecoration.BOLD), - StandardTags.decorations(TextDecoration.STRIKETHROUGH), - StandardTags.decorations(TextDecoration.UNDERLINED) + StandardTags.color(), + StandardTags.rainbow(), + StandardTags.gradient(), + StandardTags.newline(), + StandardTags.decorations(TextDecoration.ITALIC), + StandardTags.decorations(TextDecoration.BOLD), + StandardTags.decorations(TextDecoration.STRIKETHROUGH), + StandardTags.decorations(TextDecoration.UNDERLINED) )) .build(); diff --git a/Veritas/src/main/java/fns/veritas/Aggregate.java b/Veritas/src/main/java/fns/veritas/Aggregate.java index c01a1a7..16d8607 100644 --- a/Veritas/src/main/java/fns/veritas/Aggregate.java +++ b/Veritas/src/main/java/fns/veritas/Aggregate.java @@ -34,6 +34,13 @@ public class Aggregate { private static final FNS4J logger = FNS4J.getLogger("Veritas"); + private static final String FAILED_PACKET = """ + Failed to process inbound chat packet. + An offending element was found transmitted through the stream. + The element has been dropped, and ignored. + Offending element: %s + Caused by: %s + Stack Trace: %s"""; private final BotClient bot; private final Veritas plugin; private final BukkitNative bukkitNativeListener; @@ -62,8 +69,17 @@ public Aggregate(final Veritas plugin) this.bukkitNativeListener = new BukkitNative(plugin); this.serverListener = new ServerListener(plugin); - Bukkit.getServer().getPluginManager().registerEvents(this.getBukkitNativeListener(), plugin); - this.getServerListener().minecraftChatBound().subscribe(); + Bukkit.getServer() + .getPluginManager() + .registerEvents(this.getBukkitNativeListener(), plugin); + this.getServerListener() + .minecraftChatBound() + .onErrorContinue((th, v) -> Aggregate.getLogger() + .error(FAILED_PACKET.formatted( + v.getClass().getName(), + th.getCause(), + th.getMessage()))) + .subscribe(); this.bot = bot1; } @@ -87,6 +103,11 @@ public BotClient getBot() return bot; } + public BotConfig getBotConfig() + { + return bot.getConfig(); + } + public Veritas getPlugin() { return plugin; diff --git a/Veritas/src/main/java/fns/veritas/bukkit/ServerListener.java b/Veritas/src/main/java/fns/veritas/bukkit/ServerListener.java index 6d9e113..f0ac173 100644 --- a/Veritas/src/main/java/fns/veritas/bukkit/ServerListener.java +++ b/Veritas/src/main/java/fns/veritas/bukkit/ServerListener.java @@ -54,20 +54,20 @@ public ServerListener(final Veritas plugin) public Mono minecraftChatBound() { return bot.getClient() - .getEventDispatcher() - .on(MessageCreateEvent.class) - .filter(m -> m.getMessage() - .getChannelId() - .equals(bot.getChatChannelId())) - .filter(m -> m.getMember().orElse(null) != null) - .filter(m -> !m.getMessage() - .getAuthor() - .orElseThrow(IllegalAccessError::new) - .getId() - .equals(plugin.getAggregate().getBot().getClient().getSelfId())) - .doOnError(Aggregate.getLogger()::error) - .doOnNext(this::doMessageBodyDetails) - .then(); + .getEventDispatcher() + .on(MessageCreateEvent.class) + .filter(m -> m.getMessage() + .getChannelId() + .equals(bot.getConfig().getChatChannelId())) + .filter(m -> m.getMember().orElse(null) != null) + .filter(m -> !m.getMessage() + .getAuthor() + .orElseThrow(IllegalAccessError::new) + .getId() + .equals(bot.getClient().getSelfId())) + .doOnError(Aggregate.getLogger()::error) + .doOnNext(this::doMessageBodyDetails) + .then(); } private void doMessageBodyDetails(MessageCreateEvent m) @@ -81,7 +81,7 @@ private void doMessageBodyDetails(MessageCreateEvent m) .hoverEvent(HoverEvent.showText( Component.text("Click to join our Discord server!"))) .clickEvent(ClickEvent.openUrl( - plugin.getAggregate().getBot().getInviteLink()))) + plugin.getAggregate().getBotConfig().getInviteLink()))) .append(Component.text("] ", NamedTextColor.DARK_GRAY)); TextComponent user = Component.empty(); diff --git a/Veritas/src/main/java/fns/veritas/client/BotClient.java b/Veritas/src/main/java/fns/veritas/client/BotClient.java index 46bc391..a20a3ad 100644 --- a/Veritas/src/main/java/fns/veritas/client/BotClient.java +++ b/Veritas/src/main/java/fns/veritas/client/BotClient.java @@ -23,16 +23,20 @@ package fns.veritas.client; -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.PartialMember; +import discord4j.core.object.entity.Role; +import discord4j.core.object.entity.User; +import discord4j.core.object.entity.channel.Channel; import discord4j.core.object.entity.channel.TextChannel; import discord4j.core.spec.MessageCreateSpec; import fns.veritas.cmd.base.BotCommandHandler; import java.util.List; +import java.util.Objects; import reactor.core.publisher.Mono; public class BotClient @@ -58,37 +62,11 @@ public BotClient(final BotConfig config) client.on(ChatInputInteractionEvent.class, handler::handle); } - - public String getBotId() - { - return client.getSelfId().asString(); - } - - public Mono getServerGuildId() - { - return client.getGuildById(config.getId()); - } - public GatewayDiscordClient getClient() { return client; } - public Snowflake getChatChannelId() - { - return config.getChatChannelId(); - } - - public Snowflake getLogChannelId() - { - return config.getLogChannelId(); - } - - public String getInviteLink() - { - return config.getInviteLink(); - } - public void messageChatChannel(String message, boolean system) { String channelID = config.getChatChannelId().asString(); @@ -119,7 +97,6 @@ private String sanitizeChatMessage(String message) if (message.contains("@")) { - // \u200B is Zero Width Space, invisible on Discord newMessage = message.replace("@", "@\u200B"); } @@ -144,6 +121,33 @@ private String sanitizeChatMessage(String message) return deformat(newMessage); } + public Mono isAdmin(final User user) + { + return getGuild().flatMap(guild -> guild.getMemberById(user.getId())) + .flatMapMany(PartialMember::getRoles) + .filter(role -> getConfig().getAdminRoleId().asLong() == role.getId().asLong()) + .filter(Objects::nonNull) + .next() + .hasElement(); + } + + public Mono getLogsChannel() { + return getGuild().flatMap(guild -> guild.getChannelById(getConfig().getLogChannelId())); + } + + public Mono getChatChannel() { + return getGuild().flatMap(guild -> guild.getChannelById(getConfig().getChatChannelId())); + } + + public Mono getGuild() { + return getClient().getGuildById(getConfig().getGuildId()); + } + + public BotConfig getConfig() + { + return config; + } + public String deformat(String input) { return input.replaceAll("([_\\\\`*>|])", "\\\\$1"); diff --git a/Veritas/src/main/java/fns/veritas/client/BotConfig.java b/Veritas/src/main/java/fns/veritas/client/BotConfig.java index d2e1783..c498c4a 100644 --- a/Veritas/src/main/java/fns/veritas/client/BotConfig.java +++ b/Veritas/src/main/java/fns/veritas/client/BotConfig.java @@ -39,9 +39,17 @@ public class BotConfig { @NonNls - public static final String GUILD_ID = "guild_id"; + private static final String GUILD_ID = "bot_settings.guild_id"; @NonNls - private static final String BOT_TOKEN = "bot_token"; + private static final String BOT_TOKEN = "bot_settings.bot_token"; + @NonNls + private static final String MC_CHANNEL_ID = "bot_settings.mc_channel_id"; + @NonNls + private static final String LOG_CHANNEL_ID = "bot_settings.log_channel_id"; + @NonNls + private static final String INVITE_LINK = "bot_settings.invite_link"; + + private final GenericConfig config; public BotConfig(final Veritas plugin) throws IOException @@ -54,29 +62,29 @@ public String getToken() return config.getString(BOT_TOKEN); } - public String getPrefix() + public Snowflake getGuildId() { - return config.getString("bot_prefix"); + return Snowflake.of(config.getLong(GUILD_ID)); } - public Snowflake getId() + public Snowflake getChatChannelId() { - return Snowflake.of(config.getString(GUILD_ID)); + return Snowflake.of(config.getLong(MC_CHANNEL_ID)); } - public Snowflake getChatChannelId() + public Snowflake getLogChannelId() { - return Snowflake.of(config.getString("channel_id")); + return Snowflake.of(config.getLong(LOG_CHANNEL_ID)); } - public Snowflake getLogChannelId() + public Snowflake getAdminRoleId() { - return Snowflake.of(config.getString("log_channel_id")); + return Snowflake.of(config.getLong("admin_settings.admin_role_id")); } public String getInviteLink() { - return config.getString("invite_link"); + return config.getString(INVITE_LINK); } private Function f0(final Veritas plugin) @@ -94,11 +102,10 @@ private Function f0(final Veritas plugin) catch (IOException | InvalidConfigurationException ex) { fc.addDefault(BOT_TOKEN, "token"); - fc.addDefault("bot_prefix", "!"); - fc.addDefault(GUILD_ID, GUILD_ID); - fc.addDefault("channel_id", "nil"); - fc.addDefault("log_channel_id", "nil"); - fc.addDefault("invite_link", "https://discord.gg/invite"); + fc.addDefault(GUILD_ID, 0); + fc.addDefault(MC_CHANNEL_ID, 0); + fc.addDefault(LOG_CHANNEL_ID, 0); + fc.addDefault(INVITE_LINK, "https://discord.gg/invite"); fc.options().copyDefaults(true); diff --git a/Veritas/src/main/java/fns/veritas/cmd/BanCommand.java b/Veritas/src/main/java/fns/veritas/cmd/BanCommand.java new file mode 100644 index 0000000..564a5fe --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/cmd/BanCommand.java @@ -0,0 +1,98 @@ +/* + * 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.veritas.cmd; + + +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; +import discord4j.core.object.command.ApplicationCommandInteractionOption; +import discord4j.core.object.command.ApplicationCommandInteractionOptionValue; +import discord4j.core.object.entity.User; +import fns.patchwork.base.Shortcuts; +import fns.veritas.Veritas; +import fns.veritas.cmd.base.BotCommand; +import java.time.Duration; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import reactor.core.publisher.Mono; + +public class BanCommand implements BotCommand +{ + + + @Override + public String getName() + { + return "ban"; + } + + @Override + public Mono handle(ChatInputInteractionEvent event) + { + final String playerName = event.getOption("player") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asString) + .orElseThrow(); + final String reason = event.getOption("reason") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asString) + .orElseThrow(); + final Duration duration = event.getOption("duration") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asLong) + .map(Duration::ofMinutes) + .orElse(Duration.ofMinutes(5)); + + final User user = event.getInteraction().getUser(); + return Shortcuts.provideModule(Veritas.class) + .getAggregate() + .getBot() + .isAdmin(user) + .doOnSuccess(b -> + { + if (Boolean.FALSE.equals(b)) + return; + + final Player player = Bukkit.getPlayer(playerName); + if (player == null) + { + event.reply() + .withEphemeral(true) + .withContent("Player not found") + .block(); + return; + } + + player.ban(reason, duration, user.getUsername()); + event.reply() + .withContent("Kicked " + playerName) + .withEphemeral(true) + .block(); + + event.getInteractionResponse() + .createFollowupMessage(user.getUsername() + ": Kicked " + playerName) + .then(); + }) + .then(); + } +} diff --git a/Veritas/src/main/java/fns/veritas/cmd/KickCommand.java b/Veritas/src/main/java/fns/veritas/cmd/KickCommand.java new file mode 100644 index 0000000..22e5e11 --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/cmd/KickCommand.java @@ -0,0 +1,90 @@ +/* + * 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.veritas.cmd; + +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; +import discord4j.core.object.command.ApplicationCommandInteractionOption; +import discord4j.core.object.command.ApplicationCommandInteractionOptionValue; +import discord4j.core.object.entity.User; +import fns.patchwork.base.Shortcuts; +import fns.patchwork.kyori.MiniMessageWrapper; +import fns.veritas.Veritas; +import fns.veritas.cmd.base.BotCommand; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import reactor.core.publisher.Mono; + +public class KickCommand implements BotCommand +{ + @Override + public String getName() + { + return "kick"; + } + + @Override + public Mono handle(ChatInputInteractionEvent event) + { + final String playerName = event.getOption("player") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asString) + .orElseThrow(); + final String reason = event.getOption("reason") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asString) + .orElseThrow(); + + final User user = event.getInteraction().getUser(); + return Shortcuts.provideModule(Veritas.class) + .getAggregate() + .getBot() + .isAdmin(user) + .doOnSuccess(b -> + { + if (Boolean.FALSE.equals(b)) + return; + + final Player player = Bukkit.getPlayer(playerName); + if (player == null) + { + event.reply() + .withEphemeral(true) + .withContent("Player not found") + .block(); + return; + } + + player.kick(MiniMessageWrapper.deserialize(true, reason)); + event.reply() + .withContent("Kicked " + playerName) + .withEphemeral(true) + .block(); + + event.getInteractionResponse() + .createFollowupMessage(user.getUsername() + ": Kicked " + playerName) + .then(); + }) + .then(); + } +} diff --git a/Veritas/src/main/java/fns/veritas/cmd/WhisperCommand.java b/Veritas/src/main/java/fns/veritas/cmd/WhisperCommand.java new file mode 100644 index 0000000..2717910 --- /dev/null +++ b/Veritas/src/main/java/fns/veritas/cmd/WhisperCommand.java @@ -0,0 +1,75 @@ +/* + * 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.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.MiniMessageWrapper; +import fns.veritas.cmd.base.BotCommand; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import reactor.core.publisher.Mono; + +public class WhisperCommand implements BotCommand +{ + @Override + public String getName() + { + return "whisper"; + } + + @Override + public Mono handle(ChatInputInteractionEvent event) + { + final String player = event.getOption("player") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asString) + .orElseThrow(); + + final String message = event.getOption("message") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asString) + .orElseThrow(); + final Component c = MiniMessageWrapper.deserialize(true, + "[Whisper] " + + event.getInteraction().getUser().getUsername() + + ": " + + message); + + final Player actual = Bukkit.getPlayer(player); + if (actual == null) { + return event.reply("Player not found!") + .withEphemeral(true) + .then(); + } + + actual.sendMessage(c); + + return event.reply("Sent!") + .withEphemeral(true) + .then(); + } +} diff --git a/Veritas/src/main/java/fns/veritas/messaging/SimpleMessageWrapper.java b/Veritas/src/main/java/fns/veritas/messaging/SimpleMessageWrapper.java deleted file mode 100644 index 6d94b1b..0000000 --- a/Veritas/src/main/java/fns/veritas/messaging/SimpleMessageWrapper.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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/commands/ban.json b/Veritas/src/main/resources/commands/ban.json new file mode 100644 index 0000000..cddf194 --- /dev/null +++ b/Veritas/src/main/resources/commands/ban.json @@ -0,0 +1,24 @@ +{ + "name": "ban", + "description": "Bans a user from the server.", + "options": [ + { + "name": "player", + "description": "The player to ban.", + "type": 3, + "required": true + }, + { + "name": "reason", + "description": "The reason for the ban.", + "type": 3, + "required": true + }, + { + "name": "duration", + "description": "The duration of the ban, in minutes. Default is 5 minutes.", + "type": 4, + "required": false + } + ] +} \ No newline at end of file diff --git a/Veritas/src/main/resources/commands/kick.json b/Veritas/src/main/resources/commands/kick.json new file mode 100644 index 0000000..9129e21 --- /dev/null +++ b/Veritas/src/main/resources/commands/kick.json @@ -0,0 +1,18 @@ +{ + "name": "kick", + "description": "Kicks a user from the server", + "options": [ + { + "name": "player", + "type": 3, + "description": "The player to kick", + "required": true + }, + { + "name": "reason", + "type": 3, + "description": "The reason for kicking the player", + "required": true + } + ] +} \ No newline at end of file diff --git a/Veritas/src/main/resources/commands/whisper.json b/Veritas/src/main/resources/commands/whisper.json new file mode 100644 index 0000000..c6d26ac --- /dev/null +++ b/Veritas/src/main/resources/commands/whisper.json @@ -0,0 +1,18 @@ +{ + "name": "whisper", + "description": "Whisper to a user.", + "options": [ + { + "name": "player", + "type": 3, + "description": "The in-game user to whisper to.", + "required": true + }, + { + "name": "message", + "type": 3, + "description": "The message to send.", + "required": true + } + ] +} \ No newline at end of file diff --git a/Veritas/src/main/resources/config.toml b/Veritas/src/main/resources/config.toml new file mode 100644 index 0000000..5aa33e2 --- /dev/null +++ b/Veritas/src/main/resources/config.toml @@ -0,0 +1,10 @@ +[bot_settings] +bot_token = "xyz-123-REPLACE-ME" +invite_link = "https://discord.gg/invite" +guild_id = 0 +mc_channel_id = 0 +log_channel_id = 0 + +[admin_settings] +# This role will be able to use the /kick and /ban commands. +admin_role_id = 0 \ No newline at end of file From 33731b611fa081d77b80bd4031b301ba6ca94f37 Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Sat, 9 Sep 2023 18:57:15 -0500 Subject: [PATCH 6/7] Tyr Backbone Creation # Changes: ## Patchwork - Renamed FreedomExecutor to ExecutorProvider and moved the class to the provider package. - Created an SQL Registry to prevent dependencies on Datura for SQL data. SQL is returned through an Optional, in the event that there is no SQL service registered. - Created SQLResult, a generic ORM for ResultSets to avoid working directly with SQL data. ## Tyr - Created Identity, which houses a username and related secret key. - Created SQLEntry which stores the information from the Identity class into an SQL table called sessionData. - Created TOTP, a simple static class that allows easy access to TimeBasedOneTimePasswordUtils class. - Created OAuth2 which houses identities and performs the appropriate credential validations (incomplete) --- Datura/src/main/java/fns/datura/Datura.java | 2 +- .../src/main/java/fns/datura/sql/MySQL.java | 184 +++++---- .../java/fns/datura/user/SimpleUserData.java | 115 +++--- Fossil/src/main/java/fns/fossil/Fossil.java | 2 +- .../java/fns/patchwork/base/Patchwork.java | 16 +- .../java/fns/patchwork/base/Registration.java | 13 + .../java/fns/patchwork/base/Shortcuts.java | 19 +- .../ExecutorProvider.java} | 8 +- .../SubscriptionProvider.java | 6 +- .../fns/patchwork/registry/SQLRegistry.java | 44 +++ .../registry/ServiceTaskRegistry.java | 2 +- .../java/fns/patchwork/service/Service.java | 2 + .../src/main/java/fns/patchwork/sql/SQL.java | 2 +- .../java/fns/patchwork/sql/SQLResult.java | 348 ++++++++++++++++++ Tyr/build.gradle | 7 +- Tyr/src/main/java/fns/tyr/Tyr.java | 48 +++ Tyr/src/main/java/fns/tyr/data/SQLEntry.java | 93 +++++ Tyr/src/main/java/fns/tyr/oauth/Identity.java | 31 ++ Tyr/src/main/java/fns/tyr/oauth/OAuth2.java | 74 ++++ Tyr/src/main/java/fns/tyr/oauth/TOTP.java | 63 ++++ 20 files changed, 898 insertions(+), 181 deletions(-) rename Patchwork/src/main/java/fns/patchwork/{service/FreedomExecutor.java => provider/ExecutorProvider.java} (97%) rename Patchwork/src/main/java/fns/patchwork/{service => provider}/SubscriptionProvider.java (97%) create mode 100644 Patchwork/src/main/java/fns/patchwork/registry/SQLRegistry.java create mode 100644 Patchwork/src/main/java/fns/patchwork/sql/SQLResult.java create mode 100644 Tyr/src/main/java/fns/tyr/Tyr.java create mode 100644 Tyr/src/main/java/fns/tyr/data/SQLEntry.java create mode 100644 Tyr/src/main/java/fns/tyr/oauth/Identity.java create mode 100644 Tyr/src/main/java/fns/tyr/oauth/OAuth2.java create mode 100644 Tyr/src/main/java/fns/tyr/oauth/TOTP.java diff --git a/Datura/src/main/java/fns/datura/Datura.java b/Datura/src/main/java/fns/datura/Datura.java index 5fa5c92..9309f77 100644 --- a/Datura/src/main/java/fns/datura/Datura.java +++ b/Datura/src/main/java/fns/datura/Datura.java @@ -32,7 +32,7 @@ import fns.datura.sql.MySQL; import fns.patchwork.base.Registration; import fns.patchwork.command.CommandHandler; -import fns.patchwork.service.SubscriptionProvider; +import fns.patchwork.provider.SubscriptionProvider; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; diff --git a/Datura/src/main/java/fns/datura/sql/MySQL.java b/Datura/src/main/java/fns/datura/sql/MySQL.java index 00770cd..2fd4816 100644 --- a/Datura/src/main/java/fns/datura/sql/MySQL.java +++ b/Datura/src/main/java/fns/datura/sql/MySQL.java @@ -26,11 +26,11 @@ import fns.patchwork.base.Patchwork; import fns.patchwork.base.Shortcuts; import fns.patchwork.sql.SQL; +import fns.patchwork.sql.SQLResult; import fns.patchwork.utils.container.Identity; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -74,7 +74,7 @@ public void addCredentials(final String username, final String password) .append(password); } - public CompletableFuture getRow(final String table, final String column, final Identity identity) + public CompletableFuture getRow(final String table, final String column, final Identity identity) { return executeQuery("SELECT * FROM ? WHERE ? = ?", table, column, identity.getId()); } @@ -83,99 +83,104 @@ public CompletableFuture getRow(final String table, final String colu public CompletableFuture prepareStatement(final String query, final Object... args) { return getConnection() - .thenApplyAsync(connection -> - { - try - { - final PreparedStatement statement = connection.prepareStatement(query); - for (int i = 0; i < args.length; i++) - { - statement.setObject(i + 1, args[i]); - } - return statement; - } catch (SQLException ex) - { - throw new CompletionException("Failed to prepare statement: " - + query + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(connection -> + { + try + { + final PreparedStatement statement = connection.prepareStatement(query); + for (int i = 0; i < args.length; i++) + { + statement.setObject(i + 1, args[i]); + } + return statement; + } + catch (SQLException ex) + { + throw new CompletionException("Failed to prepare statement: " + + query + "\n", ex); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } private CompletableFuture getConnection() { return CompletableFuture.supplyAsync(() -> - { - try - { - return DriverManager.getConnection(url.toString()); - } catch (SQLException ex) - { - throw new CompletionException("Failed to connect to the database: " - + url.toString() + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + { + try + { + return DriverManager.getConnection(url.toString()); + } + catch (SQLException ex) + { + throw new CompletionException("Failed to connect to the database: " + + url.toString() + "\n", ex); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } @Override - public CompletableFuture executeQuery(final String query, final Object... args) + public CompletableFuture executeQuery(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> - { - try - { - return statement.executeQuery(); - } catch (SQLException ex) - { - throw new CompletionException( - "Failed to retrieve a result set from query: " + .thenApplyAsync(statement -> + { + try + { + return new SQLResult(statement.executeQuery()); + } + catch (SQLException ex) + { + throw new CompletionException( + "Failed to retrieve a result set from query: " + query + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } @Override public CompletableFuture executeUpdate(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> - { - try - { - return statement.executeUpdate(); - } catch (SQLException ex) - { - throw new CompletionException("Failed to execute update: " - + query + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(statement -> + { + try + { + return statement.executeUpdate(); + } + catch (SQLException ex) + { + throw new CompletionException("Failed to execute update: " + + query + "\n", ex); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } @Override public CompletableFuture execute(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> - { - try - { - return statement.execute(); - } catch (SQLException ex) - { - throw new CompletionException("Failed to execute statement: " - + query + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(statement -> + { + try + { + return statement.execute(); + } + catch (SQLException ex) + { + throw new CompletionException("Failed to execute statement: " + + query + "\n", ex); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } @Override @@ -201,42 +206,27 @@ public CompletableFuture getColumn(final String table, final String colum final Identity identity, final Class type) { return executeQuery("SELECT ? FROM ? WHERE ? = ?", column, table, key, identity.getId()) - .thenApplyAsync(resultSet -> - { - try - { - if (resultSet.next()) - { - return resultSet.getObject(column, type); - } - } catch (SQLException ex) - { - throw new CompletionException( - "Failed to retrieve column: " + column + " from table: " + table + " " + - "where primary key: " + key + " is equal to: " + identity.getId() + "\n", - ex); - } - return null; - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(resultSet -> (resultSet.hasNext()) ? resultSet.autoCast(1, column, type) : null, + Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } public CompletableFuture updateColumn(final String table, final String column, final Object value, final String key, final Identity identity) { return executeUpdate("UPDATE ? SET ? = ? WHERE ? = ?", table, column, value, key, identity.getId()) - .thenApplyAsync(result -> result > 0, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(result -> result > 0, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } public CompletableFuture deleteRow(final String table, final String key, final Identity identity) { return executeUpdate("DELETE FROM ? WHERE ? = ?", table, key, identity.getId()) - .thenApplyAsync(result -> result > 0, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(result -> result > 0, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } public CompletableFuture insertRow(final String table, final Object... values) diff --git a/Datura/src/main/java/fns/datura/user/SimpleUserData.java b/Datura/src/main/java/fns/datura/user/SimpleUserData.java index 0a19c6a..5462a15 100644 --- a/Datura/src/main/java/fns/datura/user/SimpleUserData.java +++ b/Datura/src/main/java/fns/datura/user/SimpleUserData.java @@ -34,10 +34,8 @@ import fns.patchwork.user.User; import fns.patchwork.user.UserData; import fns.patchwork.utils.logging.FNS4J; -import java.sql.SQLException; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; -import org.apache.commons.lang3.exception.ExceptionUtils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -69,14 +67,14 @@ public SimpleUserData(final Player player) } private SimpleUserData( - final UUID uuid, - final String username, - final User user, - final Group group, - final long playtime, - final boolean canInteract, - final long balance, - final boolean transactionsFrozen) + final UUID uuid, + final String username, + final User user, + final Group group, + final long playtime, + final boolean canInteract, + final long balance, + final boolean transactionsFrozen) { this.uuid = uuid; this.username = username; @@ -93,56 +91,53 @@ public static SimpleUserData fromSQL(final SQL sql, final String uuid) { return sql.executeQuery("SELECT * FROM users WHERE UUID = ?", uuid) .thenApplyAsync(result -> - { - try - { - if (result.next()) - { - final String g = result.getString("group"); - - final UUID u = UUID.fromString(uuid); - final String username = result.getString("username"); - - final Player player = Bukkit.getPlayer(u); - - if (player == null) - throw new IllegalStateException("Player should be online but they are not!"); - - final User user = new FreedomUser(player); - final Group group = Registration - .getGroupRegistry() - .getGroup(g); - - final long playtime = result.getLong("playtime"); - final boolean canInteract = result.getBoolean("canInteract"); - final long balance = result.getLong("balance"); - final boolean transactionsFrozen = result.getBoolean("transactionsFrozen"); - - return new SimpleUserData(u, username, user, group, playtime, - canInteract, balance, transactionsFrozen); - } - } catch (SQLException ex) - { - final String sb = "An error occurred while trying to retrieve user data for" + - " UUID " + - uuid + - " from the database." + - "\nCaused by: " + - ExceptionUtils.getRootCauseMessage(ex) + - "\nStack trace: " + - ExceptionUtils.getStackTrace(ex); - - FNS4J.getLogger("Datura") - .error(sb); - } - - final Player player = Bukkit.getPlayer(UUID.fromString(uuid)); - if (player == null) throw new IllegalStateException("Player should be online but they are not!"); - - return new SimpleUserData(player); - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()) + { + + if (result.hasNext()) + { + final String g = result.getString("group"); + + final UUID u = UUID.fromString(uuid); + final String username = result.getString("username"); + + final Player player = Bukkit.getPlayer(u); + + if (player == null) + throw new IllegalStateException( + "Player should be online but they are not!"); + + final User user = new FreedomUser(player); + final Group group = Registration + .getGroupRegistry() + .getGroup(g); + + final long playtime = result.getLong("playtime"); + final boolean canInteract = result.getBoolean("canInteract"); + final long balance = result.getLong("balance"); + final boolean transactionsFrozen = result.getBoolean("transactionsFrozen"); + + return new SimpleUserData(u, username, user, group, playtime, + canInteract, balance, transactionsFrozen); + } + else + { + final String sb = "An error occurred while trying to retrieve user data for" + + " UUID " + + uuid + + " from the database."; + + FNS4J.getLogger("Datura") + .error(sb); + } + + final Player player = Bukkit.getPlayer(UUID.fromString(uuid)); + if (player == null) + throw new IllegalStateException("Player should be online but they are not!"); + + return new SimpleUserData(player); + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()) .join(); } diff --git a/Fossil/src/main/java/fns/fossil/Fossil.java b/Fossil/src/main/java/fns/fossil/Fossil.java index a07711f..22157e4 100644 --- a/Fossil/src/main/java/fns/fossil/Fossil.java +++ b/Fossil/src/main/java/fns/fossil/Fossil.java @@ -27,7 +27,7 @@ import fns.fossil.trail.Trailer; import fns.patchwork.base.Registration; import fns.patchwork.command.CommandHandler; -import fns.patchwork.service.SubscriptionProvider; +import fns.patchwork.provider.SubscriptionProvider; import org.bukkit.plugin.java.JavaPlugin; public class Fossil extends JavaPlugin diff --git a/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java b/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java index b60d596..60c9b55 100644 --- a/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java +++ b/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java @@ -25,8 +25,8 @@ import fns.patchwork.display.adminchat.AdminChatDisplay; import fns.patchwork.event.EventBus; -import fns.patchwork.service.FreedomExecutor; -import fns.patchwork.service.SubscriptionProvider; +import fns.patchwork.provider.ExecutorProvider; +import fns.patchwork.provider.SubscriptionProvider; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; @@ -40,9 +40,9 @@ public class Patchwork extends JavaPlugin */ private EventBus eventBus; /** - * The {@link FreedomExecutor} for this plugin. + * The {@link ExecutorProvider} for this plugin. */ - private FreedomExecutor executor; + private ExecutorProvider executor; /** * The {@link AdminChatDisplay} for this plugin. */ @@ -64,7 +64,7 @@ public void onDisable() public void onEnable() { eventBus = new EventBus(this); - executor = new FreedomExecutor(this); + executor = new ExecutorProvider(this); acdisplay = new AdminChatDisplay(this); @@ -80,11 +80,11 @@ public void onEnable() } /** - * Gets the {@link FreedomExecutor} for this plugin. + * Gets the {@link ExecutorProvider} for this plugin. * - * @return the {@link FreedomExecutor} + * @return the {@link ExecutorProvider} */ - public FreedomExecutor getExecutor() + public ExecutorProvider getExecutor() { return executor; } diff --git a/Patchwork/src/main/java/fns/patchwork/base/Registration.java b/Patchwork/src/main/java/fns/patchwork/base/Registration.java index db491fa..8818cf3 100644 --- a/Patchwork/src/main/java/fns/patchwork/base/Registration.java +++ b/Patchwork/src/main/java/fns/patchwork/base/Registration.java @@ -27,6 +27,7 @@ import fns.patchwork.registry.EventRegistry; import fns.patchwork.registry.GroupRegistry; import fns.patchwork.registry.ModuleRegistry; +import fns.patchwork.registry.SQLRegistry; import fns.patchwork.registry.ServiceTaskRegistry; import fns.patchwork.registry.UserRegistry; @@ -62,6 +63,10 @@ public class Registration * The {@link ConfigRegistry} */ private static final ConfigRegistry configRegistry = new ConfigRegistry(); + /** + * The SQL Registry + */ + private static final SQLRegistry sqlRegistry = new SQLRegistry(); private Registration() { @@ -115,4 +120,12 @@ public static ConfigRegistry getConfigRegistry() { return configRegistry; } + + /** + * @return The {@link SQLRegistry} + */ + public static SQLRegistry getSQLRegistry() + { + return sqlRegistry; + } } \ No newline at end of file diff --git a/Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java b/Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java index 9482202..3dcd19e 100644 --- a/Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java +++ b/Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java @@ -23,7 +23,10 @@ package fns.patchwork.base; +import fns.patchwork.provider.ExecutorProvider; +import fns.patchwork.sql.SQL; import fns.patchwork.user.User; +import java.util.Optional; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -37,13 +40,23 @@ private Shortcuts() public static T provideModule(final Class pluginClass) { return Registration.getModuleRegistry() - .getProvider(pluginClass) - .getModule(); + .getProvider(pluginClass) + .getModule(); } public static User getUser(final Player player) { return Registration.getUserRegistry() - .getUser(player); + .getUser(player); + } + + public static ExecutorProvider getExecutors() + { + return provideModule(Patchwork.class).getExecutor(); + } + + public static Optional getSQL() + { + return Registration.getSQLRegistry().getSQL(); } } diff --git a/Patchwork/src/main/java/fns/patchwork/service/FreedomExecutor.java b/Patchwork/src/main/java/fns/patchwork/provider/ExecutorProvider.java similarity index 97% rename from Patchwork/src/main/java/fns/patchwork/service/FreedomExecutor.java rename to Patchwork/src/main/java/fns/patchwork/provider/ExecutorProvider.java index 3a5eb6c..cbdd2c9 100644 --- a/Patchwork/src/main/java/fns/patchwork/service/FreedomExecutor.java +++ b/Patchwork/src/main/java/fns/patchwork/provider/ExecutorProvider.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.service; +package fns.patchwork.provider; import fns.patchwork.base.Patchwork; import java.util.concurrent.CompletableFuture; @@ -34,7 +34,7 @@ * This class is here for both convenience purposes, and also for the sake of providing easy access to executors for * {@link CompletableFuture} invocations. */ -public class FreedomExecutor +public class ExecutorProvider { /** * An executor which runs tasks synchronously. @@ -46,9 +46,9 @@ public class FreedomExecutor private final Executor asyncExecutor; /** - * Creates a new {@link FreedomExecutor} instance. + * Creates a new {@link ExecutorProvider} instance. */ - public FreedomExecutor(final Patchwork patchwork) + public ExecutorProvider(final Patchwork patchwork) { syncExecutor = r -> Bukkit.getScheduler() .runTask(patchwork, r); diff --git a/Patchwork/src/main/java/fns/patchwork/service/SubscriptionProvider.java b/Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java similarity index 97% rename from Patchwork/src/main/java/fns/patchwork/service/SubscriptionProvider.java rename to Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java index 5d410b7..c15bf5c 100644 --- a/Patchwork/src/main/java/fns/patchwork/service/SubscriptionProvider.java +++ b/Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java @@ -21,8 +21,12 @@ * SOFTWARE. */ -package fns.patchwork.service; +package fns.patchwork.provider; +import fns.patchwork.service.Service; +import fns.patchwork.service.ServiceSubscription; +import fns.patchwork.service.Task; +import fns.patchwork.service.TaskSubscription; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; diff --git a/Patchwork/src/main/java/fns/patchwork/registry/SQLRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/SQLRegistry.java new file mode 100644 index 0000000..9f7eac3 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/registry/SQLRegistry.java @@ -0,0 +1,44 @@ +/* + * 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.registry; + +import fns.patchwork.sql.SQL; +import java.util.Optional; + +public class SQLRegistry +{ + private SQL sql; + + public SQLRegistry() { + this.sql = null; + } + + public Optional getSQL() { + return (sql == null) ? Optional.empty() : Optional.of(sql); + } + + public void setSQL(final SQL sql) { + this.sql = sql; + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java index c346c0c..00d7ffa 100644 --- a/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java @@ -25,7 +25,7 @@ import fns.patchwork.service.Service; import fns.patchwork.service.ServiceSubscription; -import fns.patchwork.service.SubscriptionProvider; +import fns.patchwork.provider.SubscriptionProvider; import fns.patchwork.service.Task; import fns.patchwork.service.TaskSubscription; import java.util.ArrayList; diff --git a/Patchwork/src/main/java/fns/patchwork/service/Service.java b/Patchwork/src/main/java/fns/patchwork/service/Service.java index b541c89..dc3a779 100644 --- a/Patchwork/src/main/java/fns/patchwork/service/Service.java +++ b/Patchwork/src/main/java/fns/patchwork/service/Service.java @@ -23,6 +23,8 @@ package fns.patchwork.service; +import fns.patchwork.provider.SubscriptionProvider; + /** * Represents a ticking service. Services may be asynchronous or synchronous, however there are some restrictions: *
    diff --git a/Patchwork/src/main/java/fns/patchwork/sql/SQL.java b/Patchwork/src/main/java/fns/patchwork/sql/SQL.java index d68409a..5b66a10 100644 --- a/Patchwork/src/main/java/fns/patchwork/sql/SQL.java +++ b/Patchwork/src/main/java/fns/patchwork/sql/SQL.java @@ -31,7 +31,7 @@ public interface SQL { CompletableFuture prepareStatement(final String query, final Object... args); - CompletableFuture executeQuery(final String query, final Object... args); + CompletableFuture executeQuery(final String query, final Object... args); CompletableFuture executeUpdate(final String query, final Object... args); diff --git a/Patchwork/src/main/java/fns/patchwork/sql/SQLResult.java b/Patchwork/src/main/java/fns/patchwork/sql/SQLResult.java new file mode 100644 index 0000000..b23dbd5 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/sql/SQLResult.java @@ -0,0 +1,348 @@ +/* + * 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.sql; + +import fns.patchwork.utils.container.Pair; +import fns.patchwork.utils.logging.FNS4J; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +public class SQLResult +{ + private final Map> resultMap = new HashMap<>(); + + /** + * This constructor will create a new SQLResult object from the specified ResultSet. + * This will iterate through all rows and columns of the ResultSet and store them in a Map. + * The Map will contain keys of integers representing the row number, and values of Maps + * containing the column names and their values. + * + * @param resultSet The ResultSet to create the SQLResult object from. + */ + public SQLResult(final ResultSet resultSet) + { + try + { + final ResultSetMetaData metaData = resultSet.getMetaData(); + final int columnCount = metaData.getColumnCount(); + + int rowIndex = 0; + + while (resultSet.next()) + { + rowIndex++; + final Map rowMap = new HashMap<>(); + + for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) + { + String columnName = metaData.getColumnName(columnIndex); + Object columnValue = resultSet.getObject(columnIndex); + rowMap.put(columnName, columnValue); + } + + resultMap.put(rowIndex, rowMap); + } + } + catch (SQLException ex) + { + FNS4J.getLogger("Tyr").error(ex.getMessage()); + } + } + + /** + * This method will return a map of all rows and their columns and values. + * + * @return A Map containing all rows and their columns and values. + */ + public Map> getResultMap() + { + return resultMap; + } + + /** + * This method will return a map of all columns and their values from the specified row. + * + * @param rowIndex The row index to get the column names from. + * @return A Map containing all column names and their values from the specified row. + */ + public Map getRow(final int rowIndex) + { + return resultMap.get(rowIndex); + } + + /** + * This method will return the value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The value from the specified row and column. + */ + public Object getValue(final int rowIndex, final String columnName) + { + return resultMap.get(rowIndex).get(columnName); + } + + /** + * This method will return the first value from the first row of the result set. + * + * @return A Pair containing the column name and the stored value. + */ + public Pair getFirst() + { + return new Pair<>(resultMap.get(1).entrySet().iterator().next().getKey(), + resultMap.get(1).entrySet().iterator().next().getValue()); + } + + /** + * This method will return the first value from the specified row of the result set. + * + * @param rowIndex The row index to get the column name from. + * @return A Pair containing the column name and the stored value. + */ + public Pair getFirst(final int rowIndex) + { + return new Pair<>(resultMap.get(rowIndex).entrySet().iterator().next().getKey(), + resultMap.get(rowIndex).entrySet().iterator().next().getValue()); + } + + /** + * This method will return the last value from the first row of the result set. + * + * @return A Pair containing the column name and the stored value. + */ + public Pair getLast() + { + return new Pair<>(resultMap.get(1).entrySet().iterator().next().getKey(), + resultMap.get(1).entrySet().iterator().next().getValue()); + } + + /** + * This method will return the last value from the specified row of the result set. + * + * @param rowIndex The row index to get the column name from. + * @return A Pair containing the column name and the stored value. + */ + public Pair getLast(final int rowIndex) + { + return new Pair<>(resultMap.get(rowIndex).entrySet().iterator().next().getKey(), + resultMap.get(rowIndex).entrySet().iterator().next().getValue()); + } + + /** + * This method will attempt to retrieve the value from the specified row and column, + * and cast it to the specified class. This will throw a {@link ClassCastException} if the + * returned value is not an instance of the provided class. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @param clazz The class to cast the value to. + * @param The expected type. + * @return The value from the specified row and column, cast to the specified class. + */ + public T autoCast(final int rowIndex, final String columnName, final Class clazz) + { + final Object value = resultMap.get(rowIndex).get(columnName); + + if (!clazz.isInstance(value)) + throw new ClassCastException("Cannot cast " + value.getClass().getName() + " to " + clazz.getName()); + + return clazz.cast(resultMap.get(rowIndex).get(columnName)); + } + + /** + * @param rowIndex The row index to get the column names from. + * @return A Set containing all column names from the specified row of the result set. + */ + public Set getColumnNames(final int rowIndex) + { + return resultMap.get(rowIndex).keySet(); + } + + /** + * @return A Set containing all column names from the first row of the result set. + */ + public Set getColumnNames() + { + return resultMap.get(1).keySet(); + } + + /** + * This method will apply the specified consumer to all rows of the result set. + * + * @param columnConsumer The consumer to apply to all rows of the result set. + */ + public void accept(final Consumer> columnConsumer) + { + this.resultMap.forEach((integer, map) -> columnConsumer.accept(map)); + } + + /** + * Checks to see if the result set contains the specified row number. + * Best used in a for loop, using {@link #rowCount()} as the upper bound. + * + * @param rowIndex The row index to check. + * @return True if the result set contains the specified row number, false otherwise. + */ + public boolean hasNext(final int rowIndex) + { + return this.resultMap.containsKey(rowIndex + 1); + } + + /** + * Checks to see if the result set has the first row. + * If row 1 doesn't exist, it's safe to say the result set is empty. + * + * @return True if the result set has row 1, false otherwise. + */ + public boolean hasNext() + { + return this.resultMap.containsKey(1); + } + + /** + * @return The number of rows in the result set. + */ + public int rowCount() + { + return this.resultMap.size(); + } + + /** + * @param rowIndex The row index from which to count columns. + * @return The number of columns in the specified row. + */ + public int columnCount(final int rowIndex) + { + return this.resultMap.get(rowIndex).size(); + } + + /** + * Retrieves a String value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The String value from the specified row and column. + * @see #autoCast(int, String, Class) + */ + public String getString(final int rowIndex, final String columnName) + { + return autoCast(rowIndex, columnName, String.class); + } + + /** + * This method will attempt to retrieve a String value from the specified column within the first row of the + * result set. + * + * @param columnName The column name to get the value from. + * @return The String value from the specified column within the first row of the result set. + * @see #getString(int, String) + */ + public String getString(final String columnName) + { + return getString(1, columnName); + } + + /** + * Retrieves an Integer value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The Integer value from the specified row and column. + * @see #autoCast(int, String, Class) + */ + public int getInteger(final int rowIndex, final String columnName) + { + return autoCast(rowIndex, columnName, Integer.class); + } + + /** + * This method will attempt to retrieve an Integer value from the specified column within the first row of the + * result set. + * + * @param columnName The column name to get the value from. + * @return The Integer value from the specified column within the first row of the result set. + * @see #getInteger(int, String) + */ + public int getInteger(final String columnName) + { + return getInteger(1, columnName); + } + + /** + * Retrieves a Long value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The Long value from the specified row and column. + * @see #autoCast(int, String, Class) + */ + public long getLong(final int rowIndex, final String columnName) + { + return autoCast(rowIndex, columnName, Long.class); + } + + /** + * This method will attempt to retrieve a Long value from the specified column within the first row of the + * result set. + * + * @param columnName The column name to get the value from. + * @return The Long value from the specified column within the first row of the result set. + * @see #getLong(int, String) + */ + public long getLong(final String columnName) + { + return getLong(1, columnName); + } + + /** + * Retrieves a Double value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The Double value from the specified row and column. + * @see #autoCast(int, String, Class) + */ + public boolean getBoolean(final int rowIndex, final String columnName) + { + return autoCast(rowIndex, columnName, Boolean.class); + } + + /** + * This method will attempt to retrieve a Boolean value from the specified column within the first row of the + * result set. + * + * @param columnName The column name to get the value from. + * @return The Boolean value from the specified column within the first row of the result set. + * @see #getBoolean(int, String) + */ + public boolean getBoolean(final String columnName) + { + return getBoolean(1, columnName); + } +} diff --git a/Tyr/build.gradle b/Tyr/build.gradle index 4bd615f..8a40b19 100644 --- a/Tyr/build.gradle +++ b/Tyr/build.gradle @@ -16,11 +16,10 @@ bukkit { } dependencies { - compileOnly project(":Patchwork") - compileOnly project(":Datura") + compileOnly project(path: ":Patchwork") + compileOnly project(path: ":Datura") - library 'com.hierynomus:sshj:0.28.0' - library 'org.bouncycastle:bcprov-jdk18on:1.76' + library 'com.j256.two-factor-auth:two-factor-auth:1.3' testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/Tyr/src/main/java/fns/tyr/Tyr.java b/Tyr/src/main/java/fns/tyr/Tyr.java new file mode 100644 index 0000000..d1c5c16 --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/Tyr.java @@ -0,0 +1,48 @@ +/* + * 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.tyr; + +import fns.datura.Datura; +import fns.patchwork.base.Shortcuts; +import fns.patchwork.sql.SQL; +import fns.patchwork.utils.logging.FNS4J; + +public class Tyr +{ + public void onEnable() + { + final SQL sql = Shortcuts.provideModule(Datura.class).getSQL(); + sql.createTable("sessionData", + "user VARCHAR(16) NOT NULL PRIMARY KEY, secretKey VARCHAR(64) NOT NULL;") + .whenCompleteAsync((result, throwable) -> + { + if (throwable != null) + FNS4J.getLogger("Tyr") + .error(throwable.getMessage()); + }, Shortcuts.getExecutors() + .getAsync()); + + + } +} diff --git a/Tyr/src/main/java/fns/tyr/data/SQLEntry.java b/Tyr/src/main/java/fns/tyr/data/SQLEntry.java new file mode 100644 index 0000000..9347c75 --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/data/SQLEntry.java @@ -0,0 +1,93 @@ +/* + * 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.tyr.data; + +import fns.patchwork.base.Shortcuts; +import fns.patchwork.utils.logging.FNS4J; +import fns.tyr.oauth.Identity; +import java.sql.SQLException; + +public class SQLEntry +{ + private final Identity identity; + + public SQLEntry(final Identity identity) + { + this.identity = identity; + } + + public static SQLEntry load(final String username) + { + return Shortcuts.getSQL() + .map(c -> + c.executeQuery("SELECT * FROM sessionData WHERE user = ?;", username) + .thenApplyAsync(result -> + { + SQLEntry entry = null; + try + { + if (result.next()) + { + final String user = result.getString("user"); + final String secretKey = result.getString("secretKey"); + + final Identity i = new Identity(user, secretKey); + + entry = new SQLEntry(i); + FNS4J.getLogger("Tyr") + .info("Loaded entry for " + username); + } + else + { + entry = new SQLEntry(Identity.of(username)); + FNS4J.getLogger("Tyr") + .info("Created a new entry for " + username); + } + } + catch (SQLException ex) + { + FNS4J.getLogger("Tyr").error(ex.getMessage()); + } + return entry; + }, Shortcuts.getExecutors() + .getAsync()) + .join()) + .orElseThrow(() -> new IllegalStateException("SQL is not initialized!")); + } + + public void save() + { + Shortcuts.getSQL() + .orElseThrow(() -> new IllegalStateException("SQL is not available!")) + .executeUpdate("INSERT INTO sessionData (user, secretKey) VALUES (?, ?);", + this.identity.username(), + this.identity.secretKey()) + .whenCompleteAsync((result, throwable) -> + { + if (throwable != null) + FNS4J.getLogger("Tyr").error(throwable.getMessage()); + }, Shortcuts.getExecutors() + .getAsync()); + } +} diff --git a/Tyr/src/main/java/fns/tyr/oauth/Identity.java b/Tyr/src/main/java/fns/tyr/oauth/Identity.java new file mode 100644 index 0000000..93b7cf2 --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/oauth/Identity.java @@ -0,0 +1,31 @@ +/* + * 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.tyr.oauth; + +public record Identity(String username, String secretKey) +{ + public static Identity of(final String username) { + return new Identity(username, TOTP.createSecretKey()); + } +} diff --git a/Tyr/src/main/java/fns/tyr/oauth/OAuth2.java b/Tyr/src/main/java/fns/tyr/oauth/OAuth2.java new file mode 100644 index 0000000..998dd3f --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/oauth/OAuth2.java @@ -0,0 +1,74 @@ +/* + * 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.tyr.oauth; + +import fns.patchwork.base.Shortcuts; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class OAuth2 +{ + private final Set identitySet; + + public OAuth2() + { + this.identitySet = new HashSet<>(); + } + + public void addIdentity(Identity identity) + { + this.identitySet.add(identity); + } + + public void removeIdentity(Identity identity) + { + this.identitySet.remove(identity); + } + + public Optional getIdentity(final String username) + { + return this.identitySet.stream() + .filter(identity -> identity.username().equals(username)) + .findFirst(); + } + + public void loadAll() + { + Shortcuts.getSQL() + .ifPresent(sql -> sql.executeQuery("SELECT * FROM sessionData;") + .thenAcceptAsync(result -> + { + for (int i = 1; i < result.rowCount(); i++) + { + final String username = result.getString(i, + "user"); + final String secretKey = result.getString(i, + "secretKey"); + this.addIdentity( + new Identity(username, secretKey)); + } + })); + } +} diff --git a/Tyr/src/main/java/fns/tyr/oauth/TOTP.java b/Tyr/src/main/java/fns/tyr/oauth/TOTP.java new file mode 100644 index 0000000..7a7420b --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/oauth/TOTP.java @@ -0,0 +1,63 @@ +/* + * 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.tyr.oauth; + +import com.j256.twofactorauth.TimeBasedOneTimePasswordUtil; +import fns.patchwork.utils.logging.FNS4J; +import java.security.GeneralSecurityException; + +/** + * User-friendly version of TimeBasedOneTimePasswordUtil. + */ +public final class TOTP +{ + private TOTP() + { + throw new AssertionError("This class cannot be instantiated."); + } + + public static String createSecretKey() + { + return TimeBasedOneTimePasswordUtil.generateBase32Secret(32); + } + + public static String createQRCode(final String username, final String secretKey) + { + return TimeBasedOneTimePasswordUtil.qrImageUrl(username, secretKey); + } + + public static boolean verify(final String secretKey, final int userCode) + { + try + { + int vCode = TimeBasedOneTimePasswordUtil.generateCurrentNumber(secretKey); + return vCode == userCode; + } + catch (GeneralSecurityException ex) + { + FNS4J.getLogger("Tyr").error("Failed to verify TOTP code: " + ex.getMessage()); + return false; + } + } +} From 07ce76e0d08ab34fcbcaa136ef97ffe6d856372f Mon Sep 17 00:00:00 2001 From: Paldiu Date: Mon, 12 Feb 2024 23:56:58 -0600 Subject: [PATCH 7/7] Some stuff --- Datura/src/main/java/fns/datura/Datura.java | 17 +- .../fns/datura/punishment/SimpleBanEntry.java | 98 ++++++++ Fossil/src/main/java/fns/fossil/Fossil.java | 2 + .../fns/fossil/reactions/CopyCatReaction.java | 28 ++- .../fns/fossil/reactions/ReactionSystem.java | 59 +++++ .../java/fns/patchwork/bans/BanEntry.java | 43 ++++ .../java/fns/patchwork/base/Patchwork.java | 77 ++++-- .../block/detector/NukerDetection.java | 47 ++++ .../patchwork/block/logger/BlockLogger.java | 51 ++++ .../block/logger/TimedBlockLogger.java | 28 +++ .../config/GenericConfiguration.java | 224 ------------------ .../provider/SubscriptionProvider.java | 14 +- .../fns/patchwork/registry/BanRegistry.java | 57 +++++ .../registry/ServiceTaskRegistry.java | 42 ++-- .../fns/patchwork/service/BukkitTimer.java | 42 ++++ .../service/ServiceSubscription.java | 30 +-- .../patchwork/service/TaskSubscription.java | 16 +- .../java/fns/patchwork/service/TimedTask.java | 55 +++++ .../java/fns/patchwork/shop/Reaction.java | 9 +- Tyr/src/main/java/fns/tyr/data/SQLEntry.java | 30 +-- build.gradle | 1 + 21 files changed, 655 insertions(+), 315 deletions(-) create mode 100644 Datura/src/main/java/fns/datura/punishment/SimpleBanEntry.java create mode 100644 Fossil/src/main/java/fns/fossil/reactions/ReactionSystem.java create mode 100644 Patchwork/src/main/java/fns/patchwork/bans/BanEntry.java create mode 100644 Patchwork/src/main/java/fns/patchwork/block/detector/NukerDetection.java create mode 100644 Patchwork/src/main/java/fns/patchwork/block/logger/BlockLogger.java create mode 100644 Patchwork/src/main/java/fns/patchwork/block/logger/TimedBlockLogger.java delete mode 100644 Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java create mode 100644 Patchwork/src/main/java/fns/patchwork/registry/BanRegistry.java create mode 100644 Patchwork/src/main/java/fns/patchwork/service/BukkitTimer.java create mode 100644 Patchwork/src/main/java/fns/patchwork/service/TimedTask.java diff --git a/Datura/src/main/java/fns/datura/Datura.java b/Datura/src/main/java/fns/datura/Datura.java index 9309f77..862c130 100644 --- a/Datura/src/main/java/fns/datura/Datura.java +++ b/Datura/src/main/java/fns/datura/Datura.java @@ -43,11 +43,10 @@ public class Datura extends JavaPlugin // Punishment private final Halter halter = new Halter(); private final Locker locker = new Locker(); - private Cager cager; - // Features private final CommandSpy commandSpy = new CommandSpy(); private final Fuckoff fuckoff = new Fuckoff(); + private Cager cager; @Override public void onEnable() @@ -55,12 +54,20 @@ public void onEnable() cager = new Cager(this); Registration.getServiceTaskRegistry() - .registerService(SubscriptionProvider.syncService(this, locker)); + .registerService(SubscriptionProvider.syncService(this, locker)); Registration.getServiceTaskRegistry() .registerService(SubscriptionProvider.syncService(this, cager)); Registration.getServiceTaskRegistry() .registerService(SubscriptionProvider.syncService(this, fuckoff)); + getSQL().createTable("bans", + "uuid VARCHAR(36) PRIMARY KEY", + "name VARCHAR(16)", + "issuer VARCHAR(16)", + "reason VARCHAR(255)", + "issued LONG", + "duration LONG"); + Bukkit.getPluginManager() .registerEvents(halter, this); Bukkit.getPluginManager() @@ -92,12 +99,12 @@ public Cager getCager() return cager; } - public CommandSpy getCommandSpy() + public CommandSpy getCommandSpy() { return commandSpy; } - public Fuckoff getFuckoff() + public Fuckoff getFuckoff() { return fuckoff; } diff --git a/Datura/src/main/java/fns/datura/punishment/SimpleBanEntry.java b/Datura/src/main/java/fns/datura/punishment/SimpleBanEntry.java new file mode 100644 index 0000000..ebdd367 --- /dev/null +++ b/Datura/src/main/java/fns/datura/punishment/SimpleBanEntry.java @@ -0,0 +1,98 @@ +/* + * 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.datura.punishment; + +import com.google.errorprone.annotations.Immutable; +import fns.patchwork.bans.BanEntry; +import fns.patchwork.kyori.PlainTextWrapper; +import java.net.Inet6Address; +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +@Immutable +public class SimpleBanEntry implements BanEntry +{ + private final String username; + private final UUID uuid; + private final String ipv6; + private final String reason; + private final Instant issued; + private final Instant expires; + private final String issuer; + + public SimpleBanEntry(final Player target, + final CommandSender issuer, + final String reason, + final Instant issued, + final Duration duration) { + this.username = PlainTextWrapper.toPlainText(target.name()); + this.uuid = target.getUniqueId(); + if (target.getAddress() != null && target.getAddress().getAddress() instanceof Inet6Address address) + this.ipv6 = address.getHostAddress(); + else + this.ipv6 = "N/A"; + this.issued = issued; + this.expires = issued.plus(duration); + this.issuer = PlainTextWrapper.toPlainText(issuer.name()); + this.reason = reason; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public UUID getUuid() { + return this.uuid; + } + + @Override + public String getIpv6() { + return this.ipv6; + } + + @Override + public String getReason() { + return this.reason; + } + + @Override + public Instant getIssued() { + return this.issued; + } + + @Override + public Instant getExpires() { + return this.expires; + } + + @Override + public String getIssuer() { + return this.issuer; + } +} diff --git a/Fossil/src/main/java/fns/fossil/Fossil.java b/Fossil/src/main/java/fns/fossil/Fossil.java index 22157e4..9d8102a 100644 --- a/Fossil/src/main/java/fns/fossil/Fossil.java +++ b/Fossil/src/main/java/fns/fossil/Fossil.java @@ -24,6 +24,7 @@ package fns.fossil; import fns.fossil.cmd.CakeCommand; +import fns.fossil.reactions.ReactionSystem; import fns.fossil.trail.Trailer; import fns.patchwork.base.Registration; import fns.patchwork.command.CommandHandler; @@ -33,6 +34,7 @@ public class Fossil extends JavaPlugin { private final Trailer trailer = new Trailer(); + @Override public void onEnable() { diff --git a/Fossil/src/main/java/fns/fossil/reactions/CopyCatReaction.java b/Fossil/src/main/java/fns/fossil/reactions/CopyCatReaction.java index 41921bf..4c8b669 100644 --- a/Fossil/src/main/java/fns/fossil/reactions/CopyCatReaction.java +++ b/Fossil/src/main/java/fns/fossil/reactions/CopyCatReaction.java @@ -37,11 +37,17 @@ public final class CopyCatReaction extends Reaction { private final long reward; + private final BossBar bossBar; public CopyCatReaction(final long reward) { super(ReactionType.COPYCAT); this.reward = reward; + this.bossBar = BossBarDisplay.builder() + .setName(getRandomCharacterString()) + .setProgress(0.0F) + .setOverlay(BossBar.Overlay.NOTCHED_10) + .build(); } @Override @@ -53,16 +59,16 @@ public long getReward() @Override public void display(final Audience audience) { - final BossBar bossBar = BossBarDisplay.builder() - .setName(getRandomCharacterString()) - .setProgress(0.0F) - .build(); + audience.showBossBar(bossBar); } @Override public void onReact(final EconomicEntity entity) { - // + entity.getEconomicData() + .addToBalance(getReward()); + + this.cancel(); } public String getRandomCharacterString() @@ -79,4 +85,16 @@ public String getRandomCharacterString() return sb.toString(); } + + @Override + public void runTimer() + { + if (bossBar.progress() >= 1.0F) + { + this.cancel(); + return; + } + + bossBar.progress(bossBar.progress() + 0.1F); + } } diff --git a/Fossil/src/main/java/fns/fossil/reactions/ReactionSystem.java b/Fossil/src/main/java/fns/fossil/reactions/ReactionSystem.java new file mode 100644 index 0000000..726feaa --- /dev/null +++ b/Fossil/src/main/java/fns/fossil/reactions/ReactionSystem.java @@ -0,0 +1,59 @@ +/* + * 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.fossil.reactions; + +import fns.fossil.Fossil; +import fns.patchwork.base.Registration; +import fns.patchwork.base.Shortcuts; +import fns.patchwork.provider.SubscriptionProvider; +import fns.patchwork.service.Task; +import fns.patchwork.service.TaskSubscription; +import java.time.Duration; + +public class ReactionSystem +{ + public static void startCopyCat() + { + final Fossil fossil = Shortcuts.provideModule(Fossil.class); + final TaskSubscription subscription = + SubscriptionProvider.runSyncTask(fossil, new CopyCatReaction(25L)); + + Registration.getServiceTaskRegistry().registerTask(subscription); + Registration.getServiceTaskRegistry().startTask(CopyCatReaction.class); + } + + private static final class SystemTask extends Task + { + private SystemTask() + { + super("sys-task", 0L, Duration.ofMinutes(15L)); + } + + @Override + public void run() + { + ReactionSystem.startCopyCat(); + } + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/bans/BanEntry.java b/Patchwork/src/main/java/fns/patchwork/bans/BanEntry.java new file mode 100644 index 0000000..aa3cc76 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/bans/BanEntry.java @@ -0,0 +1,43 @@ +package fns.patchwork.bans; + +import java.time.Instant; +import java.util.UUID; + +public interface BanEntry +{ + /** + * @return The username of the banned player. + */ + String getUsername(); + + /** + * @return The {@link UUID} of the banned player. + */ + UUID getUuid(); + + /** + * @return Either the IPv6 address of the banned player, if applicable, + * otherwise returns {@code "N/A"}. + */ + String getIpv6(); + + /** + * @return The reason for the ban. + */ + String getReason(); + + /** + * @return The {@link Instant} the ban was issued. + */ + Instant getIssued(); + + /** + * @return The {@link Instant} the ban expires. + */ + Instant getExpires(); + + /** + * @return The username of the individual who issued the ban. Can be {@code "CONSOLE"}. + */ + String getIssuer(); +} \ No newline at end of file diff --git a/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java b/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java index 60c9b55..6d97be6 100644 --- a/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java +++ b/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java @@ -27,8 +27,12 @@ import fns.patchwork.event.EventBus; import fns.patchwork.provider.ExecutorProvider; import fns.patchwork.provider.SubscriptionProvider; +import fns.patchwork.registry.ServiceTaskRegistry; +import fns.patchwork.service.Service; +import fns.patchwork.utils.logging.FNS4J; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.ApiStatus; /** * The base class for Patchwork. @@ -48,17 +52,8 @@ public class Patchwork extends JavaPlugin */ private AdminChatDisplay acdisplay; - @Override - public void onDisable() - { - Bukkit.getScheduler() - .runTaskLater(this, () -> Registration - .getServiceTaskRegistry() - .stopAllServices(), 1L); + private static final ServiceRunner runner = new ServiceRunner(); - Registration.getServiceTaskRegistry() - .unregisterService(EventBus.class); - } @Override public void onEnable() @@ -67,16 +62,39 @@ public void onEnable() executor = new ExecutorProvider(this); acdisplay = new AdminChatDisplay(this); + Registration.getServiceTaskRegistry() + .registerService(SubscriptionProvider.asyncService(this, eventBus)); Registration.getServiceTaskRegistry() - .registerService(SubscriptionProvider.asyncService(this, eventBus)); + .registerService(SubscriptionProvider.asyncService(this, runner)); + // Will execute post-world getExecutor().getSync() - .execute(() -> Registration - .getServiceTaskRegistry() - .startAllServices()); + .execute(this::postWorld); Registration.getModuleRegistry().addModule(this); + + FNS4J.PATCHWORK.info("Successfully enabled Patchwork. API is ready to go."); + } + + @Override + public void onDisable() + { + Bukkit.getScheduler() + .runTaskLater(this, () -> Registration + .getServiceTaskRegistry() + .stopAllServices(), 1L); + + Registration.getServiceTaskRegistry() + .unregisterService(EventBus.class); + + FNS4J.PATCHWORK.info("Successfully disabled Patchwork. API is no longer available."); + } + + private void postWorld() + { + Registration.getServiceTaskRegistry() + .startAllServices(); } /** @@ -95,6 +113,7 @@ public ExecutorProvider getExecutor() * * @return the {@link EventBus} */ + @ApiStatus.Experimental public EventBus getEventBus() { return eventBus; @@ -110,4 +129,34 @@ public AdminChatDisplay getAdminChatDisplay() { return acdisplay; } + + @ApiStatus.Internal + private static final class ServiceRunner extends Service + { + public ServiceRunner() + { + super("srv-runner"); + } + + @Override + public void tick() + { + final ServiceTaskRegistry r = Registration.getServiceTaskRegistry(); + r.getServices().forEach(s -> + { + if (!s.isActive()) + { + r.unregisterService(s.getService().getClass()); + } + }); + + r.getTasks().forEach(t -> + { + if (!t.isActive()) + { + r.unregisterTask(t.getTask().getClass()); + } + }); + } + } } diff --git a/Patchwork/src/main/java/fns/patchwork/block/detector/NukerDetection.java b/Patchwork/src/main/java/fns/patchwork/block/detector/NukerDetection.java new file mode 100644 index 0000000..dc4fbb2 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/block/detector/NukerDetection.java @@ -0,0 +1,47 @@ +/* + * 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.block.detector; + +import fns.patchwork.block.logger.TimedBlockLogger; +import java.util.Set; +import org.bukkit.entity.Player; + +public interface NukerDetection +{ + Set getTimedBlockLoggers(); + + boolean isNuking(); + + void addTimedBlockLogger(TimedBlockLogger timedBlockLogger); + + void removeTimedBlockLogger(TimedBlockLogger timedBlockLogger); + + default void ejectPlayer(final Player player) { + if (isNuking()) { + getTimedBlockLoggers().forEach(l -> { + + }); + } + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/block/logger/BlockLogger.java b/Patchwork/src/main/java/fns/patchwork/block/logger/BlockLogger.java new file mode 100644 index 0000000..3071dbe --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/block/logger/BlockLogger.java @@ -0,0 +1,51 @@ +/* + * 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.block.logger; + +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +public interface BlockLogger +{ + UUID getUUID(); + + int getEditedBlockCount(); + + void incrementBlockCount(); + + void decrementBlockCount(); + + boolean greaterThan(final int p0); + + default boolean isPlayer() { + return Bukkit.getPlayer(this.getUUID()) != null; + } + + default boolean isTNT() { + final Entity entity = Bukkit.getEntity(this.getUUID()); + return entity != null && entity.getType() == EntityType.PRIMED_TNT; + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/block/logger/TimedBlockLogger.java b/Patchwork/src/main/java/fns/patchwork/block/logger/TimedBlockLogger.java new file mode 100644 index 0000000..f373c01 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/block/logger/TimedBlockLogger.java @@ -0,0 +1,28 @@ +/* + * 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.block.logger; + +public interface TimedBlockLogger extends BlockLogger +{ +} diff --git a/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java deleted file mode 100644 index 410e791..0000000 --- a/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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/provider/SubscriptionProvider.java b/Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java index c15bf5c..aeced8b 100644 --- a/Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java +++ b/Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java @@ -54,8 +54,8 @@ private SubscriptionProvider() * @return The new {@link ServiceSubscription} object. */ @NotNull - @Contract(value = "_, _ -> new", pure = false) - public static final ServiceSubscription syncService(@NotNull final JavaPlugin plugin, + @Contract(value = "_, _ -> new") + public static ServiceSubscription syncService(@NotNull final JavaPlugin plugin, @NotNull final S service) { return new ServiceSubscription<>(plugin, service); @@ -73,7 +73,7 @@ public static final ServiceSubscription syncService(@NotN */ @NotNull @Contract(value = "_,_,_ -> new", pure = false) - public static final ServiceSubscription syncService(@NotNull final JavaPlugin plugin, + public static ServiceSubscription syncService(@NotNull final JavaPlugin plugin, final long interval, @NotNull final S service) { @@ -91,7 +91,7 @@ public static final ServiceSubscription syncService(@NotN */ @NotNull @Contract(value = "_, _ -> new", pure = false) - public static final ServiceSubscription asyncService(@NotNull final JavaPlugin plugin, + public static ServiceSubscription asyncService(@NotNull final JavaPlugin plugin, @NotNull final S service) { return new ServiceSubscription<>(plugin, service, true); @@ -109,7 +109,7 @@ public static final ServiceSubscription asyncService(@Not */ @NotNull @Contract(value = "_,_,_ -> new", pure = false) - public static final ServiceSubscription asyncService(@NotNull final JavaPlugin plugin, + public static ServiceSubscription asyncService(@NotNull final JavaPlugin plugin, final long interval, @NotNull final S service) { @@ -127,7 +127,7 @@ public static final ServiceSubscription asyncService(@Not */ @NotNull @Contract(value = "_, _ -> new", pure = false) - public static final TaskSubscription runSyncTask(@NotNull final JavaPlugin plugin, + public static TaskSubscription runSyncTask(@NotNull final JavaPlugin plugin, @NotNull final T task) { return new TaskSubscription<>(plugin, task, false); @@ -144,7 +144,7 @@ public static final TaskSubscription runSyncTask(@NotNull fi */ @NotNull @Contract(value = "_, _ -> new", pure = false) - public static final TaskSubscription runAsyncTask(@NotNull final JavaPlugin plugin, + public static TaskSubscription runAsyncTask(@NotNull final JavaPlugin plugin, @NotNull final T task) { return new TaskSubscription<>(plugin, task, true); diff --git a/Patchwork/src/main/java/fns/patchwork/registry/BanRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/BanRegistry.java new file mode 100644 index 0000000..9bc6bf7 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/registry/BanRegistry.java @@ -0,0 +1,57 @@ +/* + * 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.registry; + +import fns.patchwork.bans.BanEntry; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +public class BanRegistry +{ + private final Set loadedBans = new HashSet<>(); + + public Optional getBan(final UUID uuid) + { + return loadedBans.stream() + .filter(b -> b.getUuid().equals(uuid)) + .findFirst(); + } + + public void addBan(final BanEntry entry) + { + this.loadedBans.add(entry); + } + + public void removeBan(final BanEntry entry) + { + this.loadedBans.remove(entry); + } + + public void clearLocalStorage() + { + this.loadedBans.clear(); + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java index 00d7ffa..a343a7a 100644 --- a/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java @@ -23,13 +23,16 @@ package fns.patchwork.registry; +import fns.patchwork.base.Registration; +import fns.patchwork.provider.SubscriptionProvider; import fns.patchwork.service.Service; import fns.patchwork.service.ServiceSubscription; -import fns.patchwork.provider.SubscriptionProvider; import fns.patchwork.service.Task; import fns.patchwork.service.TaskSubscription; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; /** @@ -195,8 +198,18 @@ public void registerTask(final TaskSubscription task) */ public void startService(final Class clazz) { - this.getService(clazz) - .start(); + Objects.requireNonNull(this.getService(clazz)) + .start(); + } + + @ApiStatus.Internal + public List> getServices() { + return services; + } + + @ApiStatus.Internal + public List> getTasks() { + return tasks; } /** @@ -214,13 +227,12 @@ public void startService(final Class clazz) * @see ServiceSubscription */ @Nullable + @SuppressWarnings("unchecked") public ServiceSubscription getService(final Class clazz) { for (final ServiceSubscription service : this.services) { - if (service.getService() - .getClass() - .equals(clazz)) + if (clazz.isInstance(service.getService())) { return (ServiceSubscription) service; } @@ -239,8 +251,8 @@ public ServiceSubscription getService(final Class claz */ public void stopService(final Class clazz) { - this.getService(clazz) - .stop(); + Objects.requireNonNull(this.getService(clazz)) + .stop(); } /** @@ -254,8 +266,8 @@ public void stopService(final Class clazz) */ public void startTask(final Class clazz) { - this.getTask(clazz) - .start(); + Objects.requireNonNull(this.getTask(clazz)) + .start(); } /** @@ -272,13 +284,11 @@ public void startTask(final Class clazz) * @see #registerTask(TaskSubscription) * @see TaskSubscription */ - public TaskSubscription getTask(final Class clazz) + public @Nullable TaskSubscription getTask(final Class clazz) { for (final TaskSubscription task : this.tasks) { - if (task.getTask() - .getClass() - .equals(clazz)) + if (clazz.isInstance(task.getTask())) { return (TaskSubscription) task; } @@ -297,8 +307,8 @@ public TaskSubscription getTask(final Class clazz) */ public void stopTask(final Class clazz) { - this.getTask(clazz) - .stop(); + Objects.requireNonNull(this.getTask(clazz)) + .stop(); } /** diff --git a/Patchwork/src/main/java/fns/patchwork/service/BukkitTimer.java b/Patchwork/src/main/java/fns/patchwork/service/BukkitTimer.java new file mode 100644 index 0000000..2c6e0b8 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/service/BukkitTimer.java @@ -0,0 +1,42 @@ +/* + * 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.service; + +import java.time.Duration; +import java.time.Instant; +import java.util.Comparator; + +public class BukkitTimer extends TimedTask +{ + protected BukkitTimer(String name, Duration interval, Duration timeout) + { + super(name, interval, timeout); + } + + @Override + protected void runTimer() + { + + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/service/ServiceSubscription.java b/Patchwork/src/main/java/fns/patchwork/service/ServiceSubscription.java index ef178d5..0406bbb 100644 --- a/Patchwork/src/main/java/fns/patchwork/service/ServiceSubscription.java +++ b/Patchwork/src/main/java/fns/patchwork/service/ServiceSubscription.java @@ -27,6 +27,7 @@ import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; /** @@ -61,11 +62,6 @@ public final class ServiceSubscription */ private final int serviceId; - /** - * Whether the service is currently running. - */ - private boolean isActive = false; - /** * Creates a new subscription for the given service. By default, this method will mark this service as a synchronous * service. This will also initialize the default interval to a single tick. @@ -79,7 +75,8 @@ public final class ServiceSubscription * @param plugin The plugin which owns the service. * @param service The service to subscribe to. */ - ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service) + @ApiStatus.Internal + public ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service) { this(plugin, service, 1L, false); } @@ -94,7 +91,8 @@ public final class ServiceSubscription * @param service The service to subscribe to. * @param async Whether the service should be scheduled asynchronously. */ - ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final boolean async) + @ApiStatus.Internal + public ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final boolean async) { this(plugin, service, 1L, async); } @@ -111,7 +109,8 @@ public final class ServiceSubscription * @param service The service to subscribe to. * @param interval The interval at which the service should be scheduled. */ - ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final long interval) + @ApiStatus.Internal + public ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, final long interval) { this(plugin, service, interval, false); } @@ -126,8 +125,9 @@ public final class ServiceSubscription * @param interval The interval at which the service should be scheduled. * @param async Whether the service should be scheduled asynchronously. */ - ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, - final long interval, final boolean async) + @ApiStatus.Internal + public ServiceSubscription(@NotNull final JavaPlugin plugin, @NotNull final T service, + final long interval, final boolean async) { this.service = service; this.async = async; @@ -142,7 +142,8 @@ public final class ServiceSubscription .runTaskTimerAsynchronously(plugin, r, 0, interval); tempId[0] = task.getTaskId(); }; - } else + } + else { this.executor = r -> { @@ -160,7 +161,6 @@ public final class ServiceSubscription */ public void start() { - this.isActive = true; this.executor.execute(service::tick); } @@ -169,7 +169,6 @@ public void start() */ public void stop() { - this.isActive = false; Bukkit.getScheduler() .cancelTask(this.getServiceId()); } @@ -206,6 +205,9 @@ public boolean isAsync() */ public boolean isActive() { - return isActive; + return Bukkit.getScheduler() + .isQueued(this.getServiceId()) || + Bukkit.getScheduler() + .isCurrentlyRunning(this.getServiceId()); } } diff --git a/Patchwork/src/main/java/fns/patchwork/service/TaskSubscription.java b/Patchwork/src/main/java/fns/patchwork/service/TaskSubscription.java index 81aac2e..11388e2 100644 --- a/Patchwork/src/main/java/fns/patchwork/service/TaskSubscription.java +++ b/Patchwork/src/main/java/fns/patchwork/service/TaskSubscription.java @@ -29,6 +29,7 @@ import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.ApiStatus; /** * Represents a subscription to a task. Task subscriptions offer a nice wrapper for managing tasks, which are inevitably @@ -57,12 +58,6 @@ public final class TaskSubscription */ private final Executor executor; - /** - * True if the task is active, false otherwise. By default, this is set to false, and will be marked as true when - * the task is started. - */ - private boolean isActive = false; - /** * Creates a new task subscription. * @@ -70,7 +65,8 @@ public final class TaskSubscription * @param task The task that is being subscribed to. * @param async True if the task is async, false otherwise. */ - TaskSubscription(final JavaPlugin plugin, final T task, final boolean async) + @ApiStatus.Internal + public TaskSubscription(final JavaPlugin plugin, final T task, final boolean async) { this.task = task; this.async = async; @@ -184,7 +180,6 @@ private Pair getSync(final JavaPlugin plugin, final long dela */ public void start() { - this.isActive = true; executor.execute(task); } @@ -193,7 +188,6 @@ public void start() */ public void stop() { - this.isActive = false; Bukkit.getScheduler() .cancelTask(this.getTaskId()); } @@ -235,6 +229,8 @@ public Executor getExecutor() */ public boolean isActive() { - return isActive; + return !this.getTask().isCancelled() || + !Bukkit.getScheduler().isQueued(this.getTaskId()) && + !Bukkit.getScheduler().isCurrentlyRunning(this.getTaskId()); } } diff --git a/Patchwork/src/main/java/fns/patchwork/service/TimedTask.java b/Patchwork/src/main/java/fns/patchwork/service/TimedTask.java new file mode 100644 index 0000000..810111b --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/service/TimedTask.java @@ -0,0 +1,55 @@ +/* + * 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.service; + +import java.time.Duration; + +public abstract class TimedTask extends Task +{ + private final Duration timeout; + private long currentTimeSeconds = 0; + + protected TimedTask(final String name, final Duration interval, final Duration timeout) + { + super(name, 0, interval); + this.timeout = timeout; + } + + protected abstract void runTimer(); + + @Override + public void run() + { + if (this.currentTimeSeconds == 0) + this.currentTimeSeconds = System.currentTimeMillis() / 1000L; + + if (System.currentTimeMillis() / 1000L - this.currentTimeSeconds >= this.timeout.getSeconds()) + { + this.cancel(); + return; + } + + this.runTimer(); + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/shop/Reaction.java b/Patchwork/src/main/java/fns/patchwork/shop/Reaction.java index 60480a6..08f3baa 100644 --- a/Patchwork/src/main/java/fns/patchwork/shop/Reaction.java +++ b/Patchwork/src/main/java/fns/patchwork/shop/Reaction.java @@ -23,13 +23,17 @@ package fns.patchwork.shop; +import fns.patchwork.service.Service; +import fns.patchwork.service.Task; +import fns.patchwork.service.TimedTask; import java.time.Duration; import net.kyori.adventure.text.Component; +import org.bukkit.event.Listener; /** * Represents a chat reaction that can be performed by a player. */ -public abstract class Reaction implements Reactable +public abstract class Reaction extends TimedTask implements Reactable { private final Duration reactionDuration; private final ReactionType reactionType; @@ -53,6 +57,9 @@ protected Reaction(final long seconds, final long reward, final ReactionType rea protected Reaction(final Duration duration, final long reward, final ReactionType reactionType) { + super("CopyCatReaction", + Duration.ofSeconds(1), + Duration.ofSeconds(10)); this.reward = reward; this.reactionDuration = duration; this.reactionType = reactionType; diff --git a/Tyr/src/main/java/fns/tyr/data/SQLEntry.java b/Tyr/src/main/java/fns/tyr/data/SQLEntry.java index 9347c75..ffc6e4c 100644 --- a/Tyr/src/main/java/fns/tyr/data/SQLEntry.java +++ b/Tyr/src/main/java/fns/tyr/data/SQLEntry.java @@ -26,7 +26,6 @@ import fns.patchwork.base.Shortcuts; import fns.patchwork.utils.logging.FNS4J; import fns.tyr.oauth.Identity; -import java.sql.SQLException; public class SQLEntry { @@ -45,29 +44,22 @@ public static SQLEntry load(final String username) .thenApplyAsync(result -> { SQLEntry entry = null; - try + if (result.hasNext()) { - if (result.next()) - { - final String user = result.getString("user"); - final String secretKey = result.getString("secretKey"); + final String user = result.getString("user"); + final String secretKey = result.getString("secretKey"); - final Identity i = new Identity(user, secretKey); + final Identity i = new Identity(user, secretKey); - entry = new SQLEntry(i); - FNS4J.getLogger("Tyr") - .info("Loaded entry for " + username); - } - else - { - entry = new SQLEntry(Identity.of(username)); - FNS4J.getLogger("Tyr") - .info("Created a new entry for " + username); - } + entry = new SQLEntry(i); + FNS4J.getLogger("Tyr") + .info("Loaded entry for " + username); } - catch (SQLException ex) + else { - FNS4J.getLogger("Tyr").error(ex.getMessage()); + entry = new SQLEntry(Identity.of(username)); + FNS4J.getLogger("Tyr") + .info("Created a new entry for " + username); } return entry; }, Shortcuts.getExecutors() diff --git a/build.gradle b/build.gradle index 2d97049..dd20d62 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ subprojects { apply plugin: 'java-library' apply plugin: 'net.minecrell.plugin-yml.bukkit' + repositories { jcenter() mavenCentral()