From 3f8830dce767eca82425a73456027078a7a5f812 Mon Sep 17 00:00:00 2001 From: skidoodle Date: Wed, 13 Mar 2024 01:05:21 +0100 Subject: [PATCH] init: --- .gitignore | 30 ++ README.md | 2 + pom.xml | 83 +++++ src/main/java/ee/yoursit/core/Main.java | 15 + src/main/java/ee/yoursit/core/YsmpCore.java | 119 +++++++ .../core/command/bukkit/api/Argument.java | 145 ++++++++ .../core/command/bukkit/api/Command.java | 30 ++ .../command/bukkit/api/CommandContext.java | 147 ++++++++ .../command/bukkit/api/CommandException.java | 22 ++ .../core/command/bukkit/api/CommandInfo.java | 16 + .../core/command/bukkit/api/Completion.java | 46 +++ .../bukkit/impl/admin/GroupCommand.java | 322 +++++++++++++++++ .../impl/admin/group/CreateArgument.java | 54 +++ .../impl/admin/group/DeleteArgument.java | 52 +++ .../bukkit/impl/admin/group/ListArgument.java | 41 +++ .../bukkit/impl/home/DelHomeCommand.java | 35 ++ .../command/bukkit/impl/home/HomeCommand.java | 42 +++ .../bukkit/impl/home/SetHomeCommand.java | 35 ++ .../bukkit/impl/invite/VerifyCommand.java | 100 ++++++ .../bukkit/impl/message/MessageCommand.java | 56 +++ .../bukkit/impl/message/ReplyCommand.java | 51 +++ .../bukkit/impl/teleport/TeleportAccept.java | 84 +++++ .../bukkit/impl/teleport/TeleportDeny.java | 82 +++++ .../bukkit/impl/teleport/TeleportRequest.java | 55 +++ .../core/command/discord/BanCommand.java | 119 +++++++ .../core/command/discord/InviteCommand.java | 138 ++++++++ .../core/command/discord/PlayersCommand.java | 40 +++ .../core/command/discord/SmpCommand.java | 140 ++++++++ .../core/command/discord/UnbanCommand.java | 68 ++++ .../core/command/discord/VerifyCommand.java | 118 +++++++ .../java/ee/yoursit/core/config/Config.java | 329 ++++++++++++++++++ .../java/ee/yoursit/core/config/Messages.java | 148 ++++++++ .../ee/yoursit/core/data/DataManager.java | 49 +++ .../java/ee/yoursit/core/data/PlayerData.java | 167 +++++++++ .../ee/yoursit/core/database/Database.java | 39 +++ .../core/database/entity/api/Entity.java | 11 + .../database/entity/api/EntityProperty.java | 77 ++++ .../database/entity/api/EntitySerializer.java | 142 ++++++++ .../entity/api/ListEntityProperty.java | 55 +++ .../entity/api/MapEntityProperty.java | 41 +++ .../database/entity/api/MutateEntity.java | 4 + .../core/database/entity/impl/Ban.java | 42 +++ .../core/database/entity/impl/Group.java | 42 +++ .../core/database/entity/impl/Invite.java | 34 ++ .../core/database/entity/impl/User.java | 55 +++ .../database/entity/impl/Verification.java | 43 +++ .../core/database/service/BanService.java | 45 +++ .../core/database/service/GroupService.java | 82 +++++ .../core/database/service/InviteService.java | 31 ++ .../core/database/service/UserService.java | 45 +++ .../database/service/VerificationService.java | 68 ++++ .../core/module/BukkitCommandRegistry.java | 72 ++++ .../yoursit/core/module/BukkitListener.java | 170 +++++++++ .../yoursit/core/module/CommandRegistry.java | 7 + .../core/module/DiscordCommandRegistry.java | 91 +++++ .../yoursit/core/module/DiscordListener.java | 64 ++++ .../ee/yoursit/core/util/ClickAction.java | 9 + .../java/ee/yoursit/core/util/Constants.java | 16 + .../ee/yoursit/core/util/HoverAction.java | 8 + .../ee/yoursit/core/util/InviteGenerator.java | 32 ++ .../java/ee/yoursit/core/util/Message.java | 79 +++++ src/main/resources/config.yml | 23 ++ src/main/resources/messages.yml | 107 ++++++ src/main/resources/plugin.yml | 38 ++ src/test/java/GeneratorTest.java | 9 + 65 files changed, 4561 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/ee/yoursit/core/Main.java create mode 100644 src/main/java/ee/yoursit/core/YsmpCore.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/api/Argument.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/api/Command.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/api/CommandContext.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/api/CommandException.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/api/CommandInfo.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/api/Completion.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/admin/GroupCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/CreateArgument.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/DeleteArgument.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/ListArgument.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/home/DelHomeCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/home/HomeCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/home/SetHomeCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/invite/VerifyCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/message/MessageCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/message/ReplyCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportAccept.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportDeny.java create mode 100644 src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportRequest.java create mode 100644 src/main/java/ee/yoursit/core/command/discord/BanCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/discord/InviteCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/discord/PlayersCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/discord/SmpCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/discord/UnbanCommand.java create mode 100644 src/main/java/ee/yoursit/core/command/discord/VerifyCommand.java create mode 100644 src/main/java/ee/yoursit/core/config/Config.java create mode 100644 src/main/java/ee/yoursit/core/config/Messages.java create mode 100644 src/main/java/ee/yoursit/core/data/DataManager.java create mode 100644 src/main/java/ee/yoursit/core/data/PlayerData.java create mode 100644 src/main/java/ee/yoursit/core/database/Database.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/api/Entity.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/api/EntityProperty.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/api/EntitySerializer.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/api/ListEntityProperty.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/api/MapEntityProperty.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/api/MutateEntity.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/impl/Ban.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/impl/Group.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/impl/Invite.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/impl/User.java create mode 100644 src/main/java/ee/yoursit/core/database/entity/impl/Verification.java create mode 100644 src/main/java/ee/yoursit/core/database/service/BanService.java create mode 100644 src/main/java/ee/yoursit/core/database/service/GroupService.java create mode 100644 src/main/java/ee/yoursit/core/database/service/InviteService.java create mode 100644 src/main/java/ee/yoursit/core/database/service/UserService.java create mode 100644 src/main/java/ee/yoursit/core/database/service/VerificationService.java create mode 100644 src/main/java/ee/yoursit/core/module/BukkitCommandRegistry.java create mode 100644 src/main/java/ee/yoursit/core/module/BukkitListener.java create mode 100644 src/main/java/ee/yoursit/core/module/CommandRegistry.java create mode 100644 src/main/java/ee/yoursit/core/module/DiscordCommandRegistry.java create mode 100644 src/main/java/ee/yoursit/core/module/DiscordListener.java create mode 100644 src/main/java/ee/yoursit/core/util/ClickAction.java create mode 100644 src/main/java/ee/yoursit/core/util/Constants.java create mode 100644 src/main/java/ee/yoursit/core/util/HoverAction.java create mode 100644 src/main/java/ee/yoursit/core/util/InviteGenerator.java create mode 100644 src/main/java/ee/yoursit/core/util/Message.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/messages.yml create mode 100644 src/main/resources/plugin.yml create mode 100644 src/test/java/GeneratorTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88f90d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# Virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +# IntelliJ files +/YourSiteeCore.iml +/target/ +/.idea/ +/dependency-reduced-pom.xml diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4c9e68 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ysmp-core +A core plugin for the YourSitee SMP Minecraft server. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4bfd2bb --- /dev/null +++ b/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + dev.inventex.yoursitee.core + YourSiteeCore + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + + + + + org.spigotmc + spigot-api + 1.20.1-R0.1-SNAPSHOT + provided + + + + org.jetbrains + annotations + 24.0.1 + + + + org.projectlombok + lombok + 1.18.28 + provided + + + + org.mongodb + mongo-java-driver + 3.12.14 + + + dev.inventex.octa + Octa + 1.0-SNAPSHOT + + + dev.inventex.discord + DiscordClient + 1.0-SNAPSHOT + + + + + + ysmp-core + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + + + \ No newline at end of file diff --git a/src/main/java/ee/yoursit/core/Main.java b/src/main/java/ee/yoursit/core/Main.java new file mode 100644 index 0000000..fe5e037 --- /dev/null +++ b/src/main/java/ee/yoursit/core/Main.java @@ -0,0 +1,15 @@ +package ee.yoursit.core; + +import org.bukkit.plugin.java.JavaPlugin; + +public class Main extends JavaPlugin { + @Override + public void onEnable() { + YsmpCore.INSTANCE.enable(this); + } + + @Override + public void onDisable() { + YsmpCore.INSTANCE.disable(); + } +} diff --git a/src/main/java/ee/yoursit/core/YsmpCore.java b/src/main/java/ee/yoursit/core/YsmpCore.java new file mode 100644 index 0000000..79fa531 --- /dev/null +++ b/src/main/java/ee/yoursit/core/YsmpCore.java @@ -0,0 +1,119 @@ +package ee.yoursit.core; + +import dev.inventex.discord.DiscordClient; +import dev.inventex.discord.DiscordClientBuilder; +import ee.yoursit.core.config.Config; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.DataManager; +import ee.yoursit.core.data.PlayerData; +import ee.yoursit.core.database.Database; +import ee.yoursit.core.database.service.*; +import ee.yoursit.core.module.*; +import lombok.Getter; +import net.dv8tion.jda.api.requests.GatewayIntent; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.security.auth.login.LoginException; + +@Getter +public enum YsmpCore { + INSTANCE; + + private JavaPlugin plugin; + + private Config config, messages; + private DataManager dataManager; + private Database database; + + private Location spawnLoc, verifyLoc; + + private UserService userService; + private GroupService groupService; + private InviteService inviteService; + private BanService banService; + private VerificationService verificationService; + + private CommandRegistry bukkitCommands, discordCommands; + + private DiscordClient client; + + public void enable(JavaPlugin plugin) { + this.plugin = plugin; + + loadConfig(); + loadDatabase(); + + createDiscordClient(); + + loadCommands(); + + Bukkit + .getPluginManager() + .registerEvents(new BukkitListener(), plugin); + } + + private void loadConfig() { + plugin.getDataFolder().mkdir(); + + config = new Config("config"); + messages = new Config("messages"); + + config.load(); + messages.load(); + + Messages.init(messages); + + Bukkit + .getScheduler() + .runTask(plugin, () -> { + spawnLoc = config.getLocation("spawn"); + verifyLoc = config.getLocation("verify"); + }); + } + + private void loadDatabase() { + dataManager = new DataManager(); + database = new Database("127.0.0.1", 27017); + database.connect(); + + userService = new UserService(); + groupService = new GroupService(); + groupService.loadGroups(); + inviteService = new InviteService(); + banService = new BanService(); + verificationService = new VerificationService(); + } + + private void loadCommands() { + bukkitCommands = new BukkitCommandRegistry(); + bukkitCommands.load(); + + discordCommands = new DiscordCommandRegistry(); + } + + private void createDiscordClient() { + try { + String token = config.getString("bot-token"); + client = new DiscordClientBuilder(token) + .useMembers() + .useRoles() + .enableIntents(GatewayIntent.MESSAGE_CONTENT) + .build(); + client.registerEvents(new DiscordListener()); + } catch (LoginException e) { + System.err.println("Unable to create discord client"); + e.printStackTrace(); + } + } + + public void disable() { + database.disconnect(); + + bukkitCommands.unload(); + discordCommands.unload(); + + client.getJda().shutdown(); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/api/Argument.java b/src/main/java/ee/yoursit/core/command/bukkit/api/Argument.java new file mode 100644 index 0000000..766f81d --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/api/Argument.java @@ -0,0 +1,145 @@ +package ee.yoursit.core.command.bukkit.api; + +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +@Getter +public class Argument { + @NotNull + private final String name; + + @NotNull + private final String[] aliases; + + private final boolean consoleAllowed; + + @NotNull + private final List subArguments = new ArrayList<>(); + + public Argument() { + CommandInfo info = getClass().getAnnotation(CommandInfo.class); + if (info == null) + throw new IllegalStateException(getClass().getName() + " is not annotated with " + + CommandInfo.class); + name = info.name(); + aliases = info.aliases(); + consoleAllowed = info.consoleAllowed(); + create(); + } + + public void create() { + } + + public void onCommand(@NotNull CommandContext context) throws Exception { + } + + public Completion onTabComplete(@NotNull CommandContext context) throws Exception { + return Completion.EMPTY; + } + + public void onCommandError(@NotNull CommandContext context, @NotNull Throwable error) { + throw new CommandException("Error whilst handling command execution", error); + } + + public Completion onTabCompleteError(@NotNull CommandContext context, @NotNull Throwable error) { + throw new CommandException("Error whilst handling command tab completion", error); + } + + private void executeCommand(@NotNull CommandContext context) { + try { + onCommand(context); + } catch (Exception e) { + onCommandError(context, e); + } + } + + private Completion executeTabCompletion(@NotNull CommandContext context) { + try { + return onTabComplete(context); + } catch (Exception e) { + return onTabCompleteError(context, e); + } + } + + public final void addArgument(@NotNull Argument argument) { + subArguments.add(argument); + } + + public final void execute(@NotNull CommandSender sender, @NotNull String[] args) { + if (!(sender instanceof Player) && !consoleAllowed) { + sender.sendMessage(ChatColor.RED + "Console is not allowed to execute this command."); + return; + } + + if (args.length == 0 || subArguments.isEmpty()) { + executeCommand(new CommandContext(sender, args)); + return; + } + + String name = args[0]; + for (Argument argument : subArguments) { + if (!argument.test(name)) + continue; + + String[] subArgs = Arrays.copyOfRange(args, 1, args.length); + argument.execute(sender, subArgs); + return; + } + + executeCommand(new CommandContext(sender, args)); + } + + public final Completion complete(@NotNull CommandSender sender, @NotNull String[] args) { + if (subArguments.isEmpty()) + return executeTabCompletion(new CommandContext(sender, args)); + + if (args.length == 0) { + Stream stream = subArguments + .stream() + .map(Argument::getName); + return Completion.of(stream); + } + + else if (args.length == 1) { + String prefix = args[0].toLowerCase(); + Stream stream = subArguments + .stream() + .map(Argument::getName) + .filter(name -> name.startsWith(prefix)); + return Completion.of(stream); + } + + else { + String name = args[0].toLowerCase(); + for (Argument argument : subArguments) { + if (!argument.test(name)) + continue; + + String[] subArgs = Arrays.copyOfRange(args, 1, args.length); + return argument.complete(sender, subArgs); + } + } + + return executeTabCompletion(new CommandContext(sender, args)); + } + + public final boolean test(@NotNull String name) { + if (name.equals(this.name)) + return true; + + for (String alias : aliases) { + if (name.equals(alias)) + return true; + } + + return false; + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/api/Command.java b/src/main/java/ee/yoursit/core/command/bukkit/api/Command.java new file mode 100644 index 0000000..56abcd3 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/api/Command.java @@ -0,0 +1,30 @@ +package ee.yoursit.core.command.bukkit.api; + +import ee.yoursit.core.YsmpCore; +import org.bukkit.command.PluginCommand; + +import java.util.Arrays; + +public class Command extends Argument { + public final void register() { + PluginCommand command = YsmpCore.INSTANCE.getPlugin().getCommand(getName()); + if (command == null) + throw new IllegalStateException("Command '" + getName() + "' is not registered in the plugin.yml"); + + command.setExecutor((sender, cmd, label, args) -> { + execute(sender, args); + return true; + }); + command.setTabCompleter((sender, cmd, label, args) -> complete(sender, args).getData()); + command.setAliases(Arrays.asList(getAliases())); + } + + public final void unregister() { + PluginCommand command = YsmpCore.INSTANCE.getPlugin().getCommand(getName()); + if (command == null) + throw new IllegalStateException("Command '" + getName() + "' is not registered in the plugin.yml"); + + command.setExecutor(null); + command.setTabCompleter(null); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/api/CommandContext.java b/src/main/java/ee/yoursit/core/command/bukkit/api/CommandContext.java new file mode 100644 index 0000000..578c21f --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/api/CommandContext.java @@ -0,0 +1,147 @@ +package ee.yoursit.core.command.bukkit.api; + +import dev.inventex.octa.data.convertible.Convertible; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@AllArgsConstructor +public class CommandContext { + @NotNull + @Getter + private final CommandSender sender; + + @NotNull + private final String[] args; + + public boolean isPlayer() { + return sender instanceof Player; + } + + @NotNull + public Player getPlayer() { + if (!isPlayer()) + throw new IllegalStateException("CommandSender is not a player"); + return (Player) sender; + } + + public boolean isEmpty() { + return args.length == 0; + } + + public int size() { + return args.length; + } + + @NotNull + public String[] getData() { + return args; + } + + @Nullable + public String getStringRaw(int index) { + return index >= 0 && index < args.length + ? args[index] + : null; + } + + @NotNull + public Convertible getString(int index) { + return index >= 0 && index < args.length + ? Convertible.completed(args[index]) + : Convertible.empty(); + } + + @NotNull + public Convertible getInt(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], Integer::parseInt) + : Convertible.empty(); + } + + @NotNull + public Convertible getFloat(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], Float::parseFloat) + : Convertible.empty(); + } + + @NotNull + public Convertible getDouble(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], Double::parseDouble) + : Convertible.empty(); + } + + @NotNull + public Convertible getBoolean(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], value -> switch (value.toLowerCase()) { + case "enable", "allow", "true", "yes", "on", "y" -> true; + default -> false; + }) + : Convertible.empty(); + } + + @NotNull + public Convertible getPlayer(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], Bukkit::getPlayer) + : Convertible.empty(); + } + + @NotNull + public Convertible getPlayerExact(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], Bukkit::getPlayerExact) + : Convertible.empty(); + } + + @NotNull + public Convertible getWorld(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], Bukkit::getWorld) + : Convertible.empty(); + } + + public Convertible getMaterial(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], s -> Material.getMaterial(s.toUpperCase())) + : Convertible.empty(); + } + + public Convertible getPotionEffectByName(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], s -> PotionEffectType.getByName(s.toUpperCase())) + : Convertible.empty(); + } + + public Convertible getPotionEffectById(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], s -> PotionEffectType.getById(Integer.parseInt(s))) + : Convertible.empty(); + } + + public Convertible getPotionEffect(int index) { + return index >= 0 && index < args.length + ? Convertible.of(args[index], s -> { + PotionEffectType type = PotionEffectType.getByName(s.toUpperCase()); + if (type != null) + return type; + + return PotionEffectType.getById(Integer.parseInt(s)); + }) + : Convertible.empty(); + } + + public void reply(String message) { + sender.sendMessage(message); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/api/CommandException.java b/src/main/java/ee/yoursit/core/command/bukkit/api/CommandException.java new file mode 100644 index 0000000..77270c9 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/api/CommandException.java @@ -0,0 +1,22 @@ +package ee.yoursit.core.command.bukkit.api; + +public class CommandException extends RuntimeException { + public CommandException() { + } + + public CommandException(String message) { + super(message); + } + + public CommandException(String message, Throwable cause) { + super(message, cause); + } + + public CommandException(Throwable cause) { + super(cause); + } + + public CommandException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/api/CommandInfo.java b/src/main/java/ee/yoursit/core/command/bukkit/api/CommandInfo.java new file mode 100644 index 0000000..fdc9718 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/api/CommandInfo.java @@ -0,0 +1,16 @@ +package ee.yoursit.core.command.bukkit.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface CommandInfo { + String name(); + + String[] aliases() default {}; + + boolean consoleAllowed() default true; +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/api/Completion.java b/src/main/java/ee/yoursit/core/command/bukkit/api/Completion.java new file mode 100644 index 0000000..1dddee2 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/api/Completion.java @@ -0,0 +1,46 @@ +package ee.yoursit.core.command.bukkit.api; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@AllArgsConstructor +public class Completion { + public final static Completion EMPTY = of(Collections.emptyList()); + + public final static Completion PLAYERS = of((List) null); + + @Nullable + @Getter + private final List data; + + public Completion push(@NotNull String element) { + assert data != null : "Cannot push element to PLAYER_LIST completion"; + data.add(element); + return this; + } + + public static Completion of(List data) { + return new Completion(data != null ? new ArrayList<>(data) : null); + } + + public static Completion of(String... data) { + return new Completion(new ArrayList<>(Arrays.asList(data))); + } + + public static Completion of(Stream data) { + return new Completion(data != null ? data.collect(Collectors.toList()) : null); + } + + public static Completion empty() { + return new Completion(new ArrayList<>()); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/GroupCommand.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/GroupCommand.java new file mode 100644 index 0000000..dcccf13 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/GroupCommand.java @@ -0,0 +1,322 @@ +package ee.yoursit.core.command.bukkit.impl.admin; + +import dev.inventex.octa.concurrent.future.Future; +import dev.inventex.octa.data.convertible.Convertible; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.command.bukkit.api.Completion; +import ee.yoursit.core.command.bukkit.impl.admin.group.CreateArgument; +import ee.yoursit.core.command.bukkit.impl.admin.group.DeleteArgument; +import ee.yoursit.core.command.bukkit.impl.admin.group.ListArgument; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.database.entity.impl.Group; +import ee.yoursit.core.database.entity.impl.User; +import ee.yoursit.core.util.Message; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@CommandInfo(name = "group") +public class GroupCommand extends Command { + @Override + public void create() { + addArgument(new CreateArgument()); + addArgument(new DeleteArgument()); + addArgument(new ListArgument()); + } + + // TODO reword command system, so I don't need to do these manually >:( + + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + if (!(context.getSender().hasPermission("ysmp.admin"))) { + context.reply(Messages.NO_PERMISSION); + return; + } + + Convertible groupArg = context.getString(0); + if (groupArg.isEmpty()) { + context.reply(Messages.GROUP_USAGE); + return; + } + + String name = groupArg.get(); + YsmpCore.INSTANCE + .getGroupService() + .getGroup(name) + .tryThen(group -> handleGroup(context, group)) + .except(err -> + context.reply(Messages.GROUP_NOT_EXISTS + .replace("{group}", name))); + } + + private void handleGroup(CommandContext context, Group group) throws Exception { + Convertible commandArg = context.getString(1); + if (commandArg.isEmpty()) { + context.reply(Messages.GROUP_USAGE); + return; + } + + switch (commandArg.get()) { + case "meta" -> handleGroupMeta(context, group); + case "permission" -> handleGroupPermission(context, group); + case "grant" -> handleGroupGrant(context, group); + default -> context.reply(Messages.GROUP_USAGE); + } + } + + private void handleGroupMeta(CommandContext context, Group group) throws Exception { + Convertible metaTypeArg = context.getString(2); + if (metaTypeArg.isEmpty()) { + context.reply(Messages.GROUP_META_PREFIX_USAGE); + return; + } + + switch (metaTypeArg.get()) { + case "prefix" -> handleMetaPrefix(context, group); + case "suffix" -> handleMetaSuffix(context, group); + case "tabprefix" -> handleMetaTabPrefix(context, group); + case "tabsuffix" -> handleMetaTabSuffix(context, group); + case "info" -> handleMetaInfo(context, group); + default -> context.reply(Messages.GROUP_META_USAGE); + } + } + + private void handleMetaPrefix(CommandContext context, Group group) throws Exception { + if (context.getString(3).isEmpty()) { + context.reply(Messages.GROUP_META_PREFIX_USAGE); + return; + } + + String prefix = Arrays + .stream(context.getData()) + .skip(3) + .collect(Collectors.joining(" ")); + + group.getPrefix().set(prefix); + prefix = Message.translate(prefix); + + context.reply(Messages.GROUP_META_PREFIX_SET + .replace("{group}", group.getName().get()) + .replace("{prefix}", prefix)); + } + + private void handleMetaSuffix(CommandContext context, Group group) throws Exception { + if (context.getString(3).isEmpty()) { + context.reply(Messages.GROUP_META_SUFFIX_USAGE); + return; + } + + String suffix = Arrays + .stream(context.getData()) + .skip(3) + .collect(Collectors.joining(" ")); + + group.getSuffix().set(suffix); + suffix = Message.translate(suffix); + + context.reply(Messages.GROUP_META_SUFFIX_SET + .replace("{group}", group.getName().get()) + .replace("{suffix}", suffix)); + } + + private void handleMetaTabPrefix(CommandContext context, Group group) throws Exception { + if (context.getString(3).isEmpty()) { + context.reply(Messages.GROUP_META_TAB_PREFIX_USAGE); + return; + } + + String tabPrefix = Arrays + .stream(context.getData()) + .skip(3) + .collect(Collectors.joining(" ")); + + group.getTabPrefix().set(tabPrefix); + tabPrefix = Message.translate(tabPrefix); + + context.reply(Messages.GROUP_META_TAB_PREFIX_SET + .replace("{group}", group.getName().get()) + .replace("{tabprefix}", tabPrefix)); + } + + private void handleMetaTabSuffix(CommandContext context, Group group) throws Exception { + if (context.getString(3).isEmpty()) { + context.reply(Messages.GROUP_META_TAB_SUFFIX_USAGE); + return; + } + + String tabSuffix = Arrays + .stream(context.getData()) + .skip(3) + .collect(Collectors.joining(" ")); + + group.getTabSuffix().set(tabSuffix); + tabSuffix = Message.translate(tabSuffix); + + context.reply(Messages.GROUP_META_TAB_SUFFIX_SET + .replace("{group}", group.getName().get()) + .replace("{tabsuffix}", tabSuffix)); + } + + private void handleMetaInfo(CommandContext context, Group group) { + String name = group.getName().get(); + String prefix = group.getPrefix().get(); + String suffix = group.getSuffix().get(); + String tabPrefix = group.getTabPrefix().get(); + String tabSuffix = group.getTabSuffix().get(); + + String message = Messages.GROUP_META_INFO + .replace("{group}", Message.translate(name)) + .replace("{prefix}", Message.translate(prefix != null ? prefix : "not set")) + .replace("{suffix}", Message.translate(suffix != null ? suffix : "not set")) + .replace("{tabprefix}", Message.translate(tabPrefix != null ? tabPrefix : "not set")) + .replace("{tabsuffix}", Message.translate(tabSuffix != null ? tabSuffix : "not set")); + + context.reply(message); + } + + private void handleGroupPermission(CommandContext context, Group group) throws Exception { + Convertible actionArg = context.getString(4); + if (actionArg.isEmpty()) { + context.reply(Messages.GROUP_PERMISSION_USAGE); + return; + } + + switch (actionArg.get()) { + case "set" -> handleGroupPermissionSet(context, group); + case "unset" -> handleGroupPermissionUnset(context, group); + default -> context.reply(Messages.GROUP_PERMISSION_USAGE); + } + } + + private void handleGroupPermissionSet(CommandContext context, Group group) throws Exception { + Convertible permissionArg = context.getString(5); + if (permissionArg.isEmpty()) { + context.reply(Messages.GROUP_PERMISSION_SET_USAGE); + return; + } + + Convertible stateArg = context.getBoolean(6); + if (stateArg.isEmpty()) { + context.reply(Messages.GROUP_PERMISSION_SET_USAGE); + return; + } + + String permission = permissionArg.get(); + Boolean state = stateArg.get(); + + group.getPermissions().set(permission, state); + + context.reply(Messages.GROUP_PERMISSION_SET_UPDATED + .replace("{group}", group.getName().get()) + .replace("{permission}", permission) + .replace("{state}", String.valueOf(state))); + } + + private void handleGroupPermissionUnset(CommandContext context, Group group) throws Exception { + Convertible permissionArg = context.getString(5); + if (permissionArg.isEmpty()) { + context.reply(Messages.GROUP_PERMISSION_UNSET_USAGE); + return; + } + + String permission = permissionArg.get(); + group.getPermissions().unset(permission); + + context.reply(Messages.GROUP_PERMISSION_UNSET_UPDATED + .replace("{group}", group.getName().get()) + .replace("{permission}", permission)); + } + + private void handleGroupGrant(CommandContext context, Group group) throws Exception { + Convertible playerArg = context.getString(2); + if (playerArg.isEmpty()) { + context.reply(Messages.GROUP_GRANT_USAGE); + return; + } + + String player = playerArg.get(); + Future future; + + Player onlinePlayer = Bukkit.getPlayerExact(player); + if (onlinePlayer != null) { + future = YsmpCore.INSTANCE + .getDataManager() + .get(onlinePlayer) + .getUser(); + } else { + future = YsmpCore.INSTANCE + .getUserService() + .getUser(player); + } + + future + .then(user -> { + user.getGroup().set(group.getName().get()); + context.reply(Messages.GROUP_GRANT_GRANTED + .replace("{group}", group.getName().get()) + .replace("{player}", player)); + }) + .except(err -> context.reply(Messages.NO_SUCH_USER + .replace("{name}", player))); + } + + @Override + public Completion onTabComplete(@NotNull CommandContext context) throws Exception { + if (!(context.getSender().hasPermission("ysmp.admin"))) + return Completion.EMPTY; + + // this command system is a piece of shit, so I have to do it manually + if (context.size() == 0) { + Completion completion = Completion.of("create", "delete"); + List groups = YsmpCore.INSTANCE + .getGroupService() + .getGroupCache() + .values() + .stream() + .map(group -> group.getName().get()) + .toList(); + completion.getData().addAll(groups); + return completion; + } + + else if (context.size() == 1) { + String arg = context.getString(0).get(); + if (arg.equals("create") || arg.equals("delete")) + return Completion.EMPTY; + return Completion.of("meta", "permission", "grant"); + } + + else if (context.size() == 2) { + String action = context.getString(1).get(); + if (action.equals("")) + return Completion.of("meta", "permission", "grant"); + + if (action.startsWith("m")) + return Completion.of("meta"); + else if (action.startsWith("p")) + return Completion.of("permission"); + else if (action.startsWith("g")) + return Completion.of("grant"); + } + + else if (context.size() == 3) { + String action = context.getString(1).get(); + + return switch (action) { + case "meta" -> Completion.of("info", "prefix", "suffix", "tabprefix", "tabsuffix"); + case "permission" -> Completion.of("set", "unset"); + case "grant" -> Completion.PLAYERS; + default -> Completion.EMPTY; + }; + } + + return Completion.EMPTY; + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/CreateArgument.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/CreateArgument.java new file mode 100644 index 0000000..e20d2f8 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/CreateArgument.java @@ -0,0 +1,54 @@ +package ee.yoursit.core.command.bukkit.impl.admin.group; + +import dev.inventex.octa.data.convertible.Convertible; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Argument; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.Group; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; + +@CommandInfo(name = "create") +public class CreateArgument extends Argument { + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + if (!(context.getSender().hasPermission("ysmp.admin"))) { + context.reply(Messages.NO_PERMISSION); + return; + } + + Convertible nameArg = context.getString(0); + if (nameArg.isEmpty()) { + context.reply(Messages.GROUP_CREATE_USAGE); + return; + } + + String name = nameArg.get(); + + YsmpCore.INSTANCE + .getGroupService() + .getGroup(name) + .then(group -> context.reply(Messages.GROUP_CREATE_ALREADY_EXISTS + .replace("{group}", name))) + .tryExcept(err -> createGroup(context, name)); + } + + private void createGroup(CommandContext context, String name) throws Exception { + Group group = EntitySerializer.init(Group.class); + + group.getName().init(name); + group.getPermissions().init(new HashMap<>()); + + YsmpCore.INSTANCE + .getGroupService() + .addGroup(group) + .get(); + + context.reply(Messages.GROUP_CREATE_CREATED + .replace("{group}", name)); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/DeleteArgument.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/DeleteArgument.java new file mode 100644 index 0000000..6387cdc --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/DeleteArgument.java @@ -0,0 +1,52 @@ +package ee.yoursit.core.command.bukkit.impl.admin.group; + +import dev.inventex.octa.data.convertible.Convertible; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.config.Messages; +import org.jetbrains.annotations.NotNull; + +@CommandInfo(name = "delete") +public class DeleteArgument extends Command { + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + if (!(context.getSender().hasPermission("ysmp.admin"))) { + context.reply(Messages.NO_PERMISSION); + return; + } + + Convertible nameArg = context.getString(0); + if (nameArg.isEmpty()) { + context.reply(Messages.GROUP_CREATE_USAGE); + return; + } + + String name = nameArg.get(); + + YsmpCore.INSTANCE + .getGroupService() + .getGroup(name) + .tryThen(group -> { + if (group == null) { + context.reply(Messages.GROUP_DELETE_NOT_EXISTS + .replace("{group}", name)); + return; + } + + deleteGroup(context, name); + }) + .except(System.err::println); + } + + private void deleteGroup(CommandContext context, String name) throws Exception { + YsmpCore.INSTANCE + .getGroupService() + .removeGroup(name) + .get(); + + context.reply(Messages.GROUP_DELETE_DELETED + .replace("{group}", name)); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/ListArgument.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/ListArgument.java new file mode 100644 index 0000000..02ec2e2 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/admin/group/ListArgument.java @@ -0,0 +1,41 @@ +package ee.yoursit.core.command.bukkit.impl.admin.group; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Argument; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.database.entity.impl.Group; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.stream.Collectors; + +@CommandInfo(name = "list") +public class ListArgument extends Argument { + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + if (!(context.getSender().hasPermission("ysmp.admin"))) { + context.reply(Messages.NO_PERMISSION); + return; + } + + Collection groups = YsmpCore.INSTANCE + .getGroupService() + .getGroupCache() + .values(); + + if (groups.isEmpty()) { + context.reply(Messages.GROUP_NO_GROUPS); + return; + } + + String list = groups + .stream() + .map(group -> group.getName().get()) + .collect(Collectors.joining(", ")); + + context.reply(Messages.GROUP_LIST + .replace("{list}", list)); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/home/DelHomeCommand.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/home/DelHomeCommand.java new file mode 100644 index 0000000..1ebffe8 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/home/DelHomeCommand.java @@ -0,0 +1,35 @@ +package ee.yoursit.core.command.bukkit.impl.home; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@CommandInfo(name = "delhome") +public class DelHomeCommand extends Command { + @Override + public void onCommand(@NotNull CommandContext context) { + Player player = context.getPlayer(); + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(player); + + data + .getUser() + .then(user -> { + if (user.getHome().get() == null) { + context.reply(Messages.HOME_NOT_SET); + return; + } + + user.getHome().set(null); + context.reply(Messages.HOME_DELETED); + }) + .except(e -> context.reply(Messages.INTERNAL_ERROR)); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/home/HomeCommand.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/home/HomeCommand.java new file mode 100644 index 0000000..2b51b86 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/home/HomeCommand.java @@ -0,0 +1,42 @@ +package ee.yoursit.core.command.bukkit.impl.home; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.command.bukkit.api.Completion; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@CommandInfo(name = "home") +public class HomeCommand extends Command { + @Override + public void onCommand(@NotNull CommandContext context) { + Player player = context.getPlayer(); + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(player); + + data + .getUser() + .then(user -> { + Location home = user.getHome().get(); + if (home == null) { + context.reply(Messages.HOME_NOT_SET); + return; + } + + player.teleport(home); + context.reply(Messages.HOME_TELEPORTING); + }) + .except(e -> context.reply(Messages.INTERNAL_ERROR)); + } + + @Override + public Completion onTabComplete(@NotNull CommandContext context) { + return Completion.EMPTY; + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/home/SetHomeCommand.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/home/SetHomeCommand.java new file mode 100644 index 0000000..894617a --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/home/SetHomeCommand.java @@ -0,0 +1,35 @@ +package ee.yoursit.core.command.bukkit.impl.home; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.command.bukkit.api.Completion; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@CommandInfo(name = "sethome") +public class SetHomeCommand extends Command { + @Override + public void onCommand(@NotNull CommandContext context) { + Player player = context.getPlayer(); + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(player); + + data + .getUser() + .then(user -> { + user.getHome().set(player.getLocation()); + context.reply(Messages.HOME_UPDATED); + }) + .except(e -> context.reply(Messages.INTERNAL_ERROR)); + } + + @Override + public Completion onTabComplete(@NotNull CommandContext context) { + return Completion.EMPTY; + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/invite/VerifyCommand.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/invite/VerifyCommand.java new file mode 100644 index 0000000..1e66c24 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/invite/VerifyCommand.java @@ -0,0 +1,100 @@ +package ee.yoursit.core.command.bukkit.impl.invite; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.User; +import ee.yoursit.core.database.entity.impl.Verification; +import ee.yoursit.core.util.Constants; +import lombok.SneakyThrows; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@CommandInfo(name = "verify") +public class VerifyCommand extends Command { + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + Player player = context.getPlayer(); + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(player); + + if (!data.isVerifying()) { + context.reply(Messages.INVITE_ALREADY_VERIFIED); + return; + } + + String code = context.getString(0).get(); + + YsmpCore.INSTANCE + .getVerificationService() + .getVerification(code) + .then(verification -> verifyUser(player, data, verification)) + .except(e -> data.kick(Messages.INVITE_INVALID_CODE)); + } + + @SneakyThrows + private void verifyUser(Player player, PlayerData data, Verification verification) { + JDA jda = YsmpCore.INSTANCE + .getClient() + .getJda(); + + Guild guild = jda.getGuildById(Constants.YSMP_SERVER); + assert guild != null; + + Member member = guild.getMemberById(verification.getDiscordId().get()); + if (member == null) { + player.sendMessage(Messages.INVITE_NOT_JOINED_GUILD); + return; + } + + User user = createUser(player, verification); + + YsmpCore.INSTANCE + .getUserService() + .addUser(user) + .get(); + + YsmpCore.INSTANCE + .getVerificationService() + .removeVerification(verification) + .get(); + + data.setVerifying(false); + + Role role = jda.getRoleById(Constants.PLAYER); + assert role != null; + + guild + .addRoleToMember(member, role) + .queue(); + + player.sendMessage(Messages.INVITE_VERIFIED); + } + + @SneakyThrows + private User createUser(Player player, Verification verification) { + User user = EntitySerializer.init(User.class); + + user.getUsername().init(player.getName()); + user.getUuid().init(player.getUniqueId()); + user.getDiscordId().init(verification.getDiscordId().get()); + + user.getRegistered().init(System.currentTimeMillis()); + user.getLastOnline().init(System.currentTimeMillis()); + user.getPlayTime().init(0L); + + user.getInvite().set(verification.getCode().get()); + user.getGroup().init("Invited"); + + return user; + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/message/MessageCommand.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/message/MessageCommand.java new file mode 100644 index 0000000..919fa09 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/message/MessageCommand.java @@ -0,0 +1,56 @@ +package ee.yoursit.core.command.bukkit.impl.message; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.command.bukkit.api.Completion; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.DataManager; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.stream.Collectors; + +@CommandInfo(name = "message", aliases = {"msg", "m", "pm", "dm"}) +public class MessageCommand extends Command { + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + if (context.size() < 2) { + context.reply(Messages.PRIVATE_MESSAGE_USAGE); + return; + } + + Player player = context.getPlayer(); + Player target = context.getPlayer(0).get(); + + if (target == null) { + context.reply(Messages + .PLAYER_NOT_ONLINE + .replace("{player}", context.getStringRaw(0))); + return; + } + + String message = Arrays.stream(context.getData()) + .skip(1) + .collect(Collectors.joining(" ")); + + player.sendMessage(Messages.PRIVATE_MESSAGE_SEND + .replace("{player}", target.getName()) + .replace("{message}", message)); + + target.sendMessage(Messages.PRIVATE_MESSAGE_RECEIVE + .replace("{player}", player.getName()) + .replace("{message}", message)); + + DataManager dataManager = YsmpCore.INSTANCE.getDataManager(); + dataManager.get(player).setLastMessenger(target.getUniqueId()); + dataManager.get(target).setLastMessenger(player.getUniqueId()); + } + + @Override + public Completion onTabComplete(@NotNull CommandContext context) { + return Completion.PLAYERS; + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/message/ReplyCommand.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/message/ReplyCommand.java new file mode 100644 index 0000000..ea7c083 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/message/ReplyCommand.java @@ -0,0 +1,51 @@ +package ee.yoursit.core.command.bukkit.impl.message; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.command.bukkit.api.Completion; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.stream.Collectors; + +@CommandInfo(name = "reply", aliases = {"r", "repl"}) +public class ReplyCommand extends Command { + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + Player player = context.getPlayer(); + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(player); + + Player messenger = data.getLastMessenger(); + if (messenger == null) { + context.reply(Messages.PRIVATE_MESSAGE_NO_OPEN_CHANNELS); + return; + } + + String message = String.join(" ", context.getData()); + + player.sendMessage(Messages.PRIVATE_MESSAGE_SEND + .replace("{player}", messenger.getName()) + .replace("{message}", message)); + + messenger.sendMessage(Messages.PRIVATE_MESSAGE_RECEIVE + .replace("{player}", player.getName()) + .replace("{message}", message)); + + YsmpCore.INSTANCE + .getDataManager() + .get(messenger) + .setLastMessenger(player.getUniqueId()); + } + + @Override + public Completion onTabComplete(@NotNull CommandContext context) throws Exception { + return Completion.PLAYERS; + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportAccept.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportAccept.java new file mode 100644 index 0000000..06c965f --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportAccept.java @@ -0,0 +1,84 @@ +package ee.yoursit.core.command.bukkit.impl.teleport; + +import dev.inventex.octa.data.convertible.Convertible; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.command.bukkit.api.Completion; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +@CommandInfo(name = "tpaccept") +public class TeleportAccept extends Command { + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(context.getPlayer()); + + Map teleportRequests = data.getTeleportRequests(); + + if (teleportRequests.isEmpty()) { + context.reply(Messages.TELEPORT_NO_REQUESTS); + return; + } + + Convertible targetParam = context.getPlayer(0); + Player player = context.getPlayer(); + Player target; + + if (!targetParam.isEmpty()) + target = targetParam.get(); + else { + UUID next = teleportRequests + .keySet() + .iterator() + .next(); + target = Bukkit.getPlayer(next); + } + + if (target == null) { + if (targetParam.isEmpty()) + context.reply(Messages.TELEPORT_NO_REQUESTS); + else + context.reply(Messages + .TELEPORT_NO_REQUEST_FROM + .replace("{player}", context.getStringRaw(0))); + return; + } + + target.teleport(player.getLocation()); + + target.sendMessage(Messages + .TELEPORT_ACCEPT_SEND + .replace("{player}", player.getName())); + player.sendMessage(Messages + .TELEPORT_ACCEPT_RECEIVE + .replace("{player}", target.getName())); + + data.removeTeleportRequest(target); + } + + @Override + public Completion onTabComplete(@NotNull CommandContext context) { + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(context.getPlayer()); + + return Completion.of(data + .getTeleportRequests() + .keySet() + .stream() + .map(Bukkit::getPlayer) + .filter(Objects::nonNull) + .map(Player::getName)); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportDeny.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportDeny.java new file mode 100644 index 0000000..8b503ca --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportDeny.java @@ -0,0 +1,82 @@ +package ee.yoursit.core.command.bukkit.impl.teleport; + +import dev.inventex.octa.data.convertible.Convertible; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.command.bukkit.api.Completion; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +@CommandInfo(name = "tpdeny", aliases = {"tpdecline"}) +public class TeleportDeny extends Command { + @Override + public void onCommand(@NotNull CommandContext context) throws Exception { + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(context.getPlayer()); + + Map teleportRequests = data.getTeleportRequests(); + + if (teleportRequests.isEmpty()) { + context.reply(Messages.TELEPORT_NO_REQUESTS); + return; + } + + Convertible targetParam = context.getPlayer(0); + Player player = context.getPlayer(); + Player target; + + if (!targetParam.isEmpty()) + target = targetParam.get(); + else { + UUID next = teleportRequests + .keySet() + .iterator() + .next(); + target = Bukkit.getPlayer(next); + } + + if (target == null) { + if (targetParam.isEmpty()) + context.reply(Messages.TELEPORT_NO_REQUESTS); + else + context.reply(Messages + .TELEPORT_NO_REQUEST_FROM + .replace("{player}", context.getStringRaw(0))); + return; + } + + data.removeTeleportRequest(target); + + target.sendMessage(Messages + .TELEPORT_DECLINE_SEND + .replace("{player}", player.getName())); + player.sendMessage(Messages + .TELEPORT_DECLINE_RECEIVE + .replace("{player}", target.getName())); + } + + @Override + public Completion onTabComplete(@NotNull CommandContext context) { + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(context.getPlayer()); + + return Completion.of(data + .getTeleportRequests() + .keySet() + .stream() + .map(Bukkit::getPlayer) + .filter(Objects::nonNull) + .map(Player::getName)); + } +} diff --git a/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportRequest.java b/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportRequest.java new file mode 100644 index 0000000..e05057f --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/bukkit/impl/teleport/TeleportRequest.java @@ -0,0 +1,55 @@ +package ee.yoursit.core.command.bukkit.impl.teleport; + +import dev.inventex.octa.data.convertible.Convertible; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.api.CommandContext; +import ee.yoursit.core.command.bukkit.api.CommandInfo; +import ee.yoursit.core.command.bukkit.api.Completion; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@CommandInfo(name = "tpa") +public class TeleportRequest extends Command { + @Override + public void onCommand(@NotNull CommandContext context) { + Convertible playerArg = context.getPlayer(0); + if (playerArg.isEmpty()) { + context.reply(Messages.TELEPORT_REQUEST_USAGE); + return; + } + + Player player = context.getPlayer(); + Player target = playerArg.getOrDefault(null); + + if (target == null) { + context.reply(Messages.PLAYER_NOT_ONLINE); + return; + } + + PlayerData targetData = YsmpCore.INSTANCE + .getDataManager() + .get(target); + + if (targetData.getTeleportRequest(player) != null) { + context.reply(Messages + .TELEPORT_REQUEST_ALREADY + .replace("{player}", target.getName())); + return; + } + + targetData.addTeleportRequest(player); + + context.reply(Messages.TELEPORT_REQUEST_SEND + .replace("{player}", target.getName())); + target.sendMessage(Messages.TELEPORT_REQUEST_RECEIVE + .replace("{player}", target.getName())); + } + + @Override + public Completion onTabComplete(@NotNull CommandContext context) { + return Completion.PLAYERS; + } +} diff --git a/src/main/java/ee/yoursit/core/command/discord/BanCommand.java b/src/main/java/ee/yoursit/core/command/discord/BanCommand.java new file mode 100644 index 0000000..f058261 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/discord/BanCommand.java @@ -0,0 +1,119 @@ +package ee.yoursit.core.command.discord; + +import dev.inventex.discord.command.slash.CommandContext; +import dev.inventex.discord.command.slash.SlashCommand; +import dev.inventex.discord.command.slash.SlashCommandInfo; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.Ban; +import ee.yoursit.core.database.entity.impl.User; +import ee.yoursit.core.util.Constants; +import lombok.SneakyThrows; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.UUID; + +@SlashCommandInfo(name = "ban", description = "Remove a player from the server.") +public class BanCommand extends SlashCommand { + @Override + protected void initialize() { + addOption( + OptionType.USER, + "target", + "Target user to be removed", + true, false + ); + addOption( + OptionType.STRING, + "reason", + "The reason for the removal", + true, false + ); + } + + @Override + public void execute(CommandContext context) { + String reason = context.getString("reason"); + + Guild guild = YsmpCore.INSTANCE + .getClient() + .getJda() + .getGuildById(Constants.YSMP_SERVER); + assert guild != null : "Unable to fetch YSMP guild"; + + Role role = guild.getRoleById(Constants.BANNED); + assert role != null : "Unable to fetch Banned role"; + + Member member = guild.getMember(context.getUser("target")); + assert member != null : "Unable to fetch target member"; + + guild + .addRoleToMember(member, role) + .queue(); + + String nickname = member.getNickname(); + if (nickname == null) + return; + + Player player = Bukkit.getPlayer(nickname); + if (player != null) { + YsmpCore.INSTANCE + .getDataManager() + .get(player) + .kick(Messages.BANNED_FROM_SERVER); + } + + YsmpCore.INSTANCE + .getUserService() + .getUser(nickname) + .then(user -> { + Member self = context.getInteraction().getMember(); + assert self != null; + + banUser(context, user, self.getNickname(), reason); + }) + .except(Throwable::printStackTrace); + } + + @SneakyThrows + private void banUser(CommandContext context, User user, String nickname, String reason) { + user.getBanned().set(true); + + User moderator = YsmpCore.INSTANCE + .getUserService() + .getUser(nickname) + .get(); + + Ban ban = EntitySerializer.init(Ban.class); + ban.getBanId().init(UUID.randomUUID()); + ban.getModerator().init(moderator.getUuid().get()); + ban.getTarget().init(user.getUuid().get()); + ban.getReason().init(reason); + ban.getTimestamp().init(System.currentTimeMillis()); + + YsmpCore.INSTANCE + .getBanService() + .createBan(ban); + + MessageEmbed embed = new EmbedBuilder() + .setTitle("User Banned") + .setColor(Constants.TRANSPARENT) + .addField("Target", user.getUsername().get(), true) + .addField("Moderator", moderator.getUsername().get(), true) + .addField("Reason", reason, true) + .build(); + + context + .getInteraction() + .replyEmbeds(embed) + .queue(); + } +} diff --git a/src/main/java/ee/yoursit/core/command/discord/InviteCommand.java b/src/main/java/ee/yoursit/core/command/discord/InviteCommand.java new file mode 100644 index 0000000..89a2347 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/discord/InviteCommand.java @@ -0,0 +1,138 @@ +package ee.yoursit.core.command.discord; + +import dev.inventex.discord.command.slash.CommandContext; +import dev.inventex.discord.command.slash.SlashCommand; +import dev.inventex.discord.command.slash.SlashCommandInfo; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.Invite; +import ee.yoursit.core.database.entity.impl.User; +import ee.yoursit.core.util.Constants; +import ee.yoursit.core.util.InviteGenerator; +import lombok.SneakyThrows; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; + +import java.awt.*; + +@SlashCommandInfo(name = "invite", description = "Create an invite code for the YSMP server") +public class InviteCommand extends SlashCommand { + private final InviteGenerator generator = new InviteGenerator(5, 4); + + @Override + public void execute(CommandContext context) { + Member member = context + .getInteraction() + .getMember(); + assert member != null; + + YsmpCore.INSTANCE + .getUserService() + .getUser(member.getNickname()) + .then(user -> createInvite(context, user)) + .except(e -> handleNotUser(context)); + } + + private void createInvite(CommandContext context, User user) { + Guild guild = context + .getInteraction() + .getGuild(); + + Member member = context + .getInteraction() + .getMember(); + + assert member != null; + assert guild != null; + + int invites = user.getInvites().size(); + int maxInvites = getMaxInvites(member, guild); + + if (invites >= maxInvites) { + exceededInvites(context); + return; + } + + Invite invite = newInvite(user); + YsmpCore.INSTANCE + .getInviteService() + .createInvite(invite); + + MessageEmbed embed = new EmbedBuilder() + .setTitle("Invite Created") + .setDescription("```" + invite.getCode().get() + "```") + .setColor(Color.GREEN) + .build(); + + context + .getInteraction() + .deferReply(true) + .addEmbeds(embed) + .queue(); + } + + private int getMaxInvites(Member member, Guild guild) { + Role premium = guild.getRoleById(Constants.PREMIUM); + Role business = guild.getRoleById(Constants.BUSINESS); + + assert premium != null; + assert business != null; + + boolean hasPremiumOrHigher = member + .getRoles() + .stream() + .anyMatch(role -> role.getPosition() >= business.getPosition()); + + if (hasPremiumOrHigher) + return 2; + + boolean hasBusiness = member + .getRoles() + .stream() + .anyMatch(role -> role.getPosition() == premium.getPosition()); + + return hasBusiness ? 1 : 0; + } + + private void exceededInvites(CommandContext context) { + MessageEmbed embed = new EmbedBuilder() + .setTitle("Error") + .setDescription(Messages.INVITE_LIMIT_EXCEEDED) + .setColor(Color.RED) + .build(); + + context + .getInteraction() + .replyEmbeds(embed) + .queue(); + } + + @SneakyThrows + private Invite newInvite(User user) { + Invite invite = EntitySerializer.init(Invite.class); + + invite.getCode().init(generator.generate()); + invite.getUsed().init(false); + invite.getCreator().init(user.getUuid().get().toString()); + + return invite; + } + + private void handleNotUser(CommandContext context) { + MessageEmbed embed = new EmbedBuilder() + .setTitle("Error") + .setDescription(Messages.NOT_A_USER) + .setColor(Color.RED) + .build(); + + context + .getInteraction() + .deferReply(true) + .addEmbeds(embed) + .queue(); + } +} diff --git a/src/main/java/ee/yoursit/core/command/discord/PlayersCommand.java b/src/main/java/ee/yoursit/core/command/discord/PlayersCommand.java new file mode 100644 index 0000000..c0f1a70 --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/discord/PlayersCommand.java @@ -0,0 +1,40 @@ +package ee.yoursit.core.command.discord; + +import dev.inventex.discord.command.slash.CommandContext; +import dev.inventex.discord.command.slash.SlashCommand; +import dev.inventex.discord.command.slash.SlashCommandInfo; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.util.Constants; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Collection; +import java.util.stream.Collectors; + +@SlashCommandInfo(name = "players", description = "List the online players on the server.") +public class PlayersCommand extends SlashCommand { + @Override + public void execute(CommandContext context) { + Collection onlinePlayers = Bukkit.getOnlinePlayers(); + String players = onlinePlayers + .stream() + .map(Player::getName) + .collect(Collectors.joining(", ")); + + if (onlinePlayers.isEmpty()) + players = Messages.NO_PLAYERS_ONLINE; + + MessageEmbed embed = new EmbedBuilder() + .setTitle("Online Players") + .setDescription(players) + .setColor(Constants.TRANSPARENT) + .build(); + + context + .getInteraction() + .replyEmbeds(embed) + .queue(); + } +} diff --git a/src/main/java/ee/yoursit/core/command/discord/SmpCommand.java b/src/main/java/ee/yoursit/core/command/discord/SmpCommand.java new file mode 100644 index 0000000..78c41dd --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/discord/SmpCommand.java @@ -0,0 +1,140 @@ +package ee.yoursit.core.command.discord; + +import dev.inventex.discord.command.slash.CommandContext; +import dev.inventex.discord.command.slash.SlashCommand; +import dev.inventex.discord.command.slash.SlashCommandInfo; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.Invite; +import ee.yoursit.core.database.entity.impl.Verification; +import ee.yoursit.core.util.Constants; +import ee.yoursit.core.util.InviteGenerator; +import lombok.SneakyThrows; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +import java.awt.*; + +@SlashCommandInfo(name = "smp", description = "Invite yourself to the YourSitee SMP server.") +public class SmpCommand extends SlashCommand { + private final InviteGenerator generator = new InviteGenerator(5, 4); + + @Override + protected void initialize() { + addOption(OptionType.STRING, + "username", + "The in-game name of your Minecraft account", + true, false + ); + } + + @Override + @SneakyThrows + public void execute(CommandContext context) { + Member member = context + .getInteraction() + .getMember(); + assert member != null; + + Guild guild = YsmpCore.INSTANCE + .getClient() + .getJda() + .getGuildById(Constants.YSMP_SERVER); + assert guild != null; + + Member smpMember = guild.getMember(member.getUser()); + if (smpMember != null + && smpMember + .getRoles() + .stream() + .anyMatch(role -> role.getIdLong() == Constants.PLAYER) + ) { + MessageEmbed embed = new EmbedBuilder() + .setTitle("Error") + .setDescription(Messages.ALREADY_USER) + .setColor(Color.RED) + .build(); + + context + .getInteraction() + .deferReply(true) + .addEmbeds(embed) + .queue(); + + return; + } + + Verification existingVerification = YsmpCore.INSTANCE + .getVerificationService() + .getVerification(member.getIdLong()) + .getOrDefault(null); + + if (existingVerification != null) { + MessageEmbed embed = new EmbedBuilder() + .setTitle("Error") + .setDescription(Messages.INVITE_ALREADY_VERIFYING_SELF) + .setColor(Color.RED) + .build(); + + context + .getInteraction() + .deferReply(true) + .addEmbeds(embed) + .queue(); + + return; + } + + String code = generator.generate(); + + Invite invite = EntitySerializer.init(Invite.class); + invite.getCode().init(code); + invite.getCreator().init("Server"); + invite.getUsed().init(false); + + YsmpCore.INSTANCE + .getInviteService() + .createInvite(invite) + .then(e -> invite(context, member, code)) + .except(System.err::println); + } + + @SneakyThrows + private void invite(CommandContext context, Member member, String code) { + String username = context.getString("username"); + + Verification verification = EntitySerializer.init(Verification.class); + verification.getDiscordId().init(member.getIdLong()); + verification.getUsername().init(username); + verification.getCode().init(code); + verification.getCreation().init(System.currentTimeMillis()); + + YsmpCore.INSTANCE + .getVerificationService() + .addVerification(verification) + .get(); + + MessageEmbed embed = new EmbedBuilder() + .setTitle("You have been invited") + .setDescription(Messages.INVITE_VERIFICATION + .replace("{code}", code)) + .setColor(Constants.TRANSPARENT) + .build(); + + String discordLink = YsmpCore.INSTANCE + .getConfig() + .getString("ysmp-invite"); + + context + .getInteraction() + .deferReply(true) + .addEmbeds(embed) + .addContent(discordLink) + .queue(); + } +} diff --git a/src/main/java/ee/yoursit/core/command/discord/UnbanCommand.java b/src/main/java/ee/yoursit/core/command/discord/UnbanCommand.java new file mode 100644 index 0000000..ab3329f --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/discord/UnbanCommand.java @@ -0,0 +1,68 @@ +package ee.yoursit.core.command.discord; + +import dev.inventex.discord.command.slash.CommandContext; +import dev.inventex.discord.command.slash.SlashCommand; +import dev.inventex.discord.command.slash.SlashCommandInfo; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.database.entity.impl.User; +import ee.yoursit.core.util.Constants; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +@SlashCommandInfo(name = "unban", description = "Revoke the ban of a player.") +public class UnbanCommand extends SlashCommand { + @Override + protected void initialize() { + addOption( + OptionType.USER, + "target", + "Target user to be removed", + true, false + ); + } + + @Override + public void execute(CommandContext context) { + Guild guild = YsmpCore.INSTANCE + .getClient() + .getJda() + .getGuildById(Constants.YSMP_SERVER); + assert guild != null : "Unable to fetch YSMP guild"; + + Role role = guild.getRoleById(Constants.BANNED); + assert role != null : "Unable to fetch Banned role"; + + Member member = guild.getMember(context.getUser("target")); + assert member != null : "Unable to fetch target member"; + + guild + .removeRoleFromMember(member, role) + .queue(); + + YsmpCore.INSTANCE + .getUserService() + .getUser(member.getNickname()) + .then(user -> unbanUser(context, user)) + .except(Throwable::printStackTrace); + } + + private void unbanUser(CommandContext context, User user) { + user.getBanned().set(false); + + MessageEmbed embed = new EmbedBuilder() + .setTitle("User Unbanned") + .setColor(Constants.TRANSPARENT) + .addField("Target", user.getUsername().get(), true) + .addField("Moderator", context.getInteraction().getUser().getName(), true) + .build(); + + context + .getInteraction() + .replyEmbeds(embed) + .queue(); + } +} diff --git a/src/main/java/ee/yoursit/core/command/discord/VerifyCommand.java b/src/main/java/ee/yoursit/core/command/discord/VerifyCommand.java new file mode 100644 index 0000000..ade7dbd --- /dev/null +++ b/src/main/java/ee/yoursit/core/command/discord/VerifyCommand.java @@ -0,0 +1,118 @@ +package ee.yoursit.core.command.discord; + +import dev.inventex.discord.command.slash.CommandContext; +import dev.inventex.discord.command.slash.SlashCommand; +import dev.inventex.discord.command.slash.SlashCommandInfo; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.Invite; +import ee.yoursit.core.database.entity.impl.Verification; +import ee.yoursit.core.util.Constants; +import lombok.SneakyThrows; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +import java.awt.*; + +@SlashCommandInfo(name = "verify", description = "Accept an invitation from an SMP player.") +public class VerifyCommand extends SlashCommand { + @Override + protected void initialize() { + addOption( + OptionType.STRING, + "code", + "The invite code generated by a user", + true, false + ); + addOption(OptionType.STRING, + "username", + "The in-game name of your Minecraft account", + true, false + ); + } + + @Override + public void execute(CommandContext context) { + Guild guild = context.getInteraction().getGuild(); + if (guild == null || guild.getIdLong() != Constants.YSMP_SERVER) { + handleError(context, Messages.INVITE_NOT_ON_GUILD); + return; + } + + String code = context.getString("code"); + + YsmpCore.INSTANCE + .getInviteService() + .getInvite(code) + .then(invite -> acceptInvite(context, invite)) + .except(e -> handleError(context, Messages.INVITE_NOT_FOUND)); + } + + @SneakyThrows + private synchronized void acceptInvite(CommandContext context, Invite invite) { + if (invite.getUsed().get()) { + handleError(context, Messages.INVITE_NOT_FOUND); + return; + } + + Verification existingVerification = YsmpCore.INSTANCE + .getVerificationService() + .getVerification(invite.getCode().get()) + .get(); + + if (existingVerification != null) { + handleError(context, Messages.INVITE_ALREADY_VERIFYING); + return; + } + + Member member = context + .getInteraction() + .getMember(); + assert member != null; + + String username = context.getString("username"); + String code = context.getString("code"); + + Verification verification = EntitySerializer.init(Verification.class); + verification.getDiscordId().init(member.getIdLong()); + verification.getUsername().init(username); + verification.getCode().init(code); + verification.getCreation().init(System.currentTimeMillis()); + + YsmpCore.INSTANCE + .getVerificationService() + .addVerification(verification) + .get(); + + MessageEmbed embed = new EmbedBuilder() + .setTitle("Verification") + .setDescription(Messages.INVITE_VERIFICATION + .replace("{code}", code)) + .setColor(Constants.TRANSPARENT) + .build(); + + context + .getInteraction() + .deferReply(true) + .addEmbeds(embed) + .queue(); + } + + private void handleError(CommandContext context, String message) { + MessageEmbed embed = new EmbedBuilder() + .setTitle("Error") + .setDescription(message) + .setColor(Color.RED) + .build(); + + context + .getInteraction() + .deferReply(true) + .addEmbeds(embed) + .queue(); + } +} diff --git a/src/main/java/ee/yoursit/core/config/Config.java b/src/main/java/ee/yoursit/core/config/Config.java new file mode 100644 index 0000000..d2af617 --- /dev/null +++ b/src/main/java/ee/yoursit/core/config/Config.java @@ -0,0 +1,329 @@ +package ee.yoursit.core.config; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.util.Message; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class Config { + private final Executor executor = Executors.newSingleThreadExecutor(); + + @Getter + private final String name; + private final File file; + + @Getter + private FileConfiguration config; + + public Config(String name) { + this.name = name; + file = new File(YsmpCore.INSTANCE.getPlugin().getDataFolder(), name + ".yml"); + reload(); + } + + public void reload() { + if (!file.exists()) { + try { + createConfig(); + } catch (IOException e) { + Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "Unable to reload config file '" + file.getName() + "'"); + throw new RuntimeException(e); + } + } + load(); + } + + public void reloadAsync() { + if (!file.exists()) + createConfigAsync(null); + loadAsync(); + } + + public void reloadAsync(Runnable action) { + if (!file.exists()) + createConfigAsync(action); + loadAsync(); + } + + public void save() { + try { + config.save(file); + } catch (IOException e) { + Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "Unable to save config file '" + file.getName() + "'"); + e.printStackTrace(); + } + } + + public void saveAsync() { + executor.execute(this::save); + } + + public void load() { + try { + config = new YamlConfiguration(); + FileInputStream stream = new FileInputStream(file); + config.load(new InputStreamReader(stream, StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new IllegalStateException("Unable to load config file", e); + } + } + + public void loadAsync() { + executor.execute(this::load); + } + + private void createConfig() throws IOException { + if (!file.createNewFile()) + throw new RuntimeException("Unable to create config.yml"); + + InputStream stream = YsmpCore.class.getClassLoader().getResourceAsStream(name + ".yml"); + if (stream == null) + throw new RuntimeException("File config.yml is missing from class path."); + + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); + StringBuilder content = new StringBuilder(); + String line; + + while ((line = reader.readLine()) != null) + content.append(line).append('\n'); + + try (OutputStreamWriter writer = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8)) { + writer.write(content.toString()); + } + } + + private void createConfigAsync(Runnable action) { + executor.execute(() -> { + try { + createConfig(); + action.run(); + } catch (Exception e) { + Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "Error whilst creating config file '" + file.getName() + "'"); + e.printStackTrace(); + } + }); + } + + public boolean getOrDefault(String path, boolean defaultValue) { + if (config.isSet(path)) + return config.getBoolean(path); + else { + config.set(path, defaultValue); + return defaultValue; + } + } + + public int getOrDefault(String path, int defaultValue) { + if (config.isSet(path)) + return config.getInt(path); + else { + config.set(path, defaultValue); + return defaultValue; + } + } + + public int getOrDefaultNoSet(String path, int defaultValue) { + if (config.isSet(path)) + return config.getInt(path); + else + return defaultValue; + } + + public long getOrDefault(String path, long defaultValue) { + if (config.isSet(path)) + return config.getInt(path); + else { + config.set(path, defaultValue); + return defaultValue; + } + } + + public double getOrDefault(String path, double defaultValue) { + if (config.isSet(path)) + return config.getDouble(path); + else { + config.set(path, defaultValue); + return defaultValue; + } + } + + public float getOrDefault(String path, float defaultValue) { + if (config.isSet(path)) + return (float) config.getDouble(path); + else { + config.set(path, defaultValue); + return defaultValue; + } + } + + public String getOrDefault(String path, String defaultValue) { + if (config.isSet(path)) + return config.getString(path); + else { + if (defaultValue != null) + config.set(path, defaultValue); + return defaultValue; + } + } + + public String getString(String path) { + return config.getString(path); + } + + public long getLong(String path) { + return config.getLong(path); + } + + public String getOrDefaultNoSet(String path, String defaultValue) { + if (config.isSet(path)) + return config.getString(path); + else + return defaultValue; + } + + public String getF(String path) { + String string = config.getString(path); + if (string == null) + throw new IllegalStateException("No such config option: " + path); + return Message.translate(string); + } + + public String getOrDefaultF(String path, String defaultValue) { + String result; + if (config.isSet(path)) + result = config.getString(path); + else { + result = defaultValue; + if (defaultValue == null) + return null; + config.set(path, result); + } + return Message.translate(result); + } + + public String getOrDefaultFLS(String path, String... defaultValue) { + String result; + if (config.isSet(path)) { + StringBuilder builder = new StringBuilder(); + Iterator iterator = config.getStringList(path).iterator(); + + while (iterator.hasNext()) { + builder.append(iterator.next()); + if (iterator.hasNext()) + builder.append('\n'); + } + + result = builder.toString(); + } else { + result = String.join("\n", defaultValue); + config.set(path, Arrays.asList(defaultValue)); + } + return Message.translate(result); + } + + public Material getOrDefault(String path, Material defaultValue) { + if (config.isSet(path)) + return Material.getMaterial(config.getString(path)); + else + return defaultValue; + } + + public List getOrDefaultFL(String path, String... defaultValue) { + if (config.isSet(path)) + return config + .getStringList(path) + .stream() + .map(s -> Message.translate(s)) + .toList(); + + else if (defaultValue.length > 0) { + List result = Arrays + .stream(defaultValue) + .map(s -> Message.translate(s)) + .toList(); + + config.set(path, result); + return result; + } + return null; + } + + public List getOrDefault(String path, List defaultValue) { + if (config.isSet(path)) + return config.getStringList(path); + else { + config.set(path, defaultValue); + return defaultValue; + } + } + + public Location getOrDefault(String path, Location defaultValue) { + if (config.isSet(path)) { + String world = config.getString(path + ".world"); + if (world == null) + return null; + + return new Location( + Bukkit.getWorld(world), + getOrDefault(path + ".posX", 0D), + getOrDefault(path + ".posY", 0D), + getOrDefault(path + ".posZ", 0D), + getOrDefault(path + ".yaw", 0F), + getOrDefault(path + ".pitch", 0F) + ); + } else { + setLocation(path, defaultValue); + return defaultValue; + } + } + + public Location getLocation(String path) { + return config.getLocation(path); + } + + public void setLocation(String path, Location location) { + if (location == null) { + config.set(path, new HashMap<>()); + return; + } + + config.set(path + ".world", location.getWorld().getName()); + config.set(path + ".posX", location.getX()); + config.set(path + ".posY", location.getY()); + config.set(path + ".posZ", location.getZ()); + config.set(path + ".yaw", location.getYaw()); + config.set(path + ".pitch", location.getPitch()); + } + + public Set getKeys(String path) { + ConfigurationSection section = config.getConfigurationSection(path); + if (section == null) + return new HashSet<>(); + return section.getKeys(false); + } + + public void set(String path, Object value) { + config.set(path, value); + } + + public void unset(String path) { + config.set(path, null); + } + + public boolean isSet(String path) { + return config.isSet(path); + } +} \ No newline at end of file diff --git a/src/main/java/ee/yoursit/core/config/Messages.java b/src/main/java/ee/yoursit/core/config/Messages.java new file mode 100644 index 0000000..32e59f1 --- /dev/null +++ b/src/main/java/ee/yoursit/core/config/Messages.java @@ -0,0 +1,148 @@ +package ee.yoursit.core.config; + +public class Messages { + public static String INTERNAL_ERROR; + + public static String PLAYER_JOIN, PLAYER_LEAVE; + + public static String PLAYER_NOT_ONLINE, NO_PLAYERS_ONLINE, NOT_A_USER, ALREADY_USER; + + public static String TELEPORT_REQUEST_SEND, TELEPORT_REQUEST_RECEIVE, TELEPORT_REQUEST_USAGE, + TELEPORT_REQUEST_ALREADY; + + public static String TELEPORT_ACCEPT_SEND, TELEPORT_ACCEPT_RECEIVE; + public static String TELEPORT_DECLINE_SEND, TELEPORT_DECLINE_RECEIVE; + + public static String TELEPORT_NO_REQUESTS, TELEPORT_NO_REQUEST_FROM; + + public static String PRIVATE_MESSAGE_SEND, PRIVATE_MESSAGE_RECEIVE, PRIVATE_MESSAGE_USAGE, + PRIVATE_MESSAGE_NO_OPEN_CHANNELS; + + public static String HOME_UPDATED, HOME_TELEPORTING, HOME_NOT_SET, HOME_DELETED; + + public static String AUTO_SLEEP; + + public static String BANNED_FROM_SERVER, NOT_WHITELISTED, UNABLE_TO_FETCH; + + public static String ACTIVITY_PLAYERS, ACTIVITY_DISCORD, ACTIVITY_ADDRESS; + + public static String NO_SUCH_USER, NO_PERMISSION; + + public static String INVITE_LIMIT_EXCEEDED, INVITE_NOT_FOUND, INVITE_NOT_ON_GUILD, + INVITE_VERIFIED, INVITE_VERIFICATION, INVITE_ALREADY_VERIFYING, + INVITE_ALREADY_VERIFIED, INVITE_USE_VERIFY, INVITE_VERIFY_TIMEOUT, + INVITE_INVALID_CODE, INVITE_NOT_JOINED_GUILD, INVITE_ALREADY_VERIFYING_SELF; + + public static String GROUP_USAGE, GROUP_NOT_EXISTS, + GROUP_CREATE_USAGE, GROUP_CREATE_ALREADY_EXISTS, GROUP_CREATE_CREATED, + GROUP_DELETE_USAGE, GROUP_DELETE_NOT_EXISTS, GROUP_DELETE_DELETED, + GROUP_LIST, GROUP_NO_GROUPS, + GROUP_META_USAGE, GROUP_META_INFO, + GROUP_META_PREFIX_USAGE, GROUP_META_PREFIX_SET, + GROUP_META_SUFFIX_USAGE, GROUP_META_SUFFIX_SET, + GROUP_META_TAB_PREFIX_USAGE, GROUP_META_TAB_PREFIX_SET, + GROUP_META_TAB_SUFFIX_USAGE, GROUP_META_TAB_SUFFIX_SET, + GROUP_PERMISSION_USAGE, + GROUP_PERMISSION_SET_USAGE, GROUP_PERMISSION_SET_UPDATED, + GROUP_PERMISSION_UNSET_USAGE, GROUP_PERMISSION_UNSET_UPDATED, + GROUP_GRANT_USAGE, GROUP_GRANT_GRANTED; + + public static void init(Config config) { + INTERNAL_ERROR = config.getF("internal-error"); + + PLAYER_JOIN = config.getF("player-join"); + PLAYER_LEAVE = config.getF("player-leave"); + + PLAYER_NOT_ONLINE = config.getF("player-not-online"); + NO_PLAYERS_ONLINE = config.getF("no-players-online"); + NOT_A_USER = config.getF("not-a-user"); + ALREADY_USER = config.getF("already-user"); + + TELEPORT_REQUEST_SEND = config.getF("teleport-request.send"); + TELEPORT_REQUEST_RECEIVE = config.getF("teleport-request.receive"); + TELEPORT_REQUEST_USAGE = config.getF("teleport-request.usage"); + TELEPORT_REQUEST_ALREADY = config.getF("teleport-request.already"); + + TELEPORT_NO_REQUESTS = config.getF("teleport.no-requests"); + TELEPORT_NO_REQUEST_FROM = config.getF("teleport.no-request-from"); + + TELEPORT_ACCEPT_SEND = config.getF("teleport-accept.send"); + TELEPORT_ACCEPT_RECEIVE = config.getF("teleport-accept.receive"); + + TELEPORT_DECLINE_SEND = config.getF("teleport-decline.send"); + TELEPORT_DECLINE_RECEIVE = config.getF("teleport-decline.receive"); + + PRIVATE_MESSAGE_SEND = config.getF("private-message.send"); + PRIVATE_MESSAGE_RECEIVE = config.getF("private-message.receive"); + PRIVATE_MESSAGE_USAGE = config.getF("private-message.usage"); + PRIVATE_MESSAGE_NO_OPEN_CHANNELS = config.getF("private-message.no-open-channels"); + + HOME_UPDATED = config.getF("home.updated"); + HOME_TELEPORTING = config.getF("home.teleporting"); + HOME_NOT_SET = config.getF("home.not-set"); + HOME_DELETED = config.getF("home.deleted"); + + AUTO_SLEEP = config.getF("auto-sleep"); + + BANNED_FROM_SERVER = config.getF("banned-from-server"); + NOT_WHITELISTED = config.getF("not-whitelisted"); + UNABLE_TO_FETCH = config.getF("unable-to-fetch"); + + ACTIVITY_PLAYERS = config.getF("activity.players"); + ACTIVITY_DISCORD = config.getF("activity.discord"); + ACTIVITY_ADDRESS = config.getF("activity.address"); + + NO_SUCH_USER = config.getF("no-such-user"); + NO_PERMISSION = config.getF("no-permission"); + + INVITE_LIMIT_EXCEEDED = config.getF("invite.limit-exceeded"); + INVITE_NOT_FOUND = config.getF("invite.not-found"); + INVITE_NOT_ON_GUILD = config.getF("invite.not-on-guild"); + INVITE_VERIFIED = config.getF("invite.verified"); + INVITE_VERIFICATION = config.getF("invite.verification"); + INVITE_ALREADY_VERIFYING = config.getF("invite.already-verifying"); + INVITE_ALREADY_VERIFIED = config.getF("invite.already-verified"); + INVITE_USE_VERIFY = config.getF("invite.use-verify"); + INVITE_VERIFY_TIMEOUT = config.getF("invite.verify-timeout"); + INVITE_INVALID_CODE = config.getF("invite.invalid-code"); + INVITE_NOT_JOINED_GUILD = config.getF("invite.not-joined-guild"); + INVITE_ALREADY_VERIFYING_SELF = config.getF("invite.already-verifying-self"); + + GROUP_USAGE = config.getF("group.usage"); + GROUP_NOT_EXISTS = config.getF("group.not-exists"); + + GROUP_CREATE_USAGE = config.getF("group.create.usage"); + GROUP_CREATE_ALREADY_EXISTS = config.getF("group.create.already-exists"); + GROUP_CREATE_CREATED = config.getF("group.create.created"); + + GROUP_DELETE_USAGE = config.getF("group.delete.usage"); + GROUP_DELETE_NOT_EXISTS = config.getF("group.delete.not-exists"); + GROUP_DELETE_DELETED = config.getF("group.delete.deleted"); + + GROUP_LIST = config.getF("group.list"); + GROUP_NO_GROUPS = config.getF("group.no-groups"); + + GROUP_META_USAGE = config.getF("group.meta.usage"); + GROUP_META_INFO = config.getF("group.meta.info"); + + GROUP_META_PREFIX_USAGE = config.getF("group.meta.prefix.usage"); + GROUP_META_PREFIX_SET = config.getF("group.meta.prefix.set"); + + GROUP_META_TAB_PREFIX_USAGE = config.getF("group.meta.tab-prefix.usage"); + GROUP_META_TAB_PREFIX_SET = config.getF("group.meta.tab-prefix.set"); + + GROUP_META_TAB_SUFFIX_USAGE = config.getF("group.meta.tab-suffix.usage"); + GROUP_META_TAB_SUFFIX_SET = config.getF("group.meta.tab-suffix.set"); + + GROUP_PERMISSION_USAGE = config.getF("group.permission.usage"); + + GROUP_PERMISSION_SET_USAGE = config.getF("group.permission.set.usage"); + GROUP_PERMISSION_SET_UPDATED = config.getF("group.permission.set.updated"); + + GROUP_PERMISSION_UNSET_USAGE = config.getF("group.permission.unset.usage"); + GROUP_PERMISSION_UNSET_UPDATED = config.getF("group.permission.unset.updated"); + + GROUP_GRANT_USAGE = config.getF("group.grant.usage"); + GROUP_GRANT_GRANTED = config.getF("group.grant.granted"); + } +} diff --git a/src/main/java/ee/yoursit/core/data/DataManager.java b/src/main/java/ee/yoursit/core/data/DataManager.java new file mode 100644 index 0000000..42f097f --- /dev/null +++ b/src/main/java/ee/yoursit/core/data/DataManager.java @@ -0,0 +1,49 @@ +package ee.yoursit.core.data; + +import com.google.common.collect.Maps; +import ee.yoursit.core.YsmpCore; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.*; + +@Getter +public class DataManager { + private final Map playerData = Maps.newConcurrentMap(); + + public DataManager() { + Bukkit + .getOnlinePlayers() + .forEach(this::add); + + Bukkit + .getScheduler() + .scheduleSyncRepeatingTask(YsmpCore.INSTANCE.getPlugin(), + this::updateOnline, 20 * 60, 20 * 60); + } + + private void updateOnline() { + playerData + .values() + .forEach(PlayerData::updateLastOnline); + } + + public PlayerData add(Player player) { + PlayerData data = new PlayerData(player); + playerData.put(player.getUniqueId(), data); + return data; + } + + public PlayerData remove(Player player) { + return playerData.remove(player.getUniqueId()); + } + + public PlayerData get(Player player) { + return playerData.get(player.getUniqueId()); + } + + public PlayerData get(UUID uuid) { + return playerData.get(uuid); + } +} diff --git a/src/main/java/ee/yoursit/core/data/PlayerData.java b/src/main/java/ee/yoursit/core/data/PlayerData.java new file mode 100644 index 0000000..f1f68ce --- /dev/null +++ b/src/main/java/ee/yoursit/core/data/PlayerData.java @@ -0,0 +1,167 @@ +package ee.yoursit.core.data; + +import dev.inventex.octa.concurrent.future.Future; +import dev.inventex.octa.concurrent.future.FutureTimeoutException; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.database.entity.impl.Group; +import ee.yoursit.core.database.entity.impl.User; +import ee.yoursit.core.database.entity.impl.Verification; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Getter +@Setter +public class PlayerData { + public static final int TELEPORT_REQUEST_LIFETIME = 1000 * 30; + + private final Map teleportRequests = new HashMap<>(); + + private final Reference player; + + private boolean online; + private long joinTime; + + @Nullable + private UUID lastMessenger; + + private Future user; + + @Setter + private boolean verifying; + private Verification verification; + + public PlayerData(Player player) { + this.player = new WeakReference<>(player); + + Bukkit.getScheduler().scheduleSyncRepeatingTask(YsmpCore.INSTANCE.getPlugin(), + this::cleanUpExpiredRequests, 20L, 20L); + + user = YsmpCore.INSTANCE + .getUserService() + .getUser(player.getUniqueId()) + .timeout(3, TimeUnit.SECONDS) + .except(this::handleUserNotFound); + } + + private void handleUserNotFound(Throwable err) { + Player player = this.player.get(); + if (player == null) + return; + + if (err instanceof FutureTimeoutException) { + kick(Messages.UNABLE_TO_FETCH); + return; + } + + try { + Verification verification = YsmpCore.INSTANCE + .getVerificationService() + .getVerification(player) + .getOrDefault(null); + + if (verification != null) { + handleVerification(player, verification); + return; + } + } catch (Exception ignored) { + } + + kick(Messages.NOT_WHITELISTED); + } + + private void handleVerification(Player player, Verification verification) { + try { + teleport(YsmpCore.INSTANCE.getVerifyLoc()); + player.sendMessage(Messages.INVITE_USE_VERIFY); + + verifying = true; + this.verification = verification; + + long timeout = YsmpCore.INSTANCE + .getConfig() + .getLong("verify-timeout"); + + Bukkit + .getScheduler() + .scheduleSyncDelayedTask(YsmpCore.INSTANCE.getPlugin(), () -> { + if (verifying) + kick(Messages.INVITE_VERIFY_TIMEOUT); + }, timeout * 20); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void updateLastOnline() { + user.then(u -> u + .getLastOnline() + .set(System.currentTimeMillis())); + } + + public Future getGroup() { + return getUser().tryTransform(user -> + YsmpCore.INSTANCE + .getGroupService() + .getGroup(user.getGroup().get()) + .get()); + } + + public void addTeleportRequest(Player requester) { + teleportRequests.put(requester.getUniqueId(), System.currentTimeMillis()); + } + + public void removeTeleportRequest(Player requester) { + teleportRequests.remove(requester.getUniqueId()); + } + + @Nullable + public Long getTeleportRequest(Player requester) { + return teleportRequests.get(requester.getUniqueId()); + } + + private void cleanUpExpiredRequests() { + long now = System.currentTimeMillis(); + teleportRequests + .entrySet() + .removeIf(entry -> now - entry.getValue() > TELEPORT_REQUEST_LIFETIME); + } + + public Player getLastMessenger() { + if (lastMessenger == null) + return null; + return Bukkit.getPlayer(lastMessenger); + } + + public void kick(String reason) { + sync(() -> { + Player player = this.player.get(); + if (player != null) + player.kickPlayer(reason); + }); + } + + public void teleport(Location location) { + sync(() -> { + Player player = this.player.get(); + if (player != null) + player.teleport(location); + }); + } + + private void sync(Runnable task) { + Bukkit + .getScheduler() + .runTask(YsmpCore.INSTANCE.getPlugin(), task); + } +} diff --git a/src/main/java/ee/yoursit/core/database/Database.java b/src/main/java/ee/yoursit/core/database/Database.java new file mode 100644 index 0000000..72ee7bb --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/Database.java @@ -0,0 +1,39 @@ +package ee.yoursit.core.database; + +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import ee.yoursit.core.database.entity.impl.*; +import lombok.Getter; +import org.bson.Document; + +@Getter +public class Database { + private final String host; + private final int port; + + private MongoClient client; + + private MongoCollection users, groups, invites, bans, verifications; + + public Database(String host, int port) { + this.host = host; + this.port = port; + } + + public void connect() { + client = new MongoClient(host, port); + + MongoDatabase database = client.getDatabase("ysmp"); + + User.setCollection(users = database.getCollection("users")); + Group.setCollection(groups = database.getCollection("groups")); + Invite.setCollection(invites = database.getCollection("invites")); + Ban.setCollection(bans = database.getCollection("bans")); + Verification.setCollection(verifications = database.getCollection("verifications")); + } + + public void disconnect() { + client.close(); + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/api/Entity.java b/src/main/java/ee/yoursit/core/database/entity/api/Entity.java new file mode 100644 index 0000000..8625832 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/api/Entity.java @@ -0,0 +1,11 @@ +package ee.yoursit.core.database.entity.api; + +import com.mongodb.client.MongoCollection; +import org.bson.Document; +import org.bson.conversions.Bson; + +public interface Entity { + Bson query(); + + MongoCollection collection(); +} diff --git a/src/main/java/ee/yoursit/core/database/entity/api/EntityProperty.java b/src/main/java/ee/yoursit/core/database/entity/api/EntityProperty.java new file mode 100644 index 0000000..634728a --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/api/EntityProperty.java @@ -0,0 +1,77 @@ +package ee.yoursit.core.database.entity.api; + +import com.mongodb.client.model.Updates; +import dev.inventex.octa.concurrent.future.Future; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bson.conversions.Bson; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RequiredArgsConstructor +public class EntityProperty { + private static final Map, Object> FALLBACK = new HashMap<>(); + static { + FALLBACK.put(Byte.class, (byte) 0); + FALLBACK.put(Short.class, (short) 0); + FALLBACK.put(Integer.class, 0); + FALLBACK.put(Float.class, 0F); + FALLBACK.put(Double.class, 0D); + FALLBACK.put(Long.class, 0L); + FALLBACK.put(Boolean.class, false); + FALLBACK.put(Character.class, (char) 0); + FALLBACK.put(Number.class, 0); + FALLBACK.put(List.class, new ArrayList<>()); + FALLBACK.put(Map.class, new HashMap<>()); + } + + private final Entity entity; + private final String name; + + @Getter + private final Class type; + + public T value; + private boolean init = false; + + public EntityProperty(Entity entity, String name, Class type, T value) { + this(entity, name, type); + init(value); + } + + @SuppressWarnings("unchecked") + public final T get() { + if (!init && FALLBACK.containsKey(type)) + return (T) FALLBACK.get(type); + return value; + } + + @MutateEntity + public final Future set(T value) { + this.value = value; + init = true; + return mutate(); + } + + public final void init(T value) { + this.value = value; + init = true; + } + + @MutateEntity + public final Future mutate() { + return Future.completeAsync(() -> { + try { + Object serialize = EntitySerializer.serialize(type, value); + entity + .collection() + .updateOne(entity.query(), Updates.set(name, serialize)); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/api/EntitySerializer.java b/src/main/java/ee/yoursit/core/database/entity/api/EntitySerializer.java new file mode 100644 index 0000000..a4180a6 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/api/EntitySerializer.java @@ -0,0 +1,142 @@ +package ee.yoursit.core.database.entity.api; + +import org.bson.Document; +import org.bukkit.Location; + +import java.lang.reflect.*; +import java.util.*; + +public class EntitySerializer { + private static final Set> NUMBERS = new HashSet<>(); + static { + NUMBERS.add(byte.class); + NUMBERS.add(short.class); + NUMBERS.add(int.class); + NUMBERS.add(long.class); + NUMBERS.add(float.class); + NUMBERS.add(double.class); + NUMBERS.add(Byte.class); + NUMBERS.add(Short.class); + NUMBERS.add(Integer.class); + NUMBERS.add(Long.class); + NUMBERS.add(Float.class); + NUMBERS.add(Double.class); + NUMBERS.add(Number.class); + } + + public static Document serialize(T entity) { + Document document = new Document(); + + for (Field field : entity.getClass().getDeclaredFields()) { + if (Modifier.isTransient(field.getModifiers())) + continue; + + field.setAccessible(true); + + try { + @SuppressWarnings("rawtypes") + EntityProperty property = (EntityProperty) field.get(entity); + + @SuppressWarnings("unchecked") + Object serialize = serialize(property.getType(), property.get()); + document.append(field.getName(), serialize); + } catch (Exception ignored) { + } + } + + return document; + } + + public static Object serialize(Class type, Object object) { + if (object == null) + return null; + + else if (NUMBERS.contains(type)) + return type.isInstance(object) + ? object + : Double.parseDouble(String.valueOf(object)); + + else if (object instanceof UUID) + return object.toString(); + + else if (object instanceof Location location) + return location.serialize(); + + return object; + } + + public static T deserialize(Document document, Class type) throws Exception { + if (document == null) + throw new IllegalStateException("Trying to deserialize null document."); + + T entity = type.getConstructor().newInstance(); + + for (Field field : type.getDeclaredFields()) { + if (Modifier.isTransient(field.getModifiers())) + continue; + + field.setAccessible(true); + + try { + Object value = document.get(field.getName()); + + ParameterizedType genericType = (ParameterizedType) field.getGenericType(); + Class propertyType = (Class) genericType.getActualTypeArguments()[0]; + + Object deserialize = deserialize(propertyType, value); + + @SuppressWarnings({"rawtypes", "unchecked"}) + EntityProperty property = new EntityProperty(entity, field.getName(), propertyType, deserialize); + field.set(entity, property); + } catch (Exception ignored) { + } + } + + return entity; + } + + @SuppressWarnings("unchecked") + public static Object deserialize(Class type, Object object) { + if (object == null) + return null; + + if (NUMBERS.contains(type)) + return type.isInstance(object) + ? object + : Double.parseDouble(String.valueOf(object)); + + else if (UUID.class.isAssignableFrom(type)) + return UUID.fromString(String.valueOf(object)); + + else if (Location.class.isAssignableFrom(type)) + return Location.deserialize((Map) object); + return object; + } + + public static T init(Class type) throws Exception { + T entity = type.getConstructor().newInstance(); + + int fieldIndex = 0; + for (Field field : type.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) + continue; + + Class fieldType = field.getType(); + if (!EntityProperty.class.isAssignableFrom(fieldType)) + continue; + + ParameterizedType genericType = (ParameterizedType) field.getGenericType(); + Class propertyType = (Class) genericType.getActualTypeArguments()[0]; + + Constructor constructor = fieldType.getConstructor(Entity.class, String.class, Class.class); + + @SuppressWarnings({"rawtypes", "unchecked"}) + Object property = constructor.newInstance(entity, field.getName(), propertyType); + + field.setAccessible(true); + field.set(entity, property); + } + + return entity; + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/api/ListEntityProperty.java b/src/main/java/ee/yoursit/core/database/entity/api/ListEntityProperty.java new file mode 100644 index 0000000..3285503 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/api/ListEntityProperty.java @@ -0,0 +1,55 @@ +package ee.yoursit.core.database.entity.api; + +import dev.inventex.octa.concurrent.future.Future; + +import java.util.ArrayList; +import java.util.List; + +public class ListEntityProperty extends EntityProperty> { + public ListEntityProperty(Entity entity, String name, Class> type, List value) { + super(entity, name, type, value); + } + + public ListEntityProperty(Entity entity, String name, Class> type) { + super(entity, name, type); + } + + @MutateEntity + public Future push(T value) { + List list = get(); + if (list == null) + init(list = new ArrayList<>()); + boolean add = list.add(value); + return mutate().transform(e -> add); + } + + @MutateEntity + public Future pull(T value) { + List list = get(); + if (list == null) + return Future.completed(false); + boolean removed = list.remove(value); + return mutate().transform(e -> removed); + } + + @MutateEntity + public Future pop(int index) { + List list = get(); + if (list == null) + return Future.completed((T) null); + T removed = list.remove(index); + return mutate().transform(e -> removed); + } + + public T get(int index) { + List list = get(); + if (list == null) + return null; + return list.get(index); + } + + public int size() { + List list = get(); + return list != null ? list.size() : 0; + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/api/MapEntityProperty.java b/src/main/java/ee/yoursit/core/database/entity/api/MapEntityProperty.java new file mode 100644 index 0000000..51f3045 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/api/MapEntityProperty.java @@ -0,0 +1,41 @@ +package ee.yoursit.core.database.entity.api; + +import dev.inventex.octa.concurrent.future.Future; + +import java.util.HashMap; +import java.util.Map; + +public class MapEntityProperty extends EntityProperty> { + public MapEntityProperty(Entity entity, String name, Class> type, Map value) { + super(entity, name, type, value); + } + + public MapEntityProperty(Entity entity, String name, Class> type) { + super(entity, name, type); + } + + @MutateEntity + public Future set(K key, V value) { + Map map = get(); + if (map == null) + init(map = new HashMap<>()); + V oldValue = map.put(key, value); + return mutate().transform(e -> oldValue); + } + + @MutateEntity + public Future unset(K key) { + Map map = get(); + if (map == null) + return null; + V oldValue = map.remove(key); + return mutate().transform(e -> oldValue); + } + + public V get(K key) { + Map map = get(); + if (map == null) + return null; + return map.get(key); + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/api/MutateEntity.java b/src/main/java/ee/yoursit/core/database/entity/api/MutateEntity.java new file mode 100644 index 0000000..f5e04d8 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/api/MutateEntity.java @@ -0,0 +1,4 @@ +package ee.yoursit.core.database.entity.api; + +public @interface MutateEntity { +} diff --git a/src/main/java/ee/yoursit/core/database/entity/impl/Ban.java b/src/main/java/ee/yoursit/core/database/entity/impl/Ban.java new file mode 100644 index 0000000..32385b6 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/impl/Ban.java @@ -0,0 +1,42 @@ +package ee.yoursit.core.database.entity.impl; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import ee.yoursit.core.database.entity.api.Entity; +import ee.yoursit.core.database.entity.api.EntityProperty; +import lombok.Getter; +import lombok.Setter; +import org.bson.Document; +import org.bson.conversions.Bson; + +import java.util.UUID; + +@Getter +public class Ban implements Entity { + @Setter + private static MongoCollection collection; + + private EntityProperty banId, moderator, target; + + private EntityProperty reason; + + private EntityProperty timestamp; + + @Override + public Bson query() { + return Filters.eq("banId", banId.get().toString()); + } + + public static Bson queryId(UUID banId) { + return Filters.eq("banId", banId.toString()); + } + + public static Bson queryTarget(UUID target) { + return Filters.eq("target", target.toString()); + } + + @Override + public MongoCollection collection() { + return collection; + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/impl/Group.java b/src/main/java/ee/yoursit/core/database/entity/impl/Group.java new file mode 100644 index 0000000..d77ea5b --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/impl/Group.java @@ -0,0 +1,42 @@ +package ee.yoursit.core.database.entity.impl; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import ee.yoursit.core.database.entity.api.Entity; +import ee.yoursit.core.database.entity.api.EntityProperty; +import ee.yoursit.core.database.entity.api.MapEntityProperty; +import lombok.Getter; +import lombok.Setter; +import org.bson.Document; +import org.bson.conversions.Bson; + +import java.util.Map; + +import static com.mongodb.client.model.Filters.eq; + +@Getter +public class Group implements Entity { + @Setter + private static MongoCollection collection; + + private EntityProperty name; + + private EntityProperty prefix, suffix; + private EntityProperty tabPrefix, tabSuffix; + + private MapEntityProperty permissions; + + public static Bson query(String name) { + return eq("name", name); + } + + @Override + public Bson query() { + return Filters.eq("name", name.get()); + } + + @Override + public MongoCollection collection() { + return collection; + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/impl/Invite.java b/src/main/java/ee/yoursit/core/database/entity/impl/Invite.java new file mode 100644 index 0000000..b8b6b74 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/impl/Invite.java @@ -0,0 +1,34 @@ +package ee.yoursit.core.database.entity.impl; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import ee.yoursit.core.database.entity.api.Entity; +import ee.yoursit.core.database.entity.api.EntityProperty; +import lombok.Getter; +import lombok.Setter; +import org.bson.Document; +import org.bson.conversions.Bson; + +@Getter +public class Invite implements Entity { + @Setter + static MongoCollection collection; + + private EntityProperty code; + private EntityProperty used; + private EntityProperty creator; + + @Override + public Bson query() { + return Filters.eq("code", code.get()); + } + + public static Bson query(String code) { + return Filters.eq("code", code); + } + + @Override + public MongoCollection collection() { + return collection; + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/impl/User.java b/src/main/java/ee/yoursit/core/database/entity/impl/User.java new file mode 100644 index 0000000..107237c --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/impl/User.java @@ -0,0 +1,55 @@ +package ee.yoursit.core.database.entity.impl; + +import com.mongodb.client.MongoCollection; +import ee.yoursit.core.database.entity.api.Entity; +import ee.yoursit.core.database.entity.api.EntityProperty; +import ee.yoursit.core.database.entity.api.ListEntityProperty; +import lombok.Getter; +import lombok.Setter; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bukkit.Location; + +import java.util.UUID; + +import static com.mongodb.client.model.Filters.*; + +@Getter +public class User implements Entity { + @Setter + private static MongoCollection collection; + + private EntityProperty username; + private EntityProperty uuid; + private EntityProperty discordId; + + private EntityProperty registered; + private EntityProperty lastOnline; + private EntityProperty playTime; + + private EntityProperty group; + private EntityProperty home; + + private EntityProperty banned; + + private EntityProperty invite; + private ListEntityProperty invites; + + public static Bson query(UUID uuid) { + return eq("uuid", uuid.toString()); + } + + public static Bson query(String username) { + return eq("username", username); + } + + @Override + public Bson query() { + return eq("uuid", uuid.get().toString()); + } + + @Override + public MongoCollection collection() { + return collection; + } +} diff --git a/src/main/java/ee/yoursit/core/database/entity/impl/Verification.java b/src/main/java/ee/yoursit/core/database/entity/impl/Verification.java new file mode 100644 index 0000000..1c8de6b --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/entity/impl/Verification.java @@ -0,0 +1,43 @@ +package ee.yoursit.core.database.entity.impl; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import ee.yoursit.core.database.entity.api.Entity; +import ee.yoursit.core.database.entity.api.EntityProperty; +import lombok.Getter; +import lombok.Setter; +import org.bson.Document; +import org.bson.conversions.Bson; + +@Getter +public class Verification implements Entity { + @Setter + private static MongoCollection collection; + + private EntityProperty discordId; + private EntityProperty username; + private EntityProperty code; + private EntityProperty creation; + + @Override + public Bson query() { + return Filters.eq("code", code.get()); + } + + public static Bson queryCode(String code) { + return Filters.eq("code", code); + } + + public static Bson queryName(String name) { + return Filters.eq("username", name); + } + + public static Bson queryId(long discordId) { + return Filters.eq("discordId", discordId); + } + + @Override + public MongoCollection collection() { + return collection; + } +} diff --git a/src/main/java/ee/yoursit/core/database/service/BanService.java b/src/main/java/ee/yoursit/core/database/service/BanService.java new file mode 100644 index 0000000..5859412 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/service/BanService.java @@ -0,0 +1,45 @@ +package ee.yoursit.core.database.service; + +import dev.inventex.octa.concurrent.future.Future; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.Ban; +import org.bson.Document; + +import java.util.UUID; + +public class BanService { + public Future getBanById(UUID banId) { + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getInvites() + .find(Ban.queryId(banId)) + .first(); + + return EntitySerializer.deserialize(document, Ban.class); + }); + } + + public Future getBanByTarget(UUID target) { + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getInvites() + .find(Ban.queryTarget(target)) + .first(); + + return EntitySerializer.deserialize(document, Ban.class); + }); + } + + public Future createBan(Ban ban) { + return Future.tryCompleteAsync(() -> { + Document document = EntitySerializer.serialize(ban); + YsmpCore.INSTANCE + .getDatabase() + .getInvites() + .insertOne(document); + }); + } +} diff --git a/src/main/java/ee/yoursit/core/database/service/GroupService.java b/src/main/java/ee/yoursit/core/database/service/GroupService.java new file mode 100644 index 0000000..75b6bf7 --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/service/GroupService.java @@ -0,0 +1,82 @@ +package ee.yoursit.core.database.service; + +import com.google.common.collect.Maps; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCursor; +import dev.inventex.octa.concurrent.future.Future; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.Group; +import lombok.Getter; +import org.bson.Document; + +import java.util.Map; + +public class GroupService { + @Getter + private final Map groupCache = Maps.newConcurrentMap(); + + public void loadGroups() { + try (MongoCursor cursor = YsmpCore.INSTANCE + .getDatabase() + .getGroups() + .find() + .cursor()) { + while (cursor.hasNext()) { + loadGroup(cursor.next()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void loadGroup(Document document) { + try { + Group group = EntitySerializer.deserialize(document, Group.class); + groupCache.put(group.getName().get(), group); + } catch (Exception e) { + System.err.println("Unable to load group " + document); + e.printStackTrace(); + } + } + + public Future getGroup(String name) { + Group group = groupCache.get(name); + if (group != null) + return Future.completed(group); + + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getGroups() + .find(Group.query(name)) + .first(); + + return EntitySerializer.deserialize(document, Group.class); + }); + } + + public Future addGroup(Group group) { + if (groupCache.containsKey(group.getName().get())) + return Future.failed(new IllegalStateException("Group '" + group.getName() + "' already exists.")); + + return Future.tryCompleteAsync(() -> { + Document document = EntitySerializer.serialize(group); + YsmpCore.INSTANCE + .getDatabase() + .getGroups() + .insertOne(document); + }); + } + + public Future removeGroup(String name) { + groupCache.remove(name); + + return Future.tryCompleteAsync(() -> { + YsmpCore.INSTANCE + .getDatabase() + .getGroups() + .deleteOne(Group.query(name)); + }); + } +} diff --git a/src/main/java/ee/yoursit/core/database/service/InviteService.java b/src/main/java/ee/yoursit/core/database/service/InviteService.java new file mode 100644 index 0000000..d0456ef --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/service/InviteService.java @@ -0,0 +1,31 @@ +package ee.yoursit.core.database.service; + +import dev.inventex.octa.concurrent.future.Future; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.Invite; +import org.bson.Document; + +public class InviteService { + public Future getInvite(String code) { + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getInvites() + .find(Invite.query(code)) + .first(); + + return EntitySerializer.deserialize(document, Invite.class); + }); + } + + public Future createInvite(Invite invite) { + return Future.completeAsync(() -> { + Document document = EntitySerializer.serialize(invite); + YsmpCore.INSTANCE + .getDatabase() + .getInvites() + .insertOne(document); + }); + } +} diff --git a/src/main/java/ee/yoursit/core/database/service/UserService.java b/src/main/java/ee/yoursit/core/database/service/UserService.java new file mode 100644 index 0000000..cd3ed7b --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/service/UserService.java @@ -0,0 +1,45 @@ +package ee.yoursit.core.database.service; + +import dev.inventex.octa.concurrent.future.Future; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.User; +import org.bson.Document; + +import java.util.UUID; + +public class UserService { + public Future getUser(UUID uuid) { + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getUsers() + .find(User.query(uuid)) + .first(); + + return EntitySerializer.deserialize(document, User.class); + }); + } + + public Future getUser(String name) { + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getUsers() + .find(User.query(name)) + .first(); + + return EntitySerializer.deserialize(document, User.class); + }); + } + + public Future addUser(User user) { + return Future.completeAsync(() -> { + Document document = EntitySerializer.serialize(user); + YsmpCore.INSTANCE + .getDatabase() + .getUsers() + .insertOne(document); + }); + } +} diff --git a/src/main/java/ee/yoursit/core/database/service/VerificationService.java b/src/main/java/ee/yoursit/core/database/service/VerificationService.java new file mode 100644 index 0000000..ee2740a --- /dev/null +++ b/src/main/java/ee/yoursit/core/database/service/VerificationService.java @@ -0,0 +1,68 @@ +package ee.yoursit.core.database.service; + +import dev.inventex.octa.concurrent.future.Future; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.database.entity.api.EntitySerializer; +import ee.yoursit.core.database.entity.impl.User; +import ee.yoursit.core.database.entity.impl.Verification; +import org.bson.Document; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class VerificationService { + public Future getVerification(String code) { + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getVerifications() + .find(Verification.queryCode(code)) + .first(); + + return EntitySerializer.deserialize(document, Verification.class); + }); + } + + public Future getVerification(long discordId) { + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getVerifications() + .find(Verification.queryId(discordId)) + .first(); + + return EntitySerializer.deserialize(document, Verification.class); + }); + } + + public Future getVerification(Player player) { + return Future.tryCompleteAsync(() -> { + Document document = YsmpCore.INSTANCE + .getDatabase() + .getVerifications() + .find(Verification.queryName(player.getName())) + .first(); + + return EntitySerializer.deserialize(document, Verification.class); + }); + } + + public Future addVerification(Verification verification) { + return Future.completeAsync(() -> { + Document document = EntitySerializer.serialize(verification); + YsmpCore.INSTANCE + .getDatabase() + .getVerifications() + .insertOne(document); + }); + } + + public Future removeVerification(Verification verification) { + return Future.completeAsync(() -> { + YsmpCore.INSTANCE + .getDatabase() + .getVerifications() + .deleteOne(verification.query()); + }); + } +} diff --git a/src/main/java/ee/yoursit/core/module/BukkitCommandRegistry.java b/src/main/java/ee/yoursit/core/module/BukkitCommandRegistry.java new file mode 100644 index 0000000..32b6561 --- /dev/null +++ b/src/main/java/ee/yoursit/core/module/BukkitCommandRegistry.java @@ -0,0 +1,72 @@ +package ee.yoursit.core.module; + +import ee.yoursit.core.command.bukkit.api.Command; +import ee.yoursit.core.command.bukkit.impl.admin.GroupCommand; +import ee.yoursit.core.command.bukkit.impl.home.DelHomeCommand; +import ee.yoursit.core.command.bukkit.impl.home.HomeCommand; +import ee.yoursit.core.command.bukkit.impl.home.SetHomeCommand; +import ee.yoursit.core.command.bukkit.impl.invite.VerifyCommand; +import ee.yoursit.core.command.bukkit.impl.message.MessageCommand; +import ee.yoursit.core.command.bukkit.impl.message.ReplyCommand; +import ee.yoursit.core.command.bukkit.impl.teleport.TeleportAccept; +import ee.yoursit.core.command.bukkit.impl.teleport.TeleportDeny; +import ee.yoursit.core.command.bukkit.impl.teleport.TeleportRequest; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +public class BukkitCommandRegistry implements CommandRegistry { + private final Class[] classes = { + TeleportRequest.class, + TeleportAccept.class, + TeleportDeny.class, + + MessageCommand.class, + ReplyCommand.class, + + SetHomeCommand.class, + DelHomeCommand.class, + HomeCommand.class, + + VerifyCommand.class, + + GroupCommand.class + }; + + private final Constructor[] constructors = new Constructor[classes.length]; + + private final List commands = new ArrayList<>(classes.length); + + public BukkitCommandRegistry() { + for (int i = 0; i < classes.length; i++) { + Class command = classes[i]; + try { + constructors[i] = command.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + System.err.println("Unable to load command " + command.getSimpleName() + ":"); + e.printStackTrace(); + } + } + } + + @Override + public void load() { + for (Constructor constructor : constructors) { + try { + Command command = (Command) constructor.newInstance(); + commands.add(command); + command.register(); + } catch (Exception e) { + System.err.println("Unable to instantiate check " + constructor.getDeclaringClass().getSimpleName()); + e.printStackTrace(); + } + } + } + + @Override + public void unload() { + for (Command command : commands) + command.unregister(); + } +} diff --git a/src/main/java/ee/yoursit/core/module/BukkitListener.java b/src/main/java/ee/yoursit/core/module/BukkitListener.java new file mode 100644 index 0000000..4829d36 --- /dev/null +++ b/src/main/java/ee/yoursit/core/module/BukkitListener.java @@ -0,0 +1,170 @@ +package ee.yoursit.core.module; + +import dev.inventex.octa.concurrent.future.Future; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.config.Messages; +import ee.yoursit.core.data.PlayerData; +import ee.yoursit.core.database.entity.impl.Group; +import ee.yoursit.core.database.entity.impl.User; +import ee.yoursit.core.util.Message; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.*; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.util.Vector; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class BukkitListener implements Listener { + private final Set sleeping = new HashSet<>(); + + @EventHandler + public void onLogin(PlayerLoginEvent event) { + YsmpCore.INSTANCE + .getDataManager() + .add(event.getPlayer()); + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(player); + + data.setOnline(true); + data.setJoinTime(System.currentTimeMillis()); + data.updateLastOnline(); + + String message = Messages + .PLAYER_JOIN + .replace("{player}", player.getName()); + + event.setJoinMessage(message); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + YsmpCore.INSTANCE + .getDataManager() + .get(player) + .updateLastOnline(); + + YsmpCore.INSTANCE + .getDataManager() + .remove(player); + + String message = Messages + .PLAYER_LEAVE + .replace("{player}", player.getName()); + + event.setQuitMessage(message); + } + + @EventHandler + public void onBedEnter(PlayerBedEnterEvent event) { + Collection onlinePlayers = Bukkit.getOnlinePlayers(); + long sleeping = onlinePlayers + .stream() + .filter(Player::isSleeping) + .count(); + long total = onlinePlayers.size(); + + float ratio = (float) sleeping / (float) total; + if (ratio >= 2F / 3F) { + event + .getPlayer() + .getWorld() + .setTime(0); + onlinePlayers.forEach(player -> player.sendMessage(Messages.AUTO_SLEEP)); + } + } + + @EventHandler + public void onPing(ServerListPingEvent event) { + String motd = Message.translate( + """ + &b&lYourSitee SMP &f&l❘ 1.20.1 + &f ▶ Visit yoursit.ee/smp for more info + """ + ); + event.setMotd(motd); + } + + @EventHandler + public void onMove(PlayerMoveEvent event) { + Vector from = event.getFrom().toVector(); + Vector to = event.getTo().toVector(); + + double distance = from.distance(to); + if (distance < .01) + return; + + cancelVerifying(event.getPlayer(), event); + } + + @EventHandler + public void onCommand(PlayerCommandPreprocessEvent event) { + String command = event + .getMessage() + .split(" ")[0] + .substring(1); + + if (command.equals("verify")) + return; + + cancelVerifying(event.getPlayer(), event); + } + + @EventHandler + public void onChat(AsyncPlayerChatEvent event) { + Player player = event.getPlayer(); + cancelVerifying(player, event); + + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(player); + + Group group = null; + try { + User user = data + .getUser() + .get(); + + group = YsmpCore.INSTANCE + .getGroupService() + .getGroup(user.getGroup().get()) + .get(); + } catch (Exception e) { + e.printStackTrace(); + } + + String prefix = null, suffix = null; + if (group != null) { + prefix = group.getPrefix().get(); + suffix = group.getSuffix().get(); + } + if (prefix == null) prefix = ""; + if (suffix == null) suffix = ""; + + String format = "&r" + prefix + player.getName() + "&r" + suffix + "&7: &r" + event.getMessage(); + event.setFormat(Message.translate(format)); + } + + private void cancelVerifying(Player player, Cancellable cancellable) { + PlayerData data = YsmpCore.INSTANCE + .getDataManager() + .get(player); + + if (data.isVerifying()) + cancellable.setCancelled(true); + } +} diff --git a/src/main/java/ee/yoursit/core/module/CommandRegistry.java b/src/main/java/ee/yoursit/core/module/CommandRegistry.java new file mode 100644 index 0000000..ae66f7e --- /dev/null +++ b/src/main/java/ee/yoursit/core/module/CommandRegistry.java @@ -0,0 +1,7 @@ +package ee.yoursit.core.module; + +public interface CommandRegistry { + void load(); + + void unload(); +} diff --git a/src/main/java/ee/yoursit/core/module/DiscordCommandRegistry.java b/src/main/java/ee/yoursit/core/module/DiscordCommandRegistry.java new file mode 100644 index 0000000..7de88be --- /dev/null +++ b/src/main/java/ee/yoursit/core/module/DiscordCommandRegistry.java @@ -0,0 +1,91 @@ +package ee.yoursit.core.module; + +import dev.inventex.discord.command.CommandManager; +import dev.inventex.discord.command.slash.SlashCommand; +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.command.discord.*; +import ee.yoursit.core.util.Constants; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.interactions.commands.Command; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +public class DiscordCommandRegistry implements CommandRegistry { + private final Class[] classes = { + PlayersCommand.class, + BanCommand.class, + UnbanCommand.class, + InviteCommand.class, + VerifyCommand.class, + SmpCommand.class + }; + + private final Constructor[] constructors = new Constructor[classes.length]; + + private final List commands = new ArrayList<>(classes.length); + + public DiscordCommandRegistry() { + for (int i = 0; i < classes.length; i++) { + Class command = classes[i]; + try { + constructors[i] = command.getDeclaredConstructor(); + } + catch (NoSuchMethodException e) { + System.err.println("Unable to load command " + command.getSimpleName() + ":"); + e.printStackTrace(); + } + } + } + + @Override + public void load() { + JDA jda = YsmpCore.INSTANCE + .getClient() + .getJda(); + + jda + .retrieveCommands() + .queue(list -> { + for (Command command : list) { + command + .delete() + .queue(); + } + }); + + Guild guild = jda.getGuildById(Constants.YSMP_SERVER); + assert guild != null; + + CommandManager commandManager = YsmpCore.INSTANCE + .getClient() + .getCommandManager(); + + for (Constructor constructor : constructors) { + try { + SlashCommand command = (SlashCommand) constructor.newInstance(); + commands.add(command); + + if (command instanceof SmpCommand) { + Guild main = jda.getGuildById(Constants.MAIN_SERVER); + assert main != null; + + commandManager.registerToGuild(command, main); + continue; + } + + commandManager.registerToGuild(command, guild); + } + catch (Exception e) { + System.err.println("Unable to instantiate check " + constructor.getDeclaringClass().getSimpleName()); + e.printStackTrace(); + } + } + } + + @Override + public void unload() { + } +} diff --git a/src/main/java/ee/yoursit/core/module/DiscordListener.java b/src/main/java/ee/yoursit/core/module/DiscordListener.java new file mode 100644 index 0000000..1c74c64 --- /dev/null +++ b/src/main/java/ee/yoursit/core/module/DiscordListener.java @@ -0,0 +1,64 @@ +package ee.yoursit.core.module; + +import ee.yoursit.core.YsmpCore; +import ee.yoursit.core.config.Messages; +import net.dv8tion.jda.api.entities.Activity; +import net.dv8tion.jda.api.entities.SelfUser; +import net.dv8tion.jda.api.events.session.ReadyEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.bukkit.Bukkit; + +import java.util.Timer; +import java.util.TimerTask; + +public class DiscordListener extends ListenerAdapter { + private static final int ACTIVITY_UPDATE = 1000 * 5; + + private int actionIndex = 0; + + public void onReady(ReadyEvent event) { + SelfUser user = event.getJDA().getSelfUser(); + System.out.println("Logged in as " + user.getName() + "#" + user.getDiscriminator()); + + TimerTask task = new TimerTask() { + @Override + public void run() { + updateActivity(); + } + }; + + Timer timer = new Timer(); + timer.scheduleAtFixedRate(task, ACTIVITY_UPDATE, ACTIVITY_UPDATE); + + YsmpCore.INSTANCE + .getDiscordCommands() + .load(); + } + + private void updateActivity() { + ActivityState[] values = ActivityState.values(); + ActivityState state = values[actionIndex++ % values.length]; + + Activity activity = switch (state) { + case LIST_PLAYERS -> { + String count = String.valueOf(Bukkit.getOnlinePlayers().size()); + yield Activity.watching(Messages.ACTIVITY_PLAYERS + .replace("{count}", count)); + } + case SHOW_DISCORD_LINK -> Activity.playing(Messages.ACTIVITY_DISCORD); + case SHOW_SERVER_ADDRESS -> Activity.playing(Messages.ACTIVITY_ADDRESS); + }; + + YsmpCore.INSTANCE + .getClient() + .getJda() + .getPresence() + .setActivity(activity); + } + + public enum ActivityState { + LIST_PLAYERS, + SHOW_DISCORD_LINK, + SHOW_SERVER_ADDRESS + } +} diff --git a/src/main/java/ee/yoursit/core/util/ClickAction.java b/src/main/java/ee/yoursit/core/util/ClickAction.java new file mode 100644 index 0000000..780221c --- /dev/null +++ b/src/main/java/ee/yoursit/core/util/ClickAction.java @@ -0,0 +1,9 @@ +package ee.yoursit.core.util; + +public enum ClickAction { + OPEN_URL, + OPEN_FILE, + RUN_COMMAND, + SUGGEST_COMMAND, + CHANGE_PAGE +} diff --git a/src/main/java/ee/yoursit/core/util/Constants.java b/src/main/java/ee/yoursit/core/util/Constants.java new file mode 100644 index 0000000..dc33d23 --- /dev/null +++ b/src/main/java/ee/yoursit/core/util/Constants.java @@ -0,0 +1,16 @@ +package ee.yoursit.core.util; + +public class Constants { + public static final long YSMP_SERVER = 598949514052894721L, + MAIN_SERVER = 886538767995895839L; + + public static final int TRANSPARENT = 0x313338; + + public static final long SERVER_LEADER = 1107079573763543152L, + PREMIUM = 1107244269112148109L, + BUSINESS = 1107244259163246603L, + INVITED = 1107080146051158046L, + PLAYER = 1107080049716375582L; + + public static final long BANNED = 1124263377439555644L; +} diff --git a/src/main/java/ee/yoursit/core/util/HoverAction.java b/src/main/java/ee/yoursit/core/util/HoverAction.java new file mode 100644 index 0000000..733dd7c --- /dev/null +++ b/src/main/java/ee/yoursit/core/util/HoverAction.java @@ -0,0 +1,8 @@ +package ee.yoursit.core.util; + +public enum HoverAction { + SHOW_TEXT, + SHOW_ACHIEVEMENT, + SHOW_ITEM, + SHOW_ENTITY +} diff --git a/src/main/java/ee/yoursit/core/util/InviteGenerator.java b/src/main/java/ee/yoursit/core/util/InviteGenerator.java new file mode 100644 index 0000000..9e71605 --- /dev/null +++ b/src/main/java/ee/yoursit/core/util/InviteGenerator.java @@ -0,0 +1,32 @@ +package ee.yoursit.core.util; + +import lombok.RequiredArgsConstructor; + +import java.util.Random; + +@RequiredArgsConstructor +public class InviteGenerator { + private static final String LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String DIGITS = "0123456789"; + + private static final char[] CHARS = (LETTERS + DIGITS).toCharArray(); + + private static final Random random = new Random(); + + private final int length; + private final int count; + + public String generate() { + char[] buffer = new char[length * count + (count - 1)]; + for (int index = 0, offset = 0; index < buffer.length; index++) { + if (offset == length) { + buffer[index] = '-'; + offset = 0; + } else { + buffer[index] = CHARS[random.nextInt(CHARS.length)]; + offset++; + } + } + return new String(buffer); + } +} \ No newline at end of file diff --git a/src/main/java/ee/yoursit/core/util/Message.java b/src/main/java/ee/yoursit/core/util/Message.java new file mode 100644 index 0000000..1a2ae53 --- /dev/null +++ b/src/main/java/ee/yoursit/core/util/Message.java @@ -0,0 +1,79 @@ +package ee.yoursit.core.util; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.*; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Message { + private final TextComponent component; + + public Message(String message, Object... args) { + component = new TextComponent(translate(message, args)); + } + + public Message() { + component = new TextComponent(); + } + + public Message click(ClickAction action, String message, Object... args) { + ClickEvent.Action clickAction = ClickEvent.Action.valueOf(action.name()); + component.setClickEvent(new ClickEvent(clickAction, translate(message, args))); + return this; + } + + public Message click(String command, Object... args) { + return click(ClickAction.RUN_COMMAND, command, args); + } + + public Message hover(HoverAction action, String message, Object... args) { + HoverEvent.Action hoverAction = HoverEvent.Action.valueOf(action.name()); + BaseComponent[] data = new ComponentBuilder(translate(message, args)).create(); + component.setHoverEvent(new HoverEvent(hoverAction, data)); + return this; + } + + public Message hover(String message, Object... args) { + return hover(HoverAction.SHOW_TEXT, message, args); + } + + public Message add(String message, Object... args) { + component.addExtra(translate(message, args)); + return this; + } + + public Message add(Message message) { + component.addExtra(message.component); + return this; + } + + public void send(Player player) { + player.spigot().sendMessage(component); + } + + public void send(CommandSender sender) { + if (sender instanceof Player) + ((Player) sender).spigot().sendMessage(component); + else + sender.sendMessage(component.toLegacyText()); + } + + public static String translate(String message, Object... args) { + message = String.format(message, args); + + Pattern pattern = Pattern.compile("#[a-fA-F0-9]{6}"); + Matcher matcher = pattern.matcher(message); + + while (matcher.find()) { + String color = message.substring(matcher.start(), matcher.end()); + message = message.replace(color, String.valueOf(ChatColor.of(color))); + matcher = pattern.matcher(message); + } + + return ChatColor.translateAlternateColorCodes('&', message); + } +} + diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..914d3fa --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,23 @@ +bot-token: 'AAAA-AAAA-AAAA-AAAA' + +spawn: + ==: org.bukkit.Location + world: world + x: 100.0 + y: 100.0 + z: 100.0 + yaw: 0.0 + pitch: 0.0 + +verify: + ==: org.bukkit.Location + world: verification + x: 0.0 + y: 57.0 + z: 0.0 + yaw: 0.0 + pitch: 0.0 + +verify-timeout: 60 + +ysmp-invite: "https://discord.gg/3sdUGhNNHp" diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..d6c238f --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,107 @@ +internal-error: "&c&lERROR &7An internal error occurred whilst trying to perform this command" + +player-join: "&a● &f{player}" +player-leave: "&c● &f{player}" + +player-not-online: "&c{player} is not online" +no-players-online: "There are no online players at the moment" +not-a-user: "You are not a registered YSMP user" +already-user: "You are already a member of the YourSitee SMP" + +banned-from-server: "&c&lUnable to connect\n&7You have been banned from the server" +not-whitelisted: "&c&lERROR\n&7You are not whitelisted on this server" +unable-to-fetch: "&c&lERROR\n&7Unable to fetch player data\nTry again later" + +invite: + limit-exceeded: "You have exceeded your invite creation limit" + not-found: "The specified invite does not exist or has been already used" + not-on-guild: "You can only accept invites in the YSMP discord server" + not-joined-guild: "&c&lERROR &7You need to join the YSMP discord server to be able to verify" + verification: "In order to verify your Minecraft username, you need to join `smp.yoursit.ee` and type in the following command: ```/verify {code}```" + use-verify: "&6&lVERIFY &7Type &f/verify &7in order to gain access for the server" + verify-timeout: "&c&lVERIFY\n&7You have exceeded the verification timeout" + verified: "&6&lYSMP &7You are now eligible to play on the YourSitee SMP server\n" + already-verified: "&c&lERROR &7You are already verified" + already-verifying: "This invite is already being verified by another user" + already-verifying-self: "You are already in the verification process" + invalid-code: "&c&lERROR\n&7The specified invite does not exist or has been already used" + +teleport-request: + send: "&6&lTELEPORT &fYou have sent a teleport request to &e{player}" + receive: "&6&lTELEPORT &e{player} &fhas sent you a teleport request" + usage: "&c&lUSAGE &7/tpa " + already: "&c&lERROR &7You have already sent a teleport request to &f{player}" + +teleport-accept: + send: "&6&lTELEPORT &fyou have accepted &e{player}&f's teleport request" + receive: "&6&lTELEPORT &e{player} &faccepted your teleport request" + +teleport-decline: + send: "&6&lTELEPORT &fyou have decliend &e{player}&f's teleport request" + receive: "&6&lTELEPORT &e{player} &fhas declined your teleport request" + +teleport: + no-requests: "&c&lERROR &7You don't have any teleport requests" + no-request-from: "&c&lERROR &f{player} did not send you a teleport request" + +private-message: + send: "&6&lFROM &e{player} &8► &f{message}" + receive: "&6&lTO &e{player} &8► &f{message}" + usage: "&c&lUSAGE &7/msg " + no-open-channels: "&c&lERROR &7You don't have any private message channels open right now" + +home: + updated: "&6&lHOME &7You have successfully assigned a new home location" + teleporting: "&6&lHOME &7Teleporting to home" + not-set: "&c&lERROR &7You don't have a home location set" + deleted: "&6&lHOME &7You have successfully deleted your home location" + +auto-sleep: "&6&lYSMP &7More than 2/3 of the players are sleeping. Switching to day..." + +activity: + players: "{count} online players" + discord: "yoursit.ee/smp" + address: "smp.yoursit.ee" + +no-such-user: "&c&lERROR &7No such user &7{name}" +no-permission: "&c&lERROR &7You don't have permission to use this command" + +group: + not-exists: "&c&lERROR &7Group &f{group} &7does not exist" + usage: "&c&lUSAGE &7/group [...args]" + create: + usage: "&c&lUSAGE &7/group create " + already-exists: "&c&lERROR &7Group &f{group} &7already exists" + created: "&6&lGROUP &7Successfully created group &f{group}" + delete: + usage: "&c&lUSAGE &7/group delete " + not-exists: "&c&lERROR &7Group &f{group} &7does not exist" + deleted: "&6&lGROUP &7Group &f{group} &7has been deleted" + list: "&6&lGROUP &7Available groups: &f{list}" + no-groups: "&6&lGROUP &7There are no available groups yet" + meta: + usage: "&c&lUSAGE &7/group meta [..args]" + info: "&6&lGROUP &7Group &f{group} &7meta information:\n &7prefix: &r{prefix}\n &7suffix: &r{suffix}\n &7tab prefix: &r{tabprefix}\n &7tab suffix: &r{tabsuffix}" + prefix: + usage: "&c&lUSAGE &7/group meta prefix " + set: "&6&lGROUP &7Updated group &f{group} &7prefix to &r{prefix}" + suffix: + usage: "&c&lUSAGE &7/group meta suffix " + set: "&6&lGROUP &7Updated group &f{group} &7suffix to &r{suffix}" + tab-prefix: + usage: "&c&lUSAGE &7/group meta tabprefix " + set: "&6&lGROUP &7Updated group &f{group} &7tab prefix to &r{tabprefix}" + tab-suffix: + usage: "&c&lUSAGE &7/group meta tabsuffix " + set: "&6&lGROUP &7Updated group &f{group} &7tab suffix to &r{tabsuffix}" + permission: + usage: "&c&lUSAGE &7/group permission [..args]" + set: + usage: "&c&lUSAGE &7/group permission set " + updated: "&6&lGROUP &7Set group &f{group} &7permission &f{permission} &7to &f{state}" + unset: + usage: "&c&lUSAGE &7/group permission unset " + updated: "&6&lGROUP &7Unset group &f{group} &7permission &f{permission}" + grant: + usage: "&c&lUSAGE &7/group grant " + granted: "&6&lGROUP &7Granted group &f{group} &7to player &f{player}" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..6f0a442 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,38 @@ +name: ysmp-core +version: 0.1.1 +main: ee.yoursit.core.Main +author: AdvancedAntiSkid +website: yoursit.ee +api-version: 1.20 +softdepend: + - Multiverse-Core + +commands: + tpa: + usage: Send a teleportation request to the specified player + tpaccept: + usage: Accept the teleportation request of a player + tpdeny: + usage: Decline the teleportation request of a player + message: + usage: Send a private message to a player + aliases: + - msg + - m + - pm + - dm + reply: + usage: Reply to the private message to a player + aliases: + - repl + - r + sethome: + usage: Update your home location + delhome: + usage: Delete your home location + home: + usage: Teleport to your home location + verify: + usage: Verify invitation code + group: + usage: Manage groups and permissions diff --git a/src/test/java/GeneratorTest.java b/src/test/java/GeneratorTest.java new file mode 100644 index 0000000..031e3ff --- /dev/null +++ b/src/test/java/GeneratorTest.java @@ -0,0 +1,9 @@ +import ee.yoursit.core.util.InviteGenerator; + +public class GeneratorTest { + public static void main(String[] args) { + InviteGenerator generator = new InviteGenerator(5, 4); + String invite = generator.generate(); + System.out.println(invite); + } +}