From d12fceae48098944ccc7d219536f5761d0c83d36 Mon Sep 17 00:00:00 2001 From: mnewt00 <44464407+mnewt00@users.noreply.github.com> Date: Sun, 27 Mar 2022 21:26:06 +1100 Subject: [PATCH 1/5] Kotlin it is --- .gitattributes | 6 + .gitignore | 3 + BetterStaffChat-bungeecord/build.gradle | 45 -- .../bungeecord/BetterStaffChatBungeeCord.java | 180 ------ .../command/BetterStaffChatCommand.java | 55 -- .../command/MuteStaffChatCommand.java | 54 -- .../bungeecord/command/StaffChatCommand.java | 63 --- .../command/ToggleStaffChatCommand.java | 56 -- .../bungeecord/listener/PlayerListener.java | 163 ------ .../bungeecord/util/Config.java | 98 ---- .../bungeecord/util/LuckPermsUtil.java | 56 -- .../bungeecord/util/StaffChatUtil.java | 176 ------ .../src/main/resources/bungee.yml | 4 - BetterStaffChat-common/build.gradle | 24 - .../BetterStaffChatDependencyProvider.java | 58 -- .../common/dependency/DependencyEngine.java | 221 -------- .../annotations/LoaderPriority.java | 45 -- .../annotations/MavenDependency.java | 94 ---- .../annotations/MavenRepository.java | 66 --- .../dependency/annotations/Relocation.java | 92 ---- .../exception/DependencyLoadException.java | 70 --- .../exception/InvalidDependencyException.java | 72 --- .../dependency/generics/TypeDefinition.java | 44 -- .../common/dependency/maven/Dependency.java | 78 --- .../dependency/maven/DependencyLoader.java | 163 ------ .../dependency/maven/MavenDependencyInfo.java | 274 ---------- .../maven/MavenDependencyLoader.java | 302 ----------- .../dependency/maven/MavenRepositoryInfo.java | 84 --- .../maven/builder/DependencyBuilder.java | 56 -- .../maven/builder/DependencyProvider.java | 48 -- .../builder/MavenDependencyProvider.java | 76 --- .../MavenDependencyProviderBuilder.java | 234 -------- .../relocation/IsolatedClassLoader.java | 74 --- .../relocation/RelocatableDependency.java | 46 -- .../RelocatableDependencyLoader.java | 69 --- .../dependency/relocation/RelocationInfo.java | 104 ---- .../dependency/relocation/Relocator.java | 178 ------ .../common/discord/DiscordWebhook.java | 406 -------------- .../common/discord/JDAImplementation.java | 66 --- .../common/discord/MessageListener.java | 39 -- .../common/plugin/BetterStaffChatPlugin.java | 58 -- .../common/plugin/Platform.java | 5 - .../common/util/AbstractStaffChatUtil.java | 64 --- .../betterstaffchat/common/util/Callback.java | 23 - .../betterstaffchat/common/util/TextUtil.java | 85 --- .../common/util/UpdateChecker.java | 52 -- BetterStaffChat-spigot/build.gradle | 54 -- .../spigot/BetterStaffChatSpigot.java | 181 ------- .../command/BetterStaffChatCommand.java | 57 -- .../spigot/command/MuteStaffChatCommand.java | 63 --- .../spigot/command/StaffChatCommand.java | 66 --- .../command/ToggleStaffChatCommand.java | 65 --- .../spigot/listener/PlayerListener.java | 105 ---- .../spigot/reflection/ReflectionUtil.java | 44 -- .../betterstaffchat/spigot/util/Config.java | 91 ---- .../spigot/util/StaffChatUtil.java | 164 ------ .../src/main/resources/plugin.yml | 4 - build.gradle | 158 ------ build.gradle.kts | 106 ++++ bungeecord/build.gradle.kts | 11 + .../betterstaffchat/bungeecord/BSCBungee.kt | 55 ++ .../bungeecord/commands/BSCBungeeCommand.kt | 60 ++ .../bungeecord/commands/CommandManager.kt | 33 ++ .../commands/impl/BetterStaffChatCommand.kt | 23 + .../commands/impl/StaffChatCommand.kt | 23 + .../commands/impl/StaffChatMuteCommand.kt | 53 ++ .../commands/impl/StaffChatToggleCommand.kt | 60 ++ .../libby/BungeeLibraryManager.java | 51 ++ .../bungeecord/listeners/PlayerListener.kt | 60 ++ .../bungeecord/util/PlayerUtil.kt | 49 ++ common/build.gradle.kts | 1 + .../betterstaffchat/common/BSCPlugin.kt | 17 + .../betterstaffchat/common/PlayerMeta.kt | 8 + .../common/libraries/BSCLibraries.kt | 120 ++++ .../common/libraries/libby/Library.java | 511 ++++++++++++++++++ .../libraries/libby/LibraryManager.java | 455 ++++++++++++++++ .../common/libraries/libby/Repositories.java | 31 ++ .../classloader/IsolatedClassLoader.java | 51 ++ .../classloader/URLClassLoaderHelper.java | 233 ++++++++ .../libraries/libby/logging/LogLevel.java | 26 + .../libraries/libby/logging/Logger.java | 196 +++++++ .../libby/logging/adapters/JDKLogAdapter.java | 77 +++ .../libby/logging/adapters/LogAdapter.java | 25 + .../libby/relocation/Relocation.java | 184 +++++++ .../libby/relocation/RelocationHelper.java | 130 +++++ .../betterstaffchat/common/util/Config.kt | 66 +++ .../betterstaffchat/common/util/Data.kt | 46 ++ .../common/util/StaffChatUtil.kt | 65 +++ .../betterstaffchat/common/util/TextUtil.kt | 16 + gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 18 - gradlew | 53 +- gradlew.bat | 25 +- settings.gradle | 23 - settings.gradle.kts | 11 + spigot/build.gradle.kts | 12 + .../betterstaffchat/spigot/BSCSpigot.kt | 56 ++ .../spigot/commands/BSCSpigotCommand.kt | 57 ++ .../spigot/commands/CommandManager.kt | 40 ++ .../commands/impl/BetterStaffChatCommand.kt | 23 + .../spigot/commands/impl/StaffChatCommand.kt | 29 + .../commands/impl/StaffChatMuteCommand.kt | 57 ++ .../commands/impl/StaffChatToggleCommand.kt | 64 +++ .../spigot/libby/BukkitLibraryManager.java | 51 ++ .../spigot/listeners/PlayerListener.kt | 51 ++ .../betterstaffchat/spigot/util/PlayerUtil.kt | 35 ++ .../spigot/util/ReflectionUtil.kt | 27 + src/main/resources/bungeecord.yml | 3 + .../src => src}/main/resources/config.yml | 4 +- src/main/resources/plugin.yml | 3 + 110 files changed, 3404 insertions(+), 5461 deletions(-) create mode 100644 .gitattributes delete mode 100644 BetterStaffChat-bungeecord/build.gradle delete mode 100755 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/BetterStaffChatBungeeCord.java delete mode 100755 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/BetterStaffChatCommand.java delete mode 100644 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/MuteStaffChatCommand.java delete mode 100644 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/StaffChatCommand.java delete mode 100755 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/ToggleStaffChatCommand.java delete mode 100755 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/listener/PlayerListener.java delete mode 100644 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/Config.java delete mode 100755 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/LuckPermsUtil.java delete mode 100755 BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/StaffChatUtil.java delete mode 100644 BetterStaffChat-bungeecord/src/main/resources/bungee.yml delete mode 100644 BetterStaffChat-common/build.gradle delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/BetterStaffChatDependencyProvider.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/DependencyEngine.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/LoaderPriority.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/MavenDependency.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/MavenRepository.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/Relocation.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/exception/DependencyLoadException.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/exception/InvalidDependencyException.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/generics/TypeDefinition.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/Dependency.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/DependencyLoader.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenDependencyInfo.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenDependencyLoader.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenRepositoryInfo.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/DependencyBuilder.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/DependencyProvider.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/MavenDependencyProvider.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/MavenDependencyProviderBuilder.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/IsolatedClassLoader.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocatableDependency.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocatableDependencyLoader.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocationInfo.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/Relocator.java delete mode 100644 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/DiscordWebhook.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/JDAImplementation.java delete mode 100644 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/MessageListener.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/plugin/BetterStaffChatPlugin.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/plugin/Platform.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/AbstractStaffChatUtil.java delete mode 100644 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/Callback.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/TextUtil.java delete mode 100755 BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/UpdateChecker.java delete mode 100644 BetterStaffChat-spigot/build.gradle delete mode 100755 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/BetterStaffChatSpigot.java delete mode 100755 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/BetterStaffChatCommand.java delete mode 100644 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/MuteStaffChatCommand.java delete mode 100644 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/StaffChatCommand.java delete mode 100644 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/ToggleStaffChatCommand.java delete mode 100644 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/listener/PlayerListener.java delete mode 100644 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/reflection/ReflectionUtil.java delete mode 100644 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/util/Config.java delete mode 100755 BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/util/StaffChatUtil.java delete mode 100644 BetterStaffChat-spigot/src/main/resources/plugin.yml delete mode 100755 build.gradle create mode 100644 build.gradle.kts create mode 100644 bungeecord/build.gradle.kts create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/BSCBungeeCommand.kt create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/BetterStaffChatCommand.kt create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt create mode 100644 bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt create mode 100644 common/build.gradle.kts create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Library.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Data.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/util/TextUtil.kt delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts create mode 100644 spigot/build.gradle.kts create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/BSCSpigotCommand.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/BetterStaffChatCommand.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt create mode 100644 spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/ReflectionUtil.kt create mode 100644 src/main/resources/bungeecord.yml rename {BetterStaffChat-common/src => src}/main/resources/config.yml (98%) mode change 100755 => 100644 create mode 100644 src/main/resources/plugin.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..00a51af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore index 2b3de74..e87d4f1 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,6 @@ run/ # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar + +# Ignore Gradle build output directory +build diff --git a/BetterStaffChat-bungeecord/build.gradle b/BetterStaffChat-bungeecord/build.gradle deleted file mode 100644 index 6d56e23..0000000 --- a/BetterStaffChat-bungeecord/build.gradle +++ /dev/null @@ -1,45 +0,0 @@ -/* - * BetterStaffChat - build.gradle - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import org.apache.tools.ant.filters.ReplaceTokens - -plugins { - id 'com.github.johnrengelman.shadow' -} - -repositories { - maven { - name = 'sonatype' - url = 'https://oss.sonatype.org/content/groups/public/' - } -} - -dependencies { - compileOnly 'net.md-5:bungeecord-api:1.17-R0.1-SNAPSHOT' - compileOnly 'net.luckperms:api:5.3' - implementation 'org.bstats:bstats-bungeecord:2.2.1' - implementation project(":BetterStaffChat-common") -} - -processResources { - filter ReplaceTokens, tokens: [ - "version": parent.getVersion() - ] -} - -tasks.build.dependsOn tasks.shadowJar diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/BetterStaffChatBungeeCord.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/BetterStaffChatBungeeCord.java deleted file mode 100755 index 0679df8..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/BetterStaffChatBungeeCord.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * BetterStaffChat - BetterStaffChatBungeeCord.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord; - -import com.google.common.collect.Lists; -import dev.austech.betterstaffchat.bungeecord.command.BetterStaffChatCommand; -import dev.austech.betterstaffchat.bungeecord.command.MuteStaffChatCommand; -import dev.austech.betterstaffchat.bungeecord.command.StaffChatCommand; -import dev.austech.betterstaffchat.bungeecord.command.ToggleStaffChatCommand; -import dev.austech.betterstaffchat.bungeecord.listener.PlayerListener; -import dev.austech.betterstaffchat.bungeecord.util.Config; -import dev.austech.betterstaffchat.bungeecord.util.LuckPermsUtil; -import dev.austech.betterstaffchat.bungeecord.util.StaffChatUtil; -import dev.austech.betterstaffchat.common.plugin.BetterStaffChatPlugin; -import dev.austech.betterstaffchat.common.dependency.BetterStaffChatDependencyProvider; -import dev.austech.betterstaffchat.common.dependency.DependencyEngine; -import dev.austech.betterstaffchat.common.discord.JDAImplementation; -import dev.austech.betterstaffchat.common.plugin.Platform; -import dev.austech.betterstaffchat.common.util.TextUtil; -import dev.austech.betterstaffchat.common.util.UpdateChecker; -import lombok.Getter; -import lombok.Setter; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.config.Configuration; -import org.bstats.bungeecord.Metrics; - -import javax.naming.ConfigurationException; -import javax.security.auth.login.LoginException; -import java.io.File; -import java.util.ArrayList; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; - -public final class BetterStaffChatBungeeCord extends Plugin implements BetterStaffChatPlugin { - @Getter private static BetterStaffChatBungeeCord instance; - @Getter @Setter private Configuration config; - @Getter private final ArrayList ignoreStaffChat = Lists.newArrayList(); - @Getter private final ArrayList toggledStaffChat = Lists.newArrayList(); - @Getter private JDAImplementation jda; - @Getter boolean discordEnabled; - - @Override - public void onEnable() { - instance = this; - - TextUtil.init(getPlatform(), this); - Config.load(); - - if (getConfig().getBoolean("check-for-updates")) - getProxy().getScheduler().runAsync(this, () -> { - if (UpdateChecker.needsUpdate(this, getDescription().getVersion())) { - getProxy().getScheduler().schedule(this, () -> { - logPrefix("&eA new update for BetterStaffChat is available..."); - logPrefix("&ehttps://www.spigotmc.org/resources/91991"); - }, 3, TimeUnit.SECONDS); - } - }); - - new Metrics(this, 10954); - - if (getConfig().getBoolean("discord.bot.enabled") && getConfig().getBoolean("discord.webhook.enabled")) { - new ConfigurationException("Both Discord types are enabled").printStackTrace(); - return; - } - - if (this.getProxy().getPluginManager().getPlugin("LuckPerms") != null) { - LuckPermsUtil.setLuckPerms(net.luckperms.api.LuckPermsProvider.get()); - } - - this.discordEnabled = getConfig().getBoolean("discord.bot.enabled"); - - DependencyEngine dependencyEngine = DependencyEngine.createNew(new File(getPluginDataFolder(), "libs").toPath()); - dependencyEngine.addDependenciesFromProvider(BetterStaffChatDependencyProvider.getDependencies()); - - dependencyEngine.loadDependencies().thenAccept((empty) -> { - if (!dependencyEngine.getErrors().isEmpty()) { - Optional opt = dependencyEngine.getErrors().stream().filter(throwable -> throwable.getMessage().contains("Unable to make protected void java.net.URLClassLoader.addURL(java.net.URL) accessible: module java.base does not")).findFirst(); - if (opt.isPresent()) { - getLogger().log(Level.SEVERE, "An error occurred whilst starting BetterStaffChat - This is due to Java 16 and up being incompatible by default."); - getLogger().log(Level.SEVERE, "This error is fixable, please add the following flags to your startup after the \"java\":"); - getLogger().log(Level.SEVERE, ""); - getLogger().log(Level.SEVERE, "--add-opens java.base/java.net=ALL-UNNAMED"); - } else { - dependencyEngine.getErrors().forEach(Throwable::printStackTrace); - getLogger().log(Level.SEVERE, "Errors occurred whilst loading BSC."); - } - return; - } - - if (discordEnabled) { - this.getProxy().getScheduler().runAsync(this, () -> { - try { - this.jda = new JDAImplementation(net.dv8tion.jda.api.JDABuilder.createLight(getConfig().getString("discord.bot.token")).build(), StaffChatUtil.getInstance()); - jda.asJda().getPresence().setActivity(net.dv8tion.jda.api.entities.Activity.of( - net.dv8tion.jda.api.entities.Activity.ActivityType.valueOf(getConfig().getString("discord.bot.activity-type").toUpperCase().replace("PLAYING", "DEFAULT")), - getConfig().getString("discord.bot.activity") - )); - } catch (LoginException exception) { - exception.printStackTrace(); - } - }); - } - - this.getProxy().getPluginManager().registerListener(this, new PlayerListener()); - registerCommands(); - }); - } - - public File getPluginDataFolder() { - return getDataFolder(); - } - - public void logPrefix(String string) { - getProxy().getConsole().sendMessage(new TextComponent("[BetterStaffChat] " + TextUtil.colorize(string))); - } - - public void logPrefixDebug(String string) { - if (getConfig().getBoolean("debug")) - getProxy().getConsole().sendMessage(new TextComponent("[BetterStaffChat] Debug - " + TextUtil.colorize(string))); - } - - public void log(String string) { - getProxy().getConsole().sendMessage(new TextComponent(TextUtil.colorize(string))); - } - - @Override public Platform getPlatform() { - return Platform.BUNGEECORD; - } - - public boolean reloadConfig(CommandSender sender) { - boolean discordLoaded = getConfig().getBoolean("discord.bot.enabled"); - Config.load(); - - getProxy().getPluginManager().unregisterCommands(this); - registerCommands(); - - if (getConfig().getBoolean("discord.bot.enabled") != discordLoaded) { - logPrefix("&cYou enabled the discord bot in the config. Please restart the server for changes to take effect."); - if (sender instanceof ProxiedPlayer) - sender.sendMessage(TextUtil.colorizeToComponent("&cYou enabled the discord bot in the config. Please restart the server for changes to take effect.")); - return true; - } - return false; - } - - private void registerCommands() { - this.getProxy().getScheduler().runAsync(this, () -> { - this.getProxy().getPluginManager().registerCommand(this, new BetterStaffChatCommand()); - this.getProxy().getPluginManager().registerCommand(this, new StaffChatCommand("staffchat", "betterstaffchat.messages.send", getConfig().getStringList("commands.staffchat.aliases").toArray(new String[0]))); - this.getProxy().getPluginManager().registerCommand(this, new MuteStaffChatCommand("mutestaffchat", "betterstaffchat.mutestaffchat", getConfig().getStringList("commands.mutestaffchat.aliases").toArray(new String[0]))); - this.getProxy().getPluginManager().registerCommand(this, new ToggleStaffChatCommand("togglestaffchat", "betterstaffchat.togglestaffchat", getConfig().getStringList("commands.togglestaffchat.aliases").toArray(new String[0]))); - }); - } - - @Override - public void onDisable() { - if (isDiscordEnabled()) (getJda()).shutdown(); - } -} diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/BetterStaffChatCommand.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/BetterStaffChatCommand.java deleted file mode 100755 index a21f61a..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/BetterStaffChatCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * BetterStaffChat - BetterStaffChatCommand.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord.command; - -import dev.austech.betterstaffchat.bungeecord.BetterStaffChatBungeeCord; -import dev.austech.betterstaffchat.common.util.TextUtil; -import net.dv8tion.jda.api.entities.Activity; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.plugin.Command; - -public class BetterStaffChatCommand extends Command { - public BetterStaffChatCommand() { - super("betterstaffchat", null, "bsc"); - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (args.length == 1 && args[0].equalsIgnoreCase("reload") && sender.hasPermission("betterstaffchat.reload")) { - boolean reload = BetterStaffChatBungeeCord.getInstance().reloadConfig(sender); - if (!reload && BetterStaffChatBungeeCord.getInstance().getConfig().getBoolean("discord.bot.enabled")) { - BetterStaffChatBungeeCord.getInstance().getJda().asJda().getPresence().setActivity(Activity.of( - Activity.ActivityType.valueOf(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.bot.activity-type").toUpperCase().replace("PLAYING", "DEFAULT")), - BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.bot.activity") - )); - } - - sender.sendMessage(TextUtil.colorizeToComponent("&8&l&m-------------------------------")); - sender.sendMessage(TextUtil.colorizeToComponent(TextUtil.spacer(18) + "&c&l&oBetter&4&l&oStaffChat")); - sender.sendMessage(TextUtil.colorizeToComponent(TextUtil.spacer(13) + "&7Configuration Reloaded")); - sender.sendMessage(TextUtil.colorizeToComponent("&8&l&m-------------------------------")); - return; - } - - sender.sendMessage(TextUtil.colorizeToComponent("&8&l&m-------------------------------")); - sender.sendMessage(TextUtil.colorizeToComponent(TextUtil.spacer(18) + "&c&l&oBetter&4&l&oStaffChat")); - sender.sendMessage(TextUtil.colorizeToComponent(TextUtil.spacer(13) + "&7The &7&o\"better\"&r &7staff chat.")); - sender.sendMessage(TextUtil.colorizeToComponent("&8&l&m-------------------------------")); - } -} diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/MuteStaffChatCommand.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/MuteStaffChatCommand.java deleted file mode 100644 index 965faff..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/MuteStaffChatCommand.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * BetterStaffChat - MuteStaffChatCommand.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord.command; - -import dev.austech.betterstaffchat.bungeecord.BetterStaffChatBungeeCord; -import dev.austech.betterstaffchat.common.util.TextUtil; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.plugin.Command; - -public class MuteStaffChatCommand extends Command { - public MuteStaffChatCommand(String command, String permission, String... aliases) { - super(command, permission, aliases); - setPermissionMessage(TextUtil.colorize("&cNo permission.")); - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { - sender.sendMessage(TextUtil.colorizeToComponent("&cThis command can only be used by players.")); - return; - } - - if (BetterStaffChatBungeeCord.getInstance().getIgnoreStaffChat().contains(((ProxiedPlayer) sender).getUniqueId())) { - BetterStaffChatBungeeCord.getInstance().getIgnoreStaffChat().remove(((ProxiedPlayer) sender).getUniqueId()); - } else { - if (BetterStaffChatBungeeCord.getInstance().getToggledStaffChat().contains(((ProxiedPlayer) sender).getUniqueId())) { - BetterStaffChatBungeeCord.getInstance().getToggledStaffChat().remove(((ProxiedPlayer) sender).getUniqueId()); - } - BetterStaffChatBungeeCord.getInstance().getIgnoreStaffChat().add(((ProxiedPlayer) sender).getUniqueId()); - } - - sender.sendMessage(TextUtil.colorizeToComponent(BetterStaffChatBungeeCord.getInstance().getIgnoreStaffChat().contains(((ProxiedPlayer) sender).getUniqueId()) ? - BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.mute-on") : - BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.mute-off")) - ); - } -} diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/StaffChatCommand.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/StaffChatCommand.java deleted file mode 100644 index afb6c63..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/StaffChatCommand.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * BetterStaffChat - StaffChatCommand.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord.command; - -import dev.austech.betterstaffchat.bungeecord.BetterStaffChatBungeeCord; -import dev.austech.betterstaffchat.bungeecord.util.StaffChatUtil; -import dev.austech.betterstaffchat.common.util.TextUtil; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.plugin.Command; - -import java.util.Arrays; - -public class StaffChatCommand extends Command { - public StaffChatCommand(String command, String permission, String... aliases) { - super(command, permission, aliases); - setPermissionMessage(TextUtil.colorize("&cNo permission.")); - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (sender instanceof ProxiedPlayer && BetterStaffChatBungeeCord.getInstance().getIgnoreStaffChat().contains(((ProxiedPlayer) sender).getUniqueId())) { - sender.sendMessage(TextUtil.colorizeToComponent("&cYour staff chat is currently disabled.")); - return; - } - - if (args.length == 0) { - sender.sendMessage(TextUtil.colorizeToComponent("&cUsage: /" + this.getName() + " ")); - return; - } - - StaffChatUtil.getInstance().broadcast(StaffChatUtil.getInstance().getFormattedMessage(sender, String.join(" ", Arrays.copyOfRange(args, 0, args.length))), "betterstaffchat.messages.read"); - - if (sender instanceof ProxiedPlayer) - StaffChatUtil.getInstance().discord(sender, ChatColor.stripColor(TextUtil.colorize(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.staffchat-format") - .replace("%player_name%", sender.getName()) - .replace("%message%", String.join(" ", Arrays.copyOfRange(args, 0, args.length))) - .replace("%server%", StaffChatUtil.getFormattedServerName(((ProxiedPlayer) sender).getServer().getInfo().getName()))))); - else - StaffChatUtil.getInstance().discord(sender, ChatColor.stripColor(TextUtil.colorize(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.staffchat-format") - .replace("%player_name%", BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-replacement")) - .replace("%message%", String.join(" ", Arrays.copyOfRange(args, 0, args.length))) - .replace("%server%", BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-server-replacement")) - ))); - } -} diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/ToggleStaffChatCommand.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/ToggleStaffChatCommand.java deleted file mode 100755 index 4af7f74..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/command/ToggleStaffChatCommand.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * BetterStaffChat - ToggleStaffChatCommand.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord.command; - -import dev.austech.betterstaffchat.bungeecord.BetterStaffChatBungeeCord; -import dev.austech.betterstaffchat.common.util.TextUtil; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.plugin.Command; - -public class ToggleStaffChatCommand extends Command { - public ToggleStaffChatCommand(String command, String permission, String... aliases) { - super(command, permission, aliases); - setPermissionMessage(TextUtil.colorize("&cNo permission.")); - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { - sender.sendMessage(TextUtil.colorizeToComponent("&cThis command can only be used by players.")); - return; - } - - if (BetterStaffChatBungeeCord.getInstance().getIgnoreStaffChat().contains(((ProxiedPlayer) sender).getUniqueId())) { - sender.sendMessage(TextUtil.colorizeToComponent("&cYour staff chat is currently disabled.")); - return; - } - - if (BetterStaffChatBungeeCord.getInstance().getToggledStaffChat().contains(((ProxiedPlayer) sender).getUniqueId())) { - BetterStaffChatBungeeCord.getInstance().getToggledStaffChat().remove(((ProxiedPlayer) sender).getUniqueId()); - } else { - BetterStaffChatBungeeCord.getInstance().getToggledStaffChat().add(((ProxiedPlayer) sender).getUniqueId()); - } - - sender.sendMessage(TextUtil.colorizeToComponent(BetterStaffChatBungeeCord.getInstance().getToggledStaffChat().contains(((ProxiedPlayer) sender).getUniqueId()) ? - BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.toggle-on") : - BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.toggle-off")) - ); - } -} diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/listener/PlayerListener.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/listener/PlayerListener.java deleted file mode 100755 index 83f39ab..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/listener/PlayerListener.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * BetterStaffChat - PlayerListener.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord.listener; - -import dev.austech.betterstaffchat.bungeecord.BetterStaffChatBungeeCord; -import dev.austech.betterstaffchat.bungeecord.util.LuckPermsUtil; -import dev.austech.betterstaffchat.bungeecord.util.StaffChatUtil; -import dev.austech.betterstaffchat.common.util.TextUtil; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.ChatEvent; -import net.md_5.bungee.api.event.PlayerDisconnectEvent; -import net.md_5.bungee.api.event.PostLoginEvent; -import net.md_5.bungee.api.event.ServerSwitchEvent; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.api.scheduler.ScheduledTask; -import net.md_5.bungee.event.EventHandler; - -import java.util.concurrent.TimeUnit; - -public class PlayerListener implements Listener { - @EventHandler - public void onPlayerChat(ChatEvent event) { - if (event.getMessage().startsWith("/")) return; - - String prefix = BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.prefix"); - if (prefix.startsWith("/")) return; - - ProxiedPlayer player = (ProxiedPlayer) event.getSender(); - - if (player.hasPermission("betterstaffchat.messages.send") && event.getMessage().startsWith(prefix) && event.getMessage().length() > prefix.length() && !prefix.equals("")) { - event.setCancelled(true); - - if (BetterStaffChatBungeeCord.getInstance().getIgnoreStaffChat().contains(player.getUniqueId())) { - player.sendMessage(TextUtil.colorizeToComponent("&cYour staff chat is currently disabled.")); - return; - } - - StaffChatUtil.getInstance().broadcast(StaffChatUtil.getInstance().getFormattedMessage(player, event.getMessage().substring(prefix.length())), "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord( - player, - ChatColor.stripColor(TextUtil.colorize(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.staffchat-format") - .replace("%player_name%", player.getName()) - .replace("%message%", event.getMessage().substring(prefix.length())) - .replace("%server%", StaffChatUtil.getFormattedServerName(player.getServer().getInfo().getName())) - ))); - } else if (player.hasPermission("betterstaffchat.messages.send") && BetterStaffChatBungeeCord.getInstance().getToggledStaffChat().contains(player.getUniqueId())) { - event.setCancelled(true); - - StaffChatUtil.getInstance().broadcast(StaffChatUtil.getInstance().getFormattedMessage(player, event.getMessage()), "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord( - player, - ChatColor.stripColor(TextUtil.colorize(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.staffchat-format") - .replace("%player_name%", player.getName()) - .replace("%message%", event.getMessage()) - .replace("%server%", StaffChatUtil.getFormattedServerName(player.getServer().getInfo().getName())) - ))); - } - } - - @EventHandler - public void onPlayerPostLogin(PostLoginEvent event) { - if (event.getPlayer().hasPermission("betterstaffchat.messages.join") && !BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.join").equals("")) { - BetterStaffChatBungeeCord.getInstance().getProxy().getScheduler().schedule(BetterStaffChatBungeeCord.getInstance(), () -> { - // This is temporarily disable so a fix can be added. - // Although it does prevent some errors, it stops the staff-chat working in some instances. - - // if (event.getPlayer().getServer() == null) return; - - - int timeDelay = 100; - if (BetterStaffChatBungeeCord.getInstance().getConfig().get("join-delay")) timeDelay = BetterStaffChatBungeeCord.getInstance().getConfig().getInt("join-delay"); - - ScheduledTask task = BetterStaffChatBungeeCord.getInstance().getProxy().getScheduler().schedule(BetterStaffChatBungeeCord.getInstance(), () -> { - - String message = TextUtil.colorize( - BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.join") - .replace("%player_name%", event.getPlayer().getName()) - .replace("%server%", StaffChatUtil.getFormattedServerName(event.getPlayer().getServer().getInfo().getName())) - ); - - if (BetterStaffChatBungeeCord.getInstance().getProxy().getPluginManager().getPlugin("LuckPerms") != null) { - message = message.replace("%luckperms_prefix%", LuckPermsUtil.getPrefix(event.getPlayer())).replace("%luckperms_suffix%", LuckPermsUtil.getSuffix(event.getPlayer())); - } - - message = TextUtil.colorize(message); - - StaffChatUtil.getInstance().broadcast(message, "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord(event.getPlayer(), ChatColor.stripColor(TextUtil.colorize(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.staffchat-join") - .replace("%player_name%", event.getPlayer().getName()) - .replace("%server%", StaffChatUtil.getFormattedServerName(event.getPlayer().getServer().getInfo().getName()))))); - }, 100, TimeUnit.MILLISECONDS); - }, timeDelay, TimeUnit.SECONDS); - } - } - - @EventHandler - public void onPlayerDisconnect(PlayerDisconnectEvent event) { - if (event.getPlayer().hasPermission("betterstaffchat.messages.leave") && !BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.leave").equals("")) { - BetterStaffChatBungeeCord.getInstance().getProxy().getScheduler().schedule(BetterStaffChatBungeeCord.getInstance(), () -> { - if (event.getPlayer().getServer() == null) return; - String message = TextUtil.colorize( - BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.leave") - .replace("%player_name%", event.getPlayer().getName()) - .replace("%server%", StaffChatUtil.getFormattedServerName(event.getPlayer().getServer().getInfo().getName())) - ); - - if (BetterStaffChatBungeeCord.getInstance().getProxy().getPluginManager().getPlugin("LuckPerms") != null) { - message = message.replace("%luckperms_prefix%", LuckPermsUtil.getPrefix(event.getPlayer())).replace("%luckperms_suffix%", LuckPermsUtil.getSuffix(event.getPlayer())); - } - - message = TextUtil.colorize(message); - - StaffChatUtil.getInstance().broadcast(message, "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord(event.getPlayer(), ChatColor.stripColor(TextUtil.colorize(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.staffchat-leave") - .replace("%player_name%", event.getPlayer().getName()) - .replace("%server%", StaffChatUtil.getFormattedServerName(event.getPlayer().getServer().getInfo().getName()))))); - }, 1, TimeUnit.SECONDS); - } - } - - @EventHandler - public void onServerSwitch(ServerSwitchEvent event) { - if (event.getPlayer().hasPermission("betterstaffchat.messages.switch") && !BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.switch").equals("")) { - if (event.getFrom() == null) return; - - String message = TextUtil.colorize( - BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.switch") - .replace("%player_name%", event.getPlayer().getName()) - .replace("%from%", StaffChatUtil.getFormattedServerName(event.getFrom().getName())) - .replace("%to%", StaffChatUtil.getFormattedServerName(event.getPlayer().getServer().getInfo().getName())) - ); - - if (BetterStaffChatBungeeCord.getInstance().getProxy().getPluginManager().getPlugin("LuckPerms") != null) { - message = message.replace("%luckperms_prefix%", LuckPermsUtil.getPrefix(event.getPlayer())).replace("%luckperms_suffix%", LuckPermsUtil.getSuffix(event.getPlayer())); - } - - message = TextUtil.colorize(message); - - StaffChatUtil.getInstance().broadcast(message, "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord(event.getPlayer(), ChatColor.stripColor(TextUtil.colorize(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.staffchat-switch") - .replace("%player_name%", event.getPlayer().getName())) - .replace("%from%", StaffChatUtil.getFormattedServerName(event.getFrom().getName())) - .replace("%to%", StaffChatUtil.getFormattedServerName(event.getPlayer().getServer().getInfo().getName())))); - } - } -} diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/Config.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/Config.java deleted file mode 100644 index 69cea46..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/Config.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * BetterStaffChat - Config.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord.util; - -import dev.austech.betterstaffchat.bungeecord.BetterStaffChatBungeeCord; -import lombok.experimental.UtilityClass; -import net.md_5.bungee.config.Configuration; -import net.md_5.bungee.config.ConfigurationProvider; -import net.md_5.bungee.config.YamlConfiguration; -import org.yaml.snakeyaml.parser.ParserException; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.util.Optional; - -@UtilityClass -public class Config { - public void load() { - BetterStaffChatBungeeCord.getInstance().getDataFolder().mkdir(); - - File file = new File(BetterStaffChatBungeeCord.getInstance().getPluginDataFolder(), "config.yml"); - - if (!file.exists()) { - try (InputStream in = BetterStaffChatBungeeCord.getInstance().getClass().getResourceAsStream("/config.yml")) { - Files.copy(in, file.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - } - try { - Configuration config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(BetterStaffChatBungeeCord.getInstance().getPluginDataFolder(), "config.yml")); - - int currentVersion = ConfigurationProvider.getProvider(YamlConfiguration.class).load(BetterStaffChatBungeeCord.getInstance().getClass().getResourceAsStream("/config.yml")).getInt("config-version"); - - if (config.getInt("config-version") != currentVersion) - newVersion(config.getInt("config-version")); - else - BetterStaffChatBungeeCord.getInstance().setConfig(config); - } catch (IOException e) { - e.printStackTrace(); - } catch (ParserException exception) { - File broken = new File(file.getAbsolutePath() + ".broken." + System.currentTimeMillis()); - file.renameTo(broken); - BetterStaffChatBungeeCord.getInstance().logPrefix("&cThe config file is broken, and has been renamed to config.yml.broken." + System.currentTimeMillis()); - - try (InputStream in = BetterStaffChatBungeeCord.getInstance().getClass().getResourceAsStream("/config.yml")) { - Files.copy(in, file.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - - try { - BetterStaffChatBungeeCord.getInstance().setConfig(ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(BetterStaffChatBungeeCord.getInstance().getPluginDataFolder(), "config.yml"))); - } catch (IOException ex1) { - ex1.printStackTrace(); - } - } - } - - public Optional getOptional(String path) { - String find = BetterStaffChatBungeeCord.getInstance().getConfig().getString(path); - return Optional.ofNullable(find.isEmpty() ? null : find); - } - - public void newVersion(int ver) throws IOException { - File file = new File(BetterStaffChatBungeeCord.getInstance().getPluginDataFolder(), "config.yml"); - - File broken = new File(file.getAbsolutePath() + ".old." + ver); - file.renameTo(broken); - BetterStaffChatBungeeCord.getInstance().logPrefix("&cThe config file is old, and has been renamed to config.yml.old." + ver); - - try (InputStream inputStream = BetterStaffChatBungeeCord.getInstance().getClass().getResourceAsStream("/config.yml")) { - Files.copy(inputStream, file.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - - BetterStaffChatBungeeCord.getInstance().setConfig(ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(BetterStaffChatBungeeCord.getInstance().getPluginDataFolder(), "config.yml"))); - } -} diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/LuckPermsUtil.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/LuckPermsUtil.java deleted file mode 100755 index 7e7129c..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/LuckPermsUtil.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * BetterStaffChat - LuckPermsUtil.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord.util; - -import dev.austech.betterstaffchat.common.util.TextUtil; -import lombok.Setter; -import lombok.experimental.UtilityClass; -import net.luckperms.api.LuckPerms; -import net.luckperms.api.model.user.User; -import net.md_5.bungee.api.connection.ProxiedPlayer; - -@UtilityClass -public class LuckPermsUtil { - @Setter - private LuckPerms luckPerms; - - /** - * Gets a player's LuckPerms prefix. - * - * @param player the player to get the prefix - * @return the prefix - */ - public String getPrefix(ProxiedPlayer player) { - User lpUser = luckPerms.getUserManager().getUser(player.getUniqueId()); - if (lpUser.getCachedData().getMetaData().getPrefix() == null) return ""; - return TextUtil.colorize(lpUser.getCachedData().getMetaData().getPrefix()); - } - - /** - * Gets a player's LuckPerms suffix. - * - * @param player the player to get the suffix - * @return the suffix - */ - public String getSuffix(ProxiedPlayer player) { - User lpUser = luckPerms.getUserManager().getUser(player.getUniqueId()); - if (lpUser.getCachedData().getMetaData().getSuffix() == null) return ""; - return TextUtil.colorize(lpUser.getCachedData().getMetaData().getSuffix()); - } -} diff --git a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/StaffChatUtil.java b/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/StaffChatUtil.java deleted file mode 100755 index 42d48a8..0000000 --- a/BetterStaffChat-bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/util/StaffChatUtil.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * BetterStaffChat - StaffChatUtil.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.bungeecord.util; - -import dev.austech.betterstaffchat.bungeecord.BetterStaffChatBungeeCord; -import dev.austech.betterstaffchat.common.discord.DiscordWebhook; -import dev.austech.betterstaffchat.common.util.AbstractStaffChatUtil; -import dev.austech.betterstaffchat.common.util.TextUtil; -import lombok.Getter; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.connection.ProxiedPlayer; - -import java.awt.*; -import java.io.IOException; - -public class StaffChatUtil extends AbstractStaffChatUtil { - @Getter - private static final StaffChatUtil instance = new StaffChatUtil(); - - /** - * Gets the formatted server name from the config, if exists. - * - * @param serverName the server to replace - * @return the server with the formatted name or the provided server name if not found - */ - public static String getFormattedServerName(String serverName) { - return Config.getOptional("staffchat.server-replacement." + serverName).orElse(serverName); - } - - @Override - public String getFormattedMessage(Object sender, String message) { - StringBuilder builder = new StringBuilder(); - message = BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.format") - .replace("%player_name%", (sender instanceof ProxiedPlayer) ? ((ProxiedPlayer) sender).getName() : BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-replacement")) - .replace("%message%", BetterStaffChatBungeeCord.getInstance().getConfig().getBoolean("staffchat.strip-color-codes") ? ChatColor.stripColor(TextUtil.colorize(message)) : message) - .replace("%server%", (sender instanceof ProxiedPlayer) ? getFormattedServerName(((ProxiedPlayer) sender).getServer().getInfo().getName()) : BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-server-replacement")); - - if (BetterStaffChatBungeeCord.getInstance().getProxy().getPluginManager().getPlugin("LuckPerms") != null && sender instanceof ProxiedPlayer) { - message = message.replace("%luckperms_prefix%", LuckPermsUtil.getPrefix((ProxiedPlayer) sender)).replace("%luckperms_suffix%", LuckPermsUtil.getSuffix((ProxiedPlayer) sender)); - } else if (BetterStaffChatBungeeCord.getInstance().getProxy().getPluginManager().getPlugin("LuckPerms") != null) { - message = message.replace("%luckperms_prefix%", "").replace("%luckperms_suffix%", ""); - } - - for (String word : message.split(" ")) { - builder.append(TextUtil.colorize(word)); - builder.append(" "); - } - - return builder.substring(0, builder.length() - 1); - } - - @Override - public void broadcast(String string, String permission) { - for (ProxiedPlayer player : BetterStaffChatBungeeCord.getInstance().getProxy().getPlayers()) { - if (!BetterStaffChatBungeeCord.getInstance().getIgnoreStaffChat().contains(player.getUniqueId()) && player.hasPermission(permission)) { - player.sendMessage(TextUtil.colorizeToComponent(string)); - } - } - - if (BetterStaffChatBungeeCord.getInstance().getConfig().getBoolean("staffchat.log-to-console")) { - BetterStaffChatBungeeCord.getInstance().log(TextUtil.colorize(string)); - } - } - - public void discord(Object sender, String string) { - if (BetterStaffChatBungeeCord.getInstance().getProxy().getPluginManager().getPlugin("LuckPerms") != null && sender instanceof ProxiedPlayer) { - string = string.replace("%luckperms_prefix%", ChatColor.stripColor(TextUtil.colorize(LuckPermsUtil.getPrefix((ProxiedPlayer) sender)))).replace("%luckperms_suffix%", ChatColor.stripColor(TextUtil.colorize(LuckPermsUtil.getSuffix((ProxiedPlayer) sender)))); - } else if (BetterStaffChatBungeeCord.getInstance().getProxy().getPluginManager().getPlugin("LuckPerms") != null) { - string = string.replace("%luckperms_prefix%", "").replace("%luckperms_suffix%", ""); - } - - String finalString = string; - BetterStaffChatBungeeCord.getInstance().getProxy().getScheduler().runAsync(BetterStaffChatBungeeCord.getInstance(), () -> { - if (BetterStaffChatBungeeCord.getInstance().getConfig().getBoolean("discord.webhook.enabled")) { - DiscordWebhook webhook = new DiscordWebhook(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.webhook.url")); - - if (BetterStaffChatBungeeCord.getInstance().getConfig().getBoolean("discord.discord-messages.embed.enabled")) { - webhook.addEmbed(generateEmbed(sender, finalString)); - } else { - webhook.setContent(finalString); - } - - try { - webhook.execute(); - } catch (IOException exception) { - exception.printStackTrace(); - BetterStaffChatBungeeCord.getInstance().logPrefix("&cFailed to send Discord webhook."); - } - } else if (BetterStaffChatBungeeCord.getInstance().getConfig().getBoolean("discord.bot.enabled")) { - if (BetterStaffChatBungeeCord.getInstance().getConfig().getBoolean("discord.discord-messages.embed.enabled")) { - for (String guildChannelPair : BetterStaffChatBungeeCord.getInstance().getConfig().getStringList("discord.bot.channels")) { - String[] parts = guildChannelPair.split(": "); - BetterStaffChatBungeeCord.getInstance().getJda().sendEmbed(parts[0], parts[1], generateEmbed(sender, finalString)); - } - } else { - for (String guildChannelPair : BetterStaffChatBungeeCord.getInstance().getConfig().getStringList("discord.bot.channels")) { - String[] parts = guildChannelPair.split(": "); - BetterStaffChatBungeeCord.getInstance().getJda().sendMessage(parts[0], parts[1], finalString); - } - } - } - }); - } - - @Override - public void handleDiscord(Object event) { - if (BetterStaffChatBungeeCord.getInstance().getConfig().getStringList("discord.bot.channels").stream().anyMatch(a -> a.equals(((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getGuild().getId() + ": " + ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getChannel().getId()))) { - StringBuilder builder = new StringBuilder(); - - StringBuilder discordMessage = new StringBuilder(((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getMessage().getContentStripped().trim()); - - if (!((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getMessage().getAttachments().isEmpty()) { - for (net.dv8tion.jda.api.entities.Message.Attachment attachment : ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getMessage().getAttachments()) { - discordMessage.append(" ").append(attachment.getUrl()); - } - } - - String nickName = ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getMember().getNickname(); - - String name = BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.name-format") - .replace("%username%", ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getAuthor().getName()) - .replace("%discriminator%", ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getAuthor().getDiscriminator()) - .replace("%nickname%", nickName != null ? nickName : ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getAuthor().getName()); - - String message = BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.format") - .replace("%player_name%", name) - .replace("%message%", discordMessage.toString()) - .replace("%server%", "Discord") - .replaceAll("%\\S*%", ""); - - - for (String word : message.split(" ")) { - builder.append(ChatColor.translateAlternateColorCodes('&', word)); - builder.append(" "); - } - - broadcast(builder.substring(0, builder.length() - 1), "betterstaffchat.messages.read"); - } - } - - protected DiscordWebhook.EmbedObject generateEmbed(Object sender, String string) { - DiscordWebhook.EmbedObject embed = new DiscordWebhook.EmbedObject(); - embed.setColor(Color.decode(BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.embed.embed-color"))); - embed.setDescription(string); - - String footer = BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.embed.embed-footer") - .replace("%player_name%", (sender instanceof ProxiedPlayer) ? TextUtil.cleanForDiscord(((ProxiedPlayer) sender).getName()) : BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-replacement")) - .replace("%uuid%", (sender instanceof ProxiedPlayer) ? ((ProxiedPlayer) sender).getUniqueId().toString() : BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-uuid-replacement")) - .replace("%server%", (sender instanceof ProxiedPlayer) ? getFormattedServerName(((ProxiedPlayer) sender).getServer().getInfo().getName()) : BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-server-replacement")); - - String icon = BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.embed.embed-footer-icon").replace("%player_name%", (sender instanceof ProxiedPlayer) ? TextUtil.cleanForDiscord(((ProxiedPlayer) sender).getName()) : BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-replacement")) - .replace("%uuid%", (sender instanceof ProxiedPlayer) ? ((ProxiedPlayer) sender).getUniqueId().toString() : BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-uuid-replacement")) - .replace("%server%", (sender instanceof ProxiedPlayer) ? getFormattedServerName(((ProxiedPlayer) sender).getServer().getInfo().getName()) : BetterStaffChatBungeeCord.getInstance().getConfig().getString("staffchat.console-server-replacement")); - - if (!BetterStaffChatBungeeCord.getInstance().getConfig().getString("discord.discord-messages.embed.embed-footer").equals("")) { - embed.setFooter(footer, icon); - } else embed.setFooter(footer, null); - return embed; - } -} diff --git a/BetterStaffChat-bungeecord/src/main/resources/bungee.yml b/BetterStaffChat-bungeecord/src/main/resources/bungee.yml deleted file mode 100644 index d0728b8..0000000 --- a/BetterStaffChat-bungeecord/src/main/resources/bungee.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: BetterStaffChat -version: @version@ -main: dev.austech.betterstaffchat.bungeecord.BetterStaffChatBungeeCord -author: AusTechDev \ No newline at end of file diff --git a/BetterStaffChat-common/build.gradle b/BetterStaffChat-common/build.gradle deleted file mode 100644 index f3cf87d..0000000 --- a/BetterStaffChat-common/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ -/* - * BetterStaffChat - build.gradle - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -dependencies { - compileOnly 'me.lucko:jar-relocator:1.4' - compileOnly 'net.md-5:bungeecord-chat:1.16-R0.4' -} - -tasks.build.dependsOn tasks.shadowJar \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/BetterStaffChatDependencyProvider.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/BetterStaffChatDependencyProvider.java deleted file mode 100755 index a59fe1a..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/BetterStaffChatDependencyProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency; - -import dev.austech.betterstaffchat.common.dependency.maven.builder.MavenDependencyProvider; -import dev.austech.betterstaffchat.common.dependency.maven.builder.MavenDependencyProviderBuilder; -import dev.austech.betterstaffchat.common.dependency.relocation.RelocationInfo; - -public class BetterStaffChatDependencyProvider { - public static MavenDependencyProvider getDependencies() { - MavenDependencyProviderBuilder builder = new MavenDependencyProviderBuilder(); - - builder.dependency("org|slf4j", "slf4j-api", "1.7.25"); - builder.dependency("org|slf4j", "slf4j-nop", "1.7.25"); - builder.relocation("org|slf4j", "dev.austech.betterstaffchat.shaded.slf4j"); - - builder.repository("https://m2.dv8tion.net/releases/"); - builder.dependency("net|dv8tion", "JDA", "4.2.1_262"); - - builder.relocation(RelocationInfo.of("net|dv8tion|jda", "dev.austech.betterstaffchat.shaded.jda")); - - builder.dependency("com|squareup|okhttp3", "okhttp", "3.13.0"); - builder.dependency("com|squareup|okio", "okio", "1.17.2"); - builder.dependency("org|apache|commons", "commons-collections4", "4.1"); - builder.dependency("com|fasterxml|jackson|core", "jackson-databind", "2.10.1"); - builder.dependency("com|fasterxml|jackson|core", "jackson-core", "2.10.1"); - builder.dependency("com|fasterxml|jackson|core", "jackson-annotations", "2.10.1"); - - builder.dependency("net|sf|trove4j", "trove4j", "3.0.3"); - builder.dependency("com|neovisionaries", "nv-websocket-client", "2.14"); - - return (MavenDependencyProvider) builder.build(); - } -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/DependencyEngine.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/DependencyEngine.java deleted file mode 100755 index 88290a4..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/DependencyEngine.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency; - -import dev.austech.betterstaffchat.common.dependency.annotations.LoaderPriority; -import dev.austech.betterstaffchat.common.dependency.maven.DependencyLoader; -import dev.austech.betterstaffchat.common.dependency.maven.MavenDependencyLoader; -import dev.austech.betterstaffchat.common.dependency.maven.builder.DependencyProvider; -import dev.austech.betterstaffchat.common.dependency.relocation.RelocatableDependencyLoader; -import lombok.Getter; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.ForkJoinPool; - -/** - * A dependency engine is used as the starter point for adding dependencies to be downloaded and - * loaded at runtime. - */ -public final class DependencyEngine { - - /** - * The base path for dependencies. - */ - private final Path basePath; - /** - * A map containing all the dependency loaders for this dependency engine. - */ - private final Map, DependencyLoader> dependencyLoaders; - - /** - * The errors that occurred during the loading process. - */ - @Getter private final Set errors; - - /** - * Creates a new dependency engine with the specified base path. - * - * @param basePath The base path for this dependency engine. - */ - public DependencyEngine(final @NotNull Path basePath) { - this.basePath = basePath; - this.dependencyLoaders = new IdentityHashMap<>(); - this.errors = new HashSet<>(); - } - - /** - * Creates a new Dependency Engine with the specified base path. It includes all the default - * DependencyLoader's by default. - * - * @param basePath The base path for all dependencies to be downloaded - * @return A new {@link DependencyEngine} - */ - @Contract("_ -> new") - public static @NotNull DependencyEngine createNew(final @NotNull Path basePath) { - return DependencyEngine.createNew(basePath, true); - } - - /** - * Creates a new Dependency Engine with the specified base path. - * - * @param basePath The base path for all dependencies to be downloaded - * @param addDefaultLoader Whether or not to include the default dependency loaders - * @return A new {@link DependencyEngine} - */ - @Contract("_,_ -> new") - public static @NotNull DependencyEngine createNew( - final @NotNull Path basePath, - final boolean addDefaultLoader - ) { - final DependencyEngine engine = new DependencyEngine(basePath); - - if (addDefaultLoader) { - engine.addDefaultDependencyLoaders(); - } - - return engine; - } - - private void addDefaultDependencyLoaders() { - this.addDependencyLoader(new MavenDependencyLoader(this.basePath)); - } - - /** - * Adds a new dependency loader to the dependency engine. If a duplicate dependency loader is - * added an error is thrown. - * - * @param loader The loader to add to the dependency engine - * @return this - */ - @Contract("_ -> this") - public @NotNull DependencyEngine addDependencyLoader(final @NotNull DependencyLoader loader) { - if (this.dependencyLoaders.containsKey(loader.getClass())) { - throw new IllegalArgumentException(String.format( - "Loader with type '%s' already loaded.", - loader.getClass().getName() - )); - } - - this.dependencyLoaders.put(loader.getClass(), loader); - return this; - } - - /** - * Adds dependency annotations from a class to the currently loaded dependency loaders in this - * engine. These are not added retroactively, you need to add the dependency loaders first. - * - * @param clazz The class to load the annotations from - * @return this - */ - @Contract("_ -> this") - public @NotNull DependencyEngine addDependenciesFromClass(final @NotNull Class clazz) { - this.dependencyLoaders - .values() - .forEach(loader -> loader.loadDependenciesFrom(clazz)); - return this; - } - - /** - * Adds dependencies to the dependency loaders based on the input dependency provider. These are - * not added retroactively, you need to add the dependency loaders first. - * - * @param provider The provider to add the dependencies from - * @return this - */ - @Contract("_ -> this") - @SuppressWarnings("unchecked, rawtypes") - public @NotNull DependencyEngine addDependenciesFromProvider( - final @NotNull DependencyProvider provider) { - this.dependencyLoaders - .values() - .stream() - .filter(loader -> loader.getGenericType() == provider.getGenericType()) - .forEach(loader -> loader.loadDependenciesFrom((DependencyProvider) provider)); - return this; - } - - - /** - * Loads all the dependencies in the specified executor. - * - * @param executor An executor to load the dependencies in - * @return A completable future that completes when all dependencies are loaded - */ - @Contract("_ -> new") - public @NotNull CompletableFuture loadDependencies(final @NotNull Executor executor) { - return CompletableFuture.runAsync( - () -> this.dependencyLoaders - .values() - .stream() - .sorted( - Comparator.comparingInt(loader -> - loader.getClass() - .getAnnotation(LoaderPriority.class) - .value()) - ) - .forEach(loader -> { - if (!this.errors.isEmpty()) { - return; - } - - try { - loader.downloadDependencies(); - - this.errors.addAll(loader.getErrors()); - - if (this.errors.isEmpty() && loader instanceof RelocatableDependencyLoader) { - ((RelocatableDependencyLoader) loader).relocateDependencies(); - this.errors.addAll(loader.getErrors()); - } - - if (this.errors.isEmpty()) { - loader.loadDependencies((URLClassLoader) this.getClass().getClassLoader()); - } - } catch (final Exception ex) { - this.errors.add(ex); - } - }), - executor - ); - } - - /** - * Loads all dependencies in {@link ForkJoinPool#commonPool()}. - * - * @return A completable future that completes when all dependencies are loaded - */ - @Contract("-> new") - public @NotNull CompletableFuture loadDependencies() { - return this.loadDependencies(ForkJoinPool.commonPool()); - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/LoaderPriority.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/LoaderPriority.java deleted file mode 100755 index 2c7741d..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/LoaderPriority.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.annotations; - -import java.lang.annotation.*; - -/** - * This is used to define the order dependency loaders should be executed in. - */ -@Documented -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface LoaderPriority { - - /** - * The priority of the loader. Lower values go first. Default value 1000. - * - * @return The loader priority - */ - int value() default 1000; -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/MavenDependency.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/MavenDependency.java deleted file mode 100755 index 323a686..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/MavenDependency.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.annotations; - -import dev.austech.betterstaffchat.common.dependency.maven.DependencyLoader; -import org.jetbrains.annotations.NotNull; - -import java.lang.annotation.*; - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Repeatable(MavenDependency.List.class) -public @interface MavenDependency { - - /** - * Used to declare variables in a single line Gradle notation. - * - *

eg: 'com.google.guava:guava:30.1-jre'; if you would like to declare dependencies in a - * structured manner use the other variables. - * - * @return A Gradle style single line dependency string - */ - @NotNull String value() default ""; - - /** - * Used to define a group ID for a Maven dependency. - * - * @return The group ID - */ - @NotNull String groupId() default ""; - - /** - * Used to define an artifact ID for a Maven dependency. - * - * @return The artifact ID - */ - @NotNull String artifactId() default ""; - - /** - * Used to define the version for a Maven dependency. - * - * @return The version - */ - @NotNull String version() default ""; - - /** - * Used to define the package path separator. - * - * @return The separator to use instead of '.' or '/' - */ - @NotNull String separator() default DependencyLoader.DEFAULT_SEPARATOR; - - /** - * Used to store multiple {@link MavenDependency} annotations on a single class type. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - @interface List { - - /** - * Used to define an array of {@link MavenDependency} annotations. - * - * @return An array of {@link MavenDependency} annotations - */ - @NotNull MavenDependency[] value() default {}; - } - -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/MavenRepository.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/MavenRepository.java deleted file mode 100755 index b5919d3..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/MavenRepository.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.annotations; - -import dev.austech.betterstaffchat.common.dependency.maven.MavenRepositoryInfo; -import org.jetbrains.annotations.NotNull; - -import java.lang.annotation.*; - -/** - * Used to define a Maven repo URL for the dependencies. - * - * @see MavenRepositoryInfo - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Repeatable(MavenRepository.List.class) -public @interface MavenRepository { - - /** - * The URL to the Maven repository. - * - * @return A string that represents the URL to a Maven repo - */ - @NotNull String value() default "https://repo1.maven.org/maven2/"; - - /** - * Used to store multiple {@link MavenRepository} annotations on a single class type. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - @interface List { - - /** - * An array of maven repositories. - * - * @return An array of {@link MavenRepository} annotations. - */ - @NotNull MavenRepository @NotNull [] value() default {}; - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/Relocation.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/Relocation.java deleted file mode 100755 index 17915ba..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/annotations/Relocation.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.annotations; - -import dev.austech.betterstaffchat.common.dependency.maven.DependencyLoader; -import dev.austech.betterstaffchat.common.dependency.relocation.RelocationInfo; -import org.jetbrains.annotations.NotNull; - -import java.lang.annotation.*; - -/** - * Used to define relocations for any imported {@link MavenDependency} dependencies. - * - *

You can't use '.' or '/' for the package names due to maven/Gradle relocation changing those - * at compile time. The separator by default is '|' you can change the separator by changing the - * separator value in this annotation. - * - *

Examples:

- *
    - * ~Relocation(from = "com|google|guava", to = "my|package|guava") - * ~Relocation(from = "com{}google{}guava", to = "my{}package{}guava", separator = "{}" - *
- * - * @see RelocationInfo - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Repeatable(Relocation.List.class) -public @interface Relocation { - - /** - * The original package location. - * - * @return The original package location - */ - @NotNull String from(); - - /** - * Where to move the package to. - * - * @return Where to move the package to - */ - @NotNull String to(); - - /** - * The separator to use. - * - * @return The separator to use instead of '.' or '/' - */ - @NotNull String separator() default DependencyLoader.DEFAULT_SEPARATOR; - - /** - * Used to store multiple {@link Relocation} annotations on a single class type. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - @interface List { - - /** - * An array of {@link Relocation} annotations. - * - * @return An array of {@link Relocation} annotations - */ - @NotNull Relocation[] value() default {}; - } -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/exception/DependencyLoadException.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/exception/DependencyLoadException.java deleted file mode 100755 index cc278ed..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/exception/DependencyLoadException.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.exception; - -import dev.austech.betterstaffchat.common.dependency.maven.Dependency; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -/** - * Used when there is an error loading a dependency. - */ -public final class DependencyLoadException extends RuntimeException { - - /** - * The dependency that had an error while loading. - */ - @Getter private final transient @NotNull Dependency dependency; - - /** - * Creates a new dependency load exception. - * - * @param dependency The dependency that had an error while loading - * @param message The message to pass - */ - public DependencyLoadException( - final @NotNull Dependency dependency, - final @NotNull String message - ) { - super(message); - this.dependency = dependency; - } - - /** - * Creates a new dependency load exception. - * - * @param dependency The dependency that had an error while loading - * @param cause The cause of the error - */ - public DependencyLoadException( - final @NotNull Dependency dependency, - final @NotNull Throwable cause - ) { - super(cause); - this.dependency = dependency; - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/exception/InvalidDependencyException.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/exception/InvalidDependencyException.java deleted file mode 100755 index f88d141..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/exception/InvalidDependencyException.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.exception; - -import org.jetbrains.annotations.NotNull; - -/** - * When a dependency isn't able to process this error is thrown. - */ -public final class InvalidDependencyException extends RuntimeException { - - /** - * A basic error that indicates there was an error parsing a dependency. - */ - public InvalidDependencyException() { - super(); - } - - /** - * Creates a new invalid dependency exception. - * - * @param message The message to pass - * @param cause The cause of the error - */ - public InvalidDependencyException( - final @NotNull String message, - final @NotNull Throwable cause) { - super(message, cause); - } - - /** - * Creates a new invalid dependency exception. - * - * @param cause The cause of the error - */ - public InvalidDependencyException(final @NotNull Throwable cause) { - super(cause); - } - - /** - * Creates a new invalid dependency exception. - * - * @param message The message to pass - */ - public InvalidDependencyException(final @NotNull String message) { - super(message); - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/generics/TypeDefinition.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/generics/TypeDefinition.java deleted file mode 100755 index 3101a5c..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/generics/TypeDefinition.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.generics; - -import org.jetbrains.annotations.NotNull; - -/** - * Used to store the generic type of a class for comparison. - * - * @param The generic type of this definition. - */ -public interface TypeDefinition { - - /** - * Returns the generic type of this class. - * - * @return The generic type - */ - @NotNull Class getGenericType(); -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/Dependency.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/Dependency.java deleted file mode 100755 index 4bbcac9..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/Dependency.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven; - -import org.jetbrains.annotations.NotNull; - -/** - * Base dependency abstraction used to define basic information for dependency retrieval. - */ -public interface Dependency { - - /** - * If there is an issue downloading the dependency this is used to get the manual download - * location. - * - * @return The manual download url - */ - @NotNull String getManualDownloadString(); - - /** - * Used to get the download URL of a dependency. - * - * @return The relative download url - */ - @NotNull String getRelativeDownloadString(); - - /** - * Used to get where the dependency should be stored on download. The path is relative. - * - * @return The downloaded location of this dependency - */ - @NotNull String getDownloadedFileName(); - - /** - * Used to get the name of the dependency for logging information. - * - * @return The name of this dependency - */ - @NotNull String getName(); - - /** - * Set where or not this dependency has been loaded. - * - * @param loaded Whether or not this dependency has been loaded - */ - void setLoaded(boolean loaded); - - /** - * Whether or not this dependency has been loaded into the class path. - * - * @return True if it has been loaded already, false otherwise - */ - boolean isLoaded(); -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/DependencyLoader.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/DependencyLoader.java deleted file mode 100755 index f3759c6..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/DependencyLoader.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven; - -import dev.austech.betterstaffchat.common.dependency.generics.TypeDefinition; -import dev.austech.betterstaffchat.common.dependency.maven.builder.DependencyProvider; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.Method; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Abstract implementation of a dependency loader. - * - * @param Type of dependency implementation. - * @see MavenDependencyLoader - */ -public abstract class DependencyLoader implements TypeDefinition { - - /** - * The default separator used to have compatibility with gradle/Maven relocation. - */ - public static final @NotNull String DEFAULT_SEPARATOR = "|"; - - /** - * The base path for files in this dependency loader. - */ - @Getter private final @NotNull Path basePath; - - /** - * The dependencies that this dependency loader handles. - */ - @Getter private final @NotNull List dependencies; - - /** - * The errors that occurred while loading dependencies. - */ - @Getter private final @NotNull Set errors; - - /** - * Creates a new dependency loader with the specified base path. - * - * @param basePath The base path for dependencies. - */ - protected DependencyLoader(final @NotNull Path basePath) { - this.basePath = basePath; - this.dependencies = new ArrayList<>(); - this.errors = new HashSet<>(); - this.openClassLoaderJava9(); - } - - /** - * Creates a new dependency loader with the specified base path. Storage destination is used as a - * relative sub directory for dependencies. - * - * @param basePath The base path for dependencies - * @param storageDestination The relative path to dependencies in this dependency loader - */ - protected DependencyLoader( - final @NotNull Path basePath, - final @NotNull String storageDestination - ) { - this(basePath.resolve(storageDestination)); - } - - /** - * Gives this module access to the URLClassLoader. - */ - private void openClassLoaderJava9() { - AccessController.doPrivileged((PrivilegedAction) () -> { - try { - final Class moduleClass = Class.forName("java.lang.Module"); - final Method getModuleMethod = Class.class.getMethod("getModule"); - final Method addOpensMethod = moduleClass.getMethod("addOpens", String.class, moduleClass); - - final Object urlClassLoaderModule = getModuleMethod.invoke(URLClassLoader.class); - final Object thisModule = getModuleMethod.invoke(DependencyLoader.class); - - addOpensMethod - .invoke(urlClassLoaderModule, URLClassLoader.class.getPackage().getName(), thisModule); - } catch (final Exception ignored) { - // Will throw error on clazz); - - /** - * Used to load dependencies from a dependency provider. - * - * @param dependencyProvider The dependency provider to load dependencies from - */ - public abstract void loadDependenciesFrom(@NotNull DependencyProvider dependencyProvider); - - /** - * Downloads handled dependencies. - */ - public abstract void downloadDependencies(); - - /** - * Loads the dependencies to be used by the plugin. - * - * @param classLoader The class loader to add the dependencies to - */ - public abstract void loadDependencies(@NotNull URLClassLoader classLoader); -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenDependencyInfo.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenDependencyInfo.java deleted file mode 100755 index 1217a61..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenDependencyInfo.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven; - -import dev.austech.betterstaffchat.common.dependency.annotations.MavenDependency; -import dev.austech.betterstaffchat.common.dependency.exception.InvalidDependencyException; -import dev.austech.betterstaffchat.common.dependency.relocation.RelocatableDependency; -import lombok.Getter; -import lombok.Setter; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; - -/** - * This is the backend class for {@link MavenDependency} dependencies. It's responsible for - * providing basic information needed to download and relocate a dependency jar. - * - * @see MavenDependency - */ -public final class MavenDependencyInfo implements RelocatableDependency { - - /** - * This is used to validate a single line dependency. A single line dependency should have a size - * of 3 always. - */ - private static final int DEPENDENCY_SPLIT_SIZE = 3; - - /** - * This is used to make sure a dependency string is valid before we try to do extra processing on - * it. - */ - private static final @NotNull Pattern DEPENDENCY_PATTERN = Pattern.compile("^([\\w.\\-])+$"); - - /** - * The group ID of this Maven dependency. - */ - @Getter private @Nullable String groupId; - - /** - * The artifact ID of this Maven dependency. - */ - @Getter private @Nullable String artifactId; - - /** - * The version of this Maven dependency. - */ - @Getter private @Nullable String version; - - /** - * Whether or not this dependency has been loaded. - */ - @Getter @Setter private boolean loaded; - - private MavenDependencyInfo( - final @NotNull String separator, - final @NotNull String groupId, - final @NotNull String artifactId, - final @NotNull String version - ) { - this.validateAndSetValues(separator, groupId, artifactId, version); - } - - private MavenDependencyInfo(final @NotNull String separator, - final @NotNull String singleLineDependency) { - if (singleLineDependency.isEmpty()) { - throw new InvalidDependencyException(String.format( - "Invalid single line dependency passed. dependency = '%s'.", - singleLineDependency - )); - } - - final List values = new ArrayList<>(MavenDependencyInfo.DEPENDENCY_SPLIT_SIZE); - values.addAll(Arrays.asList(singleLineDependency.split(":"))); - - if (values.size() != MavenDependencyInfo.DEPENDENCY_SPLIT_SIZE - || values.get(0) == null - || values.get(1) == null - || values.get(2) == null) { - throw new InvalidDependencyException(String.format( - "Invalid dependency format '%s'", - singleLineDependency - )); - } - - this.validateAndSetValues(separator, values.get(0), values.get(1), values.get(2)); - } - - private static boolean hasInvalidCharacters(final @NotNull String validate) { - return validate.contains(".") || validate.contains("/"); - } - - private static boolean patternMismatchString(final @NotNull String validate) { - return !DEPENDENCY_PATTERN.matcher(validate).matches(); - } - - /** - * Creates a new {@link MavenDependencyInfo} based off the passed in information. - * - * @param separator The separator to use instead of '.' or '/' - * @param groupId The group ID of the dependency - * @param artifactId The artifact ID of the dependency - * @param version The version of the dependency - * @return A {@link MavenDependencyInfo} representing the passed in arguments - */ - @Contract("_,_,_,_ -> new") - public static @NotNull MavenDependencyInfo of( - final @NotNull String separator, - final @NotNull String groupId, - final @NotNull String artifactId, - final @NotNull String version - ) { - return new MavenDependencyInfo(separator, groupId, artifactId, version); - } - - /** - * Create a new dependency information object. - * - * @param separator The separator to use instead of '.' or '/' - * @param singleLineDependency A Gradle style single line dependency - * @return A {@link MavenDependencyInfo} representing the passed in arguments - */ - @Contract("_,_ -> new") - public static @NotNull MavenDependencyInfo of( - final @NotNull String separator, - final @NotNull String singleLineDependency - ) { - return new MavenDependencyInfo(separator, singleLineDependency); - } - - /** - * Creates a new {@link MavenDependencyInfo} based off of an {@link MavenDependency} annotation. - * - * @param dependency The {@link MavenDependency} annotation - * @return A {@link MavenDependencyInfo} representing the passed in arguments - */ - @Contract("_ -> new") - public static @NotNull MavenDependencyInfo of( - final @NotNull MavenDependency dependency - ) { - return dependency.value().isEmpty() - ? MavenDependencyInfo.of( - dependency.separator(), - dependency.groupId(), - dependency.artifactId(), - dependency.version() - ) - : MavenDependencyInfo.of( - dependency.separator(), - dependency.value() - ); - } - - private void validateAndSetValues( - final @NotNull String separator, - final @NotNull String groupId, - final @NotNull String artifactId, - final @NotNull String version - ) { - if (separator.isEmpty() || MavenDependencyInfo.hasInvalidCharacters(separator)) { - throw new InvalidDependencyException(String.format( - "Separator '%s' cannot contain '.' or '/'", - separator - )); - } - - if (groupId.isEmpty() || MavenDependencyInfo.hasInvalidCharacters(groupId)) { - throw new InvalidDependencyException(String.format( - "Group ID '%s' cannot contain '.' or '/', use a separator instead", - groupId - )); - } - - if (artifactId.isEmpty() || MavenDependencyInfo.hasInvalidCharacters(artifactId)) { - throw new InvalidDependencyException(String.format( - "Artifact ID '%s' cannot contain '.' or '/', use a separator instead", - artifactId - )); - } - - if (version.isEmpty()) { - throw new InvalidDependencyException("Dependency version cannot be empty"); - } - - final String validateGroupId = groupId.replace(separator, "."); - final String validateArtifactId = artifactId.replace(separator, "."); - - if (MavenDependencyInfo.patternMismatchString(validateGroupId)) { - throw new InvalidDependencyException(String.format( - "Group ID '%s' contains invalid characters", - validateGroupId - )); - } - - if (MavenDependencyInfo.patternMismatchString(validateArtifactId)) { - throw new InvalidDependencyException(String.format( - "Artifact ID '%s' contains invalid characters", - validateArtifactId - )); - } - - if (MavenDependencyInfo.patternMismatchString(version)) { - throw new InvalidDependencyException(String.format( - "Version '%s' contains invalid characters", - version - )); - } - - this.groupId = validateGroupId; - this.artifactId = validateArtifactId; - this.version = version; - } - - @Override - public @NotNull String getManualDownloadString() { - return this.getRelativeDownloadString(); - } - - @Override - public @NotNull String getRelativeDownloadString() { - return String.format( - "%s/%s/%s/%s-%s.jar", - Objects.requireNonNull(this.groupId).replace(".", "/"), - Objects.requireNonNull(this.artifactId), - Objects.requireNonNull(this.version), - Objects.requireNonNull(this.artifactId), - Objects.requireNonNull(this.version) - ); - } - - @Override - public @NotNull String getDownloadedFileName() { - return this.getName() + ".jar"; - } - - @Override - public @NotNull String getName() { - return this.artifactId + "-" + this.version; - } - - @Override - public @NotNull String getRelocatedFileName() { - return this.getName() + "-relocated.jar"; - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenDependencyLoader.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenDependencyLoader.java deleted file mode 100755 index 3f7a7d0..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenDependencyLoader.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven; - -import dev.austech.betterstaffchat.common.dependency.annotations.LoaderPriority; -import dev.austech.betterstaffchat.common.dependency.annotations.MavenDependency; -import dev.austech.betterstaffchat.common.dependency.annotations.MavenRepository; -import dev.austech.betterstaffchat.common.dependency.annotations.Relocation; -import dev.austech.betterstaffchat.common.dependency.exception.DependencyLoadException; -import dev.austech.betterstaffchat.common.dependency.exception.InvalidDependencyException; -import dev.austech.betterstaffchat.common.dependency.maven.builder.DependencyProvider; -import dev.austech.betterstaffchat.common.dependency.maven.builder.MavenDependencyProvider; -import dev.austech.betterstaffchat.common.dependency.relocation.RelocatableDependencyLoader; -import dev.austech.betterstaffchat.common.dependency.relocation.RelocationInfo; -import dev.austech.betterstaffchat.common.dependency.relocation.Relocator; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.*; -import java.util.stream.Collectors; - -/** - * The dependency loader responsible for loading Maven dependencies. - */ -@LoaderPriority -public final class MavenDependencyLoader extends - RelocatableDependencyLoader<@NotNull MavenDependencyInfo> { - - /** - * This is used as the user agent for requesting the direct download jar link. - */ - private static final AbstractMap.SimpleImmutableEntry REQUEST_USER_AGENT = - new AbstractMap.SimpleImmutableEntry<>("User-Agent", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 " - + "Firefox/3.5.2" + " (.NET CLR 3.5.30729)" - ); - - /** - * The repos attached to these dependencies. - */ - private final @NotNull Set repos; - /** - * Any relocations that should be ran on the incoming dependencies. - */ - private final @NotNull Set relocations; - - /** - * Creates a new Maven dependency loader with the specified base path. - * - * @param basePath The base path used to resolve relative file names. - */ - public MavenDependencyLoader(final @NotNull Path basePath) { - this(basePath, "maven"); - } - - /** - * Creates a new Maven dependency loader with the specified base path and destination directory - * name. - * - * @param basePath The base path used to resolve relative file names. - * @param storageDestination relative location for Maven dependencies to be stored. - */ - public MavenDependencyLoader( - final @NotNull Path basePath, - final @NotNull String storageDestination - ) { - super(basePath, storageDestination); - this.repos = new HashSet<>(); - this.repos.add(MavenRepositoryInfo.of("https://repo1.maven.org/maven2/")); - this.relocations = new HashSet<>(); - } - - @Override - public void loadDependenciesFrom(final @NotNull Class<@NotNull ?> clazz) { - if (clazz.isAnnotationPresent(MavenRepository.class)) { - this.repos.add(MavenRepositoryInfo.of(clazz.getAnnotation(MavenRepository.class))); - } - - if (clazz.isAnnotationPresent(MavenRepository.List.class)) { - Arrays.stream(clazz.getAnnotation(MavenRepository.List.class).value()) - .map(MavenRepositoryInfo::of) - .forEach(this.repos::add); - } - - if (clazz.isAnnotationPresent(Relocation.class)) { - this.relocations.add(RelocationInfo.of(clazz.getAnnotation(Relocation.class))); - } - - if (clazz.isAnnotationPresent(Relocation.List.class)) { - this.relocations.addAll( - Arrays.stream(clazz.getAnnotation(Relocation.List.class).value()) - .map(RelocationInfo::of) - .collect(Collectors.toList()) - ); - } - - if (clazz.isAnnotationPresent(MavenDependency.class)) { - super.addDependency(MavenDependencyInfo.of(clazz.getAnnotation(MavenDependency.class))); - } - - if (clazz.isAnnotationPresent(MavenDependency.List.class)) { - Arrays.stream(clazz.getAnnotation(MavenDependency.List.class).value()) - .map(MavenDependencyInfo::of) - .forEach(super::addDependency); - } - } - - @Override - public void loadDependenciesFrom( - final @NotNull DependencyProvider<@NotNull MavenDependencyInfo> dependencyProvider - ) { - final MavenDependencyProvider provider = (MavenDependencyProvider) dependencyProvider; - - this.repos.addAll(provider.getRepositories()); - this.relocations.addAll(provider.getRelocations()); - provider.getDependencies().forEach(super::addDependency); - } - - private @NotNull Optional> findRepoForDependency( - final @NotNull MavenDependencyInfo dependency - ) throws IOException { - Optional> downloadUrl = Optional.empty(); - for (final MavenRepositoryInfo repo : this.repos) { - final URL tempUrl = new URL((repo.getUrl().endsWith("/") - ? repo.getUrl() - : repo.getUrl() + "/") + dependency.getRelativeDownloadString()); - - final HttpURLConnection connection = (HttpURLConnection) tempUrl.openConnection(); - connection.setInstanceFollowRedirects(false); - connection.setRequestProperty( - MavenDependencyLoader.REQUEST_USER_AGENT.getKey(), - MavenDependencyLoader.REQUEST_USER_AGENT.getValue() - ); - connection.setRequestMethod("HEAD"); - - if (connection.getResponseCode() == HttpURLConnection.HTTP_OK - || connection.getResponseCode() == HttpURLConnection.HTTP_ACCEPTED) { - downloadUrl = Optional.of(new AbstractMap.SimpleImmutableEntry<>(repo.getUrl(), tempUrl)); - break; - } - } - - return downloadUrl; - } - - @Override - public void downloadDependencies() { - super.getDependencies() - .parallelStream() - .forEach(dependency -> { - if (dependency.isLoaded()) { - return; - } - - final Path downloadLocation = super.getBasePath() - .resolve(dependency.getDownloadedFileName()); - - if (!Files.exists(downloadLocation) - && !Files.exists(super.getBasePath().resolve(dependency.getRelocatedFileName()))) { - - try { - final Optional> downloadUrl = - this.findRepoForDependency(dependency); - - if (!downloadUrl.isPresent()) { - throw new InvalidDependencyException(String.format( - "Couldn't download dependency: '%s'.", - dependency.getName() - )); - } - - try (final InputStream is = downloadUrl.get().getValue().openStream()) { - Files.createDirectories(downloadLocation.getParent()); - Files.deleteIfExists(downloadLocation); - Files.copy(is, downloadLocation); - } - } catch (final IOException ex) { - super.addError(new DependencyLoadException(dependency, ex)); - } - } - }); - } - - private void relocateDependency( - final @NotNull Relocator relocator, - final @NotNull MavenDependencyInfo dependency - ) { - try { - final Path relocatedLocation = super.getBasePath().resolve(dependency.getRelocatedFileName()); - - if (!Files.exists(relocatedLocation)) { - relocator.relocate(this.relocations, dependency); - - Files.delete(super.getBasePath().resolve(dependency.getDownloadedFileName())); - } - } catch (final DependencyLoadException ex) { - super.addError(ex); - } catch (final Exception ex) { - super.addError(new DependencyLoadException(dependency, ex)); - } - } - - @Override - public void relocateDependencies() { - try { - - final Relocator relocator = new Relocator(super.getBasePath()); - - - super.getDependencies() - .parallelStream() - .forEach( dependency -> { - if (dependency.isLoaded()) { - return; - } - this.relocateDependency(relocator, dependency); - }); - } catch (final DependencyLoadException ex) { - super.addError(ex); - } - } - - @Override - public void loadDependencies(final @NotNull URLClassLoader classLoader) { - try { - final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - AccessController.doPrivileged((PrivilegedAction) () -> { - method.setAccessible(true); - return null; - }); - - super.getDependencies() - .parallelStream() - .forEach(dependency -> { - if (dependency.isLoaded()) { - return; - } - - try { - final Path downloadLocation = super.getBasePath() - .resolve(dependency.getDownloadedFileName()); - final Path relocatedLocation = super.getBasePath() - .resolve(dependency.getRelocatedFileName()); - - if (Files.exists(relocatedLocation)) { - method.invoke(classLoader, relocatedLocation.toUri().toURL()); - } else { - method.invoke(classLoader, downloadLocation.toUri().toURL()); - } - - dependency.setLoaded(true); - } catch (final IllegalAccessException - | InvocationTargetException - | MalformedURLException ex) { - super.addError(new DependencyLoadException(dependency, ex)); - } - }); - } catch (final Exception ex) { - super.addError(ex); - } - } - - @Override - public @NotNull Class<@NotNull MavenDependencyInfo> getGenericType() { - return MavenDependencyInfo.class; - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenRepositoryInfo.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenRepositoryInfo.java deleted file mode 100755 index 824def7..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/MavenRepositoryInfo.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven; - -import dev.austech.betterstaffchat.common.dependency.annotations.MavenRepository; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.net.URL; - -/** - * Implementation representing {@link MavenRepository}. - * - * @see MavenRepository - */ -@RequiredArgsConstructor -@EqualsAndHashCode -public final class MavenRepositoryInfo { - - /** - * The URL for this Maven repository. - */ - @Getter private final @NotNull String url; - - /** - * Creates a new Maven repository from a {@link URL}. - * - * @param url The url - * @return A new {@link MavenRepositoryInfo}. - */ - @Contract("_ -> new") - public static @NotNull MavenRepositoryInfo of(final @NotNull URL url) { - return new MavenRepositoryInfo(String.valueOf(url)); - } - - /** - * Creates a new Maven repository from a {@link MavenRepository} annotation. - * - * @param repository The annotation - * @return A new {@link MavenRepositoryInfo}. - */ - @Contract("_ -> new") - public static @NotNull MavenRepositoryInfo of(final @NotNull MavenRepository repository) { - return new MavenRepositoryInfo(repository.value()); - } - - /** - * Creates a new Maven repository from a string representing a url. - * - * @param url the url - * @return A new {@link MavenRepositoryInfo}. - */ - @Contract("_ -> new") - public static @NotNull MavenRepositoryInfo of(final @NotNull String url) { - return new MavenRepositoryInfo(url); - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/DependencyBuilder.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/DependencyBuilder.java deleted file mode 100755 index 2cee6ea..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/DependencyBuilder.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven.builder; - -import dev.austech.betterstaffchat.common.dependency.maven.Dependency; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -/** - * Base level implementation of DependencyBuilder. - * - * @param The dependency type - */ -public interface DependencyBuilder { - - /** - * Add a dependency to the dependency builder. - * - * @param dependency The dependency to add - * @return this - */ - @Contract("_ -> this") - @NotNull DependencyBuilder dependency(@NotNull T dependency); - - /** - * Creates a new {@link DependencyProvider} from the builder. - * - * @return A new {@link DependencyProvider} - */ - @Contract("-> new") - @NotNull DependencyProvider build(); -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/DependencyProvider.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/DependencyProvider.java deleted file mode 100755 index d5a5e11..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/DependencyProvider.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven.builder; - -import dev.austech.betterstaffchat.common.dependency.generics.TypeDefinition; -import dev.austech.betterstaffchat.common.dependency.maven.Dependency; -import org.jetbrains.annotations.NotNull; - -import java.util.Set; - -/** - * Base level dependency provider. - * - * @param The dependency this class provides - */ -public interface DependencyProvider extends TypeDefinition { - - /** - * Gets the set of dependencies attached to this provider. - * - * @return The set of dependencies attached to this provider - */ - @NotNull Set getDependencies(); -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/MavenDependencyProvider.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/MavenDependencyProvider.java deleted file mode 100755 index d3cf5bc..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/MavenDependencyProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven.builder; - -import dev.austech.betterstaffchat.common.dependency.maven.MavenDependencyInfo; -import dev.austech.betterstaffchat.common.dependency.maven.MavenDependencyLoader; -import dev.austech.betterstaffchat.common.dependency.maven.MavenRepositoryInfo; -import dev.austech.betterstaffchat.common.dependency.relocation.RelocationInfo; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.Set; - -/** - * This class is used to attach dependencies to a {@link MavenDependencyLoader}. - */ -@RequiredArgsConstructor(access = AccessLevel.PACKAGE) -public final class MavenDependencyProvider implements DependencyProvider { - - /** - * The repositories in this provider. - */ - @Getter private final @NotNull Set repositories; - - /** - * The dependencies in this provider. - */ - @Getter private final @NotNull Set dependencies; - - /** - * The relocations in this provider. - */ - @Getter private final @NotNull Set relocations; - - /** - * Creates a new {@link MavenDependencyProviderBuilder} instance. - * - * @return new {@link MavenDependencyProviderBuilder} - */ - @Contract("-> new") - public static MavenDependencyProviderBuilder builder() { - return new MavenDependencyProviderBuilder(); - } - - @Override - public @NotNull Class getGenericType() { - return MavenDependencyInfo.class; - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/MavenDependencyProviderBuilder.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/MavenDependencyProviderBuilder.java deleted file mode 100755 index 4b0f512..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/maven/builder/MavenDependencyProviderBuilder.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.maven.builder; - -import dev.austech.betterstaffchat.common.dependency.maven.DependencyLoader; -import dev.austech.betterstaffchat.common.dependency.maven.MavenDependencyInfo; -import dev.austech.betterstaffchat.common.dependency.maven.MavenDependencyLoader; -import dev.austech.betterstaffchat.common.dependency.maven.MavenRepositoryInfo; -import dev.austech.betterstaffchat.common.dependency.relocation.RelocationInfo; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.net.URL; -import java.util.HashSet; -import java.util.Set; - -/** - * Used to build a dependency provider that should be attached to a {@link MavenDependencyLoader} - * then loaded. - */ -public final class MavenDependencyProviderBuilder implements - DependencyBuilder { - - /** - * The set of repositories to add to the provider. - */ - private final @NotNull Set repositories; - - /** - * The set of Maven dependencies to add to the provider. - */ - private final @NotNull Set dependencies; - - /** - * The set of relocations to add to the provider. - */ - private final @NotNull Set relocations; - - /** - * Creates a new dependency builder. - */ - public MavenDependencyProviderBuilder() { - this.repositories = new HashSet<>(); - this.dependencies = new HashSet<>(); - this.relocations = new HashSet<>(); - } - - @Contract("_ -> this") - @Override - public @NotNull MavenDependencyProviderBuilder dependency( - final @NotNull MavenDependencyInfo dependency - ) { - this.dependencies.add(dependency); - return this; - } - - /** - * Creates a new Maven dependency provider builder. - * - * @param groupId The group ID of the Maven dependency - * @param artifactId The artifact ID of the Maven dependency - * @param version The version of the Maven dependency - * @return this - */ - @Contract("_,_,_ -> this") - public @NotNull MavenDependencyProviderBuilder dependency( - final @NotNull String groupId, - final @NotNull String artifactId, - final @NotNull String version - ) { - return this.dependency(MavenDependencyInfo.of( - DependencyLoader.DEFAULT_SEPARATOR, - groupId, - artifactId, - version - )); - } - - /** - * Creates a new Maven dependency provider builder. - * - * @param separator The separator used for package names - * @param groupId The group ID of the Maven dependency - * @param artifactId The artifact ID of the Maven dependency - * @param version The version of the Maven dependency - * @return this - */ - @Contract("_,_,_,_ -> this") - public @NotNull MavenDependencyProviderBuilder dependency( - final @NotNull String separator, - final @NotNull String groupId, - final @NotNull String artifactId, - final @NotNull String version - ) { - return this.dependency(MavenDependencyInfo.of(separator, groupId, artifactId, version)); - } - - /** - * Creates a new Maven dependency provider builder. - * - * @param separator The separator used for package separation - * @param singleLineDependency The Gradle style single line dependency string - * @return this - */ - @Contract("_,_ -> this") - public @NotNull MavenDependencyProviderBuilder dependency( - final @NotNull String separator, - final @NotNull String singleLineDependency - ) { - return this.dependency(MavenDependencyInfo.of(separator, singleLineDependency)); - } - - /** - * Creates a new Maven dependency provider builder. - * - * @param singleLineDependency The Gradle style single line dependency string - * @return this - */ - @Contract("_ -> this") - public @NotNull MavenDependencyProviderBuilder dependency( - final @NotNull String singleLineDependency - ) { - return this.dependency( - MavenDependencyInfo.of(DependencyLoader.DEFAULT_SEPARATOR, singleLineDependency)); - } - - /** - * Creates a new Maven dependency provider builder. - * - * @param repository The repository - * @return The same instance - * @see MavenRepositoryInfo - */ - @Contract("_ -> this") - public MavenDependencyProviderBuilder repository(final @NotNull MavenRepositoryInfo repository) { - this.repositories.add(repository); - return this; - } - - /** - * Sets the repository URL. - * - * @param url String URL representation of a Maven repository - * @return The same instance - */ - @Contract("_ -> this") - public MavenDependencyProviderBuilder repository(final @NotNull String url) { - return this.repository(MavenRepositoryInfo.of(url)); - } - - /** - * Sets the repository URL. - * - * @param url URL representation of a Maven repository - * @return The same instance - */ - @Contract("_ -> this") - public MavenDependencyProviderBuilder repository(final @NotNull URL url) { - return this.repository(MavenRepositoryInfo.of(url)); - } - - /** - * Sets the relocation. - * - * @param relocation The relocation - * @return The same instance - * @see RelocationInfo - */ - @Contract("_ -> this") - public MavenDependencyProviderBuilder relocation(final @NotNull RelocationInfo relocation) { - this.relocations.add(relocation); - return this; - } - - /** - * Sets the relocation. - * - * @param from Original package destination name - * @param to Target package destination name - * @param separator The separator to use instead of '.' or '/' - * @return The same instance - */ - @Contract("_,_,_ -> this") - public MavenDependencyProviderBuilder relocation( - final @NotNull String from, - final @NotNull String to, - final @NotNull String separator - ) { - return this.relocation(RelocationInfo.of(from, to, separator)); - } - - /** - * Sets the relocation. - * - * @param from The package to relocate from - * @param to The package to relocate to - * @return this - */ - @Contract("_,_ -> this") - public MavenDependencyProviderBuilder relocation(final @NotNull String from, - final @NotNull String to) { - return this.relocation(RelocationInfo.of(from, to, DependencyLoader.DEFAULT_SEPARATOR)); - } - - @Contract("-> new") - @Override - public @NotNull DependencyProvider build() { - return new MavenDependencyProvider(this.repositories, this.dependencies, this.relocations); - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/IsolatedClassLoader.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/IsolatedClassLoader.java deleted file mode 100755 index f855f1f..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/IsolatedClassLoader.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ -package dev.austech.betterstaffchat.common.dependency.relocation; - -import org.jetbrains.annotations.NotNull; - -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.util.Objects; - -/** - * This class handles loading some dependencies that are needed for loading other dependencies, but - * not need afterwards. - */ -public final class IsolatedClassLoader extends URLClassLoader { - - static { - ClassLoader.registerAsParallelCapable(); - } - - /** - * Instantiates a new Isolated class loader. - * - * @param urls The URLs - */ - public IsolatedClassLoader(final @NotNull URL... urls) { - super(Objects.requireNonNull(urls), ClassLoader.getSystemClassLoader().getParent()); - } - - @Override - public void addURL(final @NotNull URL url) { - super.addURL(url); - } - - /** - * Add path boolean. - * - * @param path The path - * @return The boolean - */ - public boolean addPath(final @NotNull Path path) { - try { - this.addURL(path.toUri().toURL()); - return true; - } catch (final MalformedURLException ex) { - return false; - } - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocatableDependency.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocatableDependency.java deleted file mode 100755 index 5fab9b8..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocatableDependency.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.relocation; - -import dev.austech.betterstaffchat.common.dependency.maven.Dependency; -import dev.austech.betterstaffchat.common.dependency.maven.MavenDependencyInfo; -import org.jetbrains.annotations.NotNull; - -/** - * Used to define dependencies that need to be relocated at runtime. - * - * @see MavenDependencyInfo - */ -public interface RelocatableDependency extends Dependency { - - /** - * Used to get the relocated location of a dependency. This path is relative. - * - * @return The relocation jar location of this dependency - */ - @NotNull String getRelocatedFileName(); -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocatableDependencyLoader.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocatableDependencyLoader.java deleted file mode 100755 index ed31c40..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocatableDependencyLoader.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.relocation; - -import dev.austech.betterstaffchat.common.dependency.maven.DependencyLoader; -import org.jetbrains.annotations.NotNull; - -import java.nio.file.Path; - -/** - * A {@link DependencyLoader} that supports relocations. - * - * @param A Relocatable Dependency to handle. - */ -public abstract class RelocatableDependencyLoader extends - DependencyLoader { - - /** - * Creates a new dependency loader that supports relocations with the specified base path. - * - * @param basePath The base path for dependencies in this dependency loader - */ - protected RelocatableDependencyLoader(final @NotNull Path basePath) { - super(basePath); - } - - /** - * Creates a new dependency loader that supports relocations with the specified base path. The - * storage destination is a relative sub directory for dependencies specific to this loader. - * - * @param basePath The base path for this dependency loader - * @param storageDestination The relative sub directory for dependencies - */ - protected RelocatableDependencyLoader( - final @NotNull Path basePath, - final @NotNull String storageDestination - ) { - super(basePath, storageDestination); - } - - /** - * Relocates dependencies based on provided relocations. - */ - public abstract void relocateDependencies(); -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocationInfo.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocationInfo.java deleted file mode 100755 index 29891ec..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/RelocationInfo.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.relocation; - -import dev.austech.betterstaffchat.common.dependency.annotations.Relocation; -import dev.austech.betterstaffchat.common.dependency.maven.DependencyLoader; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -/** - * Implementation representing {@link Relocation}. - * - * @see Relocation - */ -@RequiredArgsConstructor -@EqualsAndHashCode -public final class RelocationInfo { - - /** - * The package to search for. - */ - @Getter private final @NotNull String from; - - /** - * The package to place. - */ - @Getter private final @NotNull String to; - - /** - * The separate used to indicate package names. - */ - @Getter private final @NotNull String separator; - - /** - * Creates a new relocation based off of the passed in information. - * - * @param from The package to find - * @param to The package to replace with - * @param separator The separator used to indicate package names - * @return A new {@link RelocationInfo} instance - */ - @Contract("_,_,_ -> new") - public static @NotNull RelocationInfo of( - final @NotNull String from, - final @NotNull String to, - final @NotNull String separator - ) { - return new RelocationInfo(from, to, separator); - } - - /** - * Creates a new relocation based off of the passed in information. Uses the {@link - * DependencyLoader#DEFAULT_SEPARATOR} as the separator. - * - * @param from The package to find - * @param to The package to replace with - * @return A new {@link RelocationInfo} instance - */ - @Contract("_,_-> new") - public static @NotNull RelocationInfo of( - final @NotNull String from, - final @NotNull String to - ) { - return new RelocationInfo(from, to, DependencyLoader.DEFAULT_SEPARATOR); - } - - /** - * Creates a new relocation from the passed in {@link Relocation} annotation. - * - * @param relocation The {@link Relocation} annotation - * @return A new {@link RelocationInfo} instance - */ - @Contract("_ -> new") - public static @NotNull RelocationInfo of(final @NotNull Relocation relocation) { - return new RelocationInfo(relocation.from(), relocation.to(), relocation.separator()); - } -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/Relocator.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/Relocator.java deleted file mode 100755 index 2e143ed..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/dependency/relocation/Relocator.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2021 Justin Heflin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * This file is apart of PluginBase (https://github.com/Demeng7215/PluginBase) - */ - -package dev.austech.betterstaffchat.common.dependency.relocation; - -import dev.austech.betterstaffchat.common.dependency.exception.DependencyLoadException; -import dev.austech.betterstaffchat.common.dependency.maven.DependencyLoader; -import dev.austech.betterstaffchat.common.dependency.maven.MavenDependencyInfo; -import dev.austech.betterstaffchat.common.dependency.maven.MavenDependencyLoader; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * This class is used for relocating the packages inside of a downloaded jar. - */ -public final class Relocator { - - /** - * The ASM Dependency information. - */ - private static final MavenDependencyInfo ASM; - /** - * The ASM commons dependency information. - */ - private static final MavenDependencyInfo ASM_COMMONS; - /** - * The jar relocator dependency information. - */ - private static final MavenDependencyInfo JAR_RELOCATOR; - - static { - ASM = MavenDependencyInfo.of("|", "org|ow2|asm:asm:7.0"); - ASM_COMMONS = MavenDependencyInfo.of("|", "org|ow2|asm:asm-commons:7.0"); - JAR_RELOCATOR = MavenDependencyInfo.of("|", "me|lucko:jar-relocator:1.4"); - } - - /** - * The base directory for the relocator dependencies. - */ - private final @NotNull Path basePath; - /** - * The constructor for the jar relocator class. - */ - private final @NotNull Constructor jarRelocatorConstructor; - /** - * The method to run the jar relocator. - */ - private final @NotNull Method jarRelocatorRunMethod; - /** - * The relocation constructor. - */ - private final @NotNull Constructor relocationConstructor; - /** - * The isolated class loader instance. This is used to separate the classes used at only for - * relocating from everywhere else. - */ - private @Nullable IsolatedClassLoader isolatedClassLoader; - - - /** - * Creates a new relocator instance. - * - * @param basePath The base path for relocations - */ - public Relocator(final @NotNull Path basePath) { - this.basePath = basePath; - AccessController.doPrivileged( - (PrivilegedAction) () -> this.isolatedClassLoader = new IsolatedClassLoader()); - - final DependencyLoader dependencyHandler = - new MavenDependencyLoader(this.basePath.resolve("relocator")); - dependencyHandler.addDependency(ASM); - dependencyHandler.addDependency(ASM_COMMONS); - dependencyHandler.addDependency(JAR_RELOCATOR); - - dependencyHandler.downloadDependencies(); - dependencyHandler.loadDependencies(this.isolatedClassLoader); - - - if (!dependencyHandler.getErrors().isEmpty()) { - final Exception e = dependencyHandler.getErrors().stream().iterator().next(); - - throw e instanceof DependencyLoadException - ? (DependencyLoadException) e - : new DependencyLoadException(ASM, e); - } - - try { - final Class jarRelocatorClass = this.isolatedClassLoader - .loadClass("me.lucko.jarrelocator.JarRelocator"); - final Class relocationClass = this.isolatedClassLoader - .loadClass("me.lucko.jarrelocator.Relocation"); - - this.jarRelocatorConstructor = jarRelocatorClass.getConstructor( - File.class, - File.class, - Collection.class - ); - this.jarRelocatorRunMethod = jarRelocatorClass.getMethod("run"); - - this.relocationConstructor = relocationClass.getConstructor( - String.class, - String.class, - Collection.class, - Collection.class - ); - } catch (final Exception ex) { - throw new DependencyLoadException(ASM, ex); - } - } - - /** - * Relocates the packages in a jar file based on the specified relocations. - * - * @param relocations A collection of {@link RelocationInfo} used for relocation information - * @param dependency The dependency to relocate - * @throws IllegalAccessException If the relocator was denied access to any of the methods - * @throws InvocationTargetException If there was an error while relocating the jar files - * @throws InstantiationException If there was an error while creating a new relocation - * instance - */ - public void relocate( - final @NotNull Collection relocations, - final @NotNull RelocatableDependency dependency - ) throws IllegalAccessException, InvocationTargetException, InstantiationException { - final Set rules = new LinkedHashSet<>(); - - for (final RelocationInfo relocation : relocations) { - rules.add(this.relocationConstructor.newInstance( - relocation.getFrom().replace(relocation.getSeparator(), "."), - relocation.getTo().replace(relocation.getSeparator(), "."), - new ArrayList<>(), - new ArrayList<>() - )); - } - - this.jarRelocatorRunMethod.invoke(this.jarRelocatorConstructor.newInstance( - this.basePath.resolve(dependency.getDownloadedFileName()).toFile(), - this.basePath.resolve(dependency.getRelocatedFileName()).toFile(), - rules - )); - } -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/DiscordWebhook.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/DiscordWebhook.java deleted file mode 100644 index 26a2bdc..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/DiscordWebhook.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * BetterStaffChat - DiscordWebhook.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.common.discord; - -import javax.net.ssl.HttpsURLConnection; -import java.awt.*; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Array; -import java.net.URL; -import java.util.List; -import java.util.*; - -/** - * Class used to execute Discord Webhooks with low effort - */ -public class DiscordWebhook { - - private final String url; - private String content; - private String username; - private String avatarUrl; - private boolean tts; - private List embeds = new ArrayList<>(); - - /** - * Constructs a new DiscordWebhook instance - * - * @param url The webhook URL obtained in Discord - */ - public DiscordWebhook(String url) { - this.url = url; - } - - public void setContent(String content) { - this.content = content; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setAvatarUrl(String avatarUrl) { - this.avatarUrl = avatarUrl; - } - - public void setTts(boolean tts) { - this.tts = tts; - } - - public void addEmbed(EmbedObject embed) { - this.embeds.add(embed); - } - - public void execute() throws IOException { - if (this.content == null && this.embeds.isEmpty()) { - throw new IllegalArgumentException("Set content or add at least one EmbedObject"); - } - - JSONObject json = new JSONObject(); - - json.put("content", this.content); - json.put("username", this.username); - json.put("avatar_url", this.avatarUrl); - json.put("tts", this.tts); - - if (!this.embeds.isEmpty()) { - List embedObjects = new ArrayList<>(); - - for (EmbedObject embed : this.embeds) { - JSONObject jsonEmbed = new JSONObject(); - - jsonEmbed.put("title", embed.getTitle()); - jsonEmbed.put("description", embed.getDescription()); - jsonEmbed.put("url", embed.getUrl()); - - if (embed.getColor() != null) { - Color color = embed.getColor(); - int rgb = color.getRed(); - rgb = (rgb << 8) + color.getGreen(); - rgb = (rgb << 8) + color.getBlue(); - - jsonEmbed.put("color", rgb); - } - - EmbedObject.Footer footer = embed.getFooter(); - EmbedObject.Image image = embed.getImage(); - EmbedObject.Thumbnail thumbnail = embed.getThumbnail(); - EmbedObject.Author author = embed.getAuthor(); - List fields = embed.getFields(); - - if (footer != null) { - JSONObject jsonFooter = new JSONObject(); - - jsonFooter.put("text", footer.getText()); - jsonFooter.put("icon_url", footer.getIconUrl()); - jsonEmbed.put("footer", jsonFooter); - } - - if (image != null) { - JSONObject jsonImage = new JSONObject(); - - jsonImage.put("url", image.getUrl()); - jsonEmbed.put("image", jsonImage); - } - - if (thumbnail != null) { - JSONObject jsonThumbnail = new JSONObject(); - - jsonThumbnail.put("url", thumbnail.getUrl()); - jsonEmbed.put("thumbnail", jsonThumbnail); - } - - if (author != null) { - JSONObject jsonAuthor = new JSONObject(); - - jsonAuthor.put("name", author.getName()); - jsonAuthor.put("url", author.getUrl()); - jsonAuthor.put("icon_url", author.getIconUrl()); - jsonEmbed.put("author", jsonAuthor); - } - - List jsonFields = new ArrayList<>(); - for (EmbedObject.Field field : fields) { - JSONObject jsonField = new JSONObject(); - - jsonField.put("name", field.getName()); - jsonField.put("value", field.getValue()); - jsonField.put("inline", field.isInline()); - - jsonFields.add(jsonField); - } - - jsonEmbed.put("fields", jsonFields.toArray()); - embedObjects.add(jsonEmbed); - } - - json.put("embeds", embedObjects.toArray()); - } - - URL url = new URL(this.url); - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - connection.addRequestProperty("Content-Type", "application/json"); - connection.addRequestProperty("User-Agent", "Java-DiscordWebhook-BY-Gelox_"); - connection.setDoOutput(true); - connection.setRequestMethod("POST"); - - OutputStream stream = connection.getOutputStream(); - stream.write(json.toString().getBytes()); - stream.flush(); - stream.close(); - - connection.getInputStream().close(); //I'm not sure why but it doesn't work without getting the InputStream - connection.disconnect(); - } - - public static class EmbedObject { - private String title; - private String description; - private String url; - private Color color; - - private Footer footer; - private Thumbnail thumbnail; - private Image image; - private Author author; - private List fields = new ArrayList<>(); - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - public String getUrl() { - return url; - } - - public Color getColor() { - return color; - } - - public Footer getFooter() { - return footer; - } - - public Thumbnail getThumbnail() { - return thumbnail; - } - - public Image getImage() { - return image; - } - - public Author getAuthor() { - return author; - } - - public List getFields() { - return fields; - } - - public EmbedObject setTitle(String title) { - this.title = title; - return this; - } - - public EmbedObject setDescription(String description) { - this.description = description; - return this; - } - - public EmbedObject setUrl(String url) { - this.url = url; - return this; - } - - public EmbedObject setColor(Color color) { - this.color = color; - return this; - } - - public EmbedObject setFooter(String text, String icon) { - this.footer = new Footer(text, icon); - return this; - } - - public EmbedObject setThumbnail(String url) { - this.thumbnail = new Thumbnail(url); - return this; - } - - public EmbedObject setImage(String url) { - this.image = new Image(url); - return this; - } - - public EmbedObject setAuthor(String name, String url, String icon) { - this.author = new Author(name, url, icon); - return this; - } - - public EmbedObject addField(String name, String value, boolean inline) { - this.fields.add(new Field(name, value, inline)); - return this; - } - - class Footer { - private String text; - private String iconUrl; - - private Footer(String text, String iconUrl) { - this.text = text; - this.iconUrl = iconUrl; - } - - public String getText() { - return text; - } - - public String getIconUrl() { - return iconUrl; - } - } - - private class Thumbnail { - private String url; - - private Thumbnail(String url) { - this.url = url; - } - - private String getUrl() { - return url; - } - } - - private class Image { - private String url; - - private Image(String url) { - this.url = url; - } - - private String getUrl() { - return url; - } - } - - private class Author { - private String name; - private String url; - private String iconUrl; - - private Author(String name, String url, String iconUrl) { - this.name = name; - this.url = url; - this.iconUrl = iconUrl; - } - - private String getName() { - return name; - } - - private String getUrl() { - return url; - } - - private String getIconUrl() { - return iconUrl; - } - } - - private class Field { - private String name; - private String value; - private boolean inline; - - private Field(String name, String value, boolean inline) { - this.name = name; - this.value = value; - this.inline = inline; - } - - private String getName() { - return name; - } - - private String getValue() { - return value; - } - - private boolean isInline() { - return inline; - } - } - } - - private class JSONObject { - - private final HashMap map = new HashMap<>(); - - void put(String key, Object value) { - if (value != null) { - map.put(key, value); - } - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - Set> entrySet = map.entrySet(); - builder.append("{"); - - int i = 0; - for (Map.Entry entry : entrySet) { - Object val = entry.getValue(); - builder.append(quote(entry.getKey())).append(":"); - - if (val instanceof String) { - builder.append(quote(String.valueOf(val))); - } else if (val instanceof Integer) { - builder.append(Integer.valueOf(String.valueOf(val))); - } else if (val instanceof Boolean) { - builder.append(val); - } else if (val instanceof JSONObject) { - builder.append(val.toString()); - } else if (val.getClass().isArray()) { - builder.append("["); - int len = Array.getLength(val); - for (int j = 0; j < len; j++) { - builder.append(Array.get(val, j).toString()).append(j != len - 1 ? "," : ""); - } - builder.append("]"); - } - - builder.append(++i == entrySet.size() ? "}" : ","); - } - - return builder.toString(); - } - - private String quote(String string) { - return "\"" + string + "\""; - } - } - -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/JDAImplementation.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/JDAImplementation.java deleted file mode 100755 index ee82039..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/JDAImplementation.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * BetterStaffChat - JDAImplementation.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.common.discord; - -import dev.austech.betterstaffchat.common.util.AbstractStaffChatUtil; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.JDA; - -import java.util.logging.Logger; - -/* We suppress this, because if the - channel or guild is null, we don't care. - - An error to the client is fine, as they - can read it and see what is wrong. - */ -@SuppressWarnings({ "ConstantConditions" }) -public class JDAImplementation { - private final JDA jda; - - public JDAImplementation(JDA jda, AbstractStaffChatUtil staffChatUtil) { - this.jda = jda; - - jda.addEventListener(new MessageListener(staffChatUtil)); - } - - public void sendMessage(String guild, String channel, String message) { - jda.getGuildById(guild).getTextChannelById(channel).sendMessage(message).queue(); - } - - public void sendEmbed(String guild, String channel, DiscordWebhook.EmbedObject embed) { - EmbedBuilder embedBuilder = new EmbedBuilder(); - embedBuilder.setColor(embed.getColor()); - embedBuilder.setDescription(embed.getDescription()); - - if (embed.getFooter() != null) { - embedBuilder.setFooter(embed.getFooter().getText(), embed.getFooter().getIconUrl()); - } - jda.getGuildById(guild).getTextChannelById(channel).sendMessage(embedBuilder.build()).queue(); - } - - public void shutdown() { - Logger.getLogger(JDA.class.getName()).info("Shutting down"); - this.jda.shutdown(); - } - - public JDA asJda() { - return this.jda; - } -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/MessageListener.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/MessageListener.java deleted file mode 100644 index e2f4cba..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/discord/MessageListener.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * BetterStaffChat - MessageListener.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.common.discord; - -import dev.austech.betterstaffchat.common.util.AbstractStaffChatUtil; -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; -import org.jetbrains.annotations.NotNull; - -public class MessageListener extends ListenerAdapter { - private final AbstractStaffChatUtil staffChatUtil; - - public MessageListener(AbstractStaffChatUtil staffChatUtil) { - this.staffChatUtil = staffChatUtil; - } - - @Override - public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) { - if (event.getAuthor().isBot()) return; - - staffChatUtil.handleDiscord(event); - } -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/plugin/BetterStaffChatPlugin.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/plugin/BetterStaffChatPlugin.java deleted file mode 100755 index ae3393b..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/plugin/BetterStaffChatPlugin.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * BetterStaffChat - BetterStaffChatPlugin.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.common.plugin; - -import java.io.File; - -public interface BetterStaffChatPlugin { - /** - * Used to get a plugin's plugins/{name} folder. - * - * @return the plugin's data folder - */ - File getPluginDataFolder(); - - /** - * Logs a message with a prefix. - * - * @param string the message to log - */ - void logPrefix(String string); - - /** - * Logs a message with a prefix if debug is set to true in the console - * - * @param string the message to log - */ - void logPrefixDebug(String string); - - /** - * Logs a message. - * - * @param string the message to log - */ - void log(String string); - - /** - * Gets a server's "platform". - * - * @return the platform of the server - */ - Platform getPlatform(); -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/plugin/Platform.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/plugin/Platform.java deleted file mode 100755 index 64e1709..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/plugin/Platform.java +++ /dev/null @@ -1,5 +0,0 @@ -package dev.austech.betterstaffchat.common.plugin; - -public enum Platform { - BUKKIT, BUNGEECORD -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/AbstractStaffChatUtil.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/AbstractStaffChatUtil.java deleted file mode 100755 index 2662341..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/AbstractStaffChatUtil.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * BetterStaffChat - AbstractStaffChatUtil.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.common.util; - -import dev.austech.betterstaffchat.common.discord.DiscordWebhook; - -public abstract class AbstractStaffChatUtil { - /** - * Gets a formatted message, that has placeholders replaced and colour codes (&) replaced. - * - * @param sender the sender - * @param message the original message - * @return a formatted message - */ - public abstract String getFormattedMessage(Object sender, String message); - - /** - * Alternative to Bukkit's broadcast method with bungeecord support and console logging. - * - * @param string the message to broadcast - * @param permission the required permission node to view the message - */ - public abstract void broadcast(String string, String permission); - - /** - * Sends a message to Discord via a Discord bot or a {@link DiscordWebhook}. - * - * @param sender the sender - * @param string the message to send - */ - public abstract void discord(Object sender, String string); - - /** - * Method to handle a received GuildMessageCreateEvent from Discord. - * - * @param event the event to handle - */ - public abstract void handleDiscord(Object event); - - /** - * Generates an embed with placeholders replaced - * - * @param sender the sender - * @param string the message - * @return an {@link dev.austech.betterstaffchat.common.discord.DiscordWebhook.EmbedObject} - */ - protected abstract DiscordWebhook.EmbedObject generateEmbed(Object sender, String string); -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/Callback.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/Callback.java deleted file mode 100644 index 815ddd4..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/Callback.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * BetterStaffChat - Callback.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.common.util; - -public interface Callback { - void accept(T paramT); -} \ No newline at end of file diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/TextUtil.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/TextUtil.java deleted file mode 100755 index b0395f8..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/TextUtil.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * BetterStaffChat - TextUtil.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.common.util; - -import dev.austech.betterstaffchat.common.plugin.Platform; -import lombok.experimental.UtilityClass; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.TextComponent; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -@UtilityClass -public final class TextUtil { - private boolean rgbAvailable; - private Platform platform; - - public void init(Platform serverPlatform, Object plugin) { - platform = serverPlatform; - if (serverPlatform.equals(Platform.BUKKIT)) { - String packageName = plugin.getClass().getPackage().getName(); - if (Integer.parseInt(packageName.substring(packageName.lastIndexOf('.') + 1).split("_")[1]) >= 16) - rgbAvailable = true; - } - } - - /** - * Colors a specific message using the ChatColor API. - * @param string input - * @return Colored string - */ - public String colorize(String string) { - if (platform.equals(Platform.BUNGEECORD) || rgbAvailable) - return ChatColor.translateAlternateColorCodes('&', colorizeRgb(string)); - else return ChatColor.translateAlternateColorCodes('&', string); - } - - /* Pulled right from https://www.spigotmc.org/threads/hex-chat-class.449300/ */ - public String colorizeRgb(String string) { - final Pattern hexPattern = Pattern.compile("&#" + "([A-Fa-f0-9]{6})" + ""); - Matcher matcher = hexPattern.matcher(string); - StringBuffer buffer = new StringBuffer(string.length() + 4 * 8); - while (matcher.find()) - { - String group = matcher.group(1); - matcher.appendReplacement(buffer, ChatColor.COLOR_CHAR + "x" - + ChatColor.COLOR_CHAR + group.charAt(0) + ChatColor.COLOR_CHAR + group.charAt(1) - + ChatColor.COLOR_CHAR + group.charAt(2) + ChatColor.COLOR_CHAR + group.charAt(3) - + ChatColor.COLOR_CHAR + group.charAt(4) + ChatColor.COLOR_CHAR + group.charAt(5) - ); - } - return matcher.appendTail(buffer).toString(); - } - - public TextComponent colorizeToComponent(String string) { - return new TextComponent(TextComponent.fromLegacyText(string)); - } - - public String spacer(int amount) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < amount; i++) - stringBuilder.append("&r "); - return stringBuilder.toString(); - } - - public String cleanForDiscord(String string) { - return string.replaceAll("/\\*/", "\\*").replaceAll("/\\//", "\\/"); - } -} diff --git a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/UpdateChecker.java b/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/UpdateChecker.java deleted file mode 100755 index 9b8c743..0000000 --- a/BetterStaffChat-common/src/main/java/dev/austech/betterstaffchat/common/util/UpdateChecker.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * BetterStaffChat - UpdateChecker.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.common.util; - -import dev.austech.betterstaffchat.common.plugin.BetterStaffChatPlugin; -import lombok.experimental.UtilityClass; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Scanner; - -@UtilityClass -public class UpdateChecker { - /** - * Checks whether or not the plugin needs an update. - * - * @param plugin the plugin object to use for logging - * @param currentVersion the current version of the plugin - * @return if the plugin needs an update - * @see SpigotMC - Creating an update checker that checks for updates - */ - public boolean needsUpdate(BetterStaffChatPlugin plugin, String currentVersion) { - if (currentVersion.contains("dev")) return false; - - try (InputStream inputStream = new URL("https://api.spigotmc.org/legacy/update.php?resource=91991").openStream()) { - Scanner scanner = new Scanner(inputStream); - if (scanner.hasNext()) { - return !currentVersion.equals(scanner.next()); - } - } catch (IOException exception) { - plugin.logPrefix("&cFailed to check for updates..."); - } - return false; - } -} diff --git a/BetterStaffChat-spigot/build.gradle b/BetterStaffChat-spigot/build.gradle deleted file mode 100644 index f909b80..0000000 --- a/BetterStaffChat-spigot/build.gradle +++ /dev/null @@ -1,54 +0,0 @@ -/* - * BetterStaffChat - build.gradle - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import org.apache.tools.ant.filters.ReplaceTokens - -plugins { - id 'com.github.johnrengelman.shadow' -} - -repositories { - maven { - name = 'spigotmc-repo' - url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' - } - maven { - name = 'sonatype' - url = 'https://oss.sonatype.org/content/groups/public/' - } -} - -repositories { - maven { url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" } - maven { url "https://repo.extendedclip.com/content/repositories/placeholderapi/" } -} - -dependencies { - compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT' - compileOnly 'me.clip:placeholderapi:2.10.9' - implementation 'org.bstats:bstats-bukkit:2.2.1' - implementation project(':BetterStaffChat-common') -} - -processResources { - filter ReplaceTokens, tokens: [ - "version": parent.getVersion() - ] -} - -tasks.build.dependsOn tasks.shadowJar diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/BetterStaffChatSpigot.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/BetterStaffChatSpigot.java deleted file mode 100755 index 3c15f4d..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/BetterStaffChatSpigot.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * BetterStaffChat - BetterStaffChatSpigot.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot; - -import com.google.common.collect.Lists; -import dev.austech.betterstaffchat.common.plugin.BetterStaffChatPlugin; -import dev.austech.betterstaffchat.common.dependency.BetterStaffChatDependencyProvider; -import dev.austech.betterstaffchat.common.dependency.DependencyEngine; -import dev.austech.betterstaffchat.common.discord.JDAImplementation; -import dev.austech.betterstaffchat.common.plugin.Platform; -import dev.austech.betterstaffchat.common.util.TextUtil; -import dev.austech.betterstaffchat.common.util.UpdateChecker; -import dev.austech.betterstaffchat.spigot.command.BetterStaffChatCommand; -import dev.austech.betterstaffchat.spigot.command.MuteStaffChatCommand; -import dev.austech.betterstaffchat.spigot.command.StaffChatCommand; -import dev.austech.betterstaffchat.spigot.command.ToggleStaffChatCommand; -import dev.austech.betterstaffchat.spigot.listener.PlayerListener; -import dev.austech.betterstaffchat.spigot.reflection.ReflectionUtil; -import dev.austech.betterstaffchat.spigot.util.Config; -import dev.austech.betterstaffchat.spigot.util.StaffChatUtil; -import lombok.Getter; -import lombok.Setter; -import net.dv8tion.jda.api.JDABuilder; -import net.dv8tion.jda.api.entities.Activity; -import org.bstats.bukkit.Metrics; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import javax.naming.ConfigurationException; -import javax.security.auth.login.LoginException; -import java.io.File; -import java.util.ArrayList; -import java.util.Optional; -import java.util.UUID; -import java.util.logging.Level; - -public final class BetterStaffChatSpigot extends JavaPlugin implements BetterStaffChatPlugin { - @Getter private static BetterStaffChatSpigot instance; - @Getter private final ArrayList ignoreStaffChat = Lists.newArrayList(); - @Getter private final ArrayList toggledStaffChat = Lists.newArrayList(); - @Getter private JDAImplementation jda; - @Getter boolean discordEnabled; - @Getter @Setter private FileConfiguration config; - - @Override - public void onEnable() { - instance = this; - - TextUtil.init(getPlatform(), this); - Config.load(); - - if (getConfig().getBoolean("check-for-updates")) - Bukkit.getScheduler().runTaskLaterAsynchronously(this, () -> { - if (UpdateChecker.needsUpdate(this, getDescription().getVersion())) { - logPrefix("&eA new update for BetterStaffChat is available..."); - logPrefix("&ehttps://www.spigotmc.org/resources/91991"); - } - }, 20 * 3); - - new Metrics(this, 10952); - - - if (getConfig().getBoolean("discord.bot.enabled") && getConfig().getBoolean("discord.webhook.enabled")) { - new ConfigurationException("Both Discord types are enabled").printStackTrace(); - this.getPluginLoader().disablePlugin(this); - return; - } - - this.discordEnabled = getConfig().getBoolean("discord.bot.enabled"); - - - DependencyEngine dependencyEngine = DependencyEngine.createNew(new File(getPluginDataFolder(), "libs").toPath()); - dependencyEngine.addDependenciesFromProvider(BetterStaffChatDependencyProvider.getDependencies()); - dependencyEngine.loadDependencies().thenAccept((empty) -> { - if (!dependencyEngine.getErrors().isEmpty()) { - Optional opt = dependencyEngine.getErrors().stream().filter(throwable -> throwable.getMessage().contains("Unable to make protected void java.net.URLClassLoader.addURL(java.net.URL) accessible: module java.base does not")).findFirst(); - if (opt.isPresent()) { - getLogger().log(Level.SEVERE, "An error occurred whilst starting BetterStaffChat - This is due to Java 16 and up being incompatible by default."); - getLogger().log(Level.SEVERE, "This error is fixable, please add the following flags to your startup after the \"java\":"); - getLogger().log(Level.SEVERE, ""); - getLogger().log(Level.SEVERE, "--add-opens java.base/java.net=ALL-UNNAMED"); - } else { - dependencyEngine.getErrors().forEach(Throwable::printStackTrace); - getLogger().log(Level.SEVERE, "Errors occurred whilst loading BSC."); - } - this.getPluginLoader().disablePlugin(this); - return; - } - - if (discordEnabled) { - Bukkit.getScheduler().runTaskAsynchronously(this, () -> { - try { - this.jda = new JDAImplementation(JDABuilder.createLight(getConfig().getString("discord.bot.token")).build(), StaffChatUtil.getInstance()); - jda.asJda().getPresence().setActivity(Activity.of( - Activity.ActivityType.valueOf(getConfig().getString("discord.bot.activity-type").toUpperCase().replace("PLAYING", "DEFAULT")), - getConfig().getString("discord.bot.activity") - )); - } catch (LoginException exception) { - exception.printStackTrace(); - this.getPluginLoader().disablePlugin(this); - } - }); - } - }); - - registerCommands(); - - Bukkit.getPluginManager().registerEvents(new PlayerListener(), this); - } - - public boolean reloadConfig(CommandSender sender) { - boolean discordLoaded = getConfig().getBoolean("discord.bot.enabled"); - Config.load(); - - /* - * We can't unregister commands easily, without doing hacky reflection stuff - * For now, restarting the server will fix it. - */ - Bukkit.getScheduler().runTaskAsynchronously(this, this::registerCommands); - - if (getConfig().getBoolean("discord.bot.enabled") != discordLoaded) { - logPrefix(TextUtil.colorize("&cYou enabled the discord bot in the config. Please restart the server for changes to take effect.")); - if (sender instanceof Player) - sender.sendMessage(TextUtil.colorize("&cYou enabled the discord bot in the config. Please restart the server for changes to take effect.")); - return true; - } - return false; - } - - private void registerCommands() { - ReflectionUtil.registerCmd(new BetterStaffChatCommand()); - ReflectionUtil.registerCmd(new StaffChatCommand("staffchat", "The command that lets you send a message to the staffchat.", getConfig().getStringList("commands.staffchat.aliases"))); - ReflectionUtil.registerCmd(new MuteStaffChatCommand("mutestaffchat", "Allows you to toggle whether you receive staff chat or not.", getConfig().getStringList("commands.mutestaffchat.aliases"))); - ReflectionUtil.registerCmd(new ToggleStaffChatCommand("togglestaffchat", "Allows you to toggle whether or not all your messages to to staffchat.", getConfig().getStringList("commands.togglestaffchat.aliases"))); - } - - public File getPluginDataFolder() { - return getDataFolder(); - } - - public void log(String string) { - Bukkit.getConsoleSender().sendMessage(TextUtil.colorize(string)); - } - - @Override public Platform getPlatform() { - return Platform.BUKKIT; - } - - public void logPrefix(String string) { - Bukkit.getConsoleSender().sendMessage("[BetterStaffChat] " + TextUtil.colorize(string)); - } - - public void logPrefixDebug(String string) { - if (getConfig().getBoolean("debug")) - Bukkit.getConsoleSender().sendMessage("[BetterStaffChat] Debug - " + TextUtil.colorize(string)); - } - - @Override - public void onDisable() { - if (isDiscordEnabled()) (getJda()).shutdown(); - } -} diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/BetterStaffChatCommand.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/BetterStaffChatCommand.java deleted file mode 100755 index 2af5499..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/BetterStaffChatCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * BetterStaffChat - BetterStaffChatCommand.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot.command; - -import com.google.common.collect.Lists; -import dev.austech.betterstaffchat.common.util.TextUtil; -import dev.austech.betterstaffchat.spigot.BetterStaffChatSpigot; -import net.dv8tion.jda.api.entities.Activity; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; - -public class BetterStaffChatCommand extends Command { - public BetterStaffChatCommand() { - super("betterstaffchat", "The main command for BetterStaffChat", "", Lists.newArrayList("bsc")); - } - - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - if (args.length == 1 && args[0].equalsIgnoreCase("reload") && sender.hasPermission("betterstaffchat.reload")) { - boolean reload = BetterStaffChatSpigot.getInstance().reloadConfig(sender); - if (!reload && BetterStaffChatSpigot.getInstance().getConfig().getBoolean("discord.bot.enabled")) { - BetterStaffChatSpigot.getInstance().getJda().asJda().getPresence().setActivity(Activity.of( - Activity.ActivityType.valueOf(BetterStaffChatSpigot.getInstance().getConfig().getString("discord.bot.activity-type").toUpperCase().replace("PLAYING", "DEFAULT")), - BetterStaffChatSpigot.getInstance().getConfig().getString("discord.bot.activity") - )); - } - - sender.sendMessage(TextUtil.colorize("&8&l&m-------------------------------")); - sender.sendMessage(TextUtil.colorize(TextUtil.spacer(18) + "&c&l&oBetter&4&l&oStaffChat")); - sender.sendMessage(TextUtil.colorize(TextUtil.spacer(13) + "&7Configuration Reloaded")); - sender.sendMessage(TextUtil.colorize("&8&l&m-------------------------------")); - return true; - } - - sender.sendMessage(TextUtil.colorize("&8&l&m-------------------------------")); - sender.sendMessage(TextUtil.colorize(TextUtil.spacer(18) + "&c&l&oBetter&4&l&oStaffChat")); - sender.sendMessage(TextUtil.colorize(TextUtil.spacer(13) + "&7The &7&o\"better\"&r &7staff chat.")); - sender.sendMessage(TextUtil.colorize("&8&l&m-------------------------------")); - return true; - } -} diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/MuteStaffChatCommand.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/MuteStaffChatCommand.java deleted file mode 100644 index 778bb09..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/MuteStaffChatCommand.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * BetterStaffChat - MuteStaffChatCommand.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot.command; - -import dev.austech.betterstaffchat.common.util.TextUtil; -import dev.austech.betterstaffchat.spigot.BetterStaffChatSpigot; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class MuteStaffChatCommand extends Command { - public MuteStaffChatCommand(@NotNull String name, @NotNull String description, @NotNull List aliases) { - super(name, description, "", aliases); - } - - @Override - public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { - if (!sender.hasPermission("betterstaffchat.mutestaffchat")) { - sender.sendMessage(TextUtil.colorize("&cNo permission.")); - return true; - } - - if (!(sender instanceof Player)) { - sender.sendMessage(TextUtil.colorize("&cThis command can only be used by players.")); - return true; - } - - if (BetterStaffChatSpigot.getInstance().getIgnoreStaffChat().contains(((Player) sender).getUniqueId())) { - BetterStaffChatSpigot.getInstance().getIgnoreStaffChat().remove(((Player) sender).getUniqueId()); - } else { - if (BetterStaffChatSpigot.getInstance().getToggledStaffChat().contains(((Player) sender).getUniqueId())) { - BetterStaffChatSpigot.getInstance().getToggledStaffChat().remove(((Player) sender).getUniqueId()); - } - BetterStaffChatSpigot.getInstance().getIgnoreStaffChat().add(((Player) sender).getUniqueId()); - } - - sender.sendMessage(TextUtil.colorize(BetterStaffChatSpigot.getInstance().getIgnoreStaffChat().contains(((Player) sender).getUniqueId()) ? - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.mute-on") : - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.mute-off")) - ); - - return true; - } -} diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/StaffChatCommand.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/StaffChatCommand.java deleted file mode 100644 index fc50c8e..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/StaffChatCommand.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * BetterStaffChat - StaffChatCommand.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot.command; - -import com.google.common.collect.Lists; -import dev.austech.betterstaffchat.common.util.TextUtil; -import dev.austech.betterstaffchat.spigot.BetterStaffChatSpigot; -import dev.austech.betterstaffchat.spigot.util.StaffChatUtil; -import net.md_5.bungee.api.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.List; - -public class StaffChatCommand extends Command { - public StaffChatCommand(@NotNull String name, @NotNull String description, @NotNull List aliases) { - super(name, description, "", aliases); - } - - @Override - public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { - if (!sender.hasPermission("betterstaffchat.messages.send")) { - sender.sendMessage(TextUtil.colorize("&cNo permission.")); - return true; - } - - if (sender instanceof Player && BetterStaffChatSpigot.getInstance().getIgnoreStaffChat().contains(((Player) sender).getUniqueId())) { - sender.sendMessage(TextUtil.colorize("&cYour staff chat is currently disabled.")); - return true; - } - - if (args.length == 0) { - sender.sendMessage(TextUtil.colorize("&cUsage: /" + commandLabel + " " + this.getUsage())); - return true; - } - - StaffChatUtil.getInstance().broadcast(StaffChatUtil.getInstance().getFormattedMessage(sender, String.join(" ", Arrays.copyOfRange(args, 0, args.length))), "betterstaffchat.messages.read"); - - if (sender instanceof Player) - StaffChatUtil.getInstance().discord(sender, ChatColor.stripColor(TextUtil.colorize(StaffChatUtil.getInstance().placeholder((Player) sender, BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.staffchat-format").replace("%player_name%", sender.getName()).replace("%message%", String.join(" ", Arrays.copyOfRange(args, 0, args.length))))))); - else - StaffChatUtil.getInstance().discord(sender, ChatColor.stripColor(TextUtil.colorize(BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.staffchat-format").replace("%player_name%", - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.console-replacement")).replace("%message%", String.join(" ", Arrays.copyOfRange(args, 0, args.length)))))); - - return true; - } -} diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/ToggleStaffChatCommand.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/ToggleStaffChatCommand.java deleted file mode 100644 index ad98af7..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/command/ToggleStaffChatCommand.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * BetterStaffChat - ToggleStaffChatCommand.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot.command; - -import dev.austech.betterstaffchat.common.util.TextUtil; -import dev.austech.betterstaffchat.spigot.BetterStaffChatSpigot; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class ToggleStaffChatCommand extends Command { - public ToggleStaffChatCommand(@NotNull String name, @NotNull String description, @NotNull List aliases) { - super(name, description, "", aliases); - } - - @Override - public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { - if (!sender.hasPermission("betterstaffchat.togglestaffchat")) { - sender.sendMessage(TextUtil.colorize("&cNo permission.")); - return true; - } - - if (!(sender instanceof Player)) { - sender.sendMessage(TextUtil.colorize("&cThis command can only be used by players.")); - return true; - } - - if (sender instanceof Player && BetterStaffChatSpigot.getInstance().getIgnoreStaffChat().contains(((Player) sender).getUniqueId())) { - sender.sendMessage(TextUtil.colorize("&cYour staff chat is currently disabled.")); - return true; - } - - if (BetterStaffChatSpigot.getInstance().getToggledStaffChat().contains(((Player) sender).getUniqueId())) { - BetterStaffChatSpigot.getInstance().getToggledStaffChat().remove(((Player) sender).getUniqueId()); - } else { - BetterStaffChatSpigot.getInstance().getToggledStaffChat().add(((Player) sender).getUniqueId()); - } - - sender.sendMessage(TextUtil.colorize(BetterStaffChatSpigot.getInstance().getToggledStaffChat().contains(((Player) sender).getUniqueId()) ? - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.toggle-on") : - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.toggle-off")) - ); - - return true; - } -} diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/listener/PlayerListener.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/listener/PlayerListener.java deleted file mode 100644 index 9af4d49..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/listener/PlayerListener.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * BetterStaffChat - PlayerListener.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot.listener; - -import dev.austech.betterstaffchat.common.util.TextUtil; -import dev.austech.betterstaffchat.spigot.BetterStaffChatSpigot; -import dev.austech.betterstaffchat.spigot.util.StaffChatUtil; -import net.md_5.bungee.api.ChatColor; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -public class PlayerListener implements Listener { - @EventHandler(priority = EventPriority.HIGH) - public void onAsyncPlayerChat(AsyncPlayerChatEvent event) { - String prefix = BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.prefix"); - - if (event.getPlayer().hasPermission("betterstaffchat.messages.send") && event.getMessage().startsWith(prefix) && event.getMessage().length() > prefix.length() && !prefix.equals("")) { - event.setCancelled(true); - - if (BetterStaffChatSpigot.getInstance().getIgnoreStaffChat().contains(event.getPlayer().getUniqueId())) { - event.getPlayer().sendMessage(TextUtil.colorize("&cYour staff chat is currently disabled.")); - return; - } - - StaffChatUtil.getInstance().broadcast(StaffChatUtil.getInstance().getFormattedMessage(event.getPlayer(), event.getMessage().substring(prefix.length())), "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord(event.getPlayer(), ChatColor.stripColor(TextUtil.colorize(StaffChatUtil.getInstance().placeholder(event.getPlayer(), BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.staffchat-format").replace("%player_name%", event.getPlayer().getName()).replace("%message%", event.getMessage().substring(prefix.length())))))); - } else if (event.getPlayer().hasPermission("betterstaffchat.messages.send") && BetterStaffChatSpigot.getInstance().getToggledStaffChat().contains(event.getPlayer().getUniqueId())) { - event.setCancelled(true); - - StaffChatUtil.getInstance().broadcast(StaffChatUtil.getInstance().getFormattedMessage(event.getPlayer(), event.getMessage()), "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord(event.getPlayer(), ChatColor.stripColor(TextUtil.colorize(StaffChatUtil.getInstance().placeholder(event.getPlayer(), BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.staffchat-format").replace("%player_name%", event.getPlayer().getName()).replace("%message%", event.getMessage()))))); - } - } - - @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { - if (event.getPlayer().hasPermission("betterstaffchat.messages.join") && !BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.join").equals("")) { - String message = TextUtil.colorize( - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.join") - .replace("%player_name%", event.getPlayer().getName()) - ); - - message = TextUtil.colorize(StaffChatUtil.getInstance().placeholder(event.getPlayer(), message)); - - StaffChatUtil.getInstance().broadcast(message, "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord(event.getPlayer(), ChatColor.stripColor(TextUtil.colorize(StaffChatUtil.getInstance().placeholder(event.getPlayer(), BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.staffchat-join").replace("%player_name%", event.getPlayer().getName()))))); - } - } - - @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) { - if (event.getPlayer().hasPermission("betterstaffchat.messages.leave") && !BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.leave").equals("")) { - String message = TextUtil.colorize( - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.leave") - .replace("%player_name%", event.getPlayer().getName()) - ); - - message = TextUtil.colorize(StaffChatUtil.getInstance().placeholder(event.getPlayer(), message)); - - StaffChatUtil.getInstance().broadcast(message, "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord(event.getPlayer(), ChatColor.stripColor(TextUtil.colorize(StaffChatUtil.getInstance().placeholder(event.getPlayer(), BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.staffchat-leave").replace("%player_name%", event.getPlayer().getName()))))); - } - } - - @EventHandler - public void onPlayerChangeWorld(PlayerChangedWorldEvent event) { - if (event.getPlayer().hasPermission("betterstaffchat.messages.switch") && !BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.switch").equals("")) { - String message = TextUtil.colorize( - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.switch") - .replace("%player_name%", event.getPlayer().getName()) - .replace("%from%", event.getFrom().getName()) - .replace("%to%", event.getPlayer().getWorld().getName()) - ); - - message = TextUtil.colorize(StaffChatUtil.getInstance().placeholder(event.getPlayer(), message)); - - StaffChatUtil.getInstance().broadcast(message, "betterstaffchat.messages.read"); - StaffChatUtil.getInstance().discord(event.getPlayer(), ChatColor.stripColor(TextUtil.colorize(StaffChatUtil.getInstance().placeholder(event.getPlayer(), BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.staffchat-switch") - .replace("%player_name%", event.getPlayer().getName()) - .replace("%from%", event.getFrom().getName()) - .replace("%to%", event.getPlayer().getWorld().getName()))))); - } - } -} diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/reflection/ReflectionUtil.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/reflection/ReflectionUtil.java deleted file mode 100644 index b0ccfc2..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/reflection/ReflectionUtil.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * BetterStaffChat - ReflectionUtil.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot.reflection; - -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; -import org.bukkit.command.SimpleCommandMap; - -import java.lang.reflect.InvocationTargetException; - -public class ReflectionUtil { - protected static SimpleCommandMap getCommandMap() { - try { - return (SimpleCommandMap) Class.forName("org.bukkit.craftbukkit." + Bukkit.getServer().getClass().getPackage().getName().substring(23) + ".CraftServer").getDeclaredMethod("getCommandMap").invoke(Bukkit.getServer()); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - public static void registerCmd(Command command) { - CommandMap commandMap = getCommandMap(); - if (commandMap != null) - commandMap.register("betterstaffchat", command); - if (!command.isRegistered()) throw new NullPointerException("CommandMap is null"); - } -} \ No newline at end of file diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/util/Config.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/util/Config.java deleted file mode 100644 index 0bb0642..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/util/Config.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * BetterStaffChat - Config.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot.util; - -import dev.austech.betterstaffchat.spigot.BetterStaffChatSpigot; -import lombok.experimental.UtilityClass; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.file.Files; - -@UtilityClass -public class Config { - public void load() { - BetterStaffChatSpigot.getInstance().getDataFolder().mkdir(); - - File file = new File(BetterStaffChatSpigot.getInstance().getPluginDataFolder(), "config.yml"); - - if (!file.exists()) { - try (InputStream inputStream = BetterStaffChatSpigot.getInstance().getClass().getResourceAsStream("/config.yml")) { - Files.copy(inputStream, file.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - YamlConfiguration config = new YamlConfiguration(); - - try { - config.load(file); - - int currentVersion = YamlConfiguration.loadConfiguration(new InputStreamReader(BetterStaffChatSpigot.getInstance().getClass().getResourceAsStream("/config.yml"))).getInt("config-version"); - - if (config.getInt("config-version") != currentVersion) - newVersion(config.getInt("config-version")); - else - BetterStaffChatSpigot.getInstance().setConfig(config); - } catch (InvalidConfigurationException exception) { - exception.printStackTrace(); - File broken = new File(file.getAbsolutePath() + ".broken." + System.currentTimeMillis()); - file.renameTo(broken); - BetterStaffChatSpigot.getInstance().logPrefix("&cThe config file is broken, and has been renamed to config.yml.broken." + System.currentTimeMillis()); - - try (InputStream in = BetterStaffChatSpigot.getInstance().getClass().getResourceAsStream("/config.yml")) { - Files.copy(in, file.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - - BetterStaffChatSpigot.getInstance().setConfig(YamlConfiguration.loadConfiguration(file)); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void newVersion(int ver) throws IOException { - File file = new File(BetterStaffChatSpigot.getInstance().getPluginDataFolder(), "config.yml"); - - File broken = new File(file.getAbsolutePath() + ".old." + ver); - file.renameTo(broken); - BetterStaffChatSpigot.getInstance().logPrefix("&cThe config file is old, and has been renamed to config.yml.old." + ver); - - try (InputStream inputStream = BetterStaffChatSpigot.getInstance().getClass().getResourceAsStream("/config.yml")) { - Files.copy(inputStream, file.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - - BetterStaffChatSpigot.getInstance().setConfig(YamlConfiguration.loadConfiguration(new File(BetterStaffChatSpigot.getInstance().getPluginDataFolder(), "config.yml"))); - } -} diff --git a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/util/StaffChatUtil.java b/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/util/StaffChatUtil.java deleted file mode 100755 index 7a3417e..0000000 --- a/BetterStaffChat-spigot/src/main/java/dev/austech/betterstaffchat/spigot/util/StaffChatUtil.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * BetterStaffChat - StaffChatUtil.java - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.austech.betterstaffchat.spigot.util; - -import dev.austech.betterstaffchat.common.discord.DiscordWebhook; -import dev.austech.betterstaffchat.common.util.AbstractStaffChatUtil; -import dev.austech.betterstaffchat.common.util.TextUtil; -import dev.austech.betterstaffchat.spigot.BetterStaffChatSpigot; -import lombok.Getter; -import net.md_5.bungee.api.ChatColor; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.awt.*; -import java.io.IOException; - -public class StaffChatUtil extends AbstractStaffChatUtil { - @Getter private static final StaffChatUtil instance = new StaffChatUtil(); - - public String getFormattedMessage(Object sender, String message) { - message = TextUtil.colorize( - BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.format") - .replace("%player_name%", (sender instanceof Player) ? ((Player) sender).getName() : BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.console-replacement")) - .replace("%message%", BetterStaffChatSpigot.getInstance().getConfig().getBoolean("staffchat.strip-color-codes") ? ChatColor.stripColor(TextUtil.colorize(message)) : message) - ); - - if (sender instanceof Player) { - message = TextUtil.colorize(placeholder((Player) sender, message)); - } - return message; - } - - public String placeholder(Player player, String string) { - if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { - return me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, string); - } else return string; - } - - - /** - * An alternative to {@link Bukkit#broadcast} with support for staffchat toggle and Discord - */ - public void broadcast(String string, String permission) { - for (Player player : Bukkit.getOnlinePlayers()) { - if (!BetterStaffChatSpigot.getInstance().getIgnoreStaffChat().contains(player.getUniqueId()) && player.hasPermission(permission)) { - player.sendMessage(string); - } - } - - if (BetterStaffChatSpigot.getInstance().getConfig().getBoolean("staffchat.log-to-console")) { - Bukkit.getConsoleSender().sendMessage(TextUtil.colorize(string)); - } - } - - public void discord(Object sender, String string) { - if (sender instanceof Player) string = placeholder((Player) sender, string); - string = ChatColor.stripColor(TextUtil.colorize(string)); - String finalString = string; - Bukkit.getScheduler().runTaskAsynchronously(BetterStaffChatSpigot.getInstance(), () -> { - - if (BetterStaffChatSpigot.getInstance().getConfig().getBoolean("discord.webhook.enabled")) { - DiscordWebhook webhook = new DiscordWebhook(BetterStaffChatSpigot.getInstance().getConfig().getString("discord.webhook.url")); - - if (BetterStaffChatSpigot.getInstance().getConfig().getBoolean("discord.discord-messages.embed.enabled")) { - webhook.addEmbed(generateEmbed(sender, finalString)); - } else { - webhook.setContent(finalString); - } - - try { - webhook.execute(); - } catch (IOException exception) { - exception.printStackTrace(); - BetterStaffChatSpigot.getInstance().logPrefix("&cFailed to send Discord webhook."); - } - } - else if (BetterStaffChatSpigot.getInstance().getConfig().getBoolean("discord.bot.enabled")) { - if (BetterStaffChatSpigot.getInstance().getConfig().getBoolean("discord.discord-messages.embed.enabled")) { - for (String guildChannelPair : BetterStaffChatSpigot.getInstance().getConfig().getStringList("discord.bot.channels")) { - String[] parts = guildChannelPair.split(": "); - BetterStaffChatSpigot.getInstance().getJda().sendEmbed(parts[0], parts[1], generateEmbed(sender, finalString)); - } - } else { - for (String guildChannelPair : BetterStaffChatSpigot.getInstance().getConfig().getStringList("discord.bot.channels")) { - String[] parts = guildChannelPair.split(": "); - BetterStaffChatSpigot.getInstance().getJda().sendMessage(parts[0], parts[1], finalString); - } - } - } - }); - } - - @Override - public void handleDiscord(Object event) { - if (BetterStaffChatSpigot.getInstance().getConfig().getStringList("discord.bot.channels").stream().anyMatch(a -> a.equals(((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getGuild().getId() + ": " + ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getChannel().getId()))) { - StringBuilder builder = new StringBuilder(); - - StringBuilder discordMessage = new StringBuilder(((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getMessage().getContentStripped().trim()); - - if (!((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getMessage().getAttachments().isEmpty()) { - for (net.dv8tion.jda.api.entities.Message.Attachment attachment : ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getMessage().getAttachments()) { - discordMessage.append(" ").append(attachment.getUrl()); - } - } - - String nickName = ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getMember().getNickname(); - - String name = BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.name-format") - .replace("%username%", ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getAuthor().getName()) - .replace("%discriminator%", ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getAuthor().getDiscriminator()) - .replace("%nickname%", nickName != null ? nickName : ((net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent) event).getAuthor().getName()); - - String message = BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.format") - .replace("%player_name%", name) - .replace("%message%", discordMessage.toString()) - .replace("%server%", "Discord") - .replaceAll("%\\S*%", ""); - - - for (String word : message.split(" ")) { - builder.append(ChatColor.translateAlternateColorCodes('&', word)); - builder.append(" "); - } - - broadcast(builder.substring(0, builder.length() - 1), "betterstaffchat.messages.read"); - } - } - - protected DiscordWebhook.EmbedObject generateEmbed(Object sender, String string) { - DiscordWebhook.EmbedObject embed = new DiscordWebhook.EmbedObject(); - embed.setColor(Color.decode(BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.embed.embed-color"))); - embed.setDescription(string); - - String footer = BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.embed.embed-footer") - .replace("%player_name%", (sender instanceof Player) ? TextUtil.cleanForDiscord(((Player) sender).getName()) : BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.console-replacement")) - .replace("%uuid%", (sender instanceof Player) ? ((Player) sender).getUniqueId().toString() : BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.console-uuid-replacement")); - if (sender instanceof Player) footer = ChatColor.stripColor(TextUtil.colorize(placeholder(((Player) sender), footer))); - - String icon = BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.embed.embed-footer-icon").replace("%player_name%", (sender instanceof Player) ? TextUtil.cleanForDiscord(((Player) sender).getName()) : BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.console-replacement")) - .replace("%uuid%", (sender instanceof Player) ? ((Player) sender).getUniqueId().toString() : BetterStaffChatSpigot.getInstance().getConfig().getString("staffchat.console-uuid-replacement")); - if (sender instanceof Player) icon = ChatColor.stripColor(TextUtil.colorize(placeholder(((Player) sender), icon))); - - if (!BetterStaffChatSpigot.getInstance().getConfig().getString("discord.discord-messages.embed.embed-footer").equals("")) { - embed.setFooter(footer, icon); - } else embed.setFooter(footer, null); - return embed; - } -} diff --git a/BetterStaffChat-spigot/src/main/resources/plugin.yml b/BetterStaffChat-spigot/src/main/resources/plugin.yml deleted file mode 100644 index c5a88f2..0000000 --- a/BetterStaffChat-spigot/src/main/resources/plugin.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: BetterStaffChat -version: @version@ -main: dev.austech.betterstaffchat.spigot.BetterStaffChatSpigot -author: AusTechDev \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100755 index 44387de..0000000 --- a/build.gradle +++ /dev/null @@ -1,158 +0,0 @@ -/* - * BetterStaffChat - build.gradle - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -import org.apache.tools.ant.filters.ReplaceTokens -import org.eclipse.jgit.lib.Ref -import org.eclipse.jgit.lib.Repository -import org.eclipse.jgit.revwalk.RevWalk - -plugins { - id 'java' - id 'org.ajoberstar.grgit' version '4.1.1' - id 'com.github.johnrengelman.shadow' version '7.0.0' - id "io.freefair.lombok" version "6.0.0-m2" - id 'net.kyori.indra' version "2.0.4" apply false - id 'net.kyori.indra.git' version "2.0.4" -} - -group = 'dev.austech' -version = '1.1.2-SNAPSHOT' - -// https://github.com/EssentialsX/Essentials/blob/2.x/build.gradle -@SuppressWarnings('GrMethodMayBeStatic') -def commitsSinceLastTag() { - if (indraGit == null || !indraGit.isPresent() || indraGit.tags().isEmpty()) { - return -1 - } - def tags = indraGit.tags() - def depth = 0 - def walk = new RevWalk(indraGit.git().getRepository()) - def commit = walk.parseCommit(indraGit.commit()) - while (true) { - for (tag in tags) { - if (walk.parseCommit(tag.getLeaf().getObjectId()) == commit) { - walk.dispose() - indraGit.git().close() - return depth - } - } - depth++ - commit = walk.parseCommit(commit.getParents()[0]) - } -} - -@SuppressWarnings('GrMethodMayBeStatic') -def headBranchName() { - if (System.getenv("GITHUB_HEAD_REF") != null && !System.getenv("GITHUB_HEAD_REF").isEmpty()) { - return System.getenv("GITHUB_HEAD_REF") - } else if (System.getenv("GITHUB_REF") != null && !System.getenv("GITHUB_REF").isEmpty()) { - return System.getenv("GITHUB_REF").replaceFirst("refs/heads/", "") - } - - if (!indraGit.isPresent()) { - return "detached-head" - } - - Ref ref = indraGit.git().getRepository().exactRef('HEAD')?.target - if (ref == null) { - return "detached-head" - } - - return Repository.shortenRefName(ref.name) -} - -ext { - GIT_COMMIT = !indraGit.isPresent() ? "unknown" : indraGit.commit().abbreviate(7).name() - GIT_DEPTH = commitsSinceLastTag() - GIT_BRANCH = headBranchName() - - fullVersion = "${version}".replace("-SNAPSHOT", "-dev+${GIT_DEPTH}-${GIT_COMMIT}") -} - -subprojects { - apply plugin: 'java' - apply plugin: 'idea' - apply plugin: 'com.github.johnrengelman.shadow' - apply plugin: "io.freefair.lombok" - - group = project.group - version = project.version - - sourceCompatibility = '1.8' - targetCompatibility = '1.8' - - repositories { - mavenCentral() - maven { url 'https://m2.dv8tion.net/releases' } - } - - dependencies { - compileOnly 'net.dv8tion:JDA:4.2.1_262' - compileOnly 'org.projectlombok:lombok:1.18.20' - } - - processResources { - inputs.property('fullVersion', fullVersion) - filter ReplaceTokens, tokens: [ - "version": fullVersion - ] - } - - tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - } - - tasks.withType(Jar) { - archiveVersion.set(fullVersion) - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation project(path: ':BetterStaffChat-common', configuration: 'shadow') - implementation project(path: ':BetterStaffChat-spigot', configuration: 'shadow') - implementation project(path: ':BetterStaffChat-bungeecord', configuration: 'shadow') -} - -shadowJar { - relocate 'net.dv8tion.jda', 'dev.austech.betterstaffchat.shaded.jda' - relocate 'org.bstats', 'dev.austech.betterstaffchat.shaded.metrics' -} - -task copyJars(type: Copy) { - outputTasks().forEach({ from(it) }) - rename '(.*)-all.jar', ('$1' + '.jar') - rename project.name + "-" + project.version + ".jar", project.name + "-" + project.fullVersion + ".jar" - into file('jars') -} - -def outputTasks() { - ["shadowJar", ":BetterStaffChat-spigot:shadowJar", ":BetterStaffChat-bungeecord:shadowJar"].stream().map({ tasks.findByPath(it) }) -} - -task cleanJars() { - delete file('jars') -} - -clean.dependsOn cleanJars -build.dependsOn shadowJar -build.dependsOn copyJars diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..9de1c1f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,106 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + java + id("org.jetbrains.kotlin.jvm") version "1.4.31" + id("io.freefair.lombok") version "6.0.0-m2" + id("com.github.johnrengelman.shadow") version "7.0.0" +} + +group = "dev.austech" +version = "2.0.0-SNAPSHOT" + +repositories { + mavenCentral() + gradlePluginPortal() +} + +dependencies { + implementation(project(":common", "shadow")) + implementation(project(":spigot", "shadow")) +} + +tasks { + withType { + expand("version" to project.version) + } +} + +subprojects { + apply(plugin = "java") + apply(plugin = "idea") + apply(plugin = "com.github.johnrengelman.shadow") + apply(plugin = "io.freefair.lombok") + apply(plugin = "org.jetbrains.kotlin.jvm") + + group = project.group + version = project.version + + repositories { + mavenCentral() + maven { + url = uri("https://jitpack.io") + } + maven { + url = uri("https://repo.alessiodp.com/releases/") + } + } + + dependencies { + implementation(kotlin("stdlib-jdk8")) + compileOnly("com.github.simplix-softworks:SimplixStorage:3.2.3") + compileOnly("net.kyori:adventure-api:4.10.1") + compileOnly("net.kyori:adventure-text-minimessage:4.10.1") + } + + sourceSets { + main { + java.srcDir("src/main/kotlin") + resources.srcDir("src/main/resources") + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + tasks { + withType { + kotlinOptions { + jvmTarget = "1.8" + } + } + } +} + +tasks.named("shadowJar") { + relocate("net.kyori.adventure", "dev.austech.betterstaffchat.depend.adventure") + relocate("de.leonhard.storage", "dev.austech.betterstaffchat.depend.storage") + relocate("kotlin", "dev.austech.betterstaffchat.depend.kotlin") +} + +task("copyJars") { + outputTasks().forEach { from(it) } + rename("(.*)-all.jar", "$1.jar") + into("jars") +} + +fun outputTasks(): List { + return arrayOf("shadowJar", ":common:shadowJar", ":spigot:shadowJar").map { + tasks.findByPath(it) + } +} + +task("cleanJars") { + delete("jars") +} + +tasks.named("clean") { + dependsOn("cleanJars") +} + +tasks.named("build") { + dependsOn("shadowJar") + dependsOn("copyJars") +} \ No newline at end of file diff --git a/bungeecord/build.gradle.kts b/bungeecord/build.gradle.kts new file mode 100644 index 0000000..4121dc3 --- /dev/null +++ b/bungeecord/build.gradle.kts @@ -0,0 +1,11 @@ +repositories { + maven { + url = uri("https://oss.sonatype.org/content/repositories/snapshots/") + } +} + +dependencies { + compileOnly(project(":common")) + compileOnly("net.md-5:bungeecord-api:1.18-R0.1-SNAPSHOT") + compileOnly("net.kyori:adventure-platform-bungeecord:4.1.0") +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt new file mode 100644 index 0000000..c47892c --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt @@ -0,0 +1,55 @@ +package dev.austech.betterstaffchat.bungeecord + +import de.leonhard.storage.Json +import de.leonhard.storage.Yaml +import dev.austech.betterstaffchat.bungeecord.commands.CommandManager +import dev.austech.betterstaffchat.bungeecord.libby.BungeeLibraryManager +import dev.austech.betterstaffchat.bungeecord.listeners.PlayerListener +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.libraries.BSCLibraries +import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.common.util.Data +import dev.austech.betterstaffchat.common.util.StaffChatUtil +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.platform.bungeecord.BungeeAudiences +import net.md_5.bungee.api.plugin.Plugin +import java.io.File + +class BSCBungee: Plugin(), BSCPlugin { + companion object { + lateinit var instance: BSCBungee + } + + override val platform: BSCPlugin.Platform = BSCPlugin.Platform.BUNGEECORD + override val pluginDataFile: File = dataFolder + override lateinit var config: Yaml + override lateinit var consoleAudience: Audience + lateinit var staffChatUtil: StaffChatUtil + lateinit var audience: BungeeAudiences + private lateinit var data: Json + lateinit var dataWrapper: Data.Wrapper; + + override fun onLoad() { + val libraryManager = BungeeLibraryManager(this) + libraryManager.addMavenCentral() + libraryManager.addJitPack() + + BSCLibraries.getLibraries(platform).forEach { + libraryManager.loadLibrary(it) + } + } + + override fun onEnable() { + instance = this + audience = BungeeAudiences.create(this) + consoleAudience = audience.console() + config = Config(this).load() + staffChatUtil = StaffChatUtil(this) + data = Data(this).load() + dataWrapper = Data.Wrapper(data) + + CommandManager(this).registerCommands() + + proxy.pluginManager.registerListener(this, PlayerListener()) + } +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/BSCBungeeCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/BSCBungeeCommand.kt new file mode 100644 index 0000000..26dad65 --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/BSCBungeeCommand.kt @@ -0,0 +1,60 @@ +package dev.austech.betterstaffchat.bungeecord.commands + +import dev.austech.betterstaffchat.bungeecord.BSCBungee +import net.md_5.bungee.api.ChatColor +import net.md_5.bungee.api.CommandSender +import net.md_5.bungee.api.chat.TextComponent +import net.md_5.bungee.api.connection.ProxiedPlayer +import net.md_5.bungee.api.plugin.Command + +abstract class BSCBungeeCommand(name: String, permission: String?, aliases: List): Command(name, permission, *aliases.toTypedArray()) { + private lateinit var sender: CommandSender + protected lateinit var plugin: BSCBungee + + override fun execute(sender: CommandSender?, args: Array?) { + requireNotNull(sender) { "Sender cannot be null" } + + this.sender = sender + this.plugin = BSCBungee.instance + + run(sender, args ?: arrayOf()) + } + + abstract fun run(sender: CommandSender, args: Array) + + fun CommandSender.isConsole(): Boolean { + return this !is ProxiedPlayer + } + + fun CommandSender.requirePlayer(): Boolean { + if (this.isConsole()) { + errorTell("You must be a player to use this command.") + return false + } + return true + } + + fun CommandSender.requirePermission(permission: String): Boolean { + return requirePermission(permission, "You do not have permission.") + } + + fun CommandSender.requirePermission(permission: String, message: String): Boolean { + if (!hasPermission(permission)) { + errorTell(message) + return false + } + return true + } + + fun legacyTell(s: String) { + return this.sender.sendMessage(TextComponent(*TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', s)))) + } + + fun errorTell(s: String) { + return legacyTell("&c$s") + } + + fun warnTell(s: String) { + legacyTell("&e$s") + } +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt new file mode 100644 index 0000000..9c6ccf2 --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt @@ -0,0 +1,33 @@ +package dev.austech.betterstaffchat.bungeecord.commands + +import dev.austech.betterstaffchat.bungeecord.BSCBungee +import dev.austech.betterstaffchat.bungeecord.commands.impl.BetterStaffChatCommand +import dev.austech.betterstaffchat.bungeecord.commands.impl.StaffChatCommand +import dev.austech.betterstaffchat.bungeecord.commands.impl.StaffChatMuteCommand +import dev.austech.betterstaffchat.bungeecord.commands.impl.StaffChatToggleCommand +import dev.austech.betterstaffchat.common.util.Config +import net.md_5.bungee.api.plugin.Command + +class CommandManager(private val plugin: BSCBungee) { + private val commands: List = listOf( + StaffChatCommand( + plugin.config.getString(Config.Paths.COMMAND_STAFFCHAT_COMMAND.toString())?: "staffchat", + plugin.config.getStringList(Config.Paths.COMMAND_STAFFCHAT_ALIASES.toString()) + ), + StaffChatMuteCommand( + plugin.config.getString(Config.Paths.COMMAND_MUTE_COMMAND.toString())?: "mutestaffchat", + plugin.config.getStringList(Config.Paths.COMMAND_MUTE_ALIASES.toString()) + ), + StaffChatToggleCommand( + plugin.config.getString(Config.Paths.COMMAND_TOGGLE_COMMAND.toString())?: "togglestaffchat", + plugin.config.getStringList(Config.Paths.COMMAND_TOGGLE_ALIASES.toString()) + ), + BetterStaffChatCommand() + ) + + fun registerCommands() { + commands.forEach { + plugin.proxy.pluginManager.registerCommand(plugin, it) + } + } +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/BetterStaffChatCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/BetterStaffChatCommand.kt new file mode 100644 index 0000000..7914c42 --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/BetterStaffChatCommand.kt @@ -0,0 +1,23 @@ +package dev.austech.betterstaffchat.bungeecord.commands.impl + +import dev.austech.betterstaffchat.bungeecord.commands.BSCBungeeCommand +import net.md_5.bungee.api.CommandSender + +class BetterStaffChatCommand: BSCBungeeCommand("betterstaffchat", null, listOf("bsc")) { + override fun run(sender: CommandSender, args: Array) { + if (args.isEmpty()) { + legacyTell("&7Running &9BetterStaffChat &7by &6AusTech Development&7.") + return + } + + when (args[0]) { + "reload" -> { + if (!sender.requirePermission("betterstaffchat.reload")) return; + warnTell("It is not recommended to forcefully reload BetterStaffChat. It will automatically reload when the config is edited.") + plugin.config.forceReload() + } + } + + return + } +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt new file mode 100644 index 0000000..d9dbbd6 --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt @@ -0,0 +1,23 @@ +package dev.austech.betterstaffchat.bungeecord.commands.impl + +import dev.austech.betterstaffchat.bungeecord.commands.BSCBungeeCommand +import dev.austech.betterstaffchat.bungeecord.util.PlayerUtil +import dev.austech.betterstaffchat.common.PlayerMeta +import net.md_5.bungee.api.CommandSender +import net.md_5.bungee.api.connection.ProxiedPlayer + +class StaffChatCommand(name: String, aliases: List): BSCBungeeCommand(name, "betterstaffchat.messages.send", aliases) { + override fun run(sender: CommandSender, args: Array) { + if (args.isEmpty()) { + errorTell("Usage: /$name ") + return; + } + + if (sender is ProxiedPlayer && plugin.dataWrapper.getMutedPlayers().contains(sender.uniqueId)) { + errorTell("Your staffchat is currently muted.") + return; + } + + plugin.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), args.joinToString(separator = " "), PlayerUtil.getSenderName(sender), PlayerUtil.getServer(sender), PlayerMeta(null, null)) + } +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt new file mode 100644 index 0000000..2cb70d9 --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt @@ -0,0 +1,53 @@ +package dev.austech.betterstaffchat.bungeecord.commands.impl + +import dev.austech.betterstaffchat.bungeecord.commands.BSCBungeeCommand +import dev.austech.betterstaffchat.common.util.Config +import net.md_5.bungee.api.CommandSender +import net.md_5.bungee.api.connection.ProxiedPlayer + +class StaffChatMuteCommand(name: String, aliases: List): BSCBungeeCommand(name, "betterstaffchat.mutestaffchat", aliases) { + override fun run(sender: CommandSender, args: Array) { + if (sender.requirePlayer()) { + return + } + + if (sender !is ProxiedPlayer) { + throw IllegalStateException("Sender is not a player!") + } + + val mute: Boolean + val mutedPlayers = plugin.dataWrapper.getMutedPlayers() + + if (args.isNotEmpty()) { + if (args[0].equals("off", true)) { + if (!mutedPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is not muted.") + return + } + + mute = false + } else if (args[0].equals("on", true)) { + if (mutedPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is already muted.") + return + } + + mute = true + } else { + mute = !mutedPlayers.contains(sender.uniqueId) + } + } else { + mute = !mutedPlayers.contains(sender.uniqueId) + } + + if (mute) { + plugin.dataWrapper.addMutedPlayer(sender.uniqueId) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_MUTE_ON_MESSAGE.toString())) + } else { + plugin.dataWrapper.removeMutedPlayer(sender.uniqueId) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_MUTE_OFF_MESSAGE.toString())) + } + + return + } +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt new file mode 100644 index 0000000..625e31b --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt @@ -0,0 +1,60 @@ +package dev.austech.betterstaffchat.bungeecord.commands.impl + +import dev.austech.betterstaffchat.bungeecord.commands.BSCBungeeCommand +import dev.austech.betterstaffchat.common.util.Config +import net.md_5.bungee.api.CommandSender +import net.md_5.bungee.api.connection.ProxiedPlayer + +class StaffChatToggleCommand(name: String, aliases: List): BSCBungeeCommand(name, "betterstaffchat.mutestaffchat", aliases) { + override fun run(sender: CommandSender, args: Array) { + if (sender.requirePlayer()) { + return + } + + if (sender !is ProxiedPlayer) { + throw IllegalStateException("Sender is not a player!") + } + + val toggleOn: Boolean + val toggledPlayers = plugin.dataWrapper.getToggledPlayers() + + if (args.isNotEmpty()) { + if (args[0].equals("off", true)) { + if (!toggledPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is not toggled.") + return + } + + toggleOn = false + } else if (args[0].equals("on", true)) { + if (toggledPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is already toggled.") + return + } + + toggleOn = true + } else { + toggleOn = !toggledPlayers.contains(sender.uniqueId) + } + } else { + toggleOn = !toggledPlayers.contains(sender.uniqueId) + } + + if (toggleOn) { + val mutedPlayers = plugin.dataWrapper.getMutedPlayers() + + if (mutedPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is muted. Please unmute it before toggling.") + return + } + + plugin.dataWrapper.addToggledPlayer(sender.uniqueId) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_TOGGLE_ON_MESSAGE.toString())) + } else { + plugin.dataWrapper.removeToggledPlayer(sender.uniqueId) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_TOGGLE_OFF_MESSAGE.toString())) + } + + return + } +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java new file mode 100644 index 0000000..854a279 --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java @@ -0,0 +1,51 @@ +package dev.austech.betterstaffchat.bungeecord.libby; + +import dev.austech.betterstaffchat.common.libraries.libby.LibraryManager; +import dev.austech.betterstaffchat.common.libraries.libby.classloader.URLClassLoaderHelper; +import dev.austech.betterstaffchat.common.libraries.libby.logging.adapters.JDKLogAdapter; +import net.md_5.bungee.api.plugin.Plugin; + +import java.net.URLClassLoader; +import java.nio.file.Path; + +import static java.util.Objects.requireNonNull; + +/** + * A runtime dependency manager for Bungee plugins. + */ +public class BungeeLibraryManager extends LibraryManager { + /** + * Plugin classpath helper + */ + private final URLClassLoaderHelper classLoader; + + /** + * Creates a new Bungee library manager. + * + * @param plugin the plugin to manage + */ + public BungeeLibraryManager(Plugin plugin) { + this(plugin, "lib"); + } + + /** + * Creates a new Bungee library manager. + * + * @param plugin the plugin to manage + * @param directoryName download directory name + */ + public BungeeLibraryManager(Plugin plugin, String directoryName) { + super(new JDKLogAdapter(requireNonNull(plugin, "plugin").getLogger()), plugin.getDataFolder().toPath(), directoryName); + classLoader = new URLClassLoaderHelper((URLClassLoader) plugin.getClass().getClassLoader(), this); + } + + /** + * Adds a file to the Bungee plugin's classpath. + * + * @param file the file to add + */ + @Override + protected void addToClasspath(Path file) { + classLoader.addToClasspath(file); + } +} diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt new file mode 100644 index 0000000..d6b4f0e --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt @@ -0,0 +1,60 @@ +package dev.austech.betterstaffchat.bungeecord.listeners + +import dev.austech.betterstaffchat.bungeecord.BSCBungee +import dev.austech.betterstaffchat.bungeecord.util.PlayerUtil +import dev.austech.betterstaffchat.common.PlayerMeta +import dev.austech.betterstaffchat.common.util.Config +import net.md_5.bungee.api.ProxyServer +import net.md_5.bungee.api.connection.ProxiedPlayer +import net.md_5.bungee.api.event.ChatEvent +import net.md_5.bungee.api.event.PlayerDisconnectEvent +import net.md_5.bungee.api.event.PostLoginEvent +import net.md_5.bungee.api.event.ServerSwitchEvent +import net.md_5.bungee.api.plugin.Listener +import net.md_5.bungee.event.EventHandler +import java.util.concurrent.TimeUnit + +class PlayerListener: Listener { + @EventHandler + fun onPlayerChat(event: ChatEvent) { + if (event.isCommand || event.isProxyCommand) return + + val player = event.sender as ProxiedPlayer + + if (player.hasPermission("betterstaffchat.messages.send")) { + val prefix = BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT_PREFIX.toString()) ?: ""; + + if (event.message.startsWith(prefix) && event.message.length > prefix.length && prefix.isNotBlank() && !prefix.startsWith("/")) { + event.isCancelled = true; + BSCBungee.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message.substring(prefix.length), player.name, PlayerUtil.getServer(player), PlayerMeta(null, null)) + } else if (BSCBungee.instance.dataWrapper.getToggledPlayers().contains(player.uniqueId)) { + event.isCancelled = true; + BSCBungee.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message, player.name, PlayerUtil.getServer(player), PlayerMeta(null, null)) + } + } + } + + @EventHandler + fun onPlayerJoin(event: PostLoginEvent) { + if (event.player.hasPermission("betterstaffchat.messages.join")) { + ProxyServer.getInstance().scheduler.schedule(BSCBungee.instance, { + BSCBungee.instance.staffChatUtil.sendJoinMessage(PlayerUtil.getReceiveAudience(), event.player.name, PlayerUtil.getServer(event.player), PlayerMeta(null, null)) + }, 1, TimeUnit.SECONDS) + } + } + + @EventHandler + fun onPlayerQuit(event: PlayerDisconnectEvent) { + if (event.player.hasPermission("betterstaffchat.messages.leave")) { + BSCBungee.instance.staffChatUtil.sendLeaveMessage(PlayerUtil.getReceiveAudience(), event.player.name, PlayerUtil.getServer(event.player), PlayerMeta(null, null)) + } + } + + @EventHandler + fun onPlayerSwitch(event: ServerSwitchEvent) { + if (event.player.hasPermission("betterstaffchat.messages.switch")) { + requireNotNull(event.from) { "From server is null! This means you are using an old version of FlameCord or Aegis." } + BSCBungee.instance.staffChatUtil.sendSwitchMessage(PlayerUtil.getReceiveAudience(), event.player.name, PlayerUtil.getServerReplacement(event.from), PlayerUtil.getServer(event.player), PlayerMeta(null, null)) + } + } +} \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt new file mode 100644 index 0000000..31dad43 --- /dev/null +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt @@ -0,0 +1,49 @@ +package dev.austech.betterstaffchat.bungeecord.util + +import dev.austech.betterstaffchat.bungeecord.BSCBungee +import dev.austech.betterstaffchat.common.util.Config +import net.kyori.adventure.audience.Audience +import net.md_5.bungee.api.CommandSender +import net.md_5.bungee.api.ProxyServer +import net.md_5.bungee.api.config.ServerInfo +import net.md_5.bungee.api.connection.ProxiedPlayer +import java.util.* + +object PlayerUtil { + fun getSenderName(sender: CommandSender): String { + return if (sender is ProxiedPlayer) sender.name + else BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT_CONSOLE.toString()) + } + + private fun getMutedPlayers(): List { + return BSCBungee.instance.dataWrapper.getMutedPlayers() + } + + private fun getPermissionAudience(permission: String): Audience = BSCBungee.instance.audience.filter { + it.hasPermission(permission) && it is ProxiedPlayer && !getMutedPlayers().contains(it.uniqueId) + } + + private fun getMixedAudience(permission: String): Audience = BSCBungee.instance.audience.filter { + it.hasPermission(permission) && if (it is ProxiedPlayer) !getMutedPlayers().contains(it.uniqueId) else true + } + + fun getReceiveAudience(): Audience { + return if (BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT_CONSOLE_LOG.toString())) getMixedAudience( + "betterstaffchat.messages.read" + ) + else getPermissionAudience("betterstaffchat.messages.read") + } + + fun getServer(sender: CommandSender): String { + return if (sender !is ProxyServer) BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT_CONSOLE_SERVER.toString()) + else getServerReplacement((sender as ProxiedPlayer).server.info) + } + + fun getServerReplacement(info: ServerInfo): String { + BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT_SERVER_REPLACEMENTS.toString() + "." + info.name)?.let { + return it + } + + return info.name + } +} \ No newline at end of file diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 0000000..374aa04 --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1 @@ +// Nothing needs to be in this file \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt new file mode 100644 index 0000000..e2c68a1 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt @@ -0,0 +1,17 @@ +package dev.austech.betterstaffchat.common + +import de.leonhard.storage.Yaml +import net.kyori.adventure.audience.Audience +import java.io.File + +interface BSCPlugin { + val platform: Platform + val pluginDataFile: File + val config: Yaml + val consoleAudience: Audience + + enum class Platform { + BUKKIT, + BUNGEECORD + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt new file mode 100644 index 0000000..2fec9c0 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt @@ -0,0 +1,8 @@ +package dev.austech.betterstaffchat.common + +import java.util.UUID + +class PlayerMeta( + val prefix: String?, + val suffix: String? +) {} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt new file mode 100644 index 0000000..b00790e --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt @@ -0,0 +1,120 @@ +package dev.austech.betterstaffchat.common.libraries + +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.libraries.libby.Library + +object BSCLibraries { + private const val relocatePath = "dev{}austech{}betterstaffchat{}depend" + + private object Adventure { + private val adventureBuilder = Library.builder() + .groupId("net{}kyori") + .relocate("net{}kyori{}adventure", "$relocatePath{}adventure") + .relocate("net{}kyori{}examination", "$relocatePath{}adventure{}util{}examination") + + val API: Library = adventureBuilder + .artifactId("adventure-api") + .version("4.10.1") + .build() + + val NBT: Library = adventureBuilder + .artifactId("adventure-nbt") + .version("4.10.1") + .build() + + val KEY: Library = adventureBuilder + .artifactId("adventure-key") + .version("4.10.1") + .build() + + val MINIMESSAGE: Library = adventureBuilder + .artifactId("adventure-text-minimessage") + .version("4.10.1") + .build() + + val PLATFORM: Library = adventureBuilder + .artifactId("adventure-platform-api") + .version("4.1.0") + .build() + + val PLATFORM_FACET: Library = adventureBuilder + .artifactId("adventure-platform-facet") + .version("4.1.0") + .build() + + val PLATFORM_BUKKIT: Library = adventureBuilder + .artifactId("adventure-platform-bukkit") + .version("4.1.0") + .build() + + val PLATFORM_BUNGEECORD: Library = adventureBuilder + .artifactId("adventure-platform-bungee") + .version("4.1.0") + .build() + + val SERIALIZER_BUNGEECORD: Library = adventureBuilder + .artifactId("adventure-text-serializer-bungeecord") + .version("4.1.0") + .build() + + val SERIALIZER_LEGACY: Library = adventureBuilder + .artifactId("adventure-text-serializer-legacy") + .version("4.10.1") + .build() + + val SERIALIZER_GSON: Library = adventureBuilder + .artifactId("adventure-text-serializer-gson") + .version("4.10.1") + .build() + + val SERIALIZER_GSON_LEGACY_IMPL: Library = adventureBuilder + .artifactId("adventure-text-serializer-gson-legacy-impl") + .version("4.10.1") + .build() + + val EXAMINATION: Library = adventureBuilder + .artifactId("examination-api") + .version("1.3.0") + .build() + + val EXAMINATION_STRING: Library = adventureBuilder + .artifactId("examination-string") + .version("1.3.0") + .build() + } + + private val SIMPLIX_STORAGE = Library.builder() + .groupId("com{}github{}simplix-softworks") + .artifactId("SimplixStorage") + .version("3.2.3") + .relocate("de{}leonhard{}storage", "$relocatePath{}storage") + .build() + + fun getLibraries(platform: BSCPlugin.Platform?): List { + var libraryList: List = listOf( + Adventure.API, + Adventure.NBT, + Adventure.KEY, + Adventure.MINIMESSAGE, + Adventure.SERIALIZER_LEGACY, + Adventure.SERIALIZER_GSON, + Adventure.SERIALIZER_GSON_LEGACY_IMPL, + Adventure.PLATFORM, + Adventure.PLATFORM_FACET, + Adventure.EXAMINATION, + Adventure.EXAMINATION_STRING, + SIMPLIX_STORAGE + ) + + if (platform == BSCPlugin.Platform.BUKKIT) { + libraryList = libraryList.plus(Adventure.PLATFORM_BUKKIT) + } else if (platform == BSCPlugin.Platform.BUNGEECORD) { + libraryList = libraryList.plus(Adventure.PLATFORM_BUNGEECORD) + .plus(Adventure.SERIALIZER_BUNGEECORD) + } + + println("Libraries: ${libraryList.joinToString { it.artifactId.toString() }}") + + return libraryList + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Library.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Library.java new file mode 100644 index 0000000..019a53f --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Library.java @@ -0,0 +1,511 @@ +package dev.austech.betterstaffchat.common.libraries.libby; + +import dev.austech.betterstaffchat.common.libraries.libby.LibraryManager; +import dev.austech.betterstaffchat.common.libraries.libby.Repositories; +import dev.austech.betterstaffchat.common.libraries.libby.relocation.Relocation; + +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.UUID; + +import static java.util.Objects.requireNonNull; + +/** + * An immutable representation of a Maven artifact that can be downloaded, + * relocated and then loaded into a plugin's classpath at runtime. + * + * @see #builder() + */ +public class Library { + /** + * Direct download URLs for this library + */ + private final Collection urls; + + /** + * Repository URLs for this library + */ + private final Collection repositories; + + /** + * Library id (used by Isolated Class Loaders) + */ + private final String id; + + /** + * Maven group ID + */ + private final String groupId; + + /** + * Maven artifact ID + */ + private final String artifactId; + + /** + * Artifact version + */ + private final String version; + + /** + * Artifact classifier + */ + private final String classifier; + + /** + * Binary SHA-256 checksum for this library's jar file + */ + private final byte[] checksum; + + /** + * Jar relocations to apply + */ + private final Collection relocations; + + /** + * Relative Maven path to this library's artifact + */ + private final String path; + + /** + * Relative path to this library's relocated jar + */ + private final String relocatedPath; + + /** + * Should this library be loaded in an isolated class loader? + */ + private final boolean isolatedLoad; + + /** + * Creates a new library. + * + * @param urls direct download URLs + * @param id library ID + * @param groupId Maven group ID + * @param artifactId Maven artifact ID + * @param version artifact version + * @param classifier artifact classifier or null + * @param checksum binary SHA-256 checksum or null + * @param relocations jar relocations or null + * @param isolatedLoad isolated load for this library + */ + private Library(Collection urls, + String id, + String groupId, + String artifactId, + String version, + String classifier, + byte[] checksum, + Collection relocations, + boolean isolatedLoad) { + + this(urls, null, id, groupId, artifactId, version, classifier, checksum, relocations, isolatedLoad); + } + + /** + * Creates a new library. + * + * @param urls direct download URLs + * @param repositories repository URLs + * @param id library ID + * @param groupId Maven group ID + * @param artifactId Maven artifact ID + * @param version artifact version + * @param classifier artifact classifier or null + * @param checksum binary SHA-256 checksum or null + * @param relocations jar relocations or null + * @param isolatedLoad isolated load for this library + */ + private Library(Collection urls, + Collection repositories, + String id, + String groupId, + String artifactId, + String version, + String classifier, + byte[] checksum, + Collection relocations, + boolean isolatedLoad) { + + this.urls = urls != null ? Collections.unmodifiableList(new LinkedList<>(urls)) : Collections.emptyList(); + this.id = id != null ? id : UUID.randomUUID().toString(); + this.groupId = requireNonNull(groupId, "groupId").replace("{}", "."); + this.artifactId = requireNonNull(artifactId, "artifactId"); + this.version = requireNonNull(version, "version"); + this.classifier = classifier; + this.checksum = checksum; + this.relocations = relocations != null ? Collections.unmodifiableList(new LinkedList<>(relocations)) : Collections.emptyList(); + + String path = this.groupId.replace('.', '/') + '/' + artifactId + '/' + version + '/' + artifactId + '-' + version; + if (hasClassifier()) { + path += '-' + classifier; + } + + this.path = path + ".jar"; + + this.repositories = repositories != null ? Collections.unmodifiableList(new LinkedList<>(repositories)) : Collections.emptyList(); + relocatedPath = hasRelocations() ? artifactId + '-' + version + "-relocated.jar" : null; + this.isolatedLoad = isolatedLoad; + } + + /** + * Gets the direct download URLs for this library. + * + * @return direct download URLs + */ + public Collection getUrls() { + return urls; + } + + /** + * Gets the repositories URLs for this library. + * + * @return repositories URLs + */ + public Collection getRepositories() { + return repositories; + } + + /** + * Gets the library ID + * + * @return the library id + */ + public String getId() { + return id; + } + + /** + * Gets the Maven group ID for this library. + * + * @return Maven group ID + */ + public String getGroupId() { + return groupId; + } + + /** + * Gets the Maven artifact ID for this library. + * + * @return Maven artifact ID + */ + public String getArtifactId() { + return artifactId; + } + + /** + * Gets the artifact version for this library. + * + * @return artifact version + */ + public String getVersion() { + return version; + } + + /** + * Gets the artifact classifier for this library. + * + * @return artifact classifier or null + */ + public String getClassifier() { + return classifier; + } + + /** + * Gets whether this library has an artifact classifier. + * + * @return true if library has classifier, false otherwise + */ + public boolean hasClassifier() { + return classifier != null; + } + + /** + * Gets the binary SHA-256 checksum of this library's jar file. + * + * @return checksum or null + */ + public byte[] getChecksum() { + return checksum; + } + + /** + * Gets whether this library has a checksum. + * + * @return true if library has checksum, false otherwise + */ + public boolean hasChecksum() { + return checksum != null; + } + + /** + * Gets the jar relocations to apply to this library. + * + * @return jar relocations to apply + */ + public Collection getRelocations() { + return relocations; + } + + /** + * Gets whether this library has any jar relocations. + * + * @return true if library has relocations, false otherwise + */ + public boolean hasRelocations() { + return !relocations.isEmpty(); + } + + /** + * Gets the relative Maven path to this library's artifact. + * + * @return Maven path for this library + */ + public String getPath() { + return path; + } + + /** + * Gets the relative path to this library's relocated jar. + * + * @return path to relocated artifact or null if has no relocations + */ + public String getRelocatedPath() { + return relocatedPath; + } + + /** + * Is the library loaded isolated? + * + * @return true if the library is loaded isolated + */ + public boolean isIsolatedLoad() { + return isolatedLoad; + } + + /** + * Gets a concise, human-readable string representation of this library. + * + * @return string representation + */ + @Override + public String toString() { + String name = groupId + ':' + artifactId + ':' + version; + if (hasClassifier()) { + name += ':' + classifier; + } + + return name; + } + + /** + * Creates a new library builder. + * + * @return new library builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Due to the constructor complexity of an immutable {@link Library}, + * instead this fluent builder is used to configure and then construct + * a new library. + */ + public static class Builder { + /** + * Direct download URLs for this library + */ + private final Collection urls = new LinkedList<>(); + + /** + * Repository URLs for this library + */ + private final Collection repositories = new LinkedList<>(); + + /** + * The library ID + */ + private String id; + + /** + * Maven group ID + */ + private String groupId; + + /** + * Maven artifact ID + */ + private String artifactId; + + /** + * Artifact version + */ + private String version; + + /** + * Artifact classifier + */ + private String classifier; + + /** + * Binary SHA-256 checksum for this library's jar file + */ + private byte[] checksum; + + /** + * Isolated load + */ + private boolean isolatedLoad; + + /** + * Jar relocations to apply + */ + private final Collection relocations = new LinkedList<>(); + + /** + * Adds a direct download URL for this library. + * + * @param url direct download URL + * @return this builder + */ + public Builder url(String url) { + urls.add(requireNonNull(url, "url")); + return this; + } + + /** + * Adds a repository URL for this library. + *

Most common repositories can be found in {@link Repositories} class as constants. + *

Note that repositories should be preferably added to the {@link LibraryManager} via {@link LibraryManager#addRepository(String)}. + * + * @param url repository URL + * @return this builder + */ + public Builder repository(String url) { + repositories.add(requireNonNull(url, "repository").endsWith("/") ? url : url + '/'); + return this; + } + + /** + * Sets the id for this library. + * + * @param id the ID + * @return this builder + */ + public Builder id(String id) { + this.id = id != null ? id : UUID.randomUUID().toString(); + return this; + } + + /** + * Sets the Maven group ID for this library. + * + * @param groupId Maven group ID + * @return this builder + */ + public Builder groupId(String groupId) { + this.groupId = requireNonNull(groupId, "groupId"); + return this; + } + + /** + * Sets the Maven artifact ID for this library. + * + * @param artifactId Maven artifact ID + * @return this builder + */ + public Builder artifactId(String artifactId) { + this.artifactId = requireNonNull(artifactId, "artifactId"); + return this; + } + + /** + * Sets the artifact version for this library. + * + * @param version artifact version + * @return this builder + */ + public Builder version(String version) { + this.version = requireNonNull(version, "version"); + return this; + } + + /** + * Sets the artifact classifier for this library. + * + * @param classifier artifact classifier + * @return this builder + */ + public Builder classifier(String classifier) { + this.classifier = requireNonNull(classifier, "classifier"); + return this; + } + + /** + * Sets the binary SHA-256 checksum for this library. + * + * @param checksum binary SHA-256 checksum + * @return this builder + */ + public Builder checksum(byte[] checksum) { + this.checksum = requireNonNull(checksum, "checksum"); + return this; + } + + /** + * Sets the Base64-encoded SHA-256 checksum for this library. + * + * @param checksum Base64-encoded SHA-256 checksum + * @return this builder + */ + public Builder checksum(String checksum) { + return checksum(Base64.getDecoder().decode(requireNonNull(checksum, "checksum"))); + } + + /** + * Sets the isolated load for this library. + * + * @param isolatedLoad the isolated load boolean + * @return this builder + */ + public Builder isolatedLoad(boolean isolatedLoad) { + this.isolatedLoad = isolatedLoad; + return this; + } + + /** + * Adds a jar relocation to apply to this library. + * + * @param relocation jar relocation to apply + * @return this builder + */ + public Builder relocate(Relocation relocation) { + relocations.add(requireNonNull(relocation, "relocation")); + return this; + } + + /** + * Adds a jar relocation to apply to this library. + * + * @param pattern search pattern + * @param relocatedPattern replacement pattern + * @return this builder + */ + public Builder relocate(String pattern, String relocatedPattern) { + return relocate(new Relocation(pattern, relocatedPattern)); + } + + /** + * Creates a new library using this builder's configuration. + * + * @return new library + */ + public Library build() { + return new Library(urls, repositories, id, groupId, artifactId, version, classifier, checksum, relocations, isolatedLoad); + } + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java new file mode 100644 index 0000000..0e0a023 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java @@ -0,0 +1,455 @@ +package dev.austech.betterstaffchat.common.libraries.libby; + +import dev.austech.betterstaffchat.common.libraries.libby.Repositories; +import dev.austech.betterstaffchat.common.libraries.libby.classloader.IsolatedClassLoader; +import dev.austech.betterstaffchat.common.libraries.libby.logging.LogLevel; +import dev.austech.betterstaffchat.common.libraries.libby.logging.Logger; +import dev.austech.betterstaffchat.common.libraries.libby.logging.adapters.LogAdapter; +import dev.austech.betterstaffchat.common.libraries.libby.relocation.Relocation; +import dev.austech.betterstaffchat.common.libraries.libby.relocation.RelocationHelper; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +/** + * A runtime dependency manager for plugins. + *

+ * The library manager can resolve a dependency jar through the configured + * Maven repositories, download it into a local cache, relocate it and then + * load it into the plugin's classpath. + *

+ * Transitive dependencies for a library aren't downloaded automatically and + * must be explicitly loaded like every other library. + *

+ * It's recommended that libraries are relocated to prevent any namespace + * conflicts with different versions of the same library bundled with other + * plugins or maybe even bundled with the server itself. + * + * @see Library + */ +public abstract class LibraryManager { + /** + * Wrapped plugin logger + */ + protected final Logger logger; + + /** + * Directory where downloaded library jars are saved to + */ + protected final Path saveDirectory; + + /** + * Maven repositories used to resolve artifacts + */ + private final Set repositories = new LinkedHashSet<>(); + + /** + * Lazily-initialized relocation helper that uses reflection to call into + * Luck's Jar Relocator + */ + private RelocationHelper relocator; + + /** + * Map of isolated class loaders and theirs id + */ + private final Map isolatedLibraries = new HashMap<>(); + + /** + * Creates a new library manager. + * + * @param logAdapter plugin logging adapter + * @param dataDirectory plugin's data directory + * + * @deprecated Use {@link LibraryManager#LibraryManager(LogAdapter, Path, String)} + */ + @Deprecated + protected LibraryManager(LogAdapter logAdapter, Path dataDirectory) { + logger = new Logger(requireNonNull(logAdapter, "logAdapter")); + saveDirectory = requireNonNull(dataDirectory, "dataDirectory").toAbsolutePath().resolve("lib"); + } + + /** + * Creates a new library manager. + * + * @param logAdapter plugin logging adapter + * @param dataDirectory plugin's data directory + * @param directoryName download directory name + */ + protected LibraryManager(LogAdapter logAdapter, Path dataDirectory, String directoryName) { + logger = new Logger(requireNonNull(logAdapter, "logAdapter")); + saveDirectory = requireNonNull(dataDirectory, "dataDirectory").toAbsolutePath().resolve(requireNonNull(directoryName, "directoryName")); + } + + /** + * Adds a file to the plugin's classpath. + * + * @param file the file to add + */ + protected abstract void addToClasspath(Path file); + + /** + * Adds a file to the isolated class loader + * + * @param library the library to add + * @param file the file to add + */ + protected void addToIsolatedClasspath(Library library, Path file) { + IsolatedClassLoader classLoader; + String id = library.getId(); + if (id != null) { + classLoader = isolatedLibraries.computeIfAbsent(id, s -> new IsolatedClassLoader()); + } else { + classLoader = new IsolatedClassLoader(); + } + classLoader.addPath(file); + } + + /** + * Get the isolated class loader of the library + * + * @param libraryId the id of the library + */ + public IsolatedClassLoader getIsolatedClassLoaderOf(String libraryId) { + return isolatedLibraries.get(libraryId); + } + + /** + * Gets the logging level for this library manager. + * + * @return log level + */ + public LogLevel getLogLevel() { + return logger.getLevel(); + } + + /** + * Sets the logging level for this library manager. + *

+ * By setting this value, the library manager's logger will not log any + * messages with a level less severe than the configured level. This can be + * useful for silencing the download and relocation logging. + *

+ * Setting this value to {@link LogLevel#WARN} would silence informational + * logging but still print important things like invalid checksum warnings. + * + * @param level the log level to set + */ + public void setLogLevel(LogLevel level) { + logger.setLevel(level); + } + + /** + * Gets the currently added repositories used to resolve artifacts. + *

+ * For each library this list is traversed to download artifacts after the + * direct download URLs have been attempted. + * + * @return current repositories + */ + public Collection getRepositories() { + List urls; + synchronized (repositories) { + urls = new LinkedList<>(repositories); + } + + return Collections.unmodifiableList(urls); + } + + /** + * Adds a repository URL to this library manager. + *

+ * Artifacts will be resolved using this repository when attempts to locate + * the artifact through previously added repositories are all unsuccessful. + * + * @param url repository URL to add + */ + public void addRepository(String url) { + String repo = requireNonNull(url, "url").endsWith("/") ? url : url + '/'; + synchronized (repositories) { + repositories.add(repo); + } + } + + /** + * Adds the current user's local Maven repository. + */ + public void addMavenLocal() { + addRepository(Paths.get(System.getProperty("user.home")).resolve(".m2/repository").toUri().toString()); + } + + /** + * Adds the Maven Central repository. + */ + public void addMavenCentral() { + addRepository(Repositories.MAVEN_CENTRAL); + } + + /** + * Adds the Sonatype OSS repository. + */ + public void addSonatype() { + addRepository(Repositories.SONATYPE); + } + + /** + * Adds the Bintray JCenter repository. + */ + public void addJCenter() { + addRepository(Repositories.JCENTER); + } + + /** + * Adds the JitPack repository. + */ + public void addJitPack() { + addRepository(Repositories.JITPACK); + } + + /** + * Gets all of the possible download URLs for this library. Entries are + * ordered by direct download URLs first and then repository download URLs. + * + * @param library the library to resolve + * @return download URLs + */ + public Collection resolveLibrary(Library library) { + Set urls = new LinkedHashSet<>(requireNonNull(library, "library").getUrls()); + + // Try from library-declared repos first + for (String repository : library.getRepositories()) { + urls.add(repository + library.getPath()); + } + + for (String repository : getRepositories()) { + urls.add(repository + library.getPath()); + } + + return Collections.unmodifiableSet(urls); + } + + /** + * Downloads a library jar and returns the contents as a byte array. + * + * @param url the URL to the library jar + * @return downloaded jar as byte array or null if nothing was downloaded + */ + private byte[] downloadLibrary(String url) { + try { + URLConnection connection = new URL(requireNonNull(url, "url")).openConnection(); + + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"); + + try (InputStream in = connection.getInputStream()) { + int len; + byte[] buf = new byte[8192]; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + while ((len = in.read(buf)) != -1) { + out.write(buf, 0, len); + } + } catch (SocketTimeoutException e) { + logger.warn("Download timed out: " + connection.getURL()); + return null; + } + + logger.info("Downloaded library " + connection.getURL()); + return out.toByteArray(); + } + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } catch (IOException e) { + if (e instanceof FileNotFoundException) { + logger.debug("File not found: " + url); + } else if (e instanceof SocketTimeoutException) { + logger.debug("Connect timed out: " + url); + } else if (e instanceof UnknownHostException) { + logger.debug("Unknown host: " + url); + } else { + logger.debug("Unexpected IOException", e); + } + + return null; + } + } + + /** + * Downloads a library jar to the save directory if it doesn't already + * exist and returns the local file path. + *

+ * If the library has a checksum, it will be compared against the + * downloaded jar's checksum to verify the integrity of the download. If + * the checksums don't match, a warning is generated and the next download + * URL is attempted. + *

+ * Checksum comparison is ignored if the library doesn't have a checksum + * or if the library jar already exists in the save directory. + *

+ * Most of the time it is advised to use {@link #loadLibrary(Library)} + * instead of this method because this one is only concerned with + * downloading the jar and returning the local path. It's usually more + * desirable to download the jar and add it to the plugin's classpath in + * one operation. + * + * @param library the library to download + * @return local file path to library + * @see #loadLibrary(Library) + */ + public Path downloadLibrary(Library library) { + requireNonNull(library, "library"); + Path file = saveDirectory.resolve(library.getArtifactId() + "-" + library.getVersion() + ".jar"); + if (Files.exists(file)) { + return file; + } + + Collection urls = resolveLibrary(library); + if (urls.isEmpty()) { + throw new RuntimeException("Library '" + library + "' couldn't be resolved, add a repository"); + } + + MessageDigest md = null; + if (library.hasChecksum()) { + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + Path out = file.resolveSibling(file.getFileName() + ".tmp"); + out.toFile().deleteOnExit(); + + try { + Files.createDirectories(file.getParent()); + + for (String url : urls) { + byte[] bytes = downloadLibrary(url); + if (bytes == null) { + continue; + } + + if (md != null) { + byte[] checksum = md.digest(bytes); + if (!Arrays.equals(checksum, library.getChecksum())) { + logger.warn("*** INVALID CHECKSUM ***"); + logger.warn(" Library : " + library); + logger.warn(" URL : " + url); + logger.warn(" Expected : " + Base64.getEncoder().encodeToString(library.getChecksum())); + logger.warn(" Actual : " + Base64.getEncoder().encodeToString(checksum)); + continue; + } + } + + Files.write(out, bytes); + Files.move(out, file); + + return file; + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + try { + Files.deleteIfExists(out); + } catch (IOException ignored) { + } + } + + throw new RuntimeException("Failed to download library '" + library + "'"); + } + + /** + * Processes the input jar and generates an output jar with the provided + * relocation rules applied, then returns the path to the relocated jar. + * + * @param in input jar + * @param out output jar + * @param relocations relocations to apply + * @return the relocated file + * @see RelocationHelper#relocate(Path, Path, Collection) + */ + private Path relocate(Path in, String out, Collection relocations) { + requireNonNull(in, "in"); + requireNonNull(out, "out"); + requireNonNull(relocations, "relocations"); + + Path file = saveDirectory.resolve(out); + if (Files.exists(file)) { + return file; + } + + Path tmpOut = file.resolveSibling(file.getFileName() + ".tmp"); + tmpOut.toFile().deleteOnExit(); + + synchronized (this) { + if (relocator == null) { + relocator = new RelocationHelper(this); + } + } + + try { + relocator.relocate(in, tmpOut, relocations); + Files.move(tmpOut, file); + + logger.info("Relocations applied to " + saveDirectory.getParent().relativize(in)); + + return file; + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + try { + Files.deleteIfExists(tmpOut); + } catch (IOException ignored) { + } + } + } + + /** + * Loads a library jar into the plugin's classpath. If the library jar + * doesn't exist locally, it will be downloaded. + *

+ * If the provided library has any relocations, they will be applied to + * create a relocated jar and the relocated jar will be loaded instead. + * + * @param library the library to load + * @see #downloadLibrary(Library) + */ + public void loadLibrary(Library library) { + Path file = downloadLibrary(requireNonNull(library, "library")); + if (library.hasRelocations()) { + file = relocate(file, library.getRelocatedPath(), library.getRelocations()); + } + + if (library.isIsolatedLoad()) { + addToIsolatedClasspath(library, file); + } else { + addToClasspath(file); + } + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java new file mode 100644 index 0000000..1d7f40b --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java @@ -0,0 +1,31 @@ +package dev.austech.betterstaffchat.common.libraries.libby; + +/** + * Class containing URLs of public repositories. + */ +public class Repositories { + + /** + * Maven Central repository URL. + */ + public static final String MAVEN_CENTRAL = "https://repo1.maven.org/maven2/"; + + /** + * Sonatype OSS repository URL. + */ + public static final String SONATYPE = "https://oss.sonatype.org/content/groups/public/"; + + /** + * Bintray JCenter repository URL. + */ + public static final String JCENTER = "https://jcenter.bintray.com/"; + + /** + * JitPack repository URL. + */ + public static final String JITPACK = "https://jitpack.io/"; + + private Repositories() { + throw new UnsupportedOperationException("Private constructor"); + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java new file mode 100644 index 0000000..39c3737 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java @@ -0,0 +1,51 @@ +package dev.austech.betterstaffchat.common.libraries.libby.classloader; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; + +import static java.util.Objects.requireNonNull; + +/** + * This class loader is a simple child of {@code URLClassLoader} that uses + * the JVM's Extensions Class Loader as the parent instead of the system class + * loader to provide an unpolluted classpath. + */ +public class IsolatedClassLoader extends URLClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + /** + * Creates a new isolated class loader for the given URLs. + * + * @param urls the URLs to add to the classpath + */ + public IsolatedClassLoader(URL... urls) { + super(requireNonNull(urls, "urls"), ClassLoader.getSystemClassLoader().getParent()); + } + + /** + * Adds a URL to the classpath. + * + * @param url the URL to add + */ + @Override + public void addURL(URL url) { + super.addURL(url); + } + + /** + * Adds a path to the classpath. + * + * @param path the path to add + */ + public void addPath(Path path) { + try { + addURL(requireNonNull(path, "path").toUri().toURL()); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java new file mode 100644 index 0000000..ba41207 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java @@ -0,0 +1,233 @@ +package dev.austech.betterstaffchat.common.libraries.libby.classloader; + +import dev.austech.betterstaffchat.common.libraries.libby.Library; +import dev.austech.betterstaffchat.common.libraries.libby.LibraryManager; +import dev.austech.betterstaffchat.common.libraries.libby.Repositories; +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +/** + * A reflection-based wrapper around {@link URLClassLoader} for adding URLs to + * the classpath. + */ +public class URLClassLoaderHelper { + + /** + * Unsafe class instance. Used in {@link #getPrivilegedMethodHandle(Method)}. + */ + private static final Unsafe theUnsafe; + + static { + Unsafe unsafe = null; // Used to make theUnsafe field final + + // getDeclaredField("theUnsafe") is not used to avoid breakage on JVMs with changed field name + for (Field f : Unsafe.class.getDeclaredFields()) { + try { + if (f.getType() == Unsafe.class && Modifier.isStatic(f.getModifiers())) { + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } + } catch (Exception ignored) { + } + } + theUnsafe = unsafe; + } + + /** + * The class loader being managed by this helper. + */ + private final URLClassLoader classLoader; + + /** + * A reflected method in {@link URLClassLoader}, when invoked adds a URL to the classpath. + */ + private MethodHandle addURLMethodHandle = null; + + /** + * Creates a new URL class loader helper. + * + * @param classLoader the class loader to manage + * @param libraryManager the library manager used to download dependencies + */ + public URLClassLoaderHelper(URLClassLoader classLoader, LibraryManager libraryManager) { + requireNonNull(libraryManager, "libraryManager"); + this.classLoader = requireNonNull(classLoader, "classLoader"); + + try { + Method addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + + try { + openUrlClassLoaderModule(); + } catch (Exception ignored) { + } + + try { + addURLMethod.setAccessible(true); + } catch (Exception exception) { + // InaccessibleObjectException has been added in Java 9 + if (exception.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) { + // It is Java 9+, try to open java.net package + if (theUnsafe != null) + try { + addURLMethodHandle = getPrivilegedMethodHandle(addURLMethod).bindTo(classLoader); + return; // We're done + } catch (Exception ignored) { + addURLMethodHandle = null; // Just to be sure the field is set to null + } + // Cannot use privileged MethodHandles.Lookup, trying with java agent + try { + addOpensWithAgent(libraryManager); + addURLMethod.setAccessible(true); + } catch (Exception e) { + // Cannot access at all + System.err.println("Cannot access URLClassLoader#addURL(URL), if you are using Java 9+ try to add the following option to your java command: --add-opens java.base/java.net=ALL-UNNAMED"); + throw new RuntimeException("Cannot access URLClassLoader#addURL(URL)", e); + } + } else { + throw new RuntimeException("Cannot set accessible URLClassLoader#addURL(URL)", exception); + } + } + this.addURLMethodHandle = MethodHandles.lookup().unreflect(addURLMethod).bindTo(classLoader); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * Adds a URL to the class loader's classpath. + * + * @param url the URL to add + */ + public void addToClasspath(URL url) { + try { + addURLMethodHandle.invokeWithArguments(requireNonNull(url, "url")); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + /** + * Adds a path to the class loader's classpath. + * + * @param path the path to add + */ + public void addToClasspath(Path path) { + try { + addToClasspath(requireNonNull(path, "path").toUri().toURL()); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } + + private static void openUrlClassLoaderModule() throws Exception { + // + // Thanks to lucko (Luck) for this snippet used in his own class loader + // + // This is a workaround used to maintain Java 9+ support with reflections + // Thanks to this you will be able to run this class loader with Java 8+ + + // This is effectively calling: + // + // URLClassLoader.class.getModule().addOpens( + // URLClassLoader.class.getPackageName(), + // URLClassLoaderHelper.class.getModule() + // ); + // + // We use reflection since we build against Java 8. + + Class moduleClass = Class.forName("java.lang.Module"); + Method getModuleMethod = Class.class.getMethod("getModule"); + Method addOpensMethod = moduleClass.getMethod("addOpens", String.class, moduleClass); + + Object urlClassLoaderModule = getModuleMethod.invoke(URLClassLoader.class); + Object thisModule = getModuleMethod.invoke(URLClassLoaderHelper.class); + + addOpensMethod.invoke(urlClassLoaderModule, URLClassLoader.class.getPackage().getName(), thisModule); + } + + private MethodHandle getPrivilegedMethodHandle(Method method) throws Exception { + // Try to get a MethodHandle to URLClassLoader#addURL. + // The Unsafe class is used to get a privileged MethodHandles.Lookup instance. + + // Looking for MethodHandles.Lookup#IMPL_LOOKUP private static field + // getDeclaredField("IMPL_LOOKUP") is not used to avoid breakage on JVMs with changed field name + for (Field trustedLookup : MethodHandles.Lookup.class.getDeclaredFields()) { + if (trustedLookup.getType() != MethodHandles.Lookup.class || !Modifier.isStatic(trustedLookup.getModifiers()) || trustedLookup.isSynthetic()) + continue; + + try { + MethodHandles.Lookup lookup = (MethodHandles.Lookup) theUnsafe.getObject(theUnsafe.staticFieldBase(trustedLookup), theUnsafe.staticFieldOffset(trustedLookup)); + return lookup.unreflect(method); + } catch (Exception ignored) { + // Unreflect went wrong, trying the next field + } + } + + // Every field has been tried + throw new RuntimeException("Cannot get privileged method handle."); + } + + private void addOpensWithAgent(LibraryManager libraryManager) throws Exception { + // To open URLClassLoader's module we need permissions. + // Try to add a java agent at runtime (specifically, ByteBuddy's agent) and use it to open the module, + // since java agents should have such permission. + + // Download ByteBuddy's agent and load it through an IsolatedClassLoader + IsolatedClassLoader isolatedClassLoader = new IsolatedClassLoader(); + try { + isolatedClassLoader.addPath(libraryManager.downloadLibrary( + Library.builder() + .groupId("net.bytebuddy") + .artifactId("byte-buddy-agent") + .version("1.12.1") + .checksum("mcCtBT9cljUEniB5ESpPDYZMfVxEs1JRPllOiWTP+bM=") + .repository(Repositories.MAVEN_CENTRAL) + .build() + )); + + Class byteBuddyAgent = isolatedClassLoader.loadClass("net.bytebuddy.agent.ByteBuddyAgent"); + + // This is effectively calling: + // + // Instrumentation instrumentation = ByteBuddyAgent.install(); + // instrumentation.redefineModule( + // URLClassLoader.class.getModule(), + // Collections.emptySet(), + // Collections.emptyMap(), + // Collections.singletonMap("java.net", Collections.singleton(getClass().getModule())), + // Collections.emptySet(), + // Collections.emptyMap() + // ); + // + // For more information see https://docs.oracle.com/en/java/javase/16/docs/api/java.instrument/java/lang/instrument/Instrumentation.html + // + // We use reflection since we build against Java 8. + + Object instrumentation = byteBuddyAgent.getDeclaredMethod("install").invoke(null); + Class instrumentationClass = Class.forName("java.lang.instrument.Instrumentation"); + Method redefineModule = instrumentationClass.getDeclaredMethod("redefineModule", Class.forName("java.lang.Module"), Set.class, Map.class, Map.class, Set.class, Map.class); + Method getModule = Class.class.getDeclaredMethod("getModule"); + Map> toOpen = Collections.singletonMap("java.net", Collections.singleton(getModule.invoke(getClass()))); + redefineModule.invoke(instrumentation, getModule.invoke(URLClassLoader.class), Collections.emptySet(), Collections.emptyMap(), toOpen, Collections.emptySet(), Collections.emptyMap()); + } finally { + try { + isolatedClassLoader.close(); + } catch (Exception ignored) { + } + } + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java new file mode 100644 index 0000000..759c687 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java @@ -0,0 +1,26 @@ +package dev.austech.betterstaffchat.common.libraries.libby.logging; + +/** + * Represents the severity of a log message in {@link Logger}. + */ +public enum LogLevel { + /** + * Stuff that isn't useful to end-users + */ + DEBUG, + + /** + * Stuff that might be useful to know + */ + INFO, + + /** + * Non-fatal, often recoverable errors or notices + */ + WARN, + + /** + * Probably an unrecoverable error + */ + ERROR +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java new file mode 100644 index 0000000..be152fc --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java @@ -0,0 +1,196 @@ +package dev.austech.betterstaffchat.common.libraries.libby.logging; + +import dev.austech.betterstaffchat.common.libraries.libby.logging.LogLevel; +import dev.austech.betterstaffchat.common.libraries.libby.logging.adapters.LogAdapter; + +import static java.util.Objects.requireNonNull; + +/** + * A logging wrapper that logs to a log adapter and can be configured to filter + * log messages by severity. + */ +public class Logger { + /** + * Log adapter for the current platform + */ + private final LogAdapter adapter; + + /** + * Log level controlling which messages are logged + */ + private LogLevel level = LogLevel.INFO; + + /** + * Creates a new logger with the provided adapter. + * + * @param adapter the adapter to wrap + */ + public Logger(LogAdapter adapter) { + this.adapter = requireNonNull(adapter, "adapter"); + } + + /** + * Gets the current log level. + * + * @return current log level + */ + public LogLevel getLevel() { + return level; + } + + /** + * Sets a new log level. + * + * @param level new log level + */ + public void setLevel(LogLevel level) { + this.level = requireNonNull(level, "level"); + } + + /** + * Gets whether messages matching the provided level can be logged under + * the current log level setting. + *

+ * Returns true if provided log level is equal to or more severe than the + * logger's configured log level. + * + * @param level the level to check + * @return true if message can be logged, or false + */ + private boolean canLog(LogLevel level) { + return requireNonNull(level, "level").compareTo(this.level) >= 0; + } + + /** + * Logs a message with the provided level. + *

+ * If the provided log level is less severe than the logger's + * configured log level, this message won't be logged. + * + * @param level message severity level + * @param message the message to log + * @see #debug(String) + * @see #info(String) + * @see #warn(String) + * @see #error(String) + */ + public void log(LogLevel level, String message) { + if (canLog(level)) { + adapter.log(level, message); + } + } + + /** + * Logs a message and stack trace with the provided level. + *

+ * If the provided log level is less severe than the logger's + * configured log level, this message won't be logged. + * + * @param level message severity level + * @param message the message to log + * @param throwable the throwable to print + * @see #debug(String, Throwable) + * @see #info(String, Throwable) + * @see #warn(String, Throwable) + * @see #error(String, Throwable) + */ + public void log(LogLevel level, String message, Throwable throwable) { + if (canLog(level)) { + adapter.log(level, message, throwable); + } + } + + /** + * Logs a debug message. + *

+ * If the logger's configured log level is more severe than + * {@link LogLevel#DEBUG}, this message won't be logged. + * + * @param message the message to log + */ + public void debug(String message) { + log(LogLevel.DEBUG, message); + } + + /** + * Logs a debug message with a stack trace. + *

+ * If the logger's configured log level is more severe than + * {@link LogLevel#DEBUG}, this message won't be logged. + * + * @param message the message to log + * @param throwable the throwable to print + */ + public void debug(String message, Throwable throwable) { + log(LogLevel.DEBUG, message, throwable); + } + + /** + * Logs an informational message. + *

+ * If the logger's configured log level is more severe than + * {@link LogLevel#INFO}, this message won't be logged. + * + * @param message the message to log + */ + public void info(String message) { + log(LogLevel.INFO, message); + } + + /** + * Logs an informational message with a stack trace. + *

+ * If the logger's configured log level is more severe than + * {@link LogLevel#INFO}, this message won't be logged. + * + * @param message the message to log + * @param throwable the throwable to print + */ + public void info(String message, Throwable throwable) { + log(LogLevel.INFO, message, throwable); + } + + /** + * Logs a warning message. + *

+ * If the logger's configured log level is more severe than + * {@link LogLevel#WARN}, this message won't be logged. + * + * @param message the message to log + */ + public void warn(String message) { + log(LogLevel.WARN, message); + } + + /** + * Logs a warning message with a stack trace. + *

+ * If the logger's configured log level is more severe than + * {@link LogLevel#WARN}, this message won't be logged. + * + * @param message the message to log + * @param throwable the throwable to print + */ + public void warn(String message, Throwable throwable) { + log(LogLevel.WARN, message, throwable); + } + + /** + * Logs an error message. + * + * @param message the message to log + */ + public void error(String message) { + log(LogLevel.ERROR, message); + } + + /** + * Logs an error message with a stack trace. + * + * @param message message to log + * @param throwable the throwable to print + */ + public void error(String message, Throwable throwable) { + log(LogLevel.ERROR, message, throwable); + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java new file mode 100644 index 0000000..f49f4e5 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java @@ -0,0 +1,77 @@ +package dev.austech.betterstaffchat.common.libraries.libby.logging.adapters; + +import dev.austech.betterstaffchat.common.libraries.libby.logging.LogLevel; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import static java.util.Objects.requireNonNull; + +/** + * Logging adapter that logs to a JDK logger. + */ +public class JDKLogAdapter implements LogAdapter { + /** + * JDK logger + */ + private final Logger logger; + + /** + * Creates a new JDK log adapter that logs to a {@link Logger}. + * + * @param logger the JDK logger to wrap + */ + public JDKLogAdapter(Logger logger) { + this.logger = requireNonNull(logger, "logger"); + } + + /** + * Logs a message with the provided level to the JDK logger. + * + * @param level message severity level + * @param message the message to log + */ + @Override + public void log(LogLevel level, String message) { + switch (requireNonNull(level, "level")) { + case DEBUG: + logger.log(Level.FINE, message); + break; + case INFO: + logger.log(Level.INFO, message); + break; + case WARN: + logger.log(Level.WARNING, message); + break; + case ERROR: + logger.log(Level.SEVERE, message); + break; + } + } + + /** + * Logs a message and stack trace with the provided level to the JDK + * logger. + * + * @param level message severity level + * @param message the message to log + * @param throwable the throwable to print + */ + @Override + public void log(LogLevel level, String message, Throwable throwable) { + switch (requireNonNull(level, "level")) { + case DEBUG: + logger.log(Level.FINE, message, throwable); + break; + case INFO: + logger.log(Level.INFO, message, throwable); + break; + case WARN: + logger.log(Level.WARNING, message, throwable); + break; + case ERROR: + logger.log(Level.SEVERE, message, throwable); + break; + } + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java new file mode 100644 index 0000000..a5f4aaf --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java @@ -0,0 +1,25 @@ +package dev.austech.betterstaffchat.common.libraries.libby.logging.adapters; + +import dev.austech.betterstaffchat.common.libraries.libby.logging.LogLevel; + +/** + * Logging interface for adapting platform-specific loggers to our logging API. + */ +public interface LogAdapter { + /** + * Logs a message with the provided level. + * + * @param level message severity level + * @param message the message to log + */ + void log(LogLevel level, String message); + + /** + * Logs a message and stack trace with the provided level. + * + * @param level message severity level + * @param message the message to log + * @param throwable the throwable to print + */ + void log(LogLevel level, String message, Throwable throwable); +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java new file mode 100644 index 0000000..6c9b411 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java @@ -0,0 +1,184 @@ +package dev.austech.betterstaffchat.common.libraries.libby.relocation; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; + +import static java.util.Objects.requireNonNull; + +/** + * Relocations are used to describe a search and replace pattern for renaming + * packages in a library jar for the purpose of preventing namespace conflicts + * with other plugins that bundle their own version of the same library. + */ +public class Relocation { + /** + * Search pattern + */ + private final String pattern; + + /** + * Replacement pattern + */ + private final String relocatedPattern; + + /** + * Classes and resources to include + */ + private final Collection includes; + + /** + * Classes and resources to exclude + */ + private final Collection excludes; + + /** + * Creates a new relocation. + * + * @param pattern search pattern + * @param relocatedPattern replacement pattern + * @param includes classes and resources to include + * @param excludes classes and resources to exclude + */ + public Relocation(String pattern, String relocatedPattern, Collection includes, Collection excludes) { + this.pattern = requireNonNull(pattern, "pattern").replace("{}", "."); + this.relocatedPattern = requireNonNull(relocatedPattern, "relocatedPattern").replace("{}", "."); + this.includes = includes != null ? Collections.unmodifiableList(new LinkedList<>(includes)) : Collections.emptyList(); + this.excludes = excludes != null ? Collections.unmodifiableList(new LinkedList<>(excludes)) : Collections.emptyList(); + } + + /** + * Creates a new relocation with empty includes and excludes. + * + * @param pattern search pattern + * @param relocatedPattern replacement pattern + */ + public Relocation(String pattern, String relocatedPattern) { + this(pattern, relocatedPattern, null, null); + } + + /** + * Gets the search pattern. + * + * @return pattern to search + */ + public String getPattern() { + return pattern; + } + + /** + * Gets the replacement pattern. + * + * @return pattern to replace with + */ + public String getRelocatedPattern() { + return relocatedPattern; + } + + /** + * Gets included classes and resources. + * + * @return classes and resources to include + */ + public Collection getIncludes() { + return includes; + } + + /** + * Gets excluded classes and resources. + * + * @return classes and resources to exclude + */ + public Collection getExcludes() { + return excludes; + } + + /** + * Creates a new relocation builder. + * + * @return new relocation builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Provides an alternative method of creating a {@link Relocation}. This + * builder may be more intuitive for configuring relocations that also have + * any includes or excludes. + */ + public static class Builder { + /** + * Search pattern + */ + private String pattern; + + /** + * Replacement pattern + */ + private String relocatedPattern; + + /** + * Classes and resources to include + */ + private final Collection includes = new LinkedList<>(); + + /** + * Classes and resources to exclude + */ + private final Collection excludes = new LinkedList<>(); + + /** + * Sets the search pattern. + * + * @param pattern pattern to search + * @return this builder + */ + public Builder pattern(String pattern) { + this.pattern = requireNonNull(pattern, "pattern"); + return this; + } + + /** + * Sets the replacement pattern. + * + * @param relocatedPattern pattern to replace with + * @return this builder + */ + public Builder relocatedPattern(String relocatedPattern) { + this.relocatedPattern = requireNonNull(relocatedPattern, "relocatedPattern"); + return this; + } + + /** + * Adds a class or resource to be included. + * + * @param include class or resource to include + * @return this builder + */ + public Builder include(String include) { + includes.add(requireNonNull(include, "include")); + return this; + } + + /** + * Adds a class or resource to be excluded. + * + * @param exclude class or resource to exclude + * @return this builder + */ + public Builder exclude(String exclude) { + excludes.add(requireNonNull(exclude, "exclude")); + return this; + } + + /** + * Creates a new relocation using this builder's configuration. + * + * @return new relocation + */ + public Relocation build() { + return new Relocation(pattern, relocatedPattern, includes, excludes); + } + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java new file mode 100644 index 0000000..565e668 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java @@ -0,0 +1,130 @@ +package dev.austech.betterstaffchat.common.libraries.libby.relocation; + +import dev.austech.betterstaffchat.common.libraries.libby.Library; +import dev.austech.betterstaffchat.common.libraries.libby.LibraryManager; +import dev.austech.betterstaffchat.common.libraries.libby.Repositories; +import dev.austech.betterstaffchat.common.libraries.libby.classloader.IsolatedClassLoader; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import static java.util.Objects.requireNonNull; + +/** + * A reflection-based helper for relocating library jars. It automatically + * downloads and invokes Luck's Jar Relocator to perform jar relocations. + * + * @see Luck's Jar Relocator + */ +public class RelocationHelper { + /** + * Reflected constructor for creating new jar relocator instances + */ + private final Constructor jarRelocatorConstructor; + + /** + * Reflected method for running a jar relocator + */ + private final Method jarRelocatorRunMethod; + + /** + * Reflected constructor for creating relocation instances + */ + private final Constructor relocationConstructor; + + /** + * Creates a new relocation helper using the provided library manager to + * download the dependencies required for runtime relocation. + * + * @param libraryManager the library manager used to download dependencies + */ + public RelocationHelper(LibraryManager libraryManager) { + requireNonNull(libraryManager, "libraryManager"); + + IsolatedClassLoader classLoader = new IsolatedClassLoader(); + + // ObjectWeb ASM Commons + classLoader.addPath(libraryManager.downloadLibrary( + Library.builder() + .groupId("org.ow2.asm") + .artifactId("asm-commons") + .version("9.2") + .checksum("vkzlMTiiOLtSLNeBz5Hzulzi9sqT7GLUahYqEnIl4KY=") + .repository(Repositories.MAVEN_CENTRAL) + .build() + )); + + // ObjectWeb ASM + classLoader.addPath(libraryManager.downloadLibrary( + Library.builder() + .groupId("org.ow2.asm") + .artifactId("asm") + .version("9.2") + .checksum("udT+TXGTjfOIOfDspCqqpkz4sxPWeNoDbwyzyhmbR/U=") + .repository(Repositories.MAVEN_CENTRAL) + .build() + )); + + // Luck's Jar Relocator + classLoader.addPath(libraryManager.downloadLibrary( + Library.builder() + .groupId("me.lucko") + .artifactId("jar-relocator") + .version("1.5") + .checksum("0D6eM99gKpEYFNDydgnto3Df0ygZGdRVqy5ahtj0oIs=") + .repository(Repositories.MAVEN_CENTRAL) + .build() + )); + + try { + Class jarRelocatorClass = classLoader.loadClass("me.lucko.jarrelocator.JarRelocator"); + Class relocationClass = classLoader.loadClass("me.lucko.jarrelocator.Relocation"); + + // me.lucko.jarrelocator.JarRelocator(File, File, Collection) + jarRelocatorConstructor = jarRelocatorClass.getConstructor(File.class, File.class, Collection.class); + + // me.lucko.jarrelocator.JarRelocator#run() + jarRelocatorRunMethod = jarRelocatorClass.getMethod("run"); + + // me.lucko.jarrelocator.Relocation(String, String, Collection, Collection) + relocationConstructor = relocationClass.getConstructor(String.class, String.class, Collection.class, Collection.class); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + /** + * Invokes the jar relocator to process the input jar and generate an + * output jar with the provided relocation rules applied. + * + * @param in input jar + * @param out output jar + * @param relocations relocations to apply + */ + public void relocate(Path in, Path out, Collection relocations) { + requireNonNull(in, "in"); + requireNonNull(out, "out"); + requireNonNull(relocations, "relocations"); + + try { + List rules = new LinkedList<>(); + for (Relocation relocation : relocations) { + rules.add(relocationConstructor.newInstance( + relocation.getPattern(), + relocation.getRelocatedPattern(), + relocation.getIncludes(), + relocation.getExcludes() + )); + } + + jarRelocatorRunMethod.invoke(jarRelocatorConstructor.newInstance(in.toFile(), out.toFile(), rules)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt new file mode 100644 index 0000000..d70e9a9 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt @@ -0,0 +1,66 @@ +package dev.austech.betterstaffchat.common.util + +import de.leonhard.storage.LightningBuilder +import de.leonhard.storage.Yaml +import dev.austech.betterstaffchat.common.BSCPlugin +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import java.io.File +import java.nio.file.Files + +class Config(private val plugin: BSCPlugin) { + private val configFile: File = File(plugin.pluginDataFile, "config.yml") + + fun load(): Yaml { + plugin.pluginDataFile.mkdirs() + + if (!configFile.exists()) { + plugin.javaClass.getResourceAsStream((if (plugin.platform == BSCPlugin.Platform.BUKKIT) "/" else "") + "config.yml") + .use { + Files.copy(it, configFile.toPath()) + } + } + + + + return LightningBuilder.fromFile(configFile) + .reloadCallback { + plugin.consoleAudience.sendMessage( + Component.text("[BetterStaffChat] Reload requested or config.yml change. Reloading.") + ) + } + .createConfig(); + } + + enum class Paths(private val path: String) { + STAFFCHAT_FORMAT("staffchat.format"), + STAFFCHAT_PREFIX("staffchat.prefix"), + STAFFCHAT_CONSOLE("staffchat.console-replacement"), + STAFFCHAT_CONSOLE_UUID("staffchat.console-uuid-replacement"), + STAFFCHAT_CONSOLE_SERVER("staffchat.console-server-replacement"), + STAFFCHAT_CONSOLE_LOG("staffchat.log-to-console"), + STAFFCHAT_STRIP_COLOR_CODES("staffchat.strip-color-codes"), + STAFFCHAT_SERVER_REPLACEMENTS("staffchat.server-replacement"), + STAFFCHAT_JOIN_MESSAGE("staffchat.join"), + STAFFCHAT_LEAVE_MESSAGE("staffchat.leave"), + STAFFCHAT_SWITCH_MESSAGE("staffchat.switch"), + STAFFCHAT_MUTE_ON_MESSAGE("staffchat.mute-on"), + STAFFCHAT_MUTE_OFF_MESSAGE("staffchat.mute-off"), + STAFFCHAT_TOGGLE_ON_MESSAGE("staffchat.toggle-on"), + STAFFCHAT_TOGGLE_OFF_MESSAGE("staffchat.toggle-off"), + + COMMAND_STAFFCHAT_COMMAND("commands.staffchat.command"), + COMMAND_STAFFCHAT_ALIASES("commands.staffchat.aliases"), + COMMAND_MUTE_COMMAND("commands.mutestaffchat.command"), + COMMAND_MUTE_ALIASES("commands.mutestaffchat.aliases"), + COMMAND_TOGGLE_COMMAND("commands.togglestaffchat.command"), + COMMAND_TOGGLE_ALIASES("commands.togglestaffchat.aliases"), + + GLOBAL_UPDATE_CHECK("check-for-updates"), + GLOBAL_DEBUG_MODE("debug"); + + override fun toString(): String { + return path; + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Data.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Data.kt new file mode 100644 index 0000000..80dd2cb --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Data.kt @@ -0,0 +1,46 @@ +package dev.austech.betterstaffchat.common.util + +import de.leonhard.storage.Json +import dev.austech.betterstaffchat.common.BSCPlugin +import java.io.File +import java.util.* + +class Data(private val plugin: BSCPlugin) { + private val dataFile: File = File(plugin.pluginDataFile, "data.json") + + class Wrapper(private val `data`: Json) { + fun getToggledPlayers(): List { + return `data`.getOrSetDefault>("toggledPlayers", listOf()).map { UUID.fromString(it) } + } + + fun addToggledPlayer(uuid: UUID) { + return `data`.set("toggledPlayers", getToggledPlayers().plus(uuid.toString())) + } + + fun removeToggledPlayer(uuid: UUID) { + return `data`.set("toggledPlayers", getToggledPlayers().minus(uuid.toString())) + } + + fun getMutedPlayers(): List { + return `data`.getOrSetDefault>("mutedPlayers", listOf()).map { UUID.fromString(it) } + } + + fun addMutedPlayer(uuid: UUID) { + return `data`.set("mutedPlayers", getMutedPlayers().plus(uuid.toString())) + } + + fun removeMutedPlayer(uuid: UUID) { + return `data`.set("mutedPlayers", getMutedPlayers().minus(uuid.toString())) + } + } + + fun load(): Json { + plugin.pluginDataFile.mkdirs() + + if (!dataFile.exists()) { + dataFile.createNewFile() + } + + return Json(dataFile) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt new file mode 100644 index 0000000..278fd31 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt @@ -0,0 +1,65 @@ +package dev.austech.betterstaffchat.common.util + +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.PlayerMeta +import net.kyori.adventure.audience.Audience + +class StaffChatUtil(private val plugin: BSCPlugin) { + private fun replacePlaceholders(s: String, playerName: String, server: String, meta: PlayerMeta?): String { + var newString = s + .replace("%player_name%", playerName) + .replace("%server%", server); + + if (meta != null) { + if (meta.prefix != null) + newString = newString + .replace("%luckperms_prefix%", meta.prefix) + .replace("%prefix%", meta.prefix) + + if (meta.suffix != null) + newString = newString + .replace("%luckperms_suffix%", meta.suffix) + .replace("%suffix%", meta.suffix) + } + + return newString; + } + + fun sendMessage(audience: Audience, message: String, playerName: String, server: String, meta: PlayerMeta?) { + var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT_FORMAT.toString()) ?: return; + val removeColorCodes = plugin.config.getBoolean(Config.Paths.STAFFCHAT_STRIP_COLOR_CODES.toString()); + + configMessage = replacePlaceholders(configMessage, playerName, server, meta) + .replace("%message%", + if (removeColorCodes) + TextUtil.escapeTags(message) + else message + ) + + audience.sendMessage(TextUtil.parseText(configMessage)) + } + + fun sendJoinMessage(audience: Audience, playerName: String, server: String, meta: PlayerMeta?) { + var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT_JOIN_MESSAGE.toString()) ?: return; + configMessage = replacePlaceholders(configMessage, playerName, server, meta) + + audience.sendMessage(TextUtil.parseText(configMessage)) + } + + fun sendLeaveMessage(audience: Audience, playerName: String, server: String, meta: PlayerMeta?) { + var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT_LEAVE_MESSAGE.toString()) ?: return; + configMessage = replacePlaceholders(configMessage, playerName, server, meta) + + audience.sendMessage(TextUtil.parseText(configMessage)) + } + + fun sendSwitchMessage(audience: Audience, playerName: String, from: String, to: String, meta: PlayerMeta?) { + var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT_SWITCH_MESSAGE.toString()) ?: return; + configMessage = replacePlaceholders(configMessage, playerName, from, meta) + configMessage = configMessage + .replace("%from%", from) + .replace("%to%", to) + + audience.sendMessage(TextUtil.parseText(configMessage)) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/TextUtil.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/TextUtil.kt new file mode 100644 index 0000000..b3de47b --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/TextUtil.kt @@ -0,0 +1,16 @@ +package dev.austech.betterstaffchat.common.util + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.minimessage.MiniMessage + +object TextUtil { + private val miniMessage: MiniMessage = MiniMessage.miniMessage() + + fun escapeTags(s: String): String { + return miniMessage.escapeTags(s) + } + + fun parseText(s: String): Component { + return miniMessage.deserialize(s) + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 23334 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 WDWbsF$p{Gi@ZUasP|4|kdH)CXgbPdn delta 19998 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ecaf8a..0f80bbf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,21 +1,3 @@ -# -# BetterStaffChat - gradle-wrapper.properties -# Copyright (C) 2021 AusTech Development Team -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip diff --git a/gradlew b/gradlew index f45fe38..4f906e0 100755 --- a/gradlew +++ b/gradlew @@ -1,21 +1,19 @@ #!/usr/bin/env sh # -# BetterStaffChat - gradlew -# Copyright (C) 2021 AusTech Development Team +# Copyright 2015 the original author or authors. # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# https://www.apache.org/licenses/LICENSE-2.0 # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # ############################################################################## @@ -84,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -131,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -156,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -177,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 9618d8d..107acd3 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index e709c2d..0000000 --- a/settings.gradle +++ /dev/null @@ -1,23 +0,0 @@ -/* - * BetterStaffChat - settings.gradle - * Copyright (C) 2021 AusTech Development Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -rootProject.name = 'BetterStaffChat' - -include 'BetterStaffChat-common' -include 'BetterStaffChat-spigot' -include 'BetterStaffChat-bungeecord' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..e463722 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/7.0.2/userguide/multi_project_builds.html + */ + +rootProject.name = "BetterStaffChat" +include("common", "spigot", "bungeecord") diff --git a/spigot/build.gradle.kts b/spigot/build.gradle.kts new file mode 100644 index 0000000..7815745 --- /dev/null +++ b/spigot/build.gradle.kts @@ -0,0 +1,12 @@ +repositories { + mavenCentral() + maven { + url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + } +} + +dependencies { + compileOnly(project(":common")) + compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT") + compileOnly("net.kyori:adventure-platform-bukkit:4.1.0") +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt new file mode 100644 index 0000000..ad64dea --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt @@ -0,0 +1,56 @@ +package dev.austech.betterstaffchat.spigot + +import de.leonhard.storage.Json +import de.leonhard.storage.Yaml +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.libraries.BSCLibraries +import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.common.util.Data +import dev.austech.betterstaffchat.common.util.StaffChatUtil +import dev.austech.betterstaffchat.spigot.commands.CommandManager +import dev.austech.betterstaffchat.spigot.listeners.PlayerListener +import dev.austech.betterstaffchat.spigot.libby.BukkitLibraryManager +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.platform.bukkit.BukkitAudiences +import org.bukkit.Bukkit +import org.bukkit.plugin.java.JavaPlugin +import java.io.File + +class BSCSpigot: JavaPlugin(), BSCPlugin { + companion object { + lateinit var instance: BSCSpigot + } + + lateinit var staffChatUtil: StaffChatUtil + lateinit var audience: BukkitAudiences + private lateinit var data: Json + lateinit var dataWrapper: Data.Wrapper; + override lateinit var config: Yaml + override lateinit var consoleAudience: Audience; + override val platform = BSCPlugin.Platform.BUKKIT + override val pluginDataFile: File = this.dataFolder + + override fun onLoad() { + val libraryManager = BukkitLibraryManager(this) + libraryManager.addMavenCentral() + libraryManager.addJitPack() + + BSCLibraries.getLibraries(platform).forEach { + libraryManager.loadLibrary(it) + } + } + + override fun onEnable() { + instance = this; + audience = BukkitAudiences.create(this) + consoleAudience = audience.console() + config = Config(this).load() + staffChatUtil = StaffChatUtil(this) + data = Data(this).load() + dataWrapper = Data.Wrapper(data) + + CommandManager(this).registerCommands() + + Bukkit.getPluginManager().registerEvents(PlayerListener(), this) + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/BSCSpigotCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/BSCSpigotCommand.kt new file mode 100644 index 0000000..fd07fe1 --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/BSCSpigotCommand.kt @@ -0,0 +1,57 @@ +package dev.austech.betterstaffchat.spigot.commands + +import dev.austech.betterstaffchat.spigot.BSCSpigot +import org.bukkit.ChatColor +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +abstract class BSCSpigotCommand(name: String, description: String, usage: String, aliases: List) : Command(name, description, usage, aliases) { + private lateinit var sender: CommandSender + protected lateinit var plugin: BSCSpigot + + abstract fun run(sender: CommandSender, commandLabel: String, args: Array): Boolean; + + override fun execute(sender: CommandSender, commandLabel: String, args: Array): Boolean { + this.sender = sender; + this.plugin = BSCSpigot.instance + + return run(sender, commandLabel, args) + } + + fun CommandSender.isConsole(): Boolean { + return this !is Player + } + + fun CommandSender.requirePlayer(): Boolean { + if (this.isConsole()) { + errorTell("You must be a player to use this command.") + return false + } + return true + } + + fun CommandSender.requirePermission(permission: String): Boolean { + return requirePermission(permission, "You do not have permission.") + } + + fun CommandSender.requirePermission(permission: String, message: String): Boolean { + if (!hasPermission(permission)) { + errorTell(message) + return false + } + return true + } + + fun legacyTell(s: String) { + return this.sender.sendMessage(ChatColor.translateAlternateColorCodes('&', s)) + } + + fun errorTell(s: String) { + return legacyTell("&c$s") + } + + fun warnTell(s: String) { + legacyTell("&e$s") + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt new file mode 100644 index 0000000..61140bd --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt @@ -0,0 +1,40 @@ +package dev.austech.betterstaffchat.spigot.commands + +import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.spigot.BSCSpigot +import dev.austech.betterstaffchat.spigot.commands.impl.BetterStaffChatCommand +import dev.austech.betterstaffchat.spigot.commands.impl.StaffChatCommand +import dev.austech.betterstaffchat.spigot.commands.impl.StaffChatMuteCommand +import dev.austech.betterstaffchat.spigot.commands.impl.StaffChatToggleCommand +import dev.austech.betterstaffchat.spigot.util.ReflectionUtil +import org.bukkit.command.Command + +class CommandManager(private val plugin: BSCSpigot) { + private val commands: List = listOf( + StaffChatCommand( + plugin.config.getString(Config.Paths.COMMAND_STAFFCHAT_COMMAND.toString())?: "staffchat", + "This command lets you send a message to the staffchat.", + "", + plugin.config.getStringList(Config.Paths.COMMAND_STAFFCHAT_ALIASES.toString()) + ), + StaffChatMuteCommand( + plugin.config.getString(Config.Paths.COMMAND_MUTE_COMMAND.toString())?: "mutestaffchat", + "This command lets you ignore / disable your staffchat.", + "[on/off]", + plugin.config.getStringList(Config.Paths.COMMAND_MUTE_ALIASES.toString()) + ), + StaffChatToggleCommand( + plugin.config.getString(Config.Paths.COMMAND_TOGGLE_COMMAND.toString())?: "togglestaffchat", + "This command lets you ignore / disable your staffchat.", + "[on/off]", + plugin.config.getStringList(Config.Paths.COMMAND_TOGGLE_ALIASES.toString()) + ), + BetterStaffChatCommand() + ) + + fun registerCommands() { + commands.forEach { + ReflectionUtil.registerCommand(it) + } + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/BetterStaffChatCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/BetterStaffChatCommand.kt new file mode 100644 index 0000000..f7382b5 --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/BetterStaffChatCommand.kt @@ -0,0 +1,23 @@ +package dev.austech.betterstaffchat.spigot.commands.impl + +import dev.austech.betterstaffchat.spigot.commands.BSCSpigotCommand +import org.bukkit.command.CommandSender + +class BetterStaffChatCommand: BSCSpigotCommand("betterstaffchat", "The main command for BetterStaffChat", "", listOf("bsc")) { + override fun run(sender: CommandSender, commandLabel: String, args: Array): Boolean { + if (args.isEmpty()) { + legacyTell("&7Running &9BetterStaffChat &7by &6AusTech Development&7.") + return true + } + + when (args[0]) { + "reload" -> { + if (!sender.requirePermission("betterstaffchat.reload")) return true; + warnTell("It is not recommended to forcefully reload BetterStaffChat. It will automatically reload when the config is edited.") + plugin.config.forceReload() + } + } + + return true; + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt new file mode 100644 index 0000000..74b1194 --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt @@ -0,0 +1,29 @@ +package dev.austech.betterstaffchat.spigot.commands.impl + +import dev.austech.betterstaffchat.common.PlayerMeta +import dev.austech.betterstaffchat.spigot.commands.BSCSpigotCommand +import dev.austech.betterstaffchat.spigot.util.PlayerUtil +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class StaffChatCommand(name: String, description: String, usage: String, aliases: List) : BSCSpigotCommand(name, description, usage, aliases) { + override fun run(sender: CommandSender, commandLabel: String, args: Array): Boolean { + if (!sender.requirePermission("betterstaffchat.messages.send")) { + return true + } + + if (args.isEmpty()) { + errorTell("&cUsage: /$commandLabel ") + return true; + } + + if (sender is Player && plugin.dataWrapper.getMutedPlayers().contains(sender.uniqueId)) { + errorTell("Your staffchat is currently muted.") + return true + } + + plugin.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), args.joinToString(separator = " "), PlayerUtil.getSenderName(sender), "N/A", PlayerMeta(null, null)) + + return true; + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt new file mode 100644 index 0000000..05b3fcb --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt @@ -0,0 +1,57 @@ +package dev.austech.betterstaffchat.spigot.commands.impl + +import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.spigot.commands.BSCSpigotCommand +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class StaffChatMuteCommand(name: String, description: String, usage: String, aliases: List) : BSCSpigotCommand(name, description, usage, aliases) { + override fun run(sender: CommandSender, commandLabel: String, args: Array): Boolean { + if (!sender.requirePermission("betterstaffchat.mutestaffchat")) { + return true + } + + if (!sender.requirePlayer()) { + return true + } + + if (sender !is Player) { + throw IllegalStateException("Sender is not a player!") + } + + val mute: Boolean + val mutedPlayers = plugin.dataWrapper.getMutedPlayers() + + if (args.isNotEmpty()) { + if (args[0].equals("off", true)) { + if (!mutedPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is not muted.") + return true + } + + mute = false + } else if (args[0].equals("on", true)) { + if (mutedPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is already muted.") + return true + } + + mute = true + } else { + mute = !mutedPlayers.contains(sender.uniqueId) + } + } else { + mute = !mutedPlayers.contains(sender.uniqueId) + } + + if (mute) { + plugin.dataWrapper.addMutedPlayer(sender.uniqueId) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_MUTE_ON_MESSAGE.toString())) + } else { + plugin.dataWrapper.removeMutedPlayer(sender.uniqueId) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_MUTE_OFF_MESSAGE.toString())) + } + + return true + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt new file mode 100644 index 0000000..27fdb5b --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt @@ -0,0 +1,64 @@ +package dev.austech.betterstaffchat.spigot.commands.impl + +import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.spigot.commands.BSCSpigotCommand +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class StaffChatToggleCommand(name: String, description: String, usage: String, aliases: List) : BSCSpigotCommand(name, description, usage, aliases) { + override fun run(sender: CommandSender, commandLabel: String, args: Array): Boolean { + if (!sender.requirePermission("betterstaffchat.togglestaffchat")) { + return true + } + + if (!sender.requirePlayer()) { + return true + } + + if (sender !is Player) { + throw IllegalStateException("Sender is not a player!") + } + + val toggleOn: Boolean + val toggledPlayers = plugin.dataWrapper.getToggledPlayers() + + if (args.isNotEmpty()) { + if (args[0].equals("off", true)) { + if (!toggledPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is not toggled.") + return true + } + + toggleOn = false + } else if (args[0].equals("on", true)) { + if (toggledPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is already toggled.") + return true + } + + toggleOn = true + } else { + toggleOn = !toggledPlayers.contains(sender.uniqueId) + } + } else { + toggleOn = !toggledPlayers.contains(sender.uniqueId) + } + + if (toggleOn) { + val mutedPlayers = plugin.dataWrapper.getMutedPlayers() + + if (mutedPlayers.contains(sender.uniqueId)) { + errorTell("Your staffchat is muted. Please unmute it before toggling.") + return true + } + + plugin.dataWrapper.addToggledPlayer(sender.uniqueId) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_TOGGLE_ON_MESSAGE.toString())) + } else { + plugin.dataWrapper.removeToggledPlayer(sender.uniqueId) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_TOGGLE_OFF_MESSAGE.toString())) + } + + return true + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java new file mode 100644 index 0000000..6548e5c --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java @@ -0,0 +1,51 @@ +package dev.austech.betterstaffchat.spigot.libby; + +import dev.austech.betterstaffchat.common.libraries.libby.LibraryManager; +import dev.austech.betterstaffchat.common.libraries.libby.classloader.URLClassLoaderHelper; +import dev.austech.betterstaffchat.common.libraries.libby.logging.adapters.JDKLogAdapter; +import org.bukkit.plugin.Plugin; + +import java.net.URLClassLoader; +import java.nio.file.Path; + +import static java.util.Objects.requireNonNull; + +/** + * A runtime dependency manager for Bukkit plugins. + */ +public class BukkitLibraryManager extends LibraryManager { + /** + * Plugin classpath helper + */ + private final URLClassLoaderHelper classLoader; + + /** + * Creates a new Bukkit library manager. + * + * @param plugin the plugin to manage + */ + public BukkitLibraryManager(Plugin plugin) { + this(plugin, "libs"); + } + + /** + * Creates a new Bukkit library manager. + * + * @param plugin the plugin to manage + * @param directoryName download directory name + */ + public BukkitLibraryManager(Plugin plugin, String directoryName) { + super(new JDKLogAdapter(requireNonNull(plugin, "plugin").getLogger()), plugin.getDataFolder().toPath(), directoryName); + classLoader = new URLClassLoaderHelper((URLClassLoader) plugin.getClass().getClassLoader(), this); + } + + /** + * Adds a file to the Bukkit plugin's classpath. + * + * @param file the file to add + */ + @Override + protected void addToClasspath(Path file) { + classLoader.addToClasspath(file); + } +} diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt new file mode 100644 index 0000000..47a9778 --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt @@ -0,0 +1,51 @@ +package dev.austech.betterstaffchat.spigot.listeners + +import dev.austech.betterstaffchat.common.PlayerMeta +import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.spigot.BSCSpigot +import dev.austech.betterstaffchat.spigot.util.PlayerUtil +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.player.AsyncPlayerChatEvent +import org.bukkit.event.player.PlayerChangedWorldEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent + +class PlayerListener: Listener { + @EventHandler(priority = EventPriority.HIGH) + fun onAsyncPlayerChat(event: AsyncPlayerChatEvent) { + if (event.player.hasPermission("betterstaffchat.messages.send")) { + val prefix = BSCSpigot.instance.config.getString(Config.Paths.STAFFCHAT_PREFIX.toString()) ?: ""; + + if (event.message.startsWith(prefix) && event.message.length > prefix.length && prefix.isNotBlank()) { + event.isCancelled = true; + BSCSpigot.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message.substring(prefix.length), event.player.name, "N/A", PlayerMeta(null, null)) + } else if (BSCSpigot.instance.dataWrapper.getToggledPlayers().contains(event.player.uniqueId)) { + event.isCancelled = true; + BSCSpigot.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message, event.player.name, "N/A", PlayerMeta(null, null)) + } + } + } + + @EventHandler + fun onPlayerJoin(event: PlayerJoinEvent) { + if (event.player.hasPermission("betterstaffchat.messages.join")) { + BSCSpigot.instance.staffChatUtil.sendJoinMessage(PlayerUtil.getReceiveAudience(), event.player.name, "N/A", PlayerMeta(null, null)) + } + } + + @EventHandler + fun onPlayerQuit(event: PlayerQuitEvent) { + if (event.player.hasPermission("betterstaffchat.messages.leave")) { + BSCSpigot.instance.staffChatUtil.sendLeaveMessage(PlayerUtil.getReceiveAudience(), event.player.name, "N/A", PlayerMeta(null, null)) + } + } + + @EventHandler + fun onPlayerSwitch(event: PlayerChangedWorldEvent) { + if (event.player.hasPermission("betterstaffchat.messages.leave")) { + BSCSpigot.instance.staffChatUtil.sendSwitchMessage(PlayerUtil.getReceiveAudience(), event.player.name, event.from.name, event.player.world.name, PlayerMeta(null, null)) + } + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt new file mode 100644 index 0000000..f418286 --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt @@ -0,0 +1,35 @@ +package dev.austech.betterstaffchat.spigot.util + +import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.spigot.BSCSpigot +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.platform.bukkit.BukkitAudiences +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import java.util.* + +object PlayerUtil { + fun getSenderName(sender: CommandSender): String { + return if (sender is Player) sender.name + else BSCSpigot.instance.config.getString(Config.Paths.STAFFCHAT_CONSOLE.toString()) ?: "Console" + } + + private fun getMutedPlayers(): List { + return BSCSpigot.instance.dataWrapper.getMutedPlayers() + } + + private fun getPermissionAudience(permission: String): Audience = BSCSpigot.instance.audience.filter { + it.hasPermission(permission) && it is Player && !getMutedPlayers().contains(it.uniqueId) + } + + private fun getMixedAudience(permission: String): Audience = BSCSpigot.instance.audience.filter { + it.hasPermission(permission) && if (it is Player) !getMutedPlayers().contains(it.uniqueId) else true + } + + fun getReceiveAudience(): Audience { + return if (BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT_CONSOLE_LOG.toString())) getMixedAudience( + "betterstaffchat.messages.read" + ) + else getPermissionAudience("betterstaffchat.messages.read") + } +} \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/ReflectionUtil.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/ReflectionUtil.kt new file mode 100644 index 0000000..632cf40 --- /dev/null +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/ReflectionUtil.kt @@ -0,0 +1,27 @@ +package dev.austech.betterstaffchat.spigot.util + +import org.bukkit.Bukkit +import org.bukkit.command.Command +import org.bukkit.command.SimpleCommandMap + +object ReflectionUtil { + private val serverClass by lazy { + "org.bukkit.craftbukkit." + Bukkit.getServer().javaClass.`package`.name.substring(23) + ".CraftServer" + } + + private fun getCommandMap(): SimpleCommandMap? { + return try { + Class.forName(serverClass).getDeclaredMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + fun registerCommand(command: Command) { + getCommandMap()?.register(command.name, "betterstaffchat", command) + if (!command.isRegistered) { + throw RuntimeException("Failed to register command: " + command.name); + } + } +} \ No newline at end of file diff --git a/src/main/resources/bungeecord.yml b/src/main/resources/bungeecord.yml new file mode 100644 index 0000000..5e40ba7 --- /dev/null +++ b/src/main/resources/bungeecord.yml @@ -0,0 +1,3 @@ +name:name: BetterStaffChat +main: dev.austech.betterstaffchat.bungeecord.BSCBungee +version: ${version} diff --git a/BetterStaffChat-common/src/main/resources/config.yml b/src/main/resources/config.yml old mode 100755 new mode 100644 similarity index 98% rename from BetterStaffChat-common/src/main/resources/config.yml rename to src/main/resources/config.yml index efbaf9b..b1a6652 --- a/BetterStaffChat-common/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,6 +1,6 @@ # BetterStaffChat -# Copyright (c) 2021 AusTech Development Team +# Copyright (c) 2022 AusTech Development Team # Licensed under GNU General Public License v3 # Github profile: https://github.com/AusTechDev/ @@ -151,8 +151,10 @@ discord: commands: # Options for commands | Aliases can not be the same, nor can they be any base command. staffchat: # /staffchat command + command: staffchat aliases: [sc, schat] mutestaffchat: # /mutestaffchat command + command: mutestaffchat aliases: [mutesc, scmute, scm, msc, ignorestaffchat] togglestaffchat: # /togglestaffchat command aliases: [tsc, sct, sctoggle, staffchattoggle, togglesc] diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..ad7ba79 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,3 @@ +name: BetterStaffChat +main: dev.austech.betterstaffchat.spigot.BSCSpigot +version: ${version} From 44d3e42d7dd47f7185c49a97ea0095719ab7bebc Mon Sep 17 00:00:00 2001 From: mnewt00 <44464407+mnewt00@users.noreply.github.com> Date: Sun, 27 Mar 2022 21:51:54 +1100 Subject: [PATCH 2/5] fix: prevent timing attack in checksum comparison --- .../kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt | 2 +- .../betterstaffchat/common/libraries/libby/LibraryManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt index 2fec9c0..e2a8b7e 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt @@ -5,4 +5,4 @@ import java.util.UUID class PlayerMeta( val prefix: String?, val suffix: String? -) {} \ No newline at end of file +) \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java index 0e0a023..5a46629 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java @@ -357,7 +357,7 @@ public Path downloadLibrary(Library library) { if (md != null) { byte[] checksum = md.digest(bytes); - if (!Arrays.equals(checksum, library.getChecksum())) { + if (!MessageDigest.isEqual(checksum, library.getChecksum())) { logger.warn("*** INVALID CHECKSUM ***"); logger.warn(" Library : " + library); logger.warn(" URL : " + url); From 2e7715677e571ac76d55e43232df4eefae84a668 Mon Sep 17 00:00:00 2001 From: mnewt00 <44464407+mnewt00@users.noreply.github.com> Date: Mon, 28 Mar 2022 22:30:40 +1100 Subject: [PATCH 3/5] feat: add placeholderapi support and improve metadata --- build.gradle.kts | 9 +- .../bungeecord/BSCBungeeBootstrap.java | 29 ++ .../libby/BungeeLibraryManager.java | 0 .../betterstaffchat/bungeecord/BSCBungee.kt | 13 +- .../bungeecord/commands/CommandManager.kt | 18 +- .../commands/impl/BetterStaffChatCommand.kt | 10 +- .../commands/impl/StaffChatCommand.kt | 34 +- .../commands/impl/StaffChatMuteCommand.kt | 15 +- .../commands/impl/StaffChatToggleCommand.kt | 15 +- .../bungeecord/listeners/PlayerListener.kt | 109 +++- .../bungeecord/util/PlayerUtil.kt | 33 +- .../common/libraries/KotlinLibraries.java | 24 + .../common/libraries/libby/Library.java | 2 - .../libraries/libby/LibraryManager.java | 2 - .../common/libraries/libby/Repositories.java | 0 .../classloader/IsolatedClassLoader.java | 0 .../classloader/URLClassLoaderHelper.java | 0 .../libraries/libby/logging/LogLevel.java | 0 .../libraries/libby/logging/Logger.java | 1 - .../libby/logging/adapters/JDKLogAdapter.java | 0 .../libby/logging/adapters/LogAdapter.java | 0 .../libby/relocation/Relocation.java | 0 .../libby/relocation/RelocationHelper.java | 0 .../betterstaffchat/common/BSCMetadata.kt | 68 +++ .../betterstaffchat/common/BSCPlugin.kt | 3 + .../betterstaffchat/common/PlayerMeta.kt | 8 - .../common/libraries/BSCLibraries.kt | 2 - .../betterstaffchat/common/util/Config.kt | 76 ++- .../common/util/StaffChatUtil.kt | 85 +++- spigot/build.gradle.kts | 6 + .../spigot/BSCSpigotBootstrap.java | 29 ++ .../spigot/libby/BukkitLibraryManager.java | 0 .../betterstaffchat/spigot/BSCSpigot.kt | 24 +- .../spigot/commands/BSCSpigotCommand.kt | 5 + .../spigot/commands/CommandManager.kt | 12 +- .../commands/impl/BetterStaffChatCommand.kt | 8 +- .../spigot/commands/impl/StaffChatCommand.kt | 14 +- .../commands/impl/StaffChatMuteCommand.kt | 9 +- .../commands/impl/StaffChatToggleCommand.kt | 9 +- .../spigot/listeners/PlayerListener.kt | 86 +++- .../betterstaffchat/spigot/util/PlayerUtil.kt | 21 +- src/main/resources/bungeecord.yml | 4 +- src/main/resources/config.yml | 467 +++++++++++++----- src/main/resources/plugin.yml | 4 +- 44 files changed, 985 insertions(+), 269 deletions(-) create mode 100644 bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/BSCBungeeBootstrap.java rename bungeecord/src/main/{kotlin => java}/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java (100%) create mode 100644 common/src/main/java/dev/austech/betterstaffchat/common/libraries/KotlinLibraries.java rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/Library.java (98%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java (99%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java (100%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java (100%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java (100%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java (100%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java (98%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java (100%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java (100%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java (100%) rename common/src/main/{kotlin => java}/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java (100%) create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCMetadata.kt delete mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt create mode 100644 spigot/src/main/java/dev/austech/betterstaffchat/spigot/BSCSpigotBootstrap.java rename spigot/src/main/{kotlin => java}/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java (100%) diff --git a/build.gradle.kts b/build.gradle.kts index 9de1c1f..ff370a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { java - id("org.jetbrains.kotlin.jvm") version "1.4.31" + kotlin("jvm") version "1.6.10" apply false id("io.freefair.lombok") version "6.0.0-m2" id("com.github.johnrengelman.shadow") version "7.0.0" } @@ -18,6 +18,7 @@ repositories { dependencies { implementation(project(":common", "shadow")) implementation(project(":spigot", "shadow")) + implementation(project(":bungeecord", "shadow")) } tasks { @@ -29,9 +30,9 @@ tasks { subprojects { apply(plugin = "java") apply(plugin = "idea") + apply(plugin = "kotlin") apply(plugin = "com.github.johnrengelman.shadow") apply(plugin = "io.freefair.lombok") - apply(plugin = "org.jetbrains.kotlin.jvm") group = project.group version = project.version @@ -47,7 +48,7 @@ subprojects { } dependencies { - implementation(kotlin("stdlib-jdk8")) + compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10") compileOnly("com.github.simplix-softworks:SimplixStorage:3.2.3") compileOnly("net.kyori:adventure-api:4.10.1") compileOnly("net.kyori:adventure-text-minimessage:4.10.1") @@ -87,7 +88,7 @@ task("copyJars") { } fun outputTasks(): List { - return arrayOf("shadowJar", ":common:shadowJar", ":spigot:shadowJar").map { + return arrayOf("shadowJar", ":common:shadowJar", ":spigot:shadowJar", ":bungeecord:shadowJar").map { tasks.findByPath(it) } } diff --git a/bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/BSCBungeeBootstrap.java b/bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/BSCBungeeBootstrap.java new file mode 100644 index 0000000..37a3ef2 --- /dev/null +++ b/bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/BSCBungeeBootstrap.java @@ -0,0 +1,29 @@ +package dev.austech.betterstaffchat.bungeecord; + +import dev.austech.betterstaffchat.bungeecord.libby.BungeeLibraryManager; +import dev.austech.betterstaffchat.common.libraries.KotlinLibraries; +import dev.austech.betterstaffchat.common.libraries.libby.LibraryManager; +import net.md_5.bungee.api.plugin.Plugin; + +public class BSCBungeeBootstrap extends Plugin { + private BSCBungee implPlugin; + + @Override + public void onLoad() { + LibraryManager libraryManager = new BungeeLibraryManager(this); + libraryManager.addMavenCentral(); + libraryManager.addJitPack(); + + libraryManager.loadLibrary(KotlinLibraries.STDLIB); + libraryManager.loadLibrary(KotlinLibraries.STDLIB_COMMON); + libraryManager.loadLibrary(KotlinLibraries.STDLIB_JDK8); + + implPlugin = new BSCBungee(this); + implPlugin.onLoad(); + } + + @Override + public void onEnable() { + implPlugin.onEnable(); + } +} diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java b/bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java similarity index 100% rename from bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java rename to bungeecord/src/main/java/dev/austech/betterstaffchat/bungeecord/libby/BungeeLibraryManager.java diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt index c47892c..5445951 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt @@ -12,16 +12,17 @@ import dev.austech.betterstaffchat.common.util.Data import dev.austech.betterstaffchat.common.util.StaffChatUtil import net.kyori.adventure.audience.Audience import net.kyori.adventure.platform.bungeecord.BungeeAudiences +import net.md_5.bungee.api.ProxyServer import net.md_5.bungee.api.plugin.Plugin import java.io.File -class BSCBungee: Plugin(), BSCPlugin { +class BSCBungee(private val plugin: Plugin): BSCPlugin { companion object { lateinit var instance: BSCBungee } override val platform: BSCPlugin.Platform = BSCPlugin.Platform.BUNGEECORD - override val pluginDataFile: File = dataFolder + override val pluginDataFile: File = plugin.dataFolder override lateinit var config: Yaml override lateinit var consoleAudience: Audience lateinit var staffChatUtil: StaffChatUtil @@ -30,7 +31,7 @@ class BSCBungee: Plugin(), BSCPlugin { lateinit var dataWrapper: Data.Wrapper; override fun onLoad() { - val libraryManager = BungeeLibraryManager(this) + val libraryManager = BungeeLibraryManager(plugin) libraryManager.addMavenCentral() libraryManager.addJitPack() @@ -41,15 +42,15 @@ class BSCBungee: Plugin(), BSCPlugin { override fun onEnable() { instance = this - audience = BungeeAudiences.create(this) + audience = BungeeAudiences.create(plugin) consoleAudience = audience.console() config = Config(this).load() staffChatUtil = StaffChatUtil(this) data = Data(this).load() dataWrapper = Data.Wrapper(data) - CommandManager(this).registerCommands() + CommandManager(this).registerCommands(plugin) - proxy.pluginManager.registerListener(this, PlayerListener()) + ProxyServer.getInstance().pluginManager.registerListener(plugin, PlayerListener(plugin)) } } \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt index 9c6ccf2..8df23ba 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt @@ -6,28 +6,30 @@ import dev.austech.betterstaffchat.bungeecord.commands.impl.StaffChatCommand import dev.austech.betterstaffchat.bungeecord.commands.impl.StaffChatMuteCommand import dev.austech.betterstaffchat.bungeecord.commands.impl.StaffChatToggleCommand import dev.austech.betterstaffchat.common.util.Config +import net.md_5.bungee.api.ProxyServer import net.md_5.bungee.api.plugin.Command +import net.md_5.bungee.api.plugin.Plugin class CommandManager(private val plugin: BSCBungee) { private val commands: List = listOf( StaffChatCommand( - plugin.config.getString(Config.Paths.COMMAND_STAFFCHAT_COMMAND.toString())?: "staffchat", - plugin.config.getStringList(Config.Paths.COMMAND_STAFFCHAT_ALIASES.toString()) + plugin.config.getString(Config.Paths.COMMAND.STAFFCHAT_COMMAND.toString())?: "staffchat", + plugin.config.getStringList(Config.Paths.COMMAND.STAFFCHAT_ALIASES.toString()) ), StaffChatMuteCommand( - plugin.config.getString(Config.Paths.COMMAND_MUTE_COMMAND.toString())?: "mutestaffchat", - plugin.config.getStringList(Config.Paths.COMMAND_MUTE_ALIASES.toString()) + plugin.config.getString(Config.Paths.COMMAND.MUTE_COMMAND.toString())?: "mutestaffchat", + plugin.config.getStringList(Config.Paths.COMMAND.MUTE_ALIASES.toString()) ), StaffChatToggleCommand( - plugin.config.getString(Config.Paths.COMMAND_TOGGLE_COMMAND.toString())?: "togglestaffchat", - plugin.config.getStringList(Config.Paths.COMMAND_TOGGLE_ALIASES.toString()) + plugin.config.getString(Config.Paths.COMMAND.TOGGLE_COMMAND.toString())?: "togglestaffchat", + plugin.config.getStringList(Config.Paths.COMMAND.TOGGLE_ALIASES.toString()) ), BetterStaffChatCommand() ) - fun registerCommands() { + fun registerCommands(plugin: Plugin) { commands.forEach { - plugin.proxy.pluginManager.registerCommand(plugin, it) + ProxyServer.getInstance().pluginManager.registerCommand(plugin, it) } } } \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/BetterStaffChatCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/BetterStaffChatCommand.kt index 7914c42..65c81a0 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/BetterStaffChatCommand.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/BetterStaffChatCommand.kt @@ -5,17 +5,15 @@ import net.md_5.bungee.api.CommandSender class BetterStaffChatCommand: BSCBungeeCommand("betterstaffchat", null, listOf("bsc")) { override fun run(sender: CommandSender, args: Array) { - if (args.isEmpty()) { - legacyTell("&7Running &9BetterStaffChat &7by &6AusTech Development&7.") - return - } - when (args[0]) { "reload" -> { - if (!sender.requirePermission("betterstaffchat.reload")) return; + if (!sender.requirePermission("betterstaffchat.reload")) return warnTell("It is not recommended to forcefully reload BetterStaffChat. It will automatically reload when the config is edited.") plugin.config.forceReload() } + else -> { + legacyTell("&7Running &9BetterStaffChat &7by &6AusTech Development&7.") + } } return diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt index d9dbbd6..342ba63 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt @@ -2,22 +2,44 @@ package dev.austech.betterstaffchat.bungeecord.commands.impl import dev.austech.betterstaffchat.bungeecord.commands.BSCBungeeCommand import dev.austech.betterstaffchat.bungeecord.util.PlayerUtil -import dev.austech.betterstaffchat.common.PlayerMeta +import dev.austech.betterstaffchat.common.BSCMetadata import net.md_5.bungee.api.CommandSender import net.md_5.bungee.api.connection.ProxiedPlayer +import java.util.* -class StaffChatCommand(name: String, aliases: List): BSCBungeeCommand(name, "betterstaffchat.messages.send", aliases) { +class StaffChatCommand(name: String, aliases: List) : + BSCBungeeCommand(name, "betterstaffchat.messages.send", aliases) { override fun run(sender: CommandSender, args: Array) { if (args.isEmpty()) { errorTell("Usage: /$name ") return; } - if (sender is ProxiedPlayer && plugin.dataWrapper.getMutedPlayers().contains(sender.uniqueId)) { - errorTell("Your staffchat is currently muted.") - return; + if (sender is ProxiedPlayer) { + if (plugin.dataWrapper.getMutedPlayers().contains(sender.uniqueId)) { + errorTell("Your staffchat is currently muted.") + return; + } + + if (PlayerUtil.isDisabledServer(sender.server.info)) { + errorTell("You cannot use staffchat on this server.") + return; + } } - plugin.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), args.joinToString(separator = " "), PlayerUtil.getSenderName(sender), PlayerUtil.getServer(sender), PlayerMeta(null, null)) + + plugin.staffChatUtil.sendMessage( + PlayerUtil.getReceiveAudience(), args.joinToString(separator = " "), + BSCMetadata( + BSCMetadata.Player( + (if (sender is ProxiedPlayer) sender.uniqueId else null), + PlayerUtil.getSenderName(sender) + ), + BSCMetadata.Proxy( + PlayerUtil.getServer(sender), + PlayerUtil.getRawServer(sender) + ) + ) + ) } } \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt index 2cb70d9..c4f6840 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt @@ -1,12 +1,18 @@ package dev.austech.betterstaffchat.bungeecord.commands.impl import dev.austech.betterstaffchat.bungeecord.commands.BSCBungeeCommand +import dev.austech.betterstaffchat.bungeecord.util.PlayerUtil import dev.austech.betterstaffchat.common.util.Config import net.md_5.bungee.api.CommandSender import net.md_5.bungee.api.connection.ProxiedPlayer class StaffChatMuteCommand(name: String, aliases: List): BSCBungeeCommand(name, "betterstaffchat.mutestaffchat", aliases) { override fun run(sender: CommandSender, args: Array) { + if (!plugin.config.getBoolean(Config.Paths.STAFFCHAT.MUTE_ENABLED.toString())) { + errorTell("Staffchat mute is not enabled.") + return + } + if (sender.requirePlayer()) { return } @@ -15,6 +21,11 @@ class StaffChatMuteCommand(name: String, aliases: List): BSCBungeeComman throw IllegalStateException("Sender is not a player!") } + if (PlayerUtil.isDisabledServer(sender.server.info)) { + errorTell("You cannot use staffchat on this server.") + return; + } + val mute: Boolean val mutedPlayers = plugin.dataWrapper.getMutedPlayers() @@ -42,10 +53,10 @@ class StaffChatMuteCommand(name: String, aliases: List): BSCBungeeComman if (mute) { plugin.dataWrapper.addMutedPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_MUTE_ON_MESSAGE.toString())) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.MUTE_MESSAGE_ON.toString())) } else { plugin.dataWrapper.removeMutedPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_MUTE_OFF_MESSAGE.toString())) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.MUTE_MESSAGE_OFF.toString())) } return diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt index 625e31b..ebc7f78 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt @@ -1,12 +1,18 @@ package dev.austech.betterstaffchat.bungeecord.commands.impl import dev.austech.betterstaffchat.bungeecord.commands.BSCBungeeCommand +import dev.austech.betterstaffchat.bungeecord.util.PlayerUtil import dev.austech.betterstaffchat.common.util.Config import net.md_5.bungee.api.CommandSender import net.md_5.bungee.api.connection.ProxiedPlayer class StaffChatToggleCommand(name: String, aliases: List): BSCBungeeCommand(name, "betterstaffchat.mutestaffchat", aliases) { override fun run(sender: CommandSender, args: Array) { + if (!plugin.config.getBoolean(Config.Paths.STAFFCHAT.TOGGLE_ENABLED.toString())) { + errorTell("Staffchat toggle is not enabled.") + return + } + if (sender.requirePlayer()) { return } @@ -15,6 +21,11 @@ class StaffChatToggleCommand(name: String, aliases: List): BSCBungeeComm throw IllegalStateException("Sender is not a player!") } + if (PlayerUtil.isDisabledServer(sender.server.info)) { + errorTell("You cannot use staffchat on this server.") + return; + } + val toggleOn: Boolean val toggledPlayers = plugin.dataWrapper.getToggledPlayers() @@ -49,10 +60,10 @@ class StaffChatToggleCommand(name: String, aliases: List): BSCBungeeComm } plugin.dataWrapper.addToggledPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_TOGGLE_ON_MESSAGE.toString())) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.TOGGLE_MESSAGE_ON.toString())) } else { plugin.dataWrapper.removeToggledPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_TOGGLE_OFF_MESSAGE.toString())) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.TOGGLE_MESSAGE_OFF.toString())) } return diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt index d6b4f0e..651b6ff 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt @@ -2,8 +2,11 @@ package dev.austech.betterstaffchat.bungeecord.listeners import dev.austech.betterstaffchat.bungeecord.BSCBungee import dev.austech.betterstaffchat.bungeecord.util.PlayerUtil -import dev.austech.betterstaffchat.common.PlayerMeta +import dev.austech.betterstaffchat.common.BSCMetadata import dev.austech.betterstaffchat.common.util.Config +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor import net.md_5.bungee.api.ProxyServer import net.md_5.bungee.api.connection.ProxiedPlayer import net.md_5.bungee.api.event.ChatEvent @@ -11,10 +14,11 @@ import net.md_5.bungee.api.event.PlayerDisconnectEvent import net.md_5.bungee.api.event.PostLoginEvent import net.md_5.bungee.api.event.ServerSwitchEvent import net.md_5.bungee.api.plugin.Listener +import net.md_5.bungee.api.plugin.Plugin import net.md_5.bungee.event.EventHandler import java.util.concurrent.TimeUnit -class PlayerListener: Listener { +class PlayerListener(private val plugin: Plugin): Listener { @EventHandler fun onPlayerChat(event: ChatEvent) { if (event.isCommand || event.isProxyCommand) return @@ -22,39 +26,124 @@ class PlayerListener: Listener { val player = event.sender as ProxiedPlayer if (player.hasPermission("betterstaffchat.messages.send")) { - val prefix = BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT_PREFIX.toString()) ?: ""; + val prefix = BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.PREFIX.toString()) ?: ""; if (event.message.startsWith(prefix) && event.message.length > prefix.length && prefix.isNotBlank() && !prefix.startsWith("/")) { + if (PlayerUtil.isDisabledServer(player.server.info)) { + BSCBungee.instance.audience.player(player).sendMessage(Component.text("You cannot use staffchat on this server.").color(NamedTextColor.RED)) + return + } + event.isCancelled = true; - BSCBungee.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message.substring(prefix.length), player.name, PlayerUtil.getServer(player), PlayerMeta(null, null)) - } else if (BSCBungee.instance.dataWrapper.getToggledPlayers().contains(player.uniqueId)) { + BSCBungee.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message.substring(prefix.length), + BSCMetadata( + BSCMetadata.Player( + player.uniqueId, + player.name + ), + BSCMetadata.Proxy( + PlayerUtil.getServer(player), + PlayerUtil.getRawServer(player) + ) + ) + ) + } else if (BSCBungee.instance.dataWrapper.getToggledPlayers().contains(player.uniqueId) && BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.TOGGLE_ENABLED.toString())) { + if (PlayerUtil.isDisabledServer(player.server.info)) { + BSCBungee.instance.audience.player(player).sendMessage(Component.text("You cannot use staffchat on this server.").color(NamedTextColor.RED)) + return + } + event.isCancelled = true; - BSCBungee.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message, player.name, PlayerUtil.getServer(player), PlayerMeta(null, null)) + BSCBungee.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message, + BSCMetadata( + BSCMetadata.Player( + player.uniqueId, + player.name + ), + BSCMetadata.Proxy( + PlayerUtil.getServer(player), + PlayerUtil.getRawServer(player) + ) + ) + ) } } } @EventHandler fun onPlayerJoin(event: PostLoginEvent) { + if (!BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_JOIN_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.join")) { - ProxyServer.getInstance().scheduler.schedule(BSCBungee.instance, { - BSCBungee.instance.staffChatUtil.sendJoinMessage(PlayerUtil.getReceiveAudience(), event.player.name, PlayerUtil.getServer(event.player), PlayerMeta(null, null)) + if (PlayerUtil.isDisabledServer(event.player.server.info)) { + return + } + + val aud: Audience = if (!BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_JOIN_SEE_OWN.toString())) + PlayerUtil.getReceiveAudienceWithIgnored(event.player.uniqueId) + else PlayerUtil.getReceiveAudience() + + ProxyServer.getInstance().scheduler.schedule(plugin, { + BSCBungee.instance.staffChatUtil.sendJoinMessage(aud, + BSCMetadata( + BSCMetadata.Player( + event.player.uniqueId, + event.player.name + ), + BSCMetadata.Proxy( + PlayerUtil.getServer(event.player), + PlayerUtil.getRawServer(event.player) + ) + ) + ) }, 1, TimeUnit.SECONDS) } } @EventHandler fun onPlayerQuit(event: PlayerDisconnectEvent) { + if (!BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_LEAVE_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.leave")) { - BSCBungee.instance.staffChatUtil.sendLeaveMessage(PlayerUtil.getReceiveAudience(), event.player.name, PlayerUtil.getServer(event.player), PlayerMeta(null, null)) + if (PlayerUtil.isDisabledServer(event.player.server.info)) { + return + } + + BSCBungee.instance.staffChatUtil.sendLeaveMessage(PlayerUtil.getReceiveAudience(), + BSCMetadata( + BSCMetadata.Player( + event.player.uniqueId, + event.player.name + ), + BSCMetadata.Proxy( + PlayerUtil.getServer(event.player), + PlayerUtil.getRawServer(event.player) + ) + ) + ) } } @EventHandler fun onPlayerSwitch(event: ServerSwitchEvent) { + if (!BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_SWITCH_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.switch")) { requireNotNull(event.from) { "From server is null! This means you are using an old version of FlameCord or Aegis." } - BSCBungee.instance.staffChatUtil.sendSwitchMessage(PlayerUtil.getReceiveAudience(), event.player.name, PlayerUtil.getServerReplacement(event.from), PlayerUtil.getServer(event.player), PlayerMeta(null, null)) + + if (PlayerUtil.isDisabledServer(event.player.server.info)) { + return + } + + BSCBungee.instance.staffChatUtil.sendSwitchMessage(PlayerUtil.getReceiveAudience(), PlayerUtil.getServerReplacement(event.from), PlayerUtil.getServer(event.player), + BSCMetadata( + BSCMetadata.Player( + event.player.uniqueId, + event.player.name + ), + BSCMetadata.Proxy( + PlayerUtil.getServer(event.player), + PlayerUtil.getRawServer(event.player) + ) + ) + ) } } } \ No newline at end of file diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt index 31dad43..ca4a34f 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt @@ -12,13 +12,29 @@ import java.util.* object PlayerUtil { fun getSenderName(sender: CommandSender): String { return if (sender is ProxiedPlayer) sender.name - else BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT_CONSOLE.toString()) + else BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.CONSOLE_NAME.toString()) } private fun getMutedPlayers(): List { return BSCBungee.instance.dataWrapper.getMutedPlayers() } + private fun getAudienceWithIgnored(permission: String, ignored: UUID) = BSCBungee.instance.audience.filter { + it.hasPermission(permission) && it is ProxiedPlayer && it.uniqueId != ignored && !getMutedPlayers().contains(it.uniqueId) + } + + private fun getAudienceMixedWithIgnored(permission: String, ignored: UUID) = BSCBungee.instance.audience.filter { + it.hasPermission(permission) && if (it is ProxiedPlayer) it.uniqueId != ignored && !getMutedPlayers().contains(it.uniqueId) else true + } + + fun getReceiveAudienceWithIgnored(ignored: UUID): Audience { + return if (BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.CONSOLE_LOG.toString())) getAudienceMixedWithIgnored( + "betterstaffchat.messages.read", + ignored + ) + else getAudienceWithIgnored("betterstaffchat.messages.read", ignored) + } + private fun getPermissionAudience(permission: String): Audience = BSCBungee.instance.audience.filter { it.hasPermission(permission) && it is ProxiedPlayer && !getMutedPlayers().contains(it.uniqueId) } @@ -28,22 +44,31 @@ object PlayerUtil { } fun getReceiveAudience(): Audience { - return if (BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT_CONSOLE_LOG.toString())) getMixedAudience( + return if (BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.CONSOLE_LOG.toString())) getMixedAudience( "betterstaffchat.messages.read" ) else getPermissionAudience("betterstaffchat.messages.read") } fun getServer(sender: CommandSender): String { - return if (sender !is ProxyServer) BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT_CONSOLE_SERVER.toString()) + return if (sender !is ProxyServer) BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.CONSOLE_SERVER.toString()) else getServerReplacement((sender as ProxiedPlayer).server.info) } + fun getRawServer(sender: CommandSender): String { + return if (sender !is ProxyServer) BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.CONSOLE_SERVER.toString()) + else (sender as ProxiedPlayer).server.info.name + } + fun getServerReplacement(info: ServerInfo): String { - BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT_SERVER_REPLACEMENTS.toString() + "." + info.name)?.let { + BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.SERVER_REPLACEMENTS.toString() + "." + info.name)?.let { return it } return info.name } + + fun isDisabledServer(info: ServerInfo): Boolean { + return BSCBungee.instance.config.getStringList(Config.Paths.STAFFCHAT.DISABLED_SERVERS.toString())?.contains(info.name) ?: false + } } \ No newline at end of file diff --git a/common/src/main/java/dev/austech/betterstaffchat/common/libraries/KotlinLibraries.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/KotlinLibraries.java new file mode 100644 index 0000000..279e065 --- /dev/null +++ b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/KotlinLibraries.java @@ -0,0 +1,24 @@ +package dev.austech.betterstaffchat.common.libraries; + +import dev.austech.betterstaffchat.common.libraries.libby.Library; + +public class KotlinLibraries { + private static final String kotlinString = "_kotlin".replace("_", ""); + + private static final Library.Builder builder = new Library.Builder() + .groupId("org{}jetbrains{}kotlin") + .version("1.6.10") + .relocate(kotlinString, "dev{}austech{}betterstaffchat{}depend{}kotlin"); + + public static final Library STDLIB_JDK8 = builder + .artifactId(kotlinString + "-stdlib-jdk8") // Break here because shadowJar tries to replace it. + .build(); + + public static final Library STDLIB = builder + .artifactId(kotlinString + "-stdlib") + .build(); + + public static final Library STDLIB_COMMON = builder + .artifactId(kotlinString + "-stdlib-common") + .build(); +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Library.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/Library.java similarity index 98% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Library.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/Library.java index 019a53f..4967775 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Library.java +++ b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/Library.java @@ -1,7 +1,5 @@ package dev.austech.betterstaffchat.common.libraries.libby; -import dev.austech.betterstaffchat.common.libraries.libby.LibraryManager; -import dev.austech.betterstaffchat.common.libraries.libby.Repositories; import dev.austech.betterstaffchat.common.libraries.libby.relocation.Relocation; import java.util.Base64; diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java similarity index 99% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java index 5a46629..812e4d1 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java +++ b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/LibraryManager.java @@ -1,6 +1,5 @@ package dev.austech.betterstaffchat.common.libraries.libby; -import dev.austech.betterstaffchat.common.libraries.libby.Repositories; import dev.austech.betterstaffchat.common.libraries.libby.classloader.IsolatedClassLoader; import dev.austech.betterstaffchat.common.libraries.libby.logging.LogLevel; import dev.austech.betterstaffchat.common.libraries.libby.logging.Logger; @@ -23,7 +22,6 @@ import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import java.util.Base64; import java.util.Collection; import java.util.Collections; diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java similarity index 100% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/Repositories.java diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java similarity index 100% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/classloader/IsolatedClassLoader.java diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java similarity index 100% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/classloader/URLClassLoaderHelper.java diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java similarity index 100% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/LogLevel.java diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java similarity index 98% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java index be152fc..53be405 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java +++ b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/Logger.java @@ -1,6 +1,5 @@ package dev.austech.betterstaffchat.common.libraries.libby.logging; -import dev.austech.betterstaffchat.common.libraries.libby.logging.LogLevel; import dev.austech.betterstaffchat.common.libraries.libby.logging.adapters.LogAdapter; import static java.util.Objects.requireNonNull; diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java similarity index 100% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/JDKLogAdapter.java diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java similarity index 100% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/logging/adapters/LogAdapter.java diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java similarity index 100% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/relocation/Relocation.java diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java b/common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java similarity index 100% rename from common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java rename to common/src/main/java/dev/austech/betterstaffchat/common/libraries/libby/relocation/RelocationHelper.java diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCMetadata.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCMetadata.kt new file mode 100644 index 0000000..9356f09 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCMetadata.kt @@ -0,0 +1,68 @@ +package dev.austech.betterstaffchat.common + +import lombok.AllArgsConstructor +import java.util.UUID + +class BSCMetadata { + val playerMeta: Player + var luckpermsMeta: LuckPerms? = null + var proxyMeta: Proxy? = null + var spigotMeta: Spigot? = null + + + constructor(playerMeta: Player) { + this.playerMeta = playerMeta + } + + constructor(playerMeta: Player, luckpermsMeta: LuckPerms) { + this.playerMeta = playerMeta + this.luckpermsMeta = luckpermsMeta + } + + constructor(playerMeta: Player, proxyMeta: Proxy) { + this.playerMeta = playerMeta + this.proxyMeta = proxyMeta + } + + constructor(playerMeta: Player, spigotMeta: Spigot) { + this.playerMeta = playerMeta + this.spigotMeta = spigotMeta + } + + constructor(playerMeta: Player, proxyMeta: Proxy, luckpermsMeta: LuckPerms) { + this.playerMeta = playerMeta + this.luckpermsMeta = luckpermsMeta + this.proxyMeta = proxyMeta + } + + constructor(playerMeta: Player, spigotMeta: Spigot, luckpermsMeta: LuckPerms) { + this.playerMeta = playerMeta + this.luckpermsMeta = luckpermsMeta + this.spigotMeta = spigotMeta + } + + @AllArgsConstructor + class LuckPerms( + val prefix: String?, + val suffix: String?, + val group: String?, + val groupDisplayName: String?, + ) + + @AllArgsConstructor + class Player( + val uuid: UUID?, + val name: String, + ) + + @AllArgsConstructor + class Proxy( + val server: String, + val serverRaw: String + ) + + @AllArgsConstructor + class Spigot( + val world: String? + ) +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt index e2c68a1..ae3adef 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt @@ -10,6 +10,9 @@ interface BSCPlugin { val config: Yaml val consoleAudience: Audience + fun onLoad() + fun onEnable() + enum class Platform { BUKKIT, BUNGEECORD diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt deleted file mode 100644 index e2a8b7e..0000000 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/PlayerMeta.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.austech.betterstaffchat.common - -import java.util.UUID - -class PlayerMeta( - val prefix: String?, - val suffix: String? -) \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt index b00790e..0035c91 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt @@ -113,8 +113,6 @@ object BSCLibraries { .plus(Adventure.SERIALIZER_BUNGEECORD) } - println("Libraries: ${libraryList.joinToString { it.artifactId.toString() }}") - return libraryList } } \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt index d70e9a9..92baf4a 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt @@ -32,35 +32,59 @@ class Config(private val plugin: BSCPlugin) { .createConfig(); } - enum class Paths(private val path: String) { - STAFFCHAT_FORMAT("staffchat.format"), - STAFFCHAT_PREFIX("staffchat.prefix"), - STAFFCHAT_CONSOLE("staffchat.console-replacement"), - STAFFCHAT_CONSOLE_UUID("staffchat.console-uuid-replacement"), - STAFFCHAT_CONSOLE_SERVER("staffchat.console-server-replacement"), - STAFFCHAT_CONSOLE_LOG("staffchat.log-to-console"), - STAFFCHAT_STRIP_COLOR_CODES("staffchat.strip-color-codes"), - STAFFCHAT_SERVER_REPLACEMENTS("staffchat.server-replacement"), - STAFFCHAT_JOIN_MESSAGE("staffchat.join"), - STAFFCHAT_LEAVE_MESSAGE("staffchat.leave"), - STAFFCHAT_SWITCH_MESSAGE("staffchat.switch"), - STAFFCHAT_MUTE_ON_MESSAGE("staffchat.mute-on"), - STAFFCHAT_MUTE_OFF_MESSAGE("staffchat.mute-off"), - STAFFCHAT_TOGGLE_ON_MESSAGE("staffchat.toggle-on"), - STAFFCHAT_TOGGLE_OFF_MESSAGE("staffchat.toggle-off"), + class Paths { + enum class STAFFCHAT(private val path: String) { + FORMAT("staffchat.format"), + PREFIX("staffchat.prefix"), + CONSOLE_NAME("staffchat.console-replacement"), + CONSOLE_UUID("staffchat.console-uuid-replacement"), + CONSOLE_SERVER("staffchat.console-server-replacement"), + CONSOLE_LOG("staffchat.log-to-console"), + STRIP_COLOR_CODES("staffchat.strip-color-codes"), + SERVER_REPLACEMENTS("staffchat.server-replacement"), + DISABLED_SERVERS("staffchat.disabled-servers"), - COMMAND_STAFFCHAT_COMMAND("commands.staffchat.command"), - COMMAND_STAFFCHAT_ALIASES("commands.staffchat.aliases"), - COMMAND_MUTE_COMMAND("commands.mutestaffchat.command"), - COMMAND_MUTE_ALIASES("commands.mutestaffchat.aliases"), - COMMAND_TOGGLE_COMMAND("commands.togglestaffchat.command"), - COMMAND_TOGGLE_ALIASES("commands.togglestaffchat.aliases"), + EVENTS_JOIN_ENABLED("staffchat.events.join.enabled"), + EVENTS_JOIN_SEE_OWN("staffchat.events.join.see-own-join"), + EVENTS_JOIN_MESSAGE("staffchat.events.join.message"), + EVENTS_LEAVE_ENABLED("staffchat.events.leave.enabled"), + EVENTS_LEAVE_MESSAGE("staffchat.events.leave.message"), + EVENTS_SWITCH_ENABLED("staffchat.events.switch.enabled"), + EVENTS_SWITCH_USE_WORLDS("staffchat.events.switch.use-worlds-on-spigot"), + EVENTS_SWITCH_MESSAGE("staffchat.events.switch.message"), - GLOBAL_UPDATE_CHECK("check-for-updates"), - GLOBAL_DEBUG_MODE("debug"); + MUTE_ENABLED("staffchat.mute.enabled"), + MUTE_MESSAGE_ON("staffchat.mute.on"), + MUTE_MESSAGE_OFF("staffchat.mute.off"), + TOGGLE_ENABLED("staffchat.toggle.enabled"), + TOGGLE_MESSAGE_ON("staffchat.toggle.on"), + TOGGLE_MESSAGE_OFF("staffchat.toggle.off"); - override fun toString(): String { - return path; + override fun toString(): String { + return path + } + } + + enum class COMMAND(private val path: String) { + STAFFCHAT_COMMAND("commands.staffchat.command"), + STAFFCHAT_ALIASES("commands.staffchat.aliases"), + MUTE_COMMAND("commands.mutestaffchat.command"), + MUTE_ALIASES("commands.mutestaffchat.aliases"), + TOGGLE_COMMAND("commands.togglestaffchat.command"), + TOGGLE_ALIASES("commands.togglestaffchat.aliases"); + + override fun toString(): String { + return path + } + } + + enum class HOOKS(private val path: String) { + LUCKPERMS("hooks.luckperms"), + PLACEHOLDERAPI("hooks.placeholderapi"); + + override fun toString(): String { + return path + } } } } \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt index 278fd31..3c71c5a 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt @@ -1,35 +1,72 @@ package dev.austech.betterstaffchat.common.util import dev.austech.betterstaffchat.common.BSCPlugin -import dev.austech.betterstaffchat.common.PlayerMeta +import dev.austech.betterstaffchat.common.BSCMetadata import net.kyori.adventure.audience.Audience +import java.util.* class StaffChatUtil(private val plugin: BSCPlugin) { - private fun replacePlaceholders(s: String, playerName: String, server: String, meta: PlayerMeta?): String { + + lateinit var replaceProvider: (UUID, String) -> String + + constructor(plugin: BSCPlugin, consumer: (u: UUID, s: String) -> String) : this(plugin) { + replaceProvider = consumer + } + + private fun replacePlaceholders(s: String, meta: BSCMetadata): String { var newString = s - .replace("%player_name%", playerName) - .replace("%server%", server); + .replace("%player_name%", meta.playerMeta.name) + + if (meta.playerMeta.uuid != null) { + newString = newString.replace("%player_uuid%", meta.playerMeta.uuid.toString()) + } + + if (meta.proxyMeta != null) { + val proxy: BSCMetadata.Proxy = meta.proxyMeta!! + newString = newString + .replace("%player_server%", proxy.server) + .replace("%player_server_raw%", proxy.serverRaw) + } - if (meta != null) { - if (meta.prefix != null) + if (meta.spigotMeta != null) { + val spigot: BSCMetadata.Spigot = meta.spigotMeta!! + + if (spigot.world != null) { newString = newString - .replace("%luckperms_prefix%", meta.prefix) - .replace("%prefix%", meta.prefix) + .replace("%player_world%", spigot.world) + } + } - if (meta.suffix != null) + if (meta.luckpermsMeta != null) { + val luckperms: BSCMetadata.LuckPerms = meta.luckpermsMeta!! + + if (luckperms.group != null && luckperms.groupDisplayName != null) { newString = newString - .replace("%luckperms_suffix%", meta.suffix) - .replace("%suffix%", meta.suffix) + .replace("%luckperms_group%", luckperms.group) + .replace("%luckperms_group_displayname%", luckperms.groupDisplayName) + } + + if (luckperms.prefix != null) { + newString = newString.replace("%luckperms_prefix%", luckperms.prefix) + } + + if (luckperms.suffix != null) { + newString = newString.replace("%luckperms_suffix%", luckperms.suffix) + } + } + + if (meta.playerMeta.uuid != null) { + replaceProvider.invoke(meta.playerMeta.uuid, newString) } return newString; } - fun sendMessage(audience: Audience, message: String, playerName: String, server: String, meta: PlayerMeta?) { - var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT_FORMAT.toString()) ?: return; - val removeColorCodes = plugin.config.getBoolean(Config.Paths.STAFFCHAT_STRIP_COLOR_CODES.toString()); + fun sendMessage(audience: Audience, message: String, meta: BSCMetadata) { + var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT.FORMAT.toString()) ?: return; + val removeColorCodes = plugin.config.getBoolean(Config.Paths.STAFFCHAT.STRIP_COLOR_CODES.toString()); - configMessage = replacePlaceholders(configMessage, playerName, server, meta) + configMessage = replacePlaceholders(configMessage, meta) .replace("%message%", if (removeColorCodes) TextUtil.escapeTags(message) @@ -39,23 +76,23 @@ class StaffChatUtil(private val plugin: BSCPlugin) { audience.sendMessage(TextUtil.parseText(configMessage)) } - fun sendJoinMessage(audience: Audience, playerName: String, server: String, meta: PlayerMeta?) { - var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT_JOIN_MESSAGE.toString()) ?: return; - configMessage = replacePlaceholders(configMessage, playerName, server, meta) + fun sendJoinMessage(audience: Audience, meta: BSCMetadata) { + var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT.EVENTS_JOIN_MESSAGE.toString()) ?: return; + configMessage = replacePlaceholders(configMessage, meta) audience.sendMessage(TextUtil.parseText(configMessage)) } - fun sendLeaveMessage(audience: Audience, playerName: String, server: String, meta: PlayerMeta?) { - var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT_LEAVE_MESSAGE.toString()) ?: return; - configMessage = replacePlaceholders(configMessage, playerName, server, meta) + fun sendLeaveMessage(audience: Audience, meta: BSCMetadata) { + var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT.EVENTS_LEAVE_MESSAGE.toString()) ?: return; + configMessage = replacePlaceholders(configMessage, meta) audience.sendMessage(TextUtil.parseText(configMessage)) } - fun sendSwitchMessage(audience: Audience, playerName: String, from: String, to: String, meta: PlayerMeta?) { - var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT_SWITCH_MESSAGE.toString()) ?: return; - configMessage = replacePlaceholders(configMessage, playerName, from, meta) + fun sendSwitchMessage(audience: Audience, from: String, to: String, meta: BSCMetadata) { + var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT.EVENTS_SWITCH_MESSAGE.toString()) ?: return; + configMessage = replacePlaceholders(configMessage, meta) configMessage = configMessage .replace("%from%", from) .replace("%to%", to) diff --git a/spigot/build.gradle.kts b/spigot/build.gradle.kts index 7815745..ddcc58f 100644 --- a/spigot/build.gradle.kts +++ b/spigot/build.gradle.kts @@ -1,12 +1,18 @@ repositories { mavenCentral() + maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") } + + maven { + url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/") + } } dependencies { compileOnly(project(":common")) compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT") compileOnly("net.kyori:adventure-platform-bukkit:4.1.0") + compileOnly("me.clip:placeholderapi:2.11.1") } \ No newline at end of file diff --git a/spigot/src/main/java/dev/austech/betterstaffchat/spigot/BSCSpigotBootstrap.java b/spigot/src/main/java/dev/austech/betterstaffchat/spigot/BSCSpigotBootstrap.java new file mode 100644 index 0000000..ee547b2 --- /dev/null +++ b/spigot/src/main/java/dev/austech/betterstaffchat/spigot/BSCSpigotBootstrap.java @@ -0,0 +1,29 @@ +package dev.austech.betterstaffchat.spigot; + +import dev.austech.betterstaffchat.common.libraries.KotlinLibraries; +import dev.austech.betterstaffchat.common.libraries.libby.LibraryManager; +import dev.austech.betterstaffchat.spigot.libby.BukkitLibraryManager; +import org.bukkit.plugin.java.JavaPlugin; + +public class BSCSpigotBootstrap extends JavaPlugin { + private BSCSpigot implPlugin; + + @Override + public void onLoad() { + LibraryManager libraryManager = new BukkitLibraryManager(this); + libraryManager.addMavenCentral(); + libraryManager.addJitPack(); + + libraryManager.loadLibrary(KotlinLibraries.STDLIB); + libraryManager.loadLibrary(KotlinLibraries.STDLIB_COMMON); + libraryManager.loadLibrary(KotlinLibraries.STDLIB_JDK8); + + implPlugin = new BSCSpigot(this); + implPlugin.onLoad(); + } + + @Override + public void onEnable() { + implPlugin.onEnable(); + } +} diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java b/spigot/src/main/java/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java similarity index 100% rename from spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java rename to spigot/src/main/java/dev/austech/betterstaffchat/spigot/libby/BukkitLibraryManager.java diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt index ad64dea..91f6660 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt @@ -10,13 +10,15 @@ import dev.austech.betterstaffchat.common.util.StaffChatUtil import dev.austech.betterstaffchat.spigot.commands.CommandManager import dev.austech.betterstaffchat.spigot.listeners.PlayerListener import dev.austech.betterstaffchat.spigot.libby.BukkitLibraryManager +import me.clip.placeholderapi.PlaceholderAPI import net.kyori.adventure.audience.Audience import net.kyori.adventure.platform.bukkit.BukkitAudiences import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin import java.io.File +import java.util.* -class BSCSpigot: JavaPlugin(), BSCPlugin { +class BSCSpigot(private val plugin: JavaPlugin) : BSCPlugin { companion object { lateinit var instance: BSCSpigot } @@ -28,10 +30,10 @@ class BSCSpigot: JavaPlugin(), BSCPlugin { override lateinit var config: Yaml override lateinit var consoleAudience: Audience; override val platform = BSCPlugin.Platform.BUKKIT - override val pluginDataFile: File = this.dataFolder + override val pluginDataFile: File = plugin.dataFolder override fun onLoad() { - val libraryManager = BukkitLibraryManager(this) + val libraryManager = BukkitLibraryManager(plugin) libraryManager.addMavenCentral() libraryManager.addJitPack() @@ -42,15 +44,25 @@ class BSCSpigot: JavaPlugin(), BSCPlugin { override fun onEnable() { instance = this; - audience = BukkitAudiences.create(this) + audience = BukkitAudiences.create(plugin) consoleAudience = audience.console() config = Config(this).load() - staffChatUtil = StaffChatUtil(this) data = Data(this).load() dataWrapper = Data.Wrapper(data) + val shouldHookPAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") && + config.getBoolean(Config.Paths.HOOKS.PLACEHOLDERAPI.toString()) + + staffChatUtil = if (shouldHookPAPI) { + StaffChatUtil(this) { uuid, s -> + PlaceholderAPI.setPlaceholders(Bukkit.getPlayer(uuid), s) + } + } else { + StaffChatUtil(this) + } + CommandManager(this).registerCommands() - Bukkit.getPluginManager().registerEvents(PlayerListener(), this) + Bukkit.getPluginManager().registerEvents(PlayerListener(), plugin) } } \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/BSCSpigotCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/BSCSpigotCommand.kt index fd07fe1..4b534f1 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/BSCSpigotCommand.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/BSCSpigotCommand.kt @@ -1,6 +1,7 @@ package dev.austech.betterstaffchat.spigot.commands import dev.austech.betterstaffchat.spigot.BSCSpigot +import net.kyori.adventure.text.Component import org.bukkit.ChatColor import org.bukkit.command.Command import org.bukkit.command.CommandSender @@ -43,6 +44,10 @@ abstract class BSCSpigotCommand(name: String, description: String, usage: String return true } + fun tell(component: Component) { + plugin.audience.sender(this.sender).sendMessage(component) + } + fun legacyTell(s: String) { return this.sender.sendMessage(ChatColor.translateAlternateColorCodes('&', s)) } diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt index 61140bd..317e0fb 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt @@ -12,22 +12,22 @@ import org.bukkit.command.Command class CommandManager(private val plugin: BSCSpigot) { private val commands: List = listOf( StaffChatCommand( - plugin.config.getString(Config.Paths.COMMAND_STAFFCHAT_COMMAND.toString())?: "staffchat", + plugin.config.getString(Config.Paths.COMMAND.STAFFCHAT_COMMAND.toString())?: "staffchat", "This command lets you send a message to the staffchat.", "", - plugin.config.getStringList(Config.Paths.COMMAND_STAFFCHAT_ALIASES.toString()) + plugin.config.getStringList(Config.Paths.COMMAND.STAFFCHAT_ALIASES.toString()) ), StaffChatMuteCommand( - plugin.config.getString(Config.Paths.COMMAND_MUTE_COMMAND.toString())?: "mutestaffchat", + plugin.config.getString(Config.Paths.COMMAND.MUTE_COMMAND.toString())?: "mutestaffchat", "This command lets you ignore / disable your staffchat.", "[on/off]", - plugin.config.getStringList(Config.Paths.COMMAND_MUTE_ALIASES.toString()) + plugin.config.getStringList(Config.Paths.COMMAND.MUTE_ALIASES.toString()) ), StaffChatToggleCommand( - plugin.config.getString(Config.Paths.COMMAND_TOGGLE_COMMAND.toString())?: "togglestaffchat", + plugin.config.getString(Config.Paths.COMMAND.TOGGLE_COMMAND.toString())?: "togglestaffchat", "This command lets you ignore / disable your staffchat.", "[on/off]", - plugin.config.getStringList(Config.Paths.COMMAND_TOGGLE_ALIASES.toString()) + plugin.config.getStringList(Config.Paths.COMMAND.TOGGLE_ALIASES.toString()) ), BetterStaffChatCommand() ) diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/BetterStaffChatCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/BetterStaffChatCommand.kt index f7382b5..8966816 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/BetterStaffChatCommand.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/BetterStaffChatCommand.kt @@ -5,17 +5,15 @@ import org.bukkit.command.CommandSender class BetterStaffChatCommand: BSCSpigotCommand("betterstaffchat", "The main command for BetterStaffChat", "", listOf("bsc")) { override fun run(sender: CommandSender, commandLabel: String, args: Array): Boolean { - if (args.isEmpty()) { - legacyTell("&7Running &9BetterStaffChat &7by &6AusTech Development&7.") - return true - } - when (args[0]) { "reload" -> { if (!sender.requirePermission("betterstaffchat.reload")) return true; warnTell("It is not recommended to forcefully reload BetterStaffChat. It will automatically reload when the config is edited.") plugin.config.forceReload() } + else -> { + legacyTell("&7Running &9BetterStaffChat &7by &6AusTech Development&7.") + } } return true; diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt index 74b1194..aaeba68 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt @@ -1,6 +1,6 @@ package dev.austech.betterstaffchat.spigot.commands.impl -import dev.austech.betterstaffchat.common.PlayerMeta +import dev.austech.betterstaffchat.common.BSCMetadata import dev.austech.betterstaffchat.spigot.commands.BSCSpigotCommand import dev.austech.betterstaffchat.spigot.util.PlayerUtil import org.bukkit.command.CommandSender @@ -22,7 +22,17 @@ class StaffChatCommand(name: String, description: String, usage: String, aliases return true } - plugin.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), args.joinToString(separator = " "), PlayerUtil.getSenderName(sender), "N/A", PlayerMeta(null, null)) + plugin.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), args.joinToString(separator = " "), + BSCMetadata( + BSCMetadata.Player( + (if (sender is Player) sender.uniqueId else null), + PlayerUtil.getSenderName(sender) + ), + BSCMetadata.Spigot( + if (sender is Player) sender.world.name else null, + ) + ) + ) return true; } diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt index 05b3fcb..b484410 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt @@ -11,6 +11,11 @@ class StaffChatMuteCommand(name: String, description: String, usage: String, ali return true } + if (!plugin.config.getBoolean(Config.Paths.STAFFCHAT.MUTE_ENABLED.toString())) { + errorTell("Staffchat mute is not enabled.") + return true + } + if (!sender.requirePlayer()) { return true } @@ -46,10 +51,10 @@ class StaffChatMuteCommand(name: String, description: String, usage: String, ali if (mute) { plugin.dataWrapper.addMutedPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_MUTE_ON_MESSAGE.toString())) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.MUTE_MESSAGE_ON.toString())) } else { plugin.dataWrapper.removeMutedPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_MUTE_OFF_MESSAGE.toString())) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.MUTE_MESSAGE_OFF.toString())) } return true diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt index 27fdb5b..d5cff53 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt @@ -11,6 +11,11 @@ class StaffChatToggleCommand(name: String, description: String, usage: String, a return true } + if (!plugin.config.getBoolean(Config.Paths.STAFFCHAT.TOGGLE_ENABLED.toString())) { + errorTell("Staffchat toggle is not enabled.") + return true + } + if (!sender.requirePlayer()) { return true } @@ -53,10 +58,10 @@ class StaffChatToggleCommand(name: String, description: String, usage: String, a } plugin.dataWrapper.addToggledPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_TOGGLE_ON_MESSAGE.toString())) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.TOGGLE_MESSAGE_ON.toString())) } else { plugin.dataWrapper.removeToggledPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT_TOGGLE_OFF_MESSAGE.toString())) + legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.TOGGLE_MESSAGE_OFF.toString())) } return true diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt index 47a9778..cc4a8d5 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt @@ -1,9 +1,10 @@ package dev.austech.betterstaffchat.spigot.listeners -import dev.austech.betterstaffchat.common.PlayerMeta +import dev.austech.betterstaffchat.common.BSCMetadata import dev.austech.betterstaffchat.common.util.Config import dev.austech.betterstaffchat.spigot.BSCSpigot import dev.austech.betterstaffchat.spigot.util.PlayerUtil +import net.kyori.adventure.audience.Audience import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener @@ -12,40 +13,107 @@ import org.bukkit.event.player.PlayerChangedWorldEvent import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerQuitEvent -class PlayerListener: Listener { +class PlayerListener : Listener { @EventHandler(priority = EventPriority.HIGH) fun onAsyncPlayerChat(event: AsyncPlayerChatEvent) { if (event.player.hasPermission("betterstaffchat.messages.send")) { - val prefix = BSCSpigot.instance.config.getString(Config.Paths.STAFFCHAT_PREFIX.toString()) ?: ""; + val prefix = BSCSpigot.instance.config.getString(Config.Paths.STAFFCHAT.PREFIX.toString()) ?: "" if (event.message.startsWith(prefix) && event.message.length > prefix.length && prefix.isNotBlank()) { event.isCancelled = true; - BSCSpigot.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message.substring(prefix.length), event.player.name, "N/A", PlayerMeta(null, null)) - } else if (BSCSpigot.instance.dataWrapper.getToggledPlayers().contains(event.player.uniqueId)) { + BSCSpigot.instance.staffChatUtil.sendMessage( + PlayerUtil.getReceiveAudience(), event.message.substring(prefix.length), + BSCMetadata( + BSCMetadata.Player( + event.player.uniqueId, + PlayerUtil.getSenderName(event.player) + ), + BSCMetadata.Spigot( + event.player.world.name, + ) + ) + ) + } else if (BSCSpigot.instance.dataWrapper.getToggledPlayers() + .contains(event.player.uniqueId) && BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.TOGGLE_ENABLED.toString()) + ) { event.isCancelled = true; - BSCSpigot.instance.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), event.message, event.player.name, "N/A", PlayerMeta(null, null)) + BSCSpigot.instance.staffChatUtil.sendMessage( + PlayerUtil.getReceiveAudience(), event.message, + BSCMetadata( + BSCMetadata.Player( + event.player.uniqueId, + PlayerUtil.getSenderName(event.player) + ), + BSCMetadata.Spigot( + event.player.world.name, + ) + ) + ) } } } @EventHandler fun onPlayerJoin(event: PlayerJoinEvent) { + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_JOIN_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.join")) { - BSCSpigot.instance.staffChatUtil.sendJoinMessage(PlayerUtil.getReceiveAudience(), event.player.name, "N/A", PlayerMeta(null, null)) + val aud: Audience = + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_JOIN_SEE_OWN.toString())) + PlayerUtil.getReceiveAudienceWithIgnored(event.player.uniqueId) + else PlayerUtil.getReceiveAudience() + + BSCSpigot.instance.staffChatUtil.sendJoinMessage( + aud, + BSCMetadata( + BSCMetadata.Player( + event.player.uniqueId, + PlayerUtil.getSenderName(event.player) + ), + BSCMetadata.Spigot( + event.player.world.name, + ) + ) + ) } } @EventHandler fun onPlayerQuit(event: PlayerQuitEvent) { + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_LEAVE_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.leave")) { - BSCSpigot.instance.staffChatUtil.sendLeaveMessage(PlayerUtil.getReceiveAudience(), event.player.name, "N/A", PlayerMeta(null, null)) + BSCSpigot.instance.staffChatUtil.sendLeaveMessage( + PlayerUtil.getReceiveAudience(), + BSCMetadata( + BSCMetadata.Player( + event.player.uniqueId, + PlayerUtil.getSenderName(event.player) + ), + BSCMetadata.Spigot( + event.player.world.name, + ) + ) + ) } } @EventHandler fun onPlayerSwitch(event: PlayerChangedWorldEvent) { + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_SWITCH_ENABLED.toString())) return + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_SWITCH_USE_WORLDS.toString())) return + if (event.player.hasPermission("betterstaffchat.messages.leave")) { - BSCSpigot.instance.staffChatUtil.sendSwitchMessage(PlayerUtil.getReceiveAudience(), event.player.name, event.from.name, event.player.world.name, PlayerMeta(null, null)) + BSCSpigot.instance.staffChatUtil.sendSwitchMessage( + PlayerUtil.getReceiveAudience(), event.from.name, event.player.world.name, + BSCMetadata( + BSCMetadata.Player( + event.player.uniqueId, + PlayerUtil.getSenderName(event.player) + ), + BSCMetadata.Spigot( + event.player.world.name, + ) + ) + ) } } } \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt index f418286..19d246a 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt @@ -3,7 +3,6 @@ package dev.austech.betterstaffchat.spigot.util import dev.austech.betterstaffchat.common.util.Config import dev.austech.betterstaffchat.spigot.BSCSpigot import net.kyori.adventure.audience.Audience -import net.kyori.adventure.platform.bukkit.BukkitAudiences import org.bukkit.command.CommandSender import org.bukkit.entity.Player import java.util.* @@ -11,13 +10,29 @@ import java.util.* object PlayerUtil { fun getSenderName(sender: CommandSender): String { return if (sender is Player) sender.name - else BSCSpigot.instance.config.getString(Config.Paths.STAFFCHAT_CONSOLE.toString()) ?: "Console" + else BSCSpigot.instance.config.getString(Config.Paths.STAFFCHAT.CONSOLE_NAME.toString()) ?: "Console" } private fun getMutedPlayers(): List { return BSCSpigot.instance.dataWrapper.getMutedPlayers() } + private fun getAudienceWithIgnored(permission: String, ignored: UUID) = BSCSpigot.instance.audience.filter { + it.hasPermission(permission) && it is Player && it.uniqueId != ignored && !getMutedPlayers().contains(it.uniqueId) + } + + private fun getAudienceMixedWithIgnored(permission: String, ignored: UUID) = BSCSpigot.instance.audience.filter { + it.hasPermission(permission) && if (it is Player) it.uniqueId != ignored && !getMutedPlayers().contains(it.uniqueId) else true + } + + fun getReceiveAudienceWithIgnored(ignored: UUID): Audience { + return if (BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.CONSOLE_LOG.toString())) getAudienceMixedWithIgnored( + "betterstaffchat.messages.read", + ignored + ) + else getAudienceWithIgnored("betterstaffchat.messages.read", ignored) + } + private fun getPermissionAudience(permission: String): Audience = BSCSpigot.instance.audience.filter { it.hasPermission(permission) && it is Player && !getMutedPlayers().contains(it.uniqueId) } @@ -27,7 +42,7 @@ object PlayerUtil { } fun getReceiveAudience(): Audience { - return if (BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT_CONSOLE_LOG.toString())) getMixedAudience( + return if (BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.CONSOLE_LOG.toString())) getMixedAudience( "betterstaffchat.messages.read" ) else getPermissionAudience("betterstaffchat.messages.read") diff --git a/src/main/resources/bungeecord.yml b/src/main/resources/bungeecord.yml index 5e40ba7..6ec62a2 100644 --- a/src/main/resources/bungeecord.yml +++ b/src/main/resources/bungeecord.yml @@ -1,3 +1,3 @@ -name:name: BetterStaffChat -main: dev.austech.betterstaffchat.bungeecord.BSCBungee +name: BetterStaffChat +main: dev.austech.betterstaffchat.bungeecord.BSCBungeeBootstrap version: ${version} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b1a6652..2318cab 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,4 +1,5 @@ # BetterStaffChat +# The better staffchat plugin. # Copyright (c) 2022 AusTech Development Team # Licensed under GNU General Public License v3 @@ -7,12 +8,38 @@ # Spigot Profile: https://www.spigotmc.org/members/919057/ # Discord Server: https://austech.dev/to/support +# You can request a feature or make a suggestion at https://github.com/AusTechDev/BetterStaffChat/discussions/new?category=ideas-features + +# Message Placeholders in all messages (except specified otherwise): +# %player_name% - The player's name +# %player_uuid% - The player's UUID +# %player_server% - The replaced name of the server the player is on (staffchat.server-replacement) (BungeeCord Only) +# %player_server_raw% - The raw name of the server the player is on (BungeeCord Only) +# %player_world% - The name of the world the player is in (Spigot Only) + +# Hook Placeholders: +# LuckPerms +# %luckperms_group% - The player's group +# %luckperms_group_displayname% - The player's group's display name +# %luckperms_prefix% - The player's prefix +# %luckperms_suffix% - The player's suffix +# %luckperms_meta_% - The player's LP metadata for the given key + +# Message Format: +# We use modern libraries to format messages. +# Every message can be configured with MiniMessage. +# https://docs.adventure.kyori.net/minimessage/format.html#standard-tags +# +# Using legacy '&'-style formatting is not supported. It will throw an error. +# You can use this site to convert legacy formatting to MiniMessage: +# - https://tehbrian.xyz/legacy-to-minimessage/ + +# PlaceholderAPI is available in every message on Spigot ONLY. PlaceholderAPI will not work on BungeeCord. + staffchat: - # PlaceholderAPI is supported only on Spigot - # Placeholders for both spigot and bungeecord: - # %player_name% - The player's name, %message% - The message, %server% - The server the player is on (ONLY BUNGEE) - # %luckperms_prefix% - The prefix of the player from LuckPerms, %luckperms_suffix% - The suffix of the player from LuckPerms - format: "&8[&dS&8] &7%player_name%&8: &f%message%" + # Extra Placeholders available: + # %message% - The message sent by the player + format: "[S] %player_name%: %message%" # Putting whatever is below before your message will send it to staffchat # e.g # hello there @@ -28,142 +55,346 @@ staffchat: # ONLY BUNGEE console-server-replacement: "Global" - # Whether or not to send staffchat messages to console + # Whether to send staffchat messages to console log-to-console: true # If a staff member puts color codes in their message, it will remove them if the below is true. strip-color-codes: false - # Changes the display name of servers on Bungeecord. Color codes supported - server-replacement: # Format - servername: "Displayname" - hub: "Hub" # Make sure that the first value is the one equal to in the bungeecord config.yml - - # The message that shows when someone has "betterstaffchat.messages.join" joins the network - # PlaceholderAPI is supported only on Spigot - # Placeholders for both spigot and bungeecord: - # %player_name% - The player's name, %server% - The server the player is on (ONLY BUNGEE) - # %luckperms_prefix% - The prefix of the player from LuckPerms, %luckperms_suffix% - The suffix of the player from LuckPerms - join: "&8[&dS&8] &7%player_name% &fjoined &7the network." # Set to "" to disable - - # The message that shows when someone has "betterstaffchat.messages.leave" leaves the network - # PlaceholderAPI is supported only on Spigot - # Placeholders for both spigot and bungeecord: - # %player_name% - The player's name, %server% - The server the player is on (ONLY BUNGEE) - # %luckperms_prefix% - The prefix of the player from LuckPerms, %luckperms_suffix% - The suffix of the player from LuckPerms - leave: "&8[&dS&8] &7%player_name% &fleft &7the network." # Set to "" to disable - - # The message that shows when someone has "betterstaffchat.messages.switch" switches between servers/worlds - # PlaceholderAPI is supported only on Spigot - # Note, if on bungee it will show the server, however on spigot it will show the world. - # %player_name% - The player's name, %from% - The server/world the player left, %to% - The server/world the player joined - # %luckperms_prefix% - The prefix of the player from LuckPerms, %luckperms_suffix% - The suffix of the player from LuckPerms - switch: "&8[&dS&8] &7%player_name% &fswitched &7from &f%from% &7to &f%to%" # Set to "" to disable - - # The message that is shown when staff chat messages are toggled on with /mutestaffchat - # /mutestaffchat required permission betterstaffchat.mutestaffchat - mute-off: "&8[&dS&8] &7Your staff chat is now &aenabled&7." - - # The message that is shown when staff chat messages are toggled off with /mutestaffchat - # /mutestaffchat required permission betterstaffchat.mutestaffchat - mute-on: "&8[&dS&8] &7Your staff chat is now &cdisabled&7." - - # The message that is shown when all the player's messages go to the staffchat with /togglestaffchat - # /togglestaffchat required permission betterstaffchat.togglestaffchat - toggle-off: "&8[&dS&8] &7All your messages will &cno longer&7 go to staffchat." - - # The message that is shown when staff chat messages are toggled on with /mutestaffchat - # /mutestaffchat required permission betterstaffchat.mutestaffchat - toggle-on: "&8[&dS&8] &7All your messages will &anow&7 go to staffchat." + # BungeeCord Only + # Changes the display name of servers on BungeeCord. + # MiniMessage formatting is supported. + # Placeholders are not available. + server-replacement: # Format - servername: "displayname" + hub: "Hub" # Make sure that the first value is the one equal to in the BungeeCord config.yml -discord: + # BungeeCord Only + # Servers where the StaffChat has no effect. + disabled-servers: + - "testserver123" - # If you need help, we're here to assist you at https://austech.dev/to/support + events: - # Don't enable if webhook is enabled below - bot: - enabled: false + join: + # Enable or disable the join message event. + enabled: true - # The token of the Discord bot. If you need help setting it up we're here to assist you at https://austech.dev/to/support - token: "N/A" + # Whether a player sees their own join message. + see-own-join: true - # What the bot is doing in its status, e.g Playing with staffchat - activity: "with staffchat" + # This message supports all the placeholders above. + message: "[S] %player_name% joined the network." + leave: + # Enable or disable the leave message event. + enabled: true - # The different types of activities (Must be in caps) - # - PLAYING, WATCHING, LISTENING - activity-type: "PLAYING" + # This message supports all the placeholders above. + message: "[S] %player_name% left the network." - # The channels that staff messages are sent to - # Format - GuildID: ChannelID - channels: - - "N/A: N/A" + switch: + # Enable or disable the switch message event. + enabled: true + # Whether to enable this on Spigot and use worlds instead of servers. + use-worlds-on-spigot: false - # Don't enable if bot is enabled above - webhook: - enabled: false + # This message supports all the placeholders above. + message: "[S] %player_name% switched from %from% to %to%." - # The webhook URL. If you need help setting it up we're here to assist you at https://austech.dev/to/support - url: "" + # BetterStaffChat has a feature called /staffchatmute. This mutes/ignores your own staffchat, and does not + # mute the staffchat of other players. This can also be called staffchatignore. + mute: - discord-messages: + # Whether to enable the self-mute / ignore feature. + enabled: true - # The format in game, for when someone sends a message in Discord - # Available placeholders: %username%, %discriminator% - The 4 numbers in the username - # %nickname% - The nickname on Discord - name-format: "%username%#%discriminator%" + # The message to send the player when they enable their mute. + # This message supports all the placeholders above. + on: "[S] Your staff chat is now disabled." + + # The message to send the player when they disable their mute. + # This message supports all the placeholders above. + off: "[S] Your staff chat is now enabled." + + # The two above messages are "reversed", as their staffchat is disabled when they enable mute. + + # BetterStaffChat also has a feature called /togglestaffchat. This makes ALL of the + # player's messages go to staffchat, and not to a public chat. + toggle: + # Whether to enable the toggle feature. + enabled: true + + # The message to send the player when they enable their toggle. + # This message supports all the placeholders above. + on: "[S] All your messages will now go to staffchat." - embed: - # Whether or not the message sent to Discord should be an embed - # true for yes, false for no. + # The message to send the player when they enable their toggle. + # This message supports all the placeholders above. + off: "[S] All your messages will no longer go to staffchat." + +discord: + # BetterStaffChat has two types of Discord integration. + # The first type is using a Discord Bot. This allows two way chat between + # the in-game staffchat and a staffchat channel on Discord. + # This is the recommended type. + # + # The second type is using a Discord Webhook. This allows the staffchat to be + # sent to a Discord channel, but the messages in Discord will not get sent to + # the game. + # + # Bot: In-Game StaffChat -> Discord AND Discord -> In-Game StaffChat + # Webhook: In-Game StaffChat -> Discord + + enabled: false + + # The type of integration to use. + # This can be either "bot" or "webhook". + type: "bot" + + bot: + # If you are using a Discord Bot, you need to set the token here. + # You can create a Discord bot at: + # https://discord.com/developers/applications + # + # You can follow this guide: + # https://discordpy.readthedocs.io/en/stable/discord.html + token: "" + + # The status of the bot. + # This can be either "online", "idle", "dnd", or "invisible". + status: "online" + + activity: + # Whether to enable the bot's activity. + # For example, this is the "Playing with staffchat" under the bot. enabled: true - # The text that is at the bottom of the embed - # %player_name% - The player's name, %uuid% - The player's UUID, %server% - The server the player is on (ONLY BUNGEE) - embed-footer: "BetterStaffChat" # Setting this to "" will disable this and the icon. - - # The little icon next to the footer - # %player_name% - The player's name, %uuid% - The player's UUID, %server% - The server the player is on (ONLY BUNGEE) - embed-footer-icon: "https://crafatar.com/avatars/%uuid%?overlay" # Set to "" to disable - - # The color of the embed, MUST be a hex code. - embed-color: "#FFD555" - - # The format of the message sent to Discord - # %player_name% - The player_name's name, %message% - The message, %server% - The server the player is on (ONLY BUNGEE) - # %luckperms_prefix% - The prefix of the player from LuckPerms, %luckperms_suffix% - The suffix of the player from LuckPerms - staffchat-format: "**%player_name%**: %message%" - - # The message that is sent to Discord when a player with the "betterstaffchat.messages.join" permission joins - # %player_name% - The player's name, %server% - The server the player is on (ONLY BUNGEE) - # %luckperms_prefix% - The prefix of the player from LuckPerms, %luckperms_suffix% - The suffix of the player from LuckPerms - staffchat-join: "%player_name% **joined** the network" - - # The message that is sent to Discord when a player with the "betterstaffchat.messages.leave" permission leaves - # %player_name% - The player's name, %server% - The server the player is on (ONLY BUNGEE) - # %luckperms_prefix% - The prefix of the player from LuckPerms, %luckperms_suffix% - The suffix of the player from LuckPerms - staffchat-leave: "%player_name% **left** the network" - - # The message that is sent to Discord when a player with the "betterstaffchat.messages.switch" permission servers/worlds - # %player_name% - The player's name, %from% - The server/world the player left, %to% - The server/world the player joined - # %luckperms_prefix% - The prefix of the player from LuckPerms, %luckperms_suffix% - The suffix of the player from LuckPerms - staffchat-switch: "%player_name% **switched** from **%from%** to **%to%**" - -commands: # Options for commands | Aliases can not be the same, nor can they be any base command. - staffchat: # /staffchat command - command: staffchat - aliases: [sc, schat] - mutestaffchat: # /mutestaffchat command - command: mutestaffchat - aliases: [mutesc, scmute, scm, msc, ignorestaffchat] - togglestaffchat: # /togglestaffchat command - aliases: [tsc, sct, sctoggle, staffchattoggle, togglesc] - -# Disable this to stop the plugin checking for updates on startup + # The type of activity. + # This can be either "playing", "streaming", "listening", or "watching". + type: "playing" + + # The name of the activity. + status: "with staffchat" + + # The channels and guilds to send the staffchat to. + # You can find the ID of a guild and channel with this guide: + # https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID- + # + # The format for this section can be found below. + # If you wish to disable a certain event, set it to [] + # For example: + # events: + # leave: [] + # + # This would disable leave messages being sent to Discord, however, they will still function in-game. + + channels: + GuildID: + events: + staffchat: [ ChannelID ] + join: [ ChannelID ] + leave: [ ChannelID ] + switch: [ ChannelID ] + + webhook: + # https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks + webhook-url: "" + + # Whether the bot / webhook can mention certain roles & users. + # It is recommended to keep this on none, even if you don't use any mentions. + # This is to prevent people from mentioning the bot or webhook in-game which can lead to some spammy situations. + # + # This can be either "none (no-one can be mentioned), "users" (all users can be mentioned), "roles" (all roles nad users can be mentioned) + # or "all" (all roles, users and @everyone/here can be mentioned). + allowed-mentions: "none" + + messages: + # The format in game, for when someone sends a message in Discord + # The placeholders available are: + # %username% - The username of the person who sent the message + # %discriminator% - The discriminator of the person who sent the message (4 digits in their tag) + # %tag% - The tag of the person who sent the message. This is the username and discriminator combined. + # %nickname% - The nickname of the person who sent the message, or, if they don't have one, their username. + # %message% - The message that was sent. + + name-format: "%username%#%discriminator%" + + staffchat: + # This dictates how messages are sent. + # This can be either "embed" or "text". + mode: "embed" + + # You can view the parts to an embed here: + # https://anidiots.guide/.gitbook/assets/first-bot-embed-example.png + + # Please see the relative event sections for more information about the placeholders. + + embed: + author: + name: "%player_name%" + url: "" # This is optional. + icon-url: "https://minotar.net/helm/%player_name%/128.png" + body: + title: "" + title-url: "" + description: | + %message% + color: "#FFD555" + # fields: # This is commented out by default. If you wish to enable fields, uncomment this. + # 1: + # name: "" + # value: "" + # inline: false + images: + images: [ "" ] + thumbnail: "" + + footer: + timestamp: false # If this is true, the current time will be used. + text: "BetterStaffChat" + icon-url: "" + + text: "**%player_name%**: %message%" + join: + # This dictates how messages are sent. + # This can be either "embed" or "text". + mode: "embed" + + # You can view the parts to an embed here: + # https://anidiots.guide/.gitbook/assets/first-bot-embed-example.png + + # Please see the relative event sections for more information about the placeholders. + + embed: + author: + name: "%player_name%" + url: "" # This is optional. + icon-url: "https://minotar.net/helm/%player_name%/128.png" + body: + title: "" + title-url: "" + description: | + %player_name% **joined** the network. + color: "#FFD555" + # fields: # This is commented out by default. If you wish to enable fields, uncomment this. + # 1: + # name: "" + # value: "" + # inline: false + images: + images: [ "" ] + thumbnail: "" + + footer: + timestamp: false # If this is true, the current time will be used. + text: "BetterStaffChat" + icon-url: "" + + text: "%player_name% **joined** the network." + leave: + # This dictates how messages are sent. + # This can be either "embed" or "text". + mode: "embed" + + # You can view the parts to an embed here: + # https://anidiots.guide/.gitbook/assets/first-bot-embed-example.png + + # Please see the relative event sections for more information about the placeholders. + + embed: + author: + name: "%player_name%" + url: "" # This is optional. + icon-url: "https://minotar.net/helm/%player_name%/128.png" + body: + title: "" + title-url: "" + description: | + %player_name% **left** the network. + color: "#FFD555" + # fields: # This is commented out by default. If you wish to enable fields, uncomment this. + # 1: + # name: "" + # value: "" + # inline: false + images: + images: [ "" ] + thumbnail: "" + + footer: + timestamp: false # If this is true, the current time will be used. + text: "BetterStaffChat" + icon-url: "" + + text: "%player_name% **left** the network." + switch: + # This dictates how messages are sent. + # This can be either "embed" or "text". + mode: "embed" + + # You can view the parts to an embed here: + # https://anidiots.guide/.gitbook/assets/first-bot-embed-example.png + + # Please see the relative event sections for more information about the placeholders. + + embed: + author: + name: "%player_name%" + url: "" # This is optional. + icon-url: "https://minotar.net/helm/%player_name%/128.png" + body: + title: "" + title-url: "" + description: | + %player_name% **switched** from **%from%** to **%to%**. + color: "#FFD555" + # fields: # This is commented out by default. If you wish to enable fields, uncomment this. + # 1: + # name: "" + # value: "" + # inline: false + images: + images: [ "" ] + thumbnail: "" + + footer: + timestamp: false # If this is true, the current time will be used. + text: "BetterStaffChat" + icon-url: "" + + text: "%player_name% **switched** from **%from%** to **%to%**." + +commands: + # This section allows you to change the commands and aliases used for the staffchat. + + staffchat: + command: "staffchat" # The main command to be used. + aliases: [ sc, schat ] # The aliases for the command. Using the main command or duplicates won't affect anything. + + mutestaffchat: + command: "mutestaffchat" # The main command to be used. + aliases: [ msc, isc, hidesc, staffchatmute, ignorestaffchat ] # The aliases for the command. Using the main command or duplicates won't affect anything. + + togglestaffchat: + command: "staffchat" # The main command to be used. + aliases: [ tsc, sct, sctoggle, staffchattoggle, togglesc ] # The aliases for the command. Using the main command or duplicates won't affect anything. + +hooks: + # Enables hooking into LuckPerms. See placeholders above. + luckperms: true + + # Enabled hooking into PlaceHolderAPI. This will only have an effect on Spigot. + placeholderapi: true + +# Disable this to stop the plugin checking for updates on startup. check-for-updates: true +# Disable this to stop the plugin sending a message to those who have the `betterstaffchat.updatenotify` permission on join. +send-updates-to-admins: true + # Don't touch unless you know what you are doing +# Most of the time nothing should debug. debug: false # no touchy me please [DO NOT TOUCH AS IT CAN BREAK YOUR CONFIG] -config-version: 4 \ No newline at end of file +config-version: 5 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index ad7ba79..1cc8458 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,3 +1,5 @@ name: BetterStaffChat -main: dev.austech.betterstaffchat.spigot.BSCSpigot +main: dev.austech.betterstaffchat.spigot.BSCSpigotBootstrap version: ${version} + +softdepend: [ PlaceholderAPI ] \ No newline at end of file From cc7effc02c90157acde99d6bd83db65b3bc3da60 Mon Sep 17 00:00:00 2001 From: mnewt00 <44464407+mnewt00@users.noreply.github.com> Date: Thu, 31 Mar 2022 00:01:14 +1100 Subject: [PATCH 4/5] feat(discord): add discord bot --- build.gradle.kts | 8 ++ .../betterstaffchat/bungeecord/BSCBungee.kt | 8 +- .../bungeecord/commands/CommandManager.kt | 12 +- .../commands/impl/StaffChatMuteCommand.kt | 6 +- .../commands/impl/StaffChatToggleCommand.kt | 6 +- .../bungeecord/listeners/PlayerListener.kt | 12 +- .../bungeecord/util/PlayerUtil.kt | 19 +-- .../betterstaffchat/common/BSCMetadata.kt | 11 +- .../betterstaffchat/common/BSCPlugin.kt | 6 + .../common/discord/DiscordClient.kt | 34 +++++ .../common/discord/DiscordManager.kt | 45 +++++++ .../common/discord/DiscordMessageProvider.kt | 11 ++ .../discord/impl/bot/DiscordEmbedProvider.kt | 25 ++++ .../discord/impl/bot/DiscordListener.kt | 48 +++++++ .../discord/impl/bot/DiscordTextProvider.kt | 22 +++ .../common/discord/util/DiscordUtil.kt | 126 ++++++++++++++++++ .../common/libraries/BSCLibraries.kt | 91 ++++++++++++- .../common/update/UpdateChecker.kt | 48 +++++++ .../betterstaffchat/common/util/Config.kt | 39 +++++- .../common/util/IPlayerUtil.kt | 7 + .../common/util/StaffChatUtil.kt | 61 +++++++-- .../betterstaffchat/spigot/BSCSpigot.kt | 26 +++- .../spigot/commands/CommandManager.kt | 12 +- .../commands/impl/StaffChatMuteCommand.kt | 6 +- .../commands/impl/StaffChatToggleCommand.kt | 6 +- .../spigot/listeners/PlayerListener.kt | 14 +- .../betterstaffchat/spigot/util/PlayerUtil.kt | 11 +- src/main/resources/config.yml | 12 +- src/main/resources/plugin.yml | 1 + 29 files changed, 653 insertions(+), 80 deletions(-) create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordClient.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordManager.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordMessageProvider.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordEmbedProvider.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordListener.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordTextProvider.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/util/DiscordUtil.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/update/UpdateChecker.kt create mode 100644 common/src/main/kotlin/dev/austech/betterstaffchat/common/util/IPlayerUtil.kt diff --git a/build.gradle.kts b/build.gradle.kts index ff370a3..86ccd15 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,6 +52,12 @@ subprojects { compileOnly("com.github.simplix-softworks:SimplixStorage:3.2.3") compileOnly("net.kyori:adventure-api:4.10.1") compileOnly("net.kyori:adventure-text-minimessage:4.10.1") + implementation("com.squareup.okio:okio:3.0.0") { + exclude("org.jetbrains.kotlin") + } + compileOnly("net.dv8tion:JDA:5.0.0-alpha.9") { + exclude("opus-java") + } } sourceSets { @@ -79,6 +85,8 @@ tasks.named("shadowJ relocate("net.kyori.adventure", "dev.austech.betterstaffchat.depend.adventure") relocate("de.leonhard.storage", "dev.austech.betterstaffchat.depend.storage") relocate("kotlin", "dev.austech.betterstaffchat.depend.kotlin") + relocate("net.dv8tion.jda", "dev.austech.betterstaffchat.depend.discord") + relocate("okio", "dev.austech.betterstaffchat.depend.okio") } task("copyJars") { diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt index 5445951..cf43738 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/BSCBungee.kt @@ -5,10 +5,13 @@ import de.leonhard.storage.Yaml import dev.austech.betterstaffchat.bungeecord.commands.CommandManager import dev.austech.betterstaffchat.bungeecord.libby.BungeeLibraryManager import dev.austech.betterstaffchat.bungeecord.listeners.PlayerListener +import dev.austech.betterstaffchat.bungeecord.util.PlayerUtil import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.discord.DiscordManager import dev.austech.betterstaffchat.common.libraries.BSCLibraries import dev.austech.betterstaffchat.common.util.Config import dev.austech.betterstaffchat.common.util.Data +import dev.austech.betterstaffchat.common.util.IPlayerUtil import dev.austech.betterstaffchat.common.util.StaffChatUtil import net.kyori.adventure.audience.Audience import net.kyori.adventure.platform.bungeecord.BungeeAudiences @@ -25,7 +28,10 @@ class BSCBungee(private val plugin: Plugin): BSCPlugin { override val pluginDataFile: File = plugin.dataFolder override lateinit var config: Yaml override lateinit var consoleAudience: Audience - lateinit var staffChatUtil: StaffChatUtil + override lateinit var staffChatUtil: StaffChatUtil + override val playerUtil: IPlayerUtil = PlayerUtil + override val discordManager: DiscordManager + get() = TODO("Not yet implemented") lateinit var audience: BungeeAudiences private lateinit var data: Json lateinit var dataWrapper: Data.Wrapper; diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt index 8df23ba..c087902 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/CommandManager.kt @@ -13,16 +13,16 @@ import net.md_5.bungee.api.plugin.Plugin class CommandManager(private val plugin: BSCBungee) { private val commands: List = listOf( StaffChatCommand( - plugin.config.getString(Config.Paths.COMMAND.STAFFCHAT_COMMAND.toString())?: "staffchat", - plugin.config.getStringList(Config.Paths.COMMAND.STAFFCHAT_ALIASES.toString()) + plugin.config.getString(Config.Paths.Command.STAFFCHAT_COMMAND.toString())?: "staffchat", + plugin.config.getStringList(Config.Paths.Command.STAFFCHAT_ALIASES.toString()) ), StaffChatMuteCommand( - plugin.config.getString(Config.Paths.COMMAND.MUTE_COMMAND.toString())?: "mutestaffchat", - plugin.config.getStringList(Config.Paths.COMMAND.MUTE_ALIASES.toString()) + plugin.config.getString(Config.Paths.Command.MUTE_COMMAND.toString())?: "mutestaffchat", + plugin.config.getStringList(Config.Paths.Command.MUTE_ALIASES.toString()) ), StaffChatToggleCommand( - plugin.config.getString(Config.Paths.COMMAND.TOGGLE_COMMAND.toString())?: "togglestaffchat", - plugin.config.getStringList(Config.Paths.COMMAND.TOGGLE_ALIASES.toString()) + plugin.config.getString(Config.Paths.Command.TOGGLE_COMMAND.toString())?: "togglestaffchat", + plugin.config.getStringList(Config.Paths.Command.TOGGLE_ALIASES.toString()) ), BetterStaffChatCommand() ) diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt index c4f6840..4e4d341 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatMuteCommand.kt @@ -8,7 +8,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer class StaffChatMuteCommand(name: String, aliases: List): BSCBungeeCommand(name, "betterstaffchat.mutestaffchat", aliases) { override fun run(sender: CommandSender, args: Array) { - if (!plugin.config.getBoolean(Config.Paths.STAFFCHAT.MUTE_ENABLED.toString())) { + if (!plugin.config.getBoolean(Config.Paths.Staffchat.MUTE_ENABLED.toString())) { errorTell("Staffchat mute is not enabled.") return } @@ -53,10 +53,10 @@ class StaffChatMuteCommand(name: String, aliases: List): BSCBungeeComman if (mute) { plugin.dataWrapper.addMutedPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.MUTE_MESSAGE_ON.toString())) + legacyTell(plugin.config.getString(Config.Paths.Staffchat.MUTE_MESSAGE_ON.toString())) } else { plugin.dataWrapper.removeMutedPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.MUTE_MESSAGE_OFF.toString())) + legacyTell(plugin.config.getString(Config.Paths.Staffchat.MUTE_MESSAGE_OFF.toString())) } return diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt index ebc7f78..5813795 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatToggleCommand.kt @@ -8,7 +8,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer class StaffChatToggleCommand(name: String, aliases: List): BSCBungeeCommand(name, "betterstaffchat.mutestaffchat", aliases) { override fun run(sender: CommandSender, args: Array) { - if (!plugin.config.getBoolean(Config.Paths.STAFFCHAT.TOGGLE_ENABLED.toString())) { + if (!plugin.config.getBoolean(Config.Paths.Staffchat.TOGGLE_ENABLED.toString())) { errorTell("Staffchat toggle is not enabled.") return } @@ -60,10 +60,10 @@ class StaffChatToggleCommand(name: String, aliases: List): BSCBungeeComm } plugin.dataWrapper.addToggledPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.TOGGLE_MESSAGE_ON.toString())) + legacyTell(plugin.config.getString(Config.Paths.Staffchat.TOGGLE_MESSAGE_ON.toString())) } else { plugin.dataWrapper.removeToggledPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.TOGGLE_MESSAGE_OFF.toString())) + legacyTell(plugin.config.getString(Config.Paths.Staffchat.TOGGLE_MESSAGE_OFF.toString())) } return diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt index 651b6ff..34f303a 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/listeners/PlayerListener.kt @@ -26,7 +26,7 @@ class PlayerListener(private val plugin: Plugin): Listener { val player = event.sender as ProxiedPlayer if (player.hasPermission("betterstaffchat.messages.send")) { - val prefix = BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.PREFIX.toString()) ?: ""; + val prefix = BSCBungee.instance.config.getString(Config.Paths.Staffchat.PREFIX.toString()) ?: ""; if (event.message.startsWith(prefix) && event.message.length > prefix.length && prefix.isNotBlank() && !prefix.startsWith("/")) { if (PlayerUtil.isDisabledServer(player.server.info)) { @@ -47,7 +47,7 @@ class PlayerListener(private val plugin: Plugin): Listener { ) ) ) - } else if (BSCBungee.instance.dataWrapper.getToggledPlayers().contains(player.uniqueId) && BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.TOGGLE_ENABLED.toString())) { + } else if (BSCBungee.instance.dataWrapper.getToggledPlayers().contains(player.uniqueId) && BSCBungee.instance.config.getBoolean(Config.Paths.Staffchat.TOGGLE_ENABLED.toString())) { if (PlayerUtil.isDisabledServer(player.server.info)) { BSCBungee.instance.audience.player(player).sendMessage(Component.text("You cannot use staffchat on this server.").color(NamedTextColor.RED)) return @@ -72,13 +72,13 @@ class PlayerListener(private val plugin: Plugin): Listener { @EventHandler fun onPlayerJoin(event: PostLoginEvent) { - if (!BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_JOIN_ENABLED.toString())) return + if (!BSCBungee.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_JOIN_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.join")) { if (PlayerUtil.isDisabledServer(event.player.server.info)) { return } - val aud: Audience = if (!BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_JOIN_SEE_OWN.toString())) + val aud: Audience = if (!BSCBungee.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_JOIN_SEE_OWN.toString())) PlayerUtil.getReceiveAudienceWithIgnored(event.player.uniqueId) else PlayerUtil.getReceiveAudience() @@ -101,7 +101,7 @@ class PlayerListener(private val plugin: Plugin): Listener { @EventHandler fun onPlayerQuit(event: PlayerDisconnectEvent) { - if (!BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_LEAVE_ENABLED.toString())) return + if (!BSCBungee.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_LEAVE_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.leave")) { if (PlayerUtil.isDisabledServer(event.player.server.info)) { return @@ -124,7 +124,7 @@ class PlayerListener(private val plugin: Plugin): Listener { @EventHandler fun onPlayerSwitch(event: ServerSwitchEvent) { - if (!BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_SWITCH_ENABLED.toString())) return + if (!BSCBungee.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_SWITCH_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.switch")) { requireNotNull(event.from) { "From server is null! This means you are using an old version of FlameCord or Aegis." } diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt index ca4a34f..0427d40 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/util/PlayerUtil.kt @@ -2,6 +2,7 @@ package dev.austech.betterstaffchat.bungeecord.util import dev.austech.betterstaffchat.bungeecord.BSCBungee import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.common.util.IPlayerUtil import net.kyori.adventure.audience.Audience import net.md_5.bungee.api.CommandSender import net.md_5.bungee.api.ProxyServer @@ -9,10 +10,10 @@ import net.md_5.bungee.api.config.ServerInfo import net.md_5.bungee.api.connection.ProxiedPlayer import java.util.* -object PlayerUtil { +object PlayerUtil: IPlayerUtil { fun getSenderName(sender: CommandSender): String { return if (sender is ProxiedPlayer) sender.name - else BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.CONSOLE_NAME.toString()) + else BSCBungee.instance.config.getString(Config.Paths.Staffchat.CONSOLE_NAME.toString()) } private fun getMutedPlayers(): List { @@ -28,7 +29,7 @@ object PlayerUtil { } fun getReceiveAudienceWithIgnored(ignored: UUID): Audience { - return if (BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.CONSOLE_LOG.toString())) getAudienceMixedWithIgnored( + return if (BSCBungee.instance.config.getBoolean(Config.Paths.Staffchat.CONSOLE_LOG.toString())) getAudienceMixedWithIgnored( "betterstaffchat.messages.read", ignored ) @@ -43,25 +44,25 @@ object PlayerUtil { it.hasPermission(permission) && if (it is ProxiedPlayer) !getMutedPlayers().contains(it.uniqueId) else true } - fun getReceiveAudience(): Audience { - return if (BSCBungee.instance.config.getBoolean(Config.Paths.STAFFCHAT.CONSOLE_LOG.toString())) getMixedAudience( + override fun getReceiveAudience(): Audience { + return if (BSCBungee.instance.config.getBoolean(Config.Paths.Staffchat.CONSOLE_LOG.toString())) getMixedAudience( "betterstaffchat.messages.read" ) else getPermissionAudience("betterstaffchat.messages.read") } fun getServer(sender: CommandSender): String { - return if (sender !is ProxyServer) BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.CONSOLE_SERVER.toString()) + return if (sender !is ProxyServer) BSCBungee.instance.config.getString(Config.Paths.Staffchat.CONSOLE_SERVER.toString()) else getServerReplacement((sender as ProxiedPlayer).server.info) } fun getRawServer(sender: CommandSender): String { - return if (sender !is ProxyServer) BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.CONSOLE_SERVER.toString()) + return if (sender !is ProxyServer) BSCBungee.instance.config.getString(Config.Paths.Staffchat.CONSOLE_SERVER.toString()) else (sender as ProxiedPlayer).server.info.name } fun getServerReplacement(info: ServerInfo): String { - BSCBungee.instance.config.getString(Config.Paths.STAFFCHAT.SERVER_REPLACEMENTS.toString() + "." + info.name)?.let { + BSCBungee.instance.config.getString(Config.Paths.Staffchat.SERVER_REPLACEMENTS.toString() + "." + info.name)?.let { return it } @@ -69,6 +70,6 @@ object PlayerUtil { } fun isDisabledServer(info: ServerInfo): Boolean { - return BSCBungee.instance.config.getStringList(Config.Paths.STAFFCHAT.DISABLED_SERVERS.toString())?.contains(info.name) ?: false + return BSCBungee.instance.config.getStringList(Config.Paths.Staffchat.DISABLED_SERVERS.toString())?.contains(info.name) ?: false } } \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCMetadata.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCMetadata.kt index 9356f09..8bb4562 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCMetadata.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCMetadata.kt @@ -4,11 +4,15 @@ import lombok.AllArgsConstructor import java.util.UUID class BSCMetadata { - val playerMeta: Player + var playerMeta: Player? = null var luckpermsMeta: LuckPerms? = null var proxyMeta: Proxy? = null var spigotMeta: Spigot? = null + var discordMeta: Discord? = null + constructor(discordMeta: Discord) { + this.discordMeta = discordMeta + } constructor(playerMeta: Player) { this.playerMeta = playerMeta @@ -65,4 +69,9 @@ class BSCMetadata { class Spigot( val world: String? ) + + @AllArgsConstructor + class Discord( + val displayName: String? + ) } \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt index ae3adef..0af59f3 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/BSCPlugin.kt @@ -1,6 +1,9 @@ package dev.austech.betterstaffchat.common import de.leonhard.storage.Yaml +import dev.austech.betterstaffchat.common.discord.DiscordManager +import dev.austech.betterstaffchat.common.util.IPlayerUtil +import dev.austech.betterstaffchat.common.util.StaffChatUtil import net.kyori.adventure.audience.Audience import java.io.File @@ -9,6 +12,9 @@ interface BSCPlugin { val pluginDataFile: File val config: Yaml val consoleAudience: Audience + val staffChatUtil: StaffChatUtil + val playerUtil: IPlayerUtil + val discordManager: DiscordManager fun onLoad() fun onEnable() diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordClient.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordClient.kt new file mode 100644 index 0000000..5e03983 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordClient.kt @@ -0,0 +1,34 @@ +package dev.austech.betterstaffchat.common.discord + +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.discord.impl.bot.DiscordListener +import net.dv8tion.jda.api.JDABuilder +import dev.austech.betterstaffchat.common.util.Config +import net.dv8tion.jda.api.OnlineStatus +import net.dv8tion.jda.api.entities.Activity +import net.dv8tion.jda.api.requests.GatewayIntent +import net.dv8tion.jda.api.utils.cache.CacheFlag + +class DiscordClient(private val plugin: BSCPlugin) { + val jda = JDABuilder.create(plugin.config.getString(Config.Paths.Discord.BOT_TOKEN.toString()), GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MEMBERS) + .addEventListeners(DiscordListener(plugin)) + .disableCache(CacheFlag.ACTIVITY, CacheFlag.VOICE_STATE, CacheFlag.EMOTE, CacheFlag.CLIENT_STATUS, CacheFlag.ONLINE_STATUS) + .build() + + init { + jda.awaitReady() + setup() + } + + fun setup() { + if (plugin.config.getBoolean(Config.Paths.Discord.BOT_ACTIVITY.toString())) { + jda.presence.activity = Activity.of( + Activity.ActivityType.valueOf(plugin.config.getString(Config.Paths.Discord.BOT_ACTIVITY_TYPE.toString()).uppercase()), + plugin.config.getString(Config.Paths.Discord.BOT_ACTIVITY_STATUS.toString()), + "https://twitch.tv/" + ) + } + + jda.presence.setStatus(OnlineStatus.fromKey(plugin.config.getString(Config.Paths.Discord.BOT_STATUS.toString()))) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordManager.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordManager.kt new file mode 100644 index 0000000..0113d25 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordManager.kt @@ -0,0 +1,45 @@ +package dev.austech.betterstaffchat.common.discord + +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.discord.impl.bot.DiscordEmbedProvider +import dev.austech.betterstaffchat.common.discord.impl.bot.DiscordTextProvider +import dev.austech.betterstaffchat.common.util.Config + +class DiscordManager(private val plugin: BSCPlugin) { + lateinit var discordClient: DiscordClient + private val messageProviderMap = mutableMapOf() + + init { + start() + } + + fun setupMessageProviderForBot() { + arrayOf("staffchat", "join", "leave", "switch").forEach { + plugin.config.getString(Config.Paths.Discord.Message(it).mode).let { m -> + if (m == "embed") messageProviderMap[it] = DiscordEmbedProvider(discordClient.jda) + else if (m == "text") messageProviderMap[it] = DiscordTextProvider(discordClient.jda) + } + } + } + + fun getMessageProvider(mode: String): DiscordMessageProvider? { + return messageProviderMap[mode] + } + + private fun start() { + plugin.config.getBoolean(Config.Paths.Discord.ENABLED.toString()) + .takeIf { it } + ?.let { + when (plugin.config.getString(Config.Paths.Discord.TYPE.toString())) { + "bot" -> { + discordClient = DiscordClient(plugin) + setupMessageProviderForBot() + } + "webhook" -> { + + } + else -> {} + } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordMessageProvider.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordMessageProvider.kt new file mode 100644 index 0000000..9012338 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/DiscordMessageProvider.kt @@ -0,0 +1,11 @@ +package dev.austech.betterstaffchat.common.discord + +import dev.austech.betterstaffchat.common.BSCMetadata +import dev.austech.betterstaffchat.common.BSCPlugin + +interface DiscordMessageProvider { + fun handleMessage(plugin: BSCPlugin, type: String, meta: BSCMetadata, placeholderProvider: (String) -> String) +// fun handleJoin(plugin: BSCPlugin, s: String, meta: BSCMetadata) +// fun handleLeave(plugin: BSCPlugin, s: String, meta: BSCMetadata) +// fun handleSwitch(plugin: BSCPlugin, s: String, from: String, to: String, meta: BSCMetadata) +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordEmbedProvider.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordEmbedProvider.kt new file mode 100644 index 0000000..df1e0df --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordEmbedProvider.kt @@ -0,0 +1,25 @@ +package dev.austech.betterstaffchat.common.discord.impl.bot + +import dev.austech.betterstaffchat.common.BSCMetadata +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.discord.DiscordMessageProvider +import dev.austech.betterstaffchat.common.discord.util.DiscordUtil +import dev.austech.betterstaffchat.common.util.Config +import net.dv8tion.jda.api.JDA + +class DiscordEmbedProvider(private val jda: JDA): DiscordMessageProvider { + override fun handleMessage(plugin: BSCPlugin, type: String, meta: BSCMetadata, placeholderProvider: (String) -> String) { + val messages = Config.Paths.Discord.Message(type) + + val embed = DiscordUtil.buildEmbed(plugin.config.getSection(messages.embed)) { + placeholderProvider(plugin.staffChatUtil.replacePlaceholders(it, meta)) + }.build() + + DiscordUtil.getAllChannels(plugin.config, type).map { + jda.getTextChannelById(it) + }.forEach { + if (it == null) throw NullPointerException("Text channel could not be resolved!") + it.sendMessageEmbeds(embed).queue() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordListener.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordListener.kt new file mode 100644 index 0000000..a16f876 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordListener.kt @@ -0,0 +1,48 @@ +package dev.austech.betterstaffchat.common.discord.impl.bot + +import dev.austech.betterstaffchat.common.BSCMetadata +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.discord.util.DiscordUtil +import dev.austech.betterstaffchat.common.util.Config +import net.dv8tion.jda.api.entities.ChannelType +import net.dv8tion.jda.api.events.message.MessageReceivedEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter + +class DiscordListener(private val plugin: BSCPlugin) : ListenerAdapter() { + override fun onMessageReceived(event: MessageReceivedEvent) { + if (event.channelType != ChannelType.TEXT && !event.isFromGuild) return + if (event.author.isBot) return + if (!DiscordUtil.isStaffChatChannel( + plugin.config.getSection(Config.Paths.Discord.BOT_CHANNELS.toString()), + event.channel.id, + event.guildChannel.guild.id + ) + ) return; + + val formattedUsername = plugin.config.getString(Config.Paths.Discord.NAME_FORMAT.toString()) + .replace("%username%", event.author.name) + .replace("%discriminator%", event.author.discriminator) + .replace("%tag%", event.author.asTag) + .replace("%nickname", event.member!!.effectiveName) + + val message = StringBuilder(event.message.contentStripped.trim()) + + if (event.message.attachments.isNotEmpty()) { + if (message.isBlank()) { + message.append("[Attachments] ") + + message.append( + event.message.attachments.joinToString(" ") { + it.url + } + ) + } + } + + plugin.staffChatUtil.sendMessage( + plugin.playerUtil.getReceiveAudience(), + message.toString(), + BSCMetadata(BSCMetadata.Discord(formattedUsername)) + ) + } +} diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordTextProvider.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordTextProvider.kt new file mode 100644 index 0000000..59c8d8a --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/impl/bot/DiscordTextProvider.kt @@ -0,0 +1,22 @@ +package dev.austech.betterstaffchat.common.discord.impl.bot + +import dev.austech.betterstaffchat.common.BSCMetadata +import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.discord.DiscordMessageProvider +import dev.austech.betterstaffchat.common.discord.util.DiscordUtil +import dev.austech.betterstaffchat.common.util.Config +import net.dv8tion.jda.api.JDA + +class DiscordTextProvider(private val jda: JDA): DiscordMessageProvider { + override fun handleMessage(plugin: BSCPlugin, type: String, meta: BSCMetadata, placeholderProvider: (String) -> String) { + val messages = Config.Paths.Discord.Message(type) + val message = placeholderProvider(plugin.staffChatUtil.replacePlaceholders(messages.text, meta)) + + DiscordUtil.getAllChannels(plugin.config, type).map { + jda.getTextChannelById(it) + }.forEach { + if (it == null) throw NullPointerException("Text channel could not be resolved!") + it.sendMessage(message).queue() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/util/DiscordUtil.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/util/DiscordUtil.kt new file mode 100644 index 0000000..c9a35f0 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/discord/util/DiscordUtil.kt @@ -0,0 +1,126 @@ +package dev.austech.betterstaffchat.common.discord.util + +import de.leonhard.storage.Yaml +import de.leonhard.storage.sections.FlatFileSection +import dev.austech.betterstaffchat.common.util.Config +import net.dv8tion.jda.api.EmbedBuilder +import java.awt.Color +import java.time.Instant +import java.util.* + +object DiscordUtil { + fun buildEmbed(section: FlatFileSection, replaceProvider: ((String) -> String)?): EmbedBuilder { + val embedBuilder = EmbedBuilder() + + if ( + section.getString("author.name") != null && + section.getString("author.name").isNotBlank() + ) { + embedBuilder.setAuthor( + applyPlaceholders(replaceProvider, section.getString("author.name")), + if (section.getString("author.url") != null && section.getString("author.url").isNotBlank()) + applyPlaceholders(replaceProvider, section.getString("author.url")) else null, + if (section.getString("author.icon-url") != null && section.getString("author.icon-url").isNotBlank()) + applyPlaceholders(replaceProvider, section.getString("author.icon-url")) else null, + ) + } + + if ( + section.getString("body.title") != null && + section.getString("body.title").isNotBlank() + ) { + embedBuilder.setTitle( + applyPlaceholders(replaceProvider, section.getString("body.title")), + if (section.getString("body.url") != null && section.getString("body.url").isNotBlank()) + applyPlaceholders(replaceProvider, section.getString("body.url")) + else null + ) + } + + embedBuilder.setDescription( + applyPlaceholders(replaceProvider, section.getString("body.description")) + ) + + embedBuilder.setColor(Color.decode(section.getString("body.color"))) + + if ( + section.getString("images.thumbnail") != null && + section.getString("images.thumbnail").isNotBlank() + ) { + embedBuilder.setThumbnail( + applyPlaceholders(replaceProvider, section.getString("images.thumbnail")) + ) + } + + if ( + section.getString("images.image") != null && + section.getString("images.image").isNotBlank() + ) { + embedBuilder.setImage( + applyPlaceholders(replaceProvider, section.getString("images.image")) + ) + } + + if (section.getBoolean("footer.timestamp")) { + embedBuilder.setTimestamp(Instant.now()) + } + + if ( + section.getString("footer.text") != null && + section.getString("footer.text").isNotBlank() + ) { + embedBuilder.setFooter( + applyPlaceholders(replaceProvider, section.getString("footer.text")), + if (section.getString("footer.icon-url") != null && section.getString("footer.icon-url").isNotBlank()) + applyPlaceholders(replaceProvider, section.getString("footer.icon-url")) + else null + ) + } + + if (section.get("fields") != null) { + loop@ for (i in 1..25) { + if (section.get("fields.$i") != null) { + embedBuilder.addField( + applyPlaceholders(replaceProvider, section.getString("fields.$i.name")), + applyPlaceholders(replaceProvider, section.getString("fields.$i.value")), + section.getBoolean("fields.$i.inline") + ) + } else break@loop + } + + if (section.get("fields.26") != null) { + throw IllegalArgumentException("Embed has 26+ fields, Discord only supports 25.") + } + } + + return embedBuilder + } + + private fun applyPlaceholders(replaceProvider: ((String) -> String)?, s: String): String { + if (replaceProvider == null) return s + return replaceProvider.invoke(s) + } + + fun isStaffChatChannel(section: FlatFileSection, channelId: String, guildId: String): Boolean { + return getChannels(section, guildId, "staffchat").contains(channelId) + } + + fun getChannel(section: FlatFileSection, type: String) { + section.getStringList("events.$type") + } + + fun getChannels(section: FlatFileSection, guildId: String, type: String): List { + return section.getStringList("$guildId.events.$type") + } + + fun getAllChannels(config: Yaml, type: String): List { + val channels = arrayListOf() + val section = config.getSection(Config.Paths.Discord.BOT_CHANNELS.toString()) + + section.singleLayerKeySet().map { + channels.addAll(section.getStringList("$it.events.$type")) + } + + return channels + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt index 0035c91..53475ec 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/libraries/BSCLibraries.kt @@ -90,6 +90,86 @@ object BSCLibraries { .relocate("de{}leonhard{}storage", "$relocatePath{}storage") .build() + private val JDA = Library.builder() + .groupId("net{}dv8tion") + .artifactId("JDA") + .version("5.0.0-alpha.9") + .relocate("net{}dv8tion{}jda", "$relocatePath{}discord") + .relocate("com{}neovisionaries{}nv-websocket-client", "$relocatePath{}nv-websocket-client") + .relocate("okhttp", "$relocatePath{}okhttp") + .relocate("org{}slf4j", "$relocatePath{}slf4j") + .relocate("org{}apache{}commons{}commons-collections4", "$relocatePath{}commons-collections4") + .relocate("com{}squareup{}okio", "$relocatePath{}okio") + .relocate("net{}sf{}trove4j", "$relocatePath{}trove4j") + .relocate("com{}fasterxml{}jackson{}databind", "$relocatePath{}jackson-databind") + .relocate("com{}fasterxml{}jackson{}annotations", "$relocatePath{}jackson-annotations") + .relocate("com{}fasterxml{}jackson{}core", "$relocatePath{}jackson-core") + .build() + + private val OKHTTP = Library.builder() + .groupId("com{}squareup{}okhttp3") + .artifactId("okhttp") + .version("4.9.3") + .relocate("okhttp", "$relocatePath{}okhttp") + .relocate("_okio".replace("_", ""), "$relocatePath{}okio") + .relocate("_kotlin".replace("_", ""), "$relocatePath{}kotlin") + .build() + + private val NV_WEBSOCKET_CLIENT = Library.builder() + .groupId("com{}neovisionaries") + .artifactId("nv-websocket-client") + .version("2.14") + .relocate("com{}neovisionaries{}nv-websocket-client", "$relocatePath{}nv-websocket-client") + .build() + + private val SLF4J = Library.builder() + .groupId("org{}slf4j") + .artifactId("slf4j-api") + .version("1.7.25") + .relocate("org{}slf4j", "$relocatePath{}slf4j") + .build() + + private val COMMONS_COLLECTIONS4 = Library.builder() + .groupId("org{}apache{}commons") + .artifactId("commons-collections4") + .version("4.1") + .relocate("org{}apache{}commons{}commons-collections4", "$relocatePath{}commons-collections4") + .build() + + private val TROVE4J = Library.builder() + .groupId("net{}sf{}trove4j") + .artifactId("trove4j") + .version("3.0.3") + .relocate("net{}sf{}trove4j", "$relocatePath{}trove4j") + .build() + + private val JACKSON_DATABIND = Library.builder() + .groupId("com{}fasterxml{}jackson{}core") + .artifactId("jackson-databind") + .version("2.10.1") + .relocate("com{}fasterxml{}jackson{}databind", "$relocatePath{}jackson-databind") + .relocate("com{}fasterxml{}jackson{}annotations", "$relocatePath{}jackson-annotations") + .relocate("com{}fasterxml{}jackson{}core", "$relocatePath{}jackson-core") + .build() + + private val JACKSON_ANNOTATIONS = Library.builder() + .groupId("com{}fasterxml{}jackson{}core") + .artifactId("jackson-annotations") + .version("2.10.1") + .relocate("com{}fasterxml{}jackson{}databind", "$relocatePath{}jackson-databind") + .relocate("com{}fasterxml{}jackson{}annotations", "$relocatePath{}jackson-annotations") + .relocate("com{}fasterxml{}jackson{}core", "$relocatePath{}jackson-core") + .build() + + private val JACKSON_CORE = Library.builder() + .groupId("com{}fasterxml{}jackson{}core") + .artifactId("jackson-core") + .version("2.10.1") + .relocate("com{}fasterxml{}jackson{}databind", "$relocatePath{}jackson-databind") + .relocate("com{}fasterxml{}jackson{}annotations", "$relocatePath{}jackson-annotations") + .relocate("com{}fasterxml{}jackson{}core", "$relocatePath{}jackson-core") + .build() + fun getLibraries(platform: BSCPlugin.Platform?): List { var libraryList: List = listOf( Adventure.API, @@ -103,7 +183,16 @@ object BSCLibraries { Adventure.PLATFORM_FACET, Adventure.EXAMINATION, Adventure.EXAMINATION_STRING, - SIMPLIX_STORAGE + SIMPLIX_STORAGE, + JDA, + OKHTTP, + NV_WEBSOCKET_CLIENT, + SLF4J, + COMMONS_COLLECTIONS4, + TROVE4J, + JACKSON_DATABIND, + JACKSON_ANNOTATIONS, + JACKSON_CORE ) if (platform == BSCPlugin.Platform.BUKKIT) { diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/update/UpdateChecker.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/update/UpdateChecker.kt new file mode 100644 index 0000000..9936a30 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/update/UpdateChecker.kt @@ -0,0 +1,48 @@ +package dev.austech.betterstaffchat.common.update + +import de.leonhard.storage.shaded.json.JSONObject // might as well steal it +import java.io.BufferedReader +import java.io.InputStreamReader +import java.net.URL +import javax.net.ssl.HttpsURLConnection + +class UpdateChecker(private val plugin: Any) { + val needsUpdate: Boolean + val currentVersion: String + val latestVersion: String + + init { + currentVersion = pluginVersion + latestVersion = fetchUpdate() ?: "ERROR" + needsUpdate = latestVersion != currentVersion + } + + private val pluginVersion: String + get() { + val method = plugin.javaClass.getMethod("getDescription") + method.isAccessible = true + val objDescription = method.invoke(plugin) + val versionMethod = objDescription.javaClass.getDeclaredMethod("getVersion") + versionMethod.isAccessible = true + return versionMethod.invoke(objDescription) as String? ?: "" + } + + private fun fetchUpdate(): String? { + val url = URL("https://api.spigotmc.org/simple/0.2/index.php?action=getResource&id=91991") + val connection = url.openConnection() as HttpsURLConnection + connection.requestMethod = "GET" + connection.readTimeout = 5_000 + connection.connectTimeout = 5_000 + + if (connection.responseCode != 200) { + throw Exception("Spigot API returned a non-200 response code") + } + + val bufferedReader = BufferedReader(InputStreamReader(connection.inputStream)) + + val jsonObject = JSONObject(bufferedReader.readLines().joinToString("")) + + bufferedReader.close() + return jsonObject.getString("current_version") + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt index 92baf4a..49c8107 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/Config.kt @@ -4,7 +4,6 @@ import de.leonhard.storage.LightningBuilder import de.leonhard.storage.Yaml import dev.austech.betterstaffchat.common.BSCPlugin import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor import java.io.File import java.nio.file.Files @@ -21,19 +20,17 @@ class Config(private val plugin: BSCPlugin) { } } - - return LightningBuilder.fromFile(configFile) .reloadCallback { plugin.consoleAudience.sendMessage( Component.text("[BetterStaffChat] Reload requested or config.yml change. Reloading.") ) } - .createConfig(); + .createConfig() } class Paths { - enum class STAFFCHAT(private val path: String) { + enum class Staffchat(private val path: String) { FORMAT("staffchat.format"), PREFIX("staffchat.prefix"), CONSOLE_NAME("staffchat.console-replacement"), @@ -65,7 +62,7 @@ class Config(private val plugin: BSCPlugin) { } } - enum class COMMAND(private val path: String) { + enum class Command(private val path: String) { STAFFCHAT_COMMAND("commands.staffchat.command"), STAFFCHAT_ALIASES("commands.staffchat.aliases"), MUTE_COMMAND("commands.mutestaffchat.command"), @@ -78,7 +75,7 @@ class Config(private val plugin: BSCPlugin) { } } - enum class HOOKS(private val path: String) { + enum class Hooks(private val path: String) { LUCKPERMS("hooks.luckperms"), PLACEHOLDERAPI("hooks.placeholderapi"); @@ -86,5 +83,33 @@ class Config(private val plugin: BSCPlugin) { return path } } + + enum class Discord(private val path: String) { + ENABLED("discord.enabled"), + TYPE("discord.type"), + + BOT_TOKEN("discord.bot.token"), + BOT_STATUS("discord.bot.status"), + BOT_ACTIVITY("discord.bot.activity.enabled"), + BOT_ACTIVITY_TYPE("discord.bot.activity.type"), + BOT_ACTIVITY_STATUS("discord.bot.activity.status"), + BOT_CHANNELS("discord.bot.channels"), + + WEBHOOK_URL("discord.webhook.url"), + + ALLOWED_MENTIONS("discord.allowed-mentions"), + + NAME_FORMAT("discord.messages.name-format"); + + override fun toString(): String { + return path + } + + class Message(event: String) { + val mode = "discord.messages.$event.mode" + val embed = "discord.messages.$event.embed" + val text = "discord.messages.$event.text" + } + } } } \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/IPlayerUtil.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/IPlayerUtil.kt new file mode 100644 index 0000000..be5f7a8 --- /dev/null +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/IPlayerUtil.kt @@ -0,0 +1,7 @@ +package dev.austech.betterstaffchat.common.util + +import net.kyori.adventure.audience.Audience + +interface IPlayerUtil { + fun getReceiveAudience(): Audience +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt index 3c71c5a..4932392 100644 --- a/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt +++ b/common/src/main/kotlin/dev/austech/betterstaffchat/common/util/StaffChatUtil.kt @@ -7,18 +7,31 @@ import java.util.* class StaffChatUtil(private val plugin: BSCPlugin) { - lateinit var replaceProvider: (UUID, String) -> String + private lateinit var replaceProvider: (UUID, String) -> String + + private val discordEnabled: Boolean + get() = plugin.config.getBoolean(Config.Paths.Discord.ENABLED.toString()) constructor(plugin: BSCPlugin, consumer: (u: UUID, s: String) -> String) : this(plugin) { replaceProvider = consumer } - private fun replacePlaceholders(s: String, meta: BSCMetadata): String { + fun replacePlaceholders(s: String, meta: BSCMetadata): String { + if (meta.discordMeta != null) + return s + .replace("%player_name%", meta.discordMeta!!.displayName ?: "") + .replace("%player_server%", "Discord") + .replace("%player_server_raw%", "Discord") + + if (meta.playerMeta == null) return s + + val playerMeta = meta.playerMeta!! + var newString = s - .replace("%player_name%", meta.playerMeta.name) + .replace("%player_name%", playerMeta.name) - if (meta.playerMeta.uuid != null) { - newString = newString.replace("%player_uuid%", meta.playerMeta.uuid.toString()) + if (playerMeta.uuid != null) { + newString = newString.replace("%player_uuid%", playerMeta.uuid.toString()) } if (meta.proxyMeta != null) { @@ -55,16 +68,16 @@ class StaffChatUtil(private val plugin: BSCPlugin) { } } - if (meta.playerMeta.uuid != null) { - replaceProvider.invoke(meta.playerMeta.uuid, newString) + if (playerMeta.uuid != null) { + replaceProvider.invoke(playerMeta.uuid, newString) } return newString; } fun sendMessage(audience: Audience, message: String, meta: BSCMetadata) { - var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT.FORMAT.toString()) ?: return; - val removeColorCodes = plugin.config.getBoolean(Config.Paths.STAFFCHAT.STRIP_COLOR_CODES.toString()); + var configMessage: String = plugin.config.getString(Config.Paths.Staffchat.FORMAT.toString()) ?: return; + val removeColorCodes = plugin.config.getBoolean(Config.Paths.Staffchat.STRIP_COLOR_CODES.toString()); configMessage = replacePlaceholders(configMessage, meta) .replace("%message%", @@ -74,29 +87,53 @@ class StaffChatUtil(private val plugin: BSCPlugin) { ) audience.sendMessage(TextUtil.parseText(configMessage)) + + if (discordEnabled && meta.discordMeta == null) { + plugin.discordManager.getMessageProvider("staffchat")?.handleMessage(plugin, "staffchat", meta) { + it.replace("%message%", message) + } + } } fun sendJoinMessage(audience: Audience, meta: BSCMetadata) { - var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT.EVENTS_JOIN_MESSAGE.toString()) ?: return; + var configMessage: String = plugin.config.getString(Config.Paths.Staffchat.EVENTS_JOIN_MESSAGE.toString()) ?: return; configMessage = replacePlaceholders(configMessage, meta) audience.sendMessage(TextUtil.parseText(configMessage)) + + if (discordEnabled) { + plugin.discordManager.getMessageProvider("staffchat")?.handleMessage(plugin, "staffchat", meta) { + it + } + } } fun sendLeaveMessage(audience: Audience, meta: BSCMetadata) { - var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT.EVENTS_LEAVE_MESSAGE.toString()) ?: return; + var configMessage: String = plugin.config.getString(Config.Paths.Staffchat.EVENTS_LEAVE_MESSAGE.toString()) ?: return; configMessage = replacePlaceholders(configMessage, meta) audience.sendMessage(TextUtil.parseText(configMessage)) + + if (discordEnabled) { + plugin.discordManager.getMessageProvider("staffchat")?.handleMessage(plugin, "staffchat", meta) { + it + } + } } fun sendSwitchMessage(audience: Audience, from: String, to: String, meta: BSCMetadata) { - var configMessage: String = plugin.config.getString(Config.Paths.STAFFCHAT.EVENTS_SWITCH_MESSAGE.toString()) ?: return; + var configMessage: String = plugin.config.getString(Config.Paths.Staffchat.EVENTS_SWITCH_MESSAGE.toString()) ?: return; configMessage = replacePlaceholders(configMessage, meta) configMessage = configMessage .replace("%from%", from) .replace("%to%", to) audience.sendMessage(TextUtil.parseText(configMessage)) + + if (discordEnabled) { + plugin.discordManager.getMessageProvider("staffchat")?.handleMessage(plugin, "staffchat", meta) { + it.replace("%from%", from).replace("%to%", to) + } + } } } \ No newline at end of file diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt index 91f6660..a96227d 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/BSCSpigot.kt @@ -3,34 +3,41 @@ package dev.austech.betterstaffchat.spigot import de.leonhard.storage.Json import de.leonhard.storage.Yaml import dev.austech.betterstaffchat.common.BSCPlugin +import dev.austech.betterstaffchat.common.discord.DiscordManager import dev.austech.betterstaffchat.common.libraries.BSCLibraries +import dev.austech.betterstaffchat.common.update.UpdateChecker import dev.austech.betterstaffchat.common.util.Config import dev.austech.betterstaffchat.common.util.Data +import dev.austech.betterstaffchat.common.util.IPlayerUtil import dev.austech.betterstaffchat.common.util.StaffChatUtil import dev.austech.betterstaffchat.spigot.commands.CommandManager import dev.austech.betterstaffchat.spigot.listeners.PlayerListener import dev.austech.betterstaffchat.spigot.libby.BukkitLibraryManager +import dev.austech.betterstaffchat.spigot.util.PlayerUtil import me.clip.placeholderapi.PlaceholderAPI import net.kyori.adventure.audience.Audience import net.kyori.adventure.platform.bukkit.BukkitAudiences import org.bukkit.Bukkit +import org.bukkit.ChatColor import org.bukkit.plugin.java.JavaPlugin import java.io.File -import java.util.* class BSCSpigot(private val plugin: JavaPlugin) : BSCPlugin { companion object { lateinit var instance: BSCSpigot } - lateinit var staffChatUtil: StaffChatUtil + override lateinit var staffChatUtil: StaffChatUtil + override val playerUtil = PlayerUtil lateinit var audience: BukkitAudiences private lateinit var data: Json lateinit var dataWrapper: Data.Wrapper; override lateinit var config: Yaml override lateinit var consoleAudience: Audience; + override lateinit var discordManager: DiscordManager override val platform = BSCPlugin.Platform.BUKKIT override val pluginDataFile: File = plugin.dataFolder + lateinit var updateChecker: UpdateChecker override fun onLoad() { val libraryManager = BukkitLibraryManager(plugin) @@ -49,9 +56,22 @@ class BSCSpigot(private val plugin: JavaPlugin) : BSCPlugin { config = Config(this).load() data = Data(this).load() dataWrapper = Data.Wrapper(data) + updateChecker = UpdateChecker(plugin) + + Bukkit.getScheduler().runTaskAsynchronously(plugin, Runnable { + discordManager = DiscordManager(this) + }) + + Bukkit.getScheduler().runTaskLater(plugin, Runnable { + if (updateChecker.needsUpdate) { + plugin.logger.warning("An update for BetterStaffChat is available.") + plugin.logger.warning("Latest version: ${ChatColor.GREEN}${updateChecker.latestVersion}${ChatColor.YELLOW}. Your version: ${ChatColor.RED}${updateChecker.currentVersion}") + plugin.logger.warning("Update: ${ChatColor.BLUE}https://www.spigotmc.org/resources/91991/") + } + }, 1L) val shouldHookPAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") && - config.getBoolean(Config.Paths.HOOKS.PLACEHOLDERAPI.toString()) + config.getBoolean(Config.Paths.Hooks.PLACEHOLDERAPI.toString()) staffChatUtil = if (shouldHookPAPI) { StaffChatUtil(this) { uuid, s -> diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt index 317e0fb..ec8319c 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/CommandManager.kt @@ -12,22 +12,22 @@ import org.bukkit.command.Command class CommandManager(private val plugin: BSCSpigot) { private val commands: List = listOf( StaffChatCommand( - plugin.config.getString(Config.Paths.COMMAND.STAFFCHAT_COMMAND.toString())?: "staffchat", + plugin.config.getString(Config.Paths.Command.STAFFCHAT_COMMAND.toString())?: "staffchat", "This command lets you send a message to the staffchat.", "", - plugin.config.getStringList(Config.Paths.COMMAND.STAFFCHAT_ALIASES.toString()) + plugin.config.getStringList(Config.Paths.Command.STAFFCHAT_ALIASES.toString()) ), StaffChatMuteCommand( - plugin.config.getString(Config.Paths.COMMAND.MUTE_COMMAND.toString())?: "mutestaffchat", + plugin.config.getString(Config.Paths.Command.MUTE_COMMAND.toString())?: "mutestaffchat", "This command lets you ignore / disable your staffchat.", "[on/off]", - plugin.config.getStringList(Config.Paths.COMMAND.MUTE_ALIASES.toString()) + plugin.config.getStringList(Config.Paths.Command.MUTE_ALIASES.toString()) ), StaffChatToggleCommand( - plugin.config.getString(Config.Paths.COMMAND.TOGGLE_COMMAND.toString())?: "togglestaffchat", + plugin.config.getString(Config.Paths.Command.TOGGLE_COMMAND.toString())?: "togglestaffchat", "This command lets you ignore / disable your staffchat.", "[on/off]", - plugin.config.getStringList(Config.Paths.COMMAND.TOGGLE_ALIASES.toString()) + plugin.config.getStringList(Config.Paths.Command.TOGGLE_ALIASES.toString()) ), BetterStaffChatCommand() ) diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt index b484410..5e25f30 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatMuteCommand.kt @@ -11,7 +11,7 @@ class StaffChatMuteCommand(name: String, description: String, usage: String, ali return true } - if (!plugin.config.getBoolean(Config.Paths.STAFFCHAT.MUTE_ENABLED.toString())) { + if (!plugin.config.getBoolean(Config.Paths.Staffchat.MUTE_ENABLED.toString())) { errorTell("Staffchat mute is not enabled.") return true } @@ -51,10 +51,10 @@ class StaffChatMuteCommand(name: String, description: String, usage: String, ali if (mute) { plugin.dataWrapper.addMutedPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.MUTE_MESSAGE_ON.toString())) + legacyTell(plugin.config.getString(Config.Paths.Staffchat.MUTE_MESSAGE_ON.toString())) } else { plugin.dataWrapper.removeMutedPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.MUTE_MESSAGE_OFF.toString())) + legacyTell(plugin.config.getString(Config.Paths.Staffchat.MUTE_MESSAGE_OFF.toString())) } return true diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt index d5cff53..598900c 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatToggleCommand.kt @@ -11,7 +11,7 @@ class StaffChatToggleCommand(name: String, description: String, usage: String, a return true } - if (!plugin.config.getBoolean(Config.Paths.STAFFCHAT.TOGGLE_ENABLED.toString())) { + if (!plugin.config.getBoolean(Config.Paths.Staffchat.TOGGLE_ENABLED.toString())) { errorTell("Staffchat toggle is not enabled.") return true } @@ -58,10 +58,10 @@ class StaffChatToggleCommand(name: String, description: String, usage: String, a } plugin.dataWrapper.addToggledPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.TOGGLE_MESSAGE_ON.toString())) + legacyTell(plugin.config.getString(Config.Paths.Staffchat.TOGGLE_MESSAGE_ON.toString())) } else { plugin.dataWrapper.removeToggledPlayer(sender.uniqueId) - legacyTell(plugin.config.getString(Config.Paths.STAFFCHAT.TOGGLE_MESSAGE_OFF.toString())) + legacyTell(plugin.config.getString(Config.Paths.Staffchat.TOGGLE_MESSAGE_OFF.toString())) } return true diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt index cc4a8d5..9fd25ce 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/listeners/PlayerListener.kt @@ -17,7 +17,7 @@ class PlayerListener : Listener { @EventHandler(priority = EventPriority.HIGH) fun onAsyncPlayerChat(event: AsyncPlayerChatEvent) { if (event.player.hasPermission("betterstaffchat.messages.send")) { - val prefix = BSCSpigot.instance.config.getString(Config.Paths.STAFFCHAT.PREFIX.toString()) ?: "" + val prefix = BSCSpigot.instance.config.getString(Config.Paths.Staffchat.PREFIX.toString()) ?: "" if (event.message.startsWith(prefix) && event.message.length > prefix.length && prefix.isNotBlank()) { event.isCancelled = true; @@ -34,7 +34,7 @@ class PlayerListener : Listener { ) ) } else if (BSCSpigot.instance.dataWrapper.getToggledPlayers() - .contains(event.player.uniqueId) && BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.TOGGLE_ENABLED.toString()) + .contains(event.player.uniqueId) && BSCSpigot.instance.config.getBoolean(Config.Paths.Staffchat.TOGGLE_ENABLED.toString()) ) { event.isCancelled = true; BSCSpigot.instance.staffChatUtil.sendMessage( @@ -55,10 +55,10 @@ class PlayerListener : Listener { @EventHandler fun onPlayerJoin(event: PlayerJoinEvent) { - if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_JOIN_ENABLED.toString())) return + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_JOIN_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.join")) { val aud: Audience = - if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_JOIN_SEE_OWN.toString())) + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_JOIN_SEE_OWN.toString())) PlayerUtil.getReceiveAudienceWithIgnored(event.player.uniqueId) else PlayerUtil.getReceiveAudience() @@ -79,7 +79,7 @@ class PlayerListener : Listener { @EventHandler fun onPlayerQuit(event: PlayerQuitEvent) { - if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_LEAVE_ENABLED.toString())) return + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_LEAVE_ENABLED.toString())) return if (event.player.hasPermission("betterstaffchat.messages.leave")) { BSCSpigot.instance.staffChatUtil.sendLeaveMessage( PlayerUtil.getReceiveAudience(), @@ -98,8 +98,8 @@ class PlayerListener : Listener { @EventHandler fun onPlayerSwitch(event: PlayerChangedWorldEvent) { - if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_SWITCH_ENABLED.toString())) return - if (!BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.EVENTS_SWITCH_USE_WORLDS.toString())) return + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_SWITCH_ENABLED.toString())) return + if (!BSCSpigot.instance.config.getBoolean(Config.Paths.Staffchat.EVENTS_SWITCH_USE_WORLDS.toString())) return if (event.player.hasPermission("betterstaffchat.messages.leave")) { BSCSpigot.instance.staffChatUtil.sendSwitchMessage( diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt index 19d246a..e762fbb 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/util/PlayerUtil.kt @@ -1,16 +1,17 @@ package dev.austech.betterstaffchat.spigot.util import dev.austech.betterstaffchat.common.util.Config +import dev.austech.betterstaffchat.common.util.IPlayerUtil import dev.austech.betterstaffchat.spigot.BSCSpigot import net.kyori.adventure.audience.Audience import org.bukkit.command.CommandSender import org.bukkit.entity.Player import java.util.* -object PlayerUtil { +object PlayerUtil: IPlayerUtil { fun getSenderName(sender: CommandSender): String { return if (sender is Player) sender.name - else BSCSpigot.instance.config.getString(Config.Paths.STAFFCHAT.CONSOLE_NAME.toString()) ?: "Console" + else BSCSpigot.instance.config.getString(Config.Paths.Staffchat.CONSOLE_NAME.toString()) ?: "Console" } private fun getMutedPlayers(): List { @@ -26,7 +27,7 @@ object PlayerUtil { } fun getReceiveAudienceWithIgnored(ignored: UUID): Audience { - return if (BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.CONSOLE_LOG.toString())) getAudienceMixedWithIgnored( + return if (BSCSpigot.instance.config.getBoolean(Config.Paths.Staffchat.CONSOLE_LOG.toString())) getAudienceMixedWithIgnored( "betterstaffchat.messages.read", ignored ) @@ -41,8 +42,8 @@ object PlayerUtil { it.hasPermission(permission) && if (it is Player) !getMutedPlayers().contains(it.uniqueId) else true } - fun getReceiveAudience(): Audience { - return if (BSCSpigot.instance.config.getBoolean(Config.Paths.STAFFCHAT.CONSOLE_LOG.toString())) getMixedAudience( + override fun getReceiveAudience(): Audience { + return if (BSCSpigot.instance.config.getBoolean(Config.Paths.Staffchat.CONSOLE_LOG.toString())) getMixedAudience( "betterstaffchat.messages.read" ) else getPermissionAudience("betterstaffchat.messages.read") diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2318cab..8688914 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -158,6 +158,10 @@ discord: # # You can follow this guide: # https://discordpy.readthedocs.io/en/stable/discord.html + + # !!! [IMPORTANT] Please make sure the "Server Members Intent" and "Message Content Intent" are enabled. + # If you do not enable it, Discord -> In-Game StaffChat will not work. + token: "" # The status of the bot. @@ -246,7 +250,7 @@ discord: # value: "" # inline: false images: - images: [ "" ] + image: "" thumbnail: "" footer: @@ -282,7 +286,7 @@ discord: # value: "" # inline: false images: - images: [ "" ] + image: "" thumbnail: "" footer: @@ -318,7 +322,7 @@ discord: # value: "" # inline: false images: - images: [ "" ] + image: "" thumbnail: "" footer: @@ -354,7 +358,7 @@ discord: # value: "" # inline: false images: - images: [ "" ] + image: "" thumbnail: "" footer: diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1cc8458..cf88abc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,6 @@ name: BetterStaffChat main: dev.austech.betterstaffchat.spigot.BSCSpigotBootstrap version: ${version} +api-version: 1.13 softdepend: [ PlaceholderAPI ] \ No newline at end of file From c96098bf8b51df6c70d70b15b3988d0b6dee6344 Mon Sep 17 00:00:00 2001 From: mnewt00 <44464407+mnewt00@users.noreply.github.com> Date: Thu, 31 Mar 2022 00:22:48 +1100 Subject: [PATCH 5/5] fix: remove some unnecessary brackets --- .../bungeecord/commands/impl/StaffChatCommand.kt | 2 +- .../betterstaffchat/spigot/commands/impl/StaffChatCommand.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt index 342ba63..c35d60e 100644 --- a/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt +++ b/bungeecord/src/main/kotlin/dev/austech/betterstaffchat/bungeecord/commands/impl/StaffChatCommand.kt @@ -32,7 +32,7 @@ class StaffChatCommand(name: String, aliases: List) : PlayerUtil.getReceiveAudience(), args.joinToString(separator = " "), BSCMetadata( BSCMetadata.Player( - (if (sender is ProxiedPlayer) sender.uniqueId else null), + if (sender is ProxiedPlayer) sender.uniqueId else null, PlayerUtil.getSenderName(sender) ), BSCMetadata.Proxy( diff --git a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt index aaeba68..bbc6398 100644 --- a/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt +++ b/spigot/src/main/kotlin/dev/austech/betterstaffchat/spigot/commands/impl/StaffChatCommand.kt @@ -25,7 +25,7 @@ class StaffChatCommand(name: String, description: String, usage: String, aliases plugin.staffChatUtil.sendMessage(PlayerUtil.getReceiveAudience(), args.joinToString(separator = " "), BSCMetadata( BSCMetadata.Player( - (if (sender is Player) sender.uniqueId else null), + if (sender is Player) sender.uniqueId else null, PlayerUtil.getSenderName(sender) ), BSCMetadata.Spigot(