diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..204bc74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,145 @@ +# Created by https://www.gitignore.io/api/maven,eclipse,intellij,netbeans,osx,windows,notepadpp,windows,java + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + + +### Eclipse ### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm + +*.iml +.idea/ + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +nbactions.xml +.nb-gradle/ + + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Java ### +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* \ No newline at end of file diff --git a/.idea/artifacts/PluginVersions.xml b/.idea/artifacts/PluginVersions.xml deleted file mode 100644 index 82aade5..0000000 --- a/.idea/artifacts/PluginVersions.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - $PROJECT_DIR$/out/artifacts/PluginVersions - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 29a45bd..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 506d1e2..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index a22edde..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__aopalliance_aopalliance_1_0.xml b/.idea/libraries/Maven__aopalliance_aopalliance_1_0.xml deleted file mode 100644 index 30ff5cb..0000000 --- a/.idea/libraries/Maven__aopalliance_aopalliance_1_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_google_code_gson_gson_2_8_0.xml b/.idea/libraries/Maven__com_google_code_gson_gson_2_8_0.xml deleted file mode 100644 index 6e5d5b7..0000000 --- a/.idea/libraries/Maven__com_google_code_gson_gson_2_8_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_google_guava_guava_21_0.xml b/.idea/libraries/Maven__com_google_guava_guava_21_0.xml deleted file mode 100644 index a923456..0000000 --- a/.idea/libraries/Maven__com_google_guava_guava_21_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_google_inject_guice_5_0_1.xml b/.idea/libraries/Maven__com_google_inject_guice_5_0_1.xml deleted file mode 100644 index 5af03b2..0000000 --- a/.idea/libraries/Maven__com_google_inject_guice_5_0_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_moandjiezana_toml_toml4j_0_7_2.xml b/.idea/libraries/Maven__com_moandjiezana_toml_toml4j_0_7_2.xml deleted file mode 100644 index 3f15348..0000000 --- a/.idea/libraries/Maven__com_moandjiezana_toml_toml4j_0_7_2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_typesafe_config_1_4_0.xml b/.idea/libraries/Maven__com_typesafe_config_1_4_0.xml deleted file mode 100644 index f7b44dd..0000000 --- a/.idea/libraries/Maven__com_typesafe_config_1_4_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_velocitypowered_velocity_api_3_0_1.xml b/.idea/libraries/Maven__com_velocitypowered_velocity_api_3_0_1.xml deleted file mode 100644 index f4f33e6..0000000 --- a/.idea/libraries/Maven__com_velocitypowered_velocity_api_3_0_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_velocitypowered_velocity_brigadier_1_0_0_SNAPSHOT.xml b/.idea/libraries/Maven__com_velocitypowered_velocity_brigadier_1_0_0_SNAPSHOT.xml deleted file mode 100644 index 5d6a442..0000000 --- a/.idea/libraries/Maven__com_velocitypowered_velocity_brigadier_1_0_0_SNAPSHOT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__commons_lang_commons_lang_2_6.xml b/.idea/libraries/Maven__commons_lang_commons_lang_2_6.xml deleted file mode 100644 index 2ec8376..0000000 --- a/.idea/libraries/Maven__commons_lang_commons_lang_2_6.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__io_netty_netty_buffer_4_1_63_Final.xml b/.idea/libraries/Maven__io_netty_netty_buffer_4_1_63_Final.xml deleted file mode 100644 index b0a2bbf..0000000 --- a/.idea/libraries/Maven__io_netty_netty_buffer_4_1_63_Final.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__io_netty_netty_codec_4_1_63_Final.xml b/.idea/libraries/Maven__io_netty_netty_codec_4_1_63_Final.xml deleted file mode 100644 index d8e04b4..0000000 --- a/.idea/libraries/Maven__io_netty_netty_codec_4_1_63_Final.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__io_netty_netty_common_4_1_63_Final.xml b/.idea/libraries/Maven__io_netty_netty_common_4_1_63_Final.xml deleted file mode 100644 index b9a734d..0000000 --- a/.idea/libraries/Maven__io_netty_netty_common_4_1_63_Final.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__io_netty_netty_resolver_4_1_63_Final.xml b/.idea/libraries/Maven__io_netty_netty_resolver_4_1_63_Final.xml deleted file mode 100644 index b4beb6c..0000000 --- a/.idea/libraries/Maven__io_netty_netty_resolver_4_1_63_Final.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__io_netty_netty_transport_4_1_63_Final.xml b/.idea/libraries/Maven__io_netty_netty_transport_4_1_63_Final.xml deleted file mode 100644 index f95d968..0000000 --- a/.idea/libraries/Maven__io_netty_netty_transport_4_1_63_Final.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__io_netty_netty_transport_native_unix_common_4_1_63_Final.xml b/.idea/libraries/Maven__io_netty_netty_transport_native_unix_common_4_1_63_Final.xml deleted file mode 100644 index 7108f07..0000000 --- a/.idea/libraries/Maven__io_netty_netty_transport_native_unix_common_4_1_63_Final.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__javax_inject_javax_inject_1.xml b/.idea/libraries/Maven__javax_inject_javax_inject_1.xml deleted file mode 100644 index 93cf65a..0000000 --- a/.idea/libraries/Maven__javax_inject_javax_inject_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_kyori_adventure_api_4_8_1.xml b/.idea/libraries/Maven__net_kyori_adventure_api_4_8_1.xml deleted file mode 100644 index f252faa..0000000 --- a/.idea/libraries/Maven__net_kyori_adventure_api_4_8_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_kyori_adventure_key_4_8_1.xml b/.idea/libraries/Maven__net_kyori_adventure_key_4_8_1.xml deleted file mode 100644 index 054b4bb..0000000 --- a/.idea/libraries/Maven__net_kyori_adventure_key_4_8_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_kyori_adventure_text_serializer_gson_4_8_1.xml b/.idea/libraries/Maven__net_kyori_adventure_text_serializer_gson_4_8_1.xml deleted file mode 100644 index e53f62d..0000000 --- a/.idea/libraries/Maven__net_kyori_adventure_text_serializer_gson_4_8_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_kyori_adventure_text_serializer_legacy_4_8_1.xml b/.idea/libraries/Maven__net_kyori_adventure_text_serializer_legacy_4_8_1.xml deleted file mode 100644 index 3a29bd9..0000000 --- a/.idea/libraries/Maven__net_kyori_adventure_text_serializer_legacy_4_8_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_kyori_adventure_text_serializer_plain_4_8_1.xml b/.idea/libraries/Maven__net_kyori_adventure_text_serializer_plain_4_8_1.xml deleted file mode 100644 index ce54385..0000000 --- a/.idea/libraries/Maven__net_kyori_adventure_text_serializer_plain_4_8_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_kyori_examination_api_1_1_0.xml b/.idea/libraries/Maven__net_kyori_examination_api_1_1_0.xml deleted file mode 100644 index 3495e84..0000000 --- a/.idea/libraries/Maven__net_kyori_examination_api_1_1_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_kyori_examination_string_1_1_0.xml b/.idea/libraries/Maven__net_kyori_examination_string_1_1_0.xml deleted file mode 100644 index c171567..0000000 --- a/.idea/libraries/Maven__net_kyori_examination_string_1_1_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_md_5_brigadier_1_0_16_SNAPSHOT.xml b/.idea/libraries/Maven__net_md_5_brigadier_1_0_16_SNAPSHOT.xml deleted file mode 100644 index 844cdda..0000000 --- a/.idea/libraries/Maven__net_md_5_brigadier_1_0_16_SNAPSHOT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_md_5_bungeecord_api_1_16_R0_5_SNAPSHOT.xml b/.idea/libraries/Maven__net_md_5_bungeecord_api_1_16_R0_5_SNAPSHOT.xml deleted file mode 100644 index c6f9c0e..0000000 --- a/.idea/libraries/Maven__net_md_5_bungeecord_api_1_16_R0_5_SNAPSHOT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_md_5_bungeecord_chat_1_13_SNAPSHOT.xml b/.idea/libraries/Maven__net_md_5_bungeecord_chat_1_13_SNAPSHOT.xml deleted file mode 100644 index a5b0ea6..0000000 --- a/.idea/libraries/Maven__net_md_5_bungeecord_chat_1_13_SNAPSHOT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_md_5_bungeecord_config_1_16_R0_5_SNAPSHOT.xml b/.idea/libraries/Maven__net_md_5_bungeecord_config_1_16_R0_5_SNAPSHOT.xml deleted file mode 100644 index 55cbd0d..0000000 --- a/.idea/libraries/Maven__net_md_5_bungeecord_config_1_16_R0_5_SNAPSHOT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_md_5_bungeecord_event_1_16_R0_5_SNAPSHOT.xml b/.idea/libraries/Maven__net_md_5_bungeecord_event_1_16_R0_5_SNAPSHOT.xml deleted file mode 100644 index 6bb2299..0000000 --- a/.idea/libraries/Maven__net_md_5_bungeecord_event_1_16_R0_5_SNAPSHOT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_md_5_bungeecord_protocol_1_16_R0_5_SNAPSHOT.xml b/.idea/libraries/Maven__net_md_5_bungeecord_protocol_1_16_R0_5_SNAPSHOT.xml deleted file mode 100644 index 3f06669..0000000 --- a/.idea/libraries/Maven__net_md_5_bungeecord_protocol_1_16_R0_5_SNAPSHOT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__net_sf_trove4j_core_3_1_0.xml b/.idea/libraries/Maven__net_sf_trove4j_core_3_1_0.xml deleted file mode 100644 index 9460109..0000000 --- a/.idea/libraries/Maven__net_sf_trove4j_core_3_1_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_checkerframework_checker_qual_3_6_1.xml b/.idea/libraries/Maven__org_checkerframework_checker_qual_3_6_1.xml deleted file mode 100644 index 98bd4c0..0000000 --- a/.idea/libraries/Maven__org_checkerframework_checker_qual_3_6_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_jetbrains_annotations_21_0_1.xml b/.idea/libraries/Maven__org_jetbrains_annotations_21_0_1.xml deleted file mode 100644 index 335bdda..0000000 --- a/.idea/libraries/Maven__org_jetbrains_annotations_21_0_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_30.xml b/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_30.xml deleted file mode 100644 index 02b6812..0000000 --- a/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_30.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_spigotmc_spigot_api_1_14_4_R0_1_SNAPSHOT.xml b/.idea/libraries/Maven__org_spigotmc_spigot_api_1_14_4_R0_1_SNAPSHOT.xml deleted file mode 100644 index cccf07f..0000000 --- a/.idea/libraries/Maven__org_spigotmc_spigot_api_1_14_4_R0_1_SNAPSHOT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_spongepowered_configurate_core_3_7_2.xml b/.idea/libraries/Maven__org_spongepowered_configurate_core_3_7_2.xml deleted file mode 100644 index 88501de..0000000 --- a/.idea/libraries/Maven__org_spongepowered_configurate_core_3_7_2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_spongepowered_configurate_gson_3_7_2.xml b/.idea/libraries/Maven__org_spongepowered_configurate_gson_3_7_2.xml deleted file mode 100644 index af82b8e..0000000 --- a/.idea/libraries/Maven__org_spongepowered_configurate_gson_3_7_2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_spongepowered_configurate_hocon_3_7_2.xml b/.idea/libraries/Maven__org_spongepowered_configurate_hocon_3_7_2.xml deleted file mode 100644 index 345865f..0000000 --- a/.idea/libraries/Maven__org_spongepowered_configurate_hocon_3_7_2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_spongepowered_configurate_yaml_3_7_2.xml b/.idea/libraries/Maven__org_spongepowered_configurate_yaml_3_7_2.xml deleted file mode 100644 index 4313df5..0000000 --- a/.idea/libraries/Maven__org_spongepowered_configurate_yaml_3_7_2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_yaml_snakeyaml_1_23.xml b/.idea/libraries/Maven__org_yaml_snakeyaml_1_23.xml deleted file mode 100644 index 7e63769..0000000 --- a/.idea/libraries/Maven__org_yaml_snakeyaml_1_23.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__se_llbit_jo_nbt_1_3_0.xml b/.idea/libraries/Maven__se_llbit_jo_nbt_1_3_0.xml deleted file mode 100644 index d3ebcf7..0000000 --- a/.idea/libraries/Maven__se_llbit_jo_nbt_1_3_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index c080f67..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 812eefa..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index e96534f..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/PluginVersions.iml b/PluginVersions.iml deleted file mode 100644 index 5528d48..0000000 --- a/PluginVersions.iml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index a007d34..fbf6694 100644 --- a/README.md +++ b/README.md @@ -3,25 +3,30 @@ Create an alphabetically-sorted list of loaded plugins and versions. ### Tested Environments + **PluginVersions** was originally written for Spigot 1.8.8, and has worked for every Spigot version since then. All commands may be executed at the console, and in-game with appropriate permissions. ### Commands + > _pluginversions list_ > Create an alphabetically-sorted columnar list of plugins and versions. -> +> > _pluginversions reload_ > Reload configuration file. > At present, the only configuration option is whether to send metrics to MCStats.org. ### Command aliases + - pv :: Spigot, Paper - pvb :: BungeeCord, Waterfall - pvv :: Velocity ### Permissions + Permissions reflect the commands they allow. All permissions default to op. > _pluginversions.list_ -> _pluginversions.reload_ + +> _pluginversions.reload_ \ No newline at end of file diff --git a/libs/ServerListPlus.jar b/libs/ServerListPlus.jar deleted file mode 100644 index 6d43215..0000000 Binary files a/libs/ServerListPlus.jar and /dev/null differ diff --git a/out/artifacts/PluginVersions/PluginVersions.jar b/out/artifacts/PluginVersions/PluginVersions.jar deleted file mode 100644 index eefafbc..0000000 Binary files a/out/artifacts/PluginVersions/PluginVersions.jar and /dev/null differ diff --git a/pom.xml b/pom.xml index 6fb50a4..bb5df33 100644 --- a/pom.xml +++ b/pom.xml @@ -1,94 +1,59 @@ - - 4.0.0 - com.straight8.rambeau - PluginVersions - 1.3.5 - PluginVersions - List installed plugins and versions alphabetically - - 11 - 11 - + + 4.0.0 + com.straight8.rambeau + PluginVersions + 1.3.6 + PluginVersions + List installed plugins and versions alphabetically - - - maven-snapshots - https://repository.apache.org/content/repositories/snapshots/ - - + + 25 + 25 + UTF-8 + - - - minecrell - https://repo.minecrell.net/snapshots - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - bukkit-repo - https://hub.spigotmc.org/nexus/content/groups/public/ - - - CodeMC - https://repo.codemc.org/repository/maven-public - - - bungeecord-repo - https://oss.sonatype.org/content/repositories/snapshots - - - velocity - https://nexus.velocitypowered.com/repository/maven-public/ - - - jitpack.io - https://jitpack.io + + + papermc + https://repo.papermc.io/repository/maven-public/ - - - - - net.minecrell - ServerListPlus - latest - system - ${project.basedir}/libs/ServerListPlus.jar - - - org.spigotmc - spigot-api - 1.14.4-R0.1-SNAPSHOT - provided - - - com.velocitypowered - velocity-api - 3.1.1 - provided - - - net.md-5 - bungeecord-api - 1.16-R0.5-SNAPSHOT - jar - provided - - - net.md-5 - bungeecord-api - 1.16-R0.5-SNAPSHOT - javadoc - provided - - - com.github.SlimeDog - SlimeDogCore - 1.1.0 - compile - - + + jitpack.io + https://jitpack.io + + + + + + io.papermc.paper + paper-api + 1.21.11-R0.1-SNAPSHOT + provided + + + com.github.SlimeDog + SlimeDogCore + 1.0.10 + + + net.kyori + adventure-text-serializer-legacy + 4.26.1 + provided + + + net.kyori + adventure-api + 4.26.1 + provided + + + org.jspecify + jspecify + 1.0.0 + + PluginVersions-${project.version} @@ -101,27 +66,25 @@ org.apache.maven.plugins - maven-surefire-plugin - 3.0.0 + maven-dependency-plugin + 3.9.0 org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.14.1 - 11 - 11 + + -Xlint:deprecation + -Xlint:-options + -Xdiags:verbose + - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - org.apache.maven.plugins maven-shade-plugin - 3.4.1 + 3.6.1 package @@ -132,23 +95,32 @@ false true - + *:* - META-INF/maven/ + META-INF/maven/** META-INF/*.MF + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + META-INF/services/** + META-INF/versions/** + org/jetbrains/** + org/intellij/** + org/jspecify/** - - dev.ratas.slimedogcore - ${project.groupId}.${project.artifactId}.core - + + dev.ratas.slimedogcore + ${project.groupId}.${project.artifactId}.core + diff --git a/src/main/java/com/straight8/rambeau/bukkit/Messages.java b/src/main/java/com/straight8/rambeau/bukkit/Messages.java index b4de399..3e08b5f 100644 --- a/src/main/java/com/straight8/rambeau/bukkit/Messages.java +++ b/src/main/java/com/straight8/rambeau/bukkit/Messages.java @@ -17,7 +17,7 @@ protected Messages(SDCCustomConfig config) { } private void load() { - pageHeader = MsgUtil.singleContext("{page}", page -> String.valueOf(page), + pageHeader = MsgUtil.singleContext("{page}", String::valueOf, getRawMessage("page-header-format", "PluginVersions ===== page {page} =====")); enabledVersion = MsgUtil.tripleContext("{name}", name -> name, "{spacing}", spacing -> spacing, "{version}", version -> version, diff --git a/src/main/java/com/straight8/rambeau/bukkit/PluginComparator.java b/src/main/java/com/straight8/rambeau/bukkit/PluginComparator.java index fd6aeb1..7c12e98 100644 --- a/src/main/java/com/straight8/rambeau/bukkit/PluginComparator.java +++ b/src/main/java/com/straight8/rambeau/bukkit/PluginComparator.java @@ -1,16 +1,15 @@ package com.straight8.rambeau.bukkit; import java.util.Comparator; - import org.bukkit.plugin.Plugin; public class PluginComparator implements Comparator { - @Override - public int compare(Plugin p1, Plugin p2) { - if (!(p1 instanceof Plugin) || !(p2 instanceof Plugin)) { - throw new ClassCastException(); - } - return p1.getName().compareToIgnoreCase(p2.getName()); - } + @Override + public int compare(Plugin p1, Plugin p2) { + if (p1 == null || p2 == null) { + throw new ClassCastException(); + } + return p1.getName().compareToIgnoreCase(p2.getName()); + } } diff --git a/src/main/java/com/straight8/rambeau/bukkit/PluginVersionsBukkit.java b/src/main/java/com/straight8/rambeau/bukkit/PluginVersionsBukkit.java index 396a85e..382fc1c 100644 --- a/src/main/java/com/straight8/rambeau/bukkit/PluginVersionsBukkit.java +++ b/src/main/java/com/straight8/rambeau/bukkit/PluginVersionsBukkit.java @@ -8,138 +8,78 @@ package com.straight8.rambeau.bukkit; import com.straight8.rambeau.bukkit.command.PluginVersionsCommand; -import com.straight8.rambeau.metrics.SpigotMetrics; - import dev.ratas.slimedogcore.impl.SlimeDogCore; -import dev.ratas.slimedogcore.impl.utils.UpdateChecker; - -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.plugin.PluginDescriptionFile; - import java.io.File; -import java.util.function.BiConsumer; +import java.util.Objects; import java.util.logging.Logger; // import org.bukkit.Bukkit; // Imports for Metrics public class PluginVersionsBukkit extends SlimeDogCore { - private static final int SPIGOT_ID = 5509; - private static final String HANGAR_AUTHOR = "SlimeDog"; - private static final String HANGAR_SLUG = "PluginVersions"; - public final Logger logger = Logger.getLogger("Minecraft"); + public final Logger logger = Logger.getLogger("Minecraft"); - private Messages messages; - - // Configuration values: - private boolean configurationSendMetrics = true; - private boolean checkUpdates = true; + private Messages messages; - // Fired when plugin is first enabled + // Fired when plugin is first enabled @Override public void pluginEnabled() { - // Enable is logged automatically. - - // Create config.yml and plugin directory tree, if they do not exist. - CreateConfigFileIfMissing(); - - // Read the configuration values from config.yml. - ReadConfigValuesFromFile(); - - // Submit plugin usage data to MCStats.org. - if (configurationSendMetrics) { - new SpigotMetrics(this, 5509); - } + // Enable is logged automatically. - if (checkUpdates) { - String source = getDefaultConfig().getConfig().getString("update-source", "Hangar"); - BiConsumer consumer = (response, version) -> { - switch (response) { - case LATEST: - getLogger().info("Already on latest version"); - break; - case FOUND_NEW: - getLogger().info("Found new version: " + version); - break; - case UNAVAILABLE: - getLogger().info("Version information not available"); - break; - } - }; - UpdateChecker checker; - if (source.equalsIgnoreCase("Hangar")) { - checker = UpdateChecker.forHangar(this, consumer, HANGAR_AUTHOR, HANGAR_SLUG); - } else { - checker = UpdateChecker.forSpigot(this, consumer, SPIGOT_ID); - } - checker.check(); - } + // Create config.yml and plugin directory tree, if they do not exist. + CreateConfigFileIfMissing(); - messages = new Messages(getDefaultConfig()); + // Read the configuration values from config.yml. + ReadConfigValuesFromFile(); + messages = new Messages(getDefaultConfig()); + Objects.requireNonNull(getCommand("pluginversions")).setExecutor(new PluginVersionsCommand(this)); + } - getCommand("pluginversions").setExecutor(new PluginVersionsCommand(this)); + public Messages getMessages() { + return messages; } - public Messages getMessages() { - return messages; - } - // Fired when plugin is disabled @Override public void pluginDisabled() { - // Disable is logged automatically. - } - + // Disable is logged automatically. + } + public void CreateConfigFileIfMissing() { - try { - PluginDescriptionFile pdfFile = this.getDescription(); - - if (!getDataFolder().exists()) { - this.log(pdfFile.getName() + ": folder doesn't exist"); - this.log(pdfFile.getName() + ": creating folder"); - try { - getDataFolder().mkdirs(); - } catch(Exception e) { - this.log(pdfFile.getName() + ": could not create folder"); - return; - } - this.log(pdfFile.getName() + ": folder created at " + getDataFolder()); - } - - File configFile = new File(getDataFolder(), "config.yml"); - if (!configFile.exists()) { - this.log(pdfFile.getName() + ": config.yml not found, creating!"); - // Copy config.yml from the jar. - try { - saveDefaultConfig(); - } catch(Exception e) { - this.log(pdfFile.getName() + ": could not save config.yml"); - } - // Do not saveConfig() or comments below the header will be deleted. - // There are code samples on the internet that resolve the issue, - // but not needed, since we don't want to change values on the fly. - // FileConfiguration config = getConfig(); - // config.options().copyDefaults(true); - // saveConfig(); - } - } catch(Exception e) { - e.printStackTrace(); - } + try { + String pdfFile = this.getPluginMeta().getDescription(); + if (!getDataFolder().exists()) { + this.log(pdfFile + ": folder doesn't exist"); + this.log(pdfFile + ": creating folder"); + try { + //noinspection ResultOfMethodCallIgnored + getDataFolder().mkdirs(); + } catch (Exception e) { + this.log(pdfFile + ": could not create folder"); + return; + } + this.log(pdfFile + ": folder created at " + getDataFolder()); + } + File configFile = new File(getDataFolder(), "config.yml"); + if (!configFile.exists()) { + this.log(pdfFile + ": config.yml not found, creating!"); + try { + saveDefaultConfig(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } catch (RuntimeException e) { + throw new RuntimeException(e); + } } public void ReadConfigValuesFromFile() { - this.reloadConfig(); - - FileConfiguration reloadedConfig = getConfig(); - // Optimized the code to read the configuration options - configurationSendMetrics = reloadedConfig.getBoolean("enable-metrics", true); - checkUpdates = reloadedConfig.getBoolean("check-for-updates", true); - if (messages != null) { // ignore first time around - messages.reload(); - } - } + this.reloadConfig(); + if (messages != null) messages.reload(); + } public void log(String logString) { - this.logger.info("[" + this.getName() + "] " + logString); + this.logger.info("[" + this.getName() + "] " + logString); } } diff --git a/src/main/java/com/straight8/rambeau/bukkit/command/PluginVersionsCommand.java b/src/main/java/com/straight8/rambeau/bukkit/command/PluginVersionsCommand.java index f421570..05e3be8 100644 --- a/src/main/java/com/straight8/rambeau/bukkit/command/PluginVersionsCommand.java +++ b/src/main/java/com/straight8/rambeau/bukkit/command/PluginVersionsCommand.java @@ -1,10 +1,9 @@ package com.straight8.rambeau.bukkit.command; import com.straight8.rambeau.bukkit.PluginVersionsBukkit; - -import dev.ratas.slimedogcore.impl.commands.BukkitFacingParentCommand; import com.straight8.rambeau.bukkit.command.sub.ListSub; import com.straight8.rambeau.bukkit.command.sub.ReloadSub; +import dev.ratas.slimedogcore.impl.commands.BukkitFacingParentCommand; public class PluginVersionsCommand extends BukkitFacingParentCommand { @@ -12,5 +11,4 @@ public PluginVersionsCommand(PluginVersionsBukkit plugin) { addSubCommand(new ListSub(plugin)); addSubCommand(new ReloadSub(plugin)); } - } \ No newline at end of file diff --git a/src/main/java/com/straight8/rambeau/bukkit/command/sub/ListSub.java b/src/main/java/com/straight8/rambeau/bukkit/command/sub/ListSub.java index aacad76..a594816 100644 --- a/src/main/java/com/straight8/rambeau/bukkit/command/sub/ListSub.java +++ b/src/main/java/com/straight8/rambeau/bukkit/command/sub/ListSub.java @@ -1,26 +1,25 @@ package com.straight8.rambeau.bukkit.command.sub; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.bukkit.plugin.Plugin; - import com.straight8.rambeau.bukkit.PluginComparator; import com.straight8.rambeau.bukkit.PluginVersionsBukkit; import com.straight8.rambeau.util.CommandPageUtils; - import dev.ratas.slimedogcore.api.commands.SDCCommandOptionSet; -import dev.ratas.slimedogcore.api.messaging.factory.SDCTripleContextMessageFactory; import dev.ratas.slimedogcore.api.messaging.recipient.SDCPlayerRecipient; import dev.ratas.slimedogcore.api.messaging.recipient.SDCRecipient; import dev.ratas.slimedogcore.impl.commands.AbstractSubCommand; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.bukkit.plugin.Plugin; +import org.jspecify.annotations.NonNull; public class ListSub extends AbstractSubCommand { + private static final String NAME = "list"; private static final String PERMS = "pluginversions.list"; private static final String USAGE = "/pv list [page]"; private static final int LINES_PER_PAGE = 10; + private final PluginVersionsBukkit plugin; public ListSub(PluginVersionsBukkit plugin) { @@ -29,68 +28,56 @@ public ListSub(PluginVersionsBukkit plugin) { } @Override - public List onTabComplete(SDCRecipient sender, String[] args) { + public List onTabComplete(SDCRecipient sender, String @NonNull [] args) { if (args.length == 1 && !(sender instanceof SDCPlayerRecipient)) { - return CommandPageUtils.getNextInteger(args[0], - (plugin.getServer().getPluginManager().getPlugins().length + LINES_PER_PAGE - 1) / LINES_PER_PAGE); + var totalPages = (plugin.getServer().getPluginManager().getPlugins().length + LINES_PER_PAGE - 1) / LINES_PER_PAGE; + return CommandPageUtils.getNextInteger(args[0], totalPages); } return Collections.emptyList(); } @Override public boolean onOptionedCommand(SDCRecipient sender, String[] args, SDCCommandOptionSet opts) { - Plugin[] pluginList = plugin.getServer().getPluginManager().getPlugins(); + var pluginList = plugin.getServer().getPluginManager().getPlugins(); if (pluginList.length == 0) { sender.sendRawMessage("No plugins loaded"); return true; } + Arrays.sort(pluginList, new PluginComparator()); - // Identify the page to display. Page 0 indicates the entire list. - int page = 0; - if (args.length > 0) { + // Determine page to display + int page = Arrays.stream(args).findFirst().map(arg -> { try { - page = Integer.parseInt(args[0]); - } catch (Exception ignored) { + return Integer.parseInt(arg); + } catch (NumberFormatException e) { + return 0; } - } - // Set page to 0 if illegal page was requested + }).orElse(0); page = Math.max(page, 0); if (page > 0) { - if (((page - 1) * LINES_PER_PAGE) < pluginList.length) { + if ((page - 1) * LINES_PER_PAGE < pluginList.length) { sender.sendMessage(plugin.getMessages().getPageHeader().createWith(page)); } - int maxSpacing = CommandPageUtils.getMaxNameLength(plugin -> plugin.getName(), - CommandPageUtils.getPage(Arrays.asList(pluginList), page, LINES_PER_PAGE)); - for (int i = ((page - 1) * LINES_PER_PAGE); i < pluginList.length && i < (page * LINES_PER_PAGE); i++) { - Plugin p = pluginList[i]; + int maxSpacing = CommandPageUtils.getMaxNameLength(Plugin::getName, CommandPageUtils.getPage(Arrays.asList(pluginList), page, LINES_PER_PAGE)); - SDCTripleContextMessageFactory msg; - if (p.isEnabled()) { - msg = plugin.getMessages().getEnabledVersion(); - } else { - msg = plugin.getMessages().getDisabledVersion(); - } - String spacing = CommandPageUtils.getSpacingFor(p.getName(), maxSpacing, - sender instanceof SDCPlayerRecipient); - sender.sendMessage(msg.createWith(p.getName(), spacing, p.getDescription().getVersion())); + for (int i = (page - 1) * LINES_PER_PAGE; i < pluginList.length && i < page * LINES_PER_PAGE; i++) { + sendPluginMessage(sender, pluginList[i], maxSpacing); } } else { - int maxSpacing = CommandPageUtils.getMaxNameLength(plugin -> plugin.getName(), Arrays.asList(pluginList)); - for (Plugin p : pluginList) { - SDCTripleContextMessageFactory msg; - if (p.isEnabled()) { - msg = plugin.getMessages().getEnabledVersion(); - } else { - msg = plugin.getMessages().getDisabledVersion(); - } - String spacing = CommandPageUtils.getSpacingFor(p.getName(), maxSpacing, - sender instanceof SDCPlayerRecipient); - sender.sendMessage(msg.createWith(p.getName(), spacing, p.getDescription().getVersion())); + int maxSpacing = CommandPageUtils.getMaxNameLength(Plugin::getName, Arrays.asList(pluginList)); + for (var p : pluginList) { + sendPluginMessage(sender, p, maxSpacing); } } + return true; } -} + private void sendPluginMessage(@NonNull SDCRecipient sender, @NonNull Plugin p, int maxSpacing) { + var msg = p.isEnabled() ? plugin.getMessages().getEnabledVersion() : plugin.getMessages().getDisabledVersion(); + String spacing = CommandPageUtils.getSpacingFor(p.getName(), maxSpacing, sender instanceof SDCPlayerRecipient); + sender.sendMessage(msg.createWith(p.getName(), spacing, p.getPluginMeta().getVersion())); + } +} \ No newline at end of file diff --git a/src/main/java/com/straight8/rambeau/bukkit/command/sub/ReloadSub.java b/src/main/java/com/straight8/rambeau/bukkit/command/sub/ReloadSub.java index 8f3cdda..27fbac0 100644 --- a/src/main/java/com/straight8/rambeau/bukkit/command/sub/ReloadSub.java +++ b/src/main/java/com/straight8/rambeau/bukkit/command/sub/ReloadSub.java @@ -1,15 +1,15 @@ package com.straight8.rambeau.bukkit.command.sub; -import java.util.Collections; -import java.util.List; - -import org.bukkit.ChatColor; - import com.straight8.rambeau.bukkit.PluginVersionsBukkit; - import dev.ratas.slimedogcore.api.commands.SDCCommandOptionSet; import dev.ratas.slimedogcore.api.messaging.recipient.SDCRecipient; import dev.ratas.slimedogcore.impl.commands.AbstractSubCommand; +import java.util.Collections; +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.jspecify.annotations.NonNull; public class ReloadSub extends AbstractSubCommand { private static final String NAME = "reload"; @@ -28,11 +28,12 @@ public List onTabComplete(SDCRecipient sender, String[] args) { } @Override - public boolean onOptionedCommand(SDCRecipient sender, String[] args, SDCCommandOptionSet opts) { + public boolean onOptionedCommand(@NonNull SDCRecipient sender, String[] args, SDCCommandOptionSet opts) { plugin.CreateConfigFileIfMissing(); plugin.ReadConfigValuesFromFile(); - sender.sendRawMessage("Reloaded " + ChatColor.AQUA + this.getName() + "/config.yml"); + Component message = Component.text("Reloaded ").append(Component.text(this.getName() + "/config.yml", NamedTextColor.AQUA)); + String raw = LegacyComponentSerializer.legacySection().serialize(message); + sender.sendRawMessage(raw); return true; } - } diff --git a/src/main/java/com/straight8/rambeau/bungee/PluginComparator.java b/src/main/java/com/straight8/rambeau/bungee/PluginComparator.java deleted file mode 100644 index 5e95441..0000000 --- a/src/main/java/com/straight8/rambeau/bungee/PluginComparator.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.straight8.rambeau.bungee; - -import net.md_5.bungee.api.plugin.Plugin; - -import java.util.Comparator; - -public class PluginComparator implements Comparator { - - @Override - public int compare(Plugin p1, Plugin p2) { - if (!(p1 instanceof Plugin) || !(p2 instanceof Plugin)) { - throw new ClassCastException(); - } - return p1.getDescription().getName().compareToIgnoreCase(p2.getDescription().getName()); - } -} diff --git a/src/main/java/com/straight8/rambeau/bungee/PluginVersionsBungee.java b/src/main/java/com/straight8/rambeau/bungee/PluginVersionsBungee.java deleted file mode 100644 index 8462430..0000000 --- a/src/main/java/com/straight8/rambeau/bungee/PluginVersionsBungee.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.straight8.rambeau.bungee; - -import com.straight8.rambeau.metrics.BungeeMetrics; -import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.config.Configuration; -// Imports for Metrics - -public class PluginVersionsBungee extends Plugin { - - private static PluginVersionsBungee instance; - - private boolean configurationSendMetrics = true; - private boolean checkUpdates = true; - - private Configuration config; - - @Override - public void onEnable() { - instance = this; - - YamlConfig.createFiles("config"); - - this.ReadConfigValuesFromFile(); - - if (configurationSendMetrics) { - new BungeeMetrics(this, 13031); - } - - this.getProxy().getPluginManager().registerCommand(this, new PluginVersionsCmd(this)); - - if (checkUpdates) { - new UpdateChecker(this, (response, version)-> { - switch (response) { - case LATEST: { - getLogger().info("Running latest version!"); - break; - } - case UNAVAILABLE: { - getLogger().info("Unable to check for new version"); - break; - } - case FOUND_NEW: { - getLogger().warning("Running outdated version! New version available:" + version); - break; - } - } - }).check(); - } - } - - public static PluginVersionsBungee getInstance() { - return instance; - } - - public void ReadConfigValuesFromFile() { - Configuration reloadedConfig = YamlConfig.getConfiguration("config"); - - // Optimized the code to read the configuration options - configurationSendMetrics = reloadedConfig.getBoolean("enable-metrics", true); - checkUpdates = reloadedConfig.getBoolean("check-for-updates", true); - config = reloadedConfig; - } - - public Configuration getConfig() { - return config; - } - - public void log(String logString) { - this.getLogger().info("[" + this.getDescription().getName() + "] " + logString); - } -} diff --git a/src/main/java/com/straight8/rambeau/bungee/PluginVersionsCmd.java b/src/main/java/com/straight8/rambeau/bungee/PluginVersionsCmd.java deleted file mode 100644 index 71d00ad..0000000 --- a/src/main/java/com/straight8/rambeau/bungee/PluginVersionsCmd.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.straight8.rambeau.bungee; - -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 net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.api.plugin.TabExecutor; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.straight8.rambeau.util.CommandPageUtils; -import com.straight8.rambeau.util.StringUtil; - -public class PluginVersionsCmd extends Command implements TabExecutor { - private static final int LINES_PER_PAGE = 10; - - private final PluginVersionsBungee plugin; - - public PluginVersionsCmd(PluginVersionsBungee plugin) { - super("pluginversions", "pluginversions.list", "pvb"); - - this.plugin = plugin; - } - - private void showUsage(CommandSender source) { - StringBuilder sb = new StringBuilder(); - if (source.hasPermission("pluginversions.list")) { - sb.append("/pvb list [page]"); - } - if (source.hasPermission("pluginversions.reload")) { - if (sb.length() > 1) { - sb.append("\n"); - } - sb.append("/pvb reload"); - } - source.sendMessage(sb.toString()); - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (args.length == 0) { - showUsage(sender); - return; - } - String cmdLowercase = args[0].toLowerCase(); - - if (sender instanceof ProxiedPlayer) { - if (!sender.hasPermission("pluginversions." + cmdLowercase)) { - String senderName = sender.getName(); - sender.sendMessage("You do not have permission to run this command"); - this.plugin - .log(senderName + " attempted to run command pv " + cmdLowercase + ", but lacked permissions"); - return; - } - } - - if (cmdLowercase.equals("list")) { - List pluginList = new ArrayList<>(this.plugin.getProxy().getPluginManager().getPlugins()); - - if (pluginList.isEmpty()) { - sender.sendMessage("No plugins loaded"); - return; - } - - pluginList.sort(new PluginComparator()); - - // Identify the page to display. Page 0 indicates the entire list. - int page = 0; - if (args.length > 1) { - try { - page = Integer.parseInt(args[1]); - } catch (Exception ignored) { - } - } - // Set page to 0 if illegal page was requested - page = Math.max(page, 0); - - int linesPerPage = 10; - if (page > 0) { - int maxSpacing = CommandPageUtils.getMaxNameLength(plugin -> plugin.getDescription().getName(), - CommandPageUtils.getPage(pluginList, page, LINES_PER_PAGE)); - if (((page - 1) * linesPerPage) < pluginList.size()) { - String msg = color(plugin.getConfig().getString("page-header-format", - "PluginVersions ===== page {page} =====")); - sender.sendMessage("PluginVersions ===== page " + page + " ====="); - } - for (int i = ((page - 1) * linesPerPage); i < pluginList.size() && i < (page * linesPerPage); i++) { - Plugin p = pluginList.get(i); - String msg = color( - plugin.getConfig().getString("enabled-version-format", "&a{name}{spacing}&e{version}")); - String spacing = CommandPageUtils.getSpacingFor(p.getDescription().getName(), maxSpacing, - sender instanceof ProxiedPlayer); - sender.sendMessage(msg.replace("{name}", p.getDescription().getName()).replace("{version}", - p.getDescription().getVersion()).replace("{spacing}", spacing)); - } - } else { - int maxSpacing = CommandPageUtils.getMaxNameLength(plugin -> plugin.getDescription().getName(), - pluginList); - for (Plugin p : pluginList) { - String msg = color( - plugin.getConfig().getString("enabled-version-format", "&a{name}{spacing}&e{version}")); - String spacing = CommandPageUtils.getSpacingFor(p.getDescription().getName(), maxSpacing, - sender instanceof ProxiedPlayer); - sender.sendMessage(msg.replace("{name}", p.getDescription().getName()).replace("{version}", - p.getDescription().getVersion()).replace("{spacing}", spacing)); - } - } - // break; - } else if (cmdLowercase.equals("reload")) { - YamlConfig.createFiles("config"); - PluginVersionsBungee.getInstance().ReadConfigValuesFromFile(); - - sender.sendMessage("Reloaded §bPluginVersions/config.yml"); - } else { - sender.sendMessage("Unrecognized command option " + cmdLowercase); - } - } - - @Override - public Iterable onTabComplete(CommandSender sender, String[] args) { - if (args.length == 1) { - List options = new ArrayList<>(); - if (sender.hasPermission("pluginversions.list")) { - options.add("list"); - } - if (sender.hasPermission("pluginversions.reload")) { - options.add("relaod"); - } - return StringUtil.copyPartialMatches(args[0], options, new ArrayList<>()); - } else if (args.length == 2 && args[0].equalsIgnoreCase("list")) { - return CommandPageUtils.getNextInteger(args[1], - (plugin.getProxy().getPluginManager().getPlugins().size() + LINES_PER_PAGE - 1) / LINES_PER_PAGE); - } else { - return Collections.emptyList(); - } - } - - public static String color(String msg) { - return ChatColor.translateAlternateColorCodes('&', msg); - } - -} diff --git a/src/main/java/com/straight8/rambeau/bungee/UpdateChecker.java b/src/main/java/com/straight8/rambeau/bungee/UpdateChecker.java deleted file mode 100644 index 4118aee..0000000 --- a/src/main/java/com/straight8/rambeau/bungee/UpdateChecker.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.straight8.rambeau.bungee; - -import com.google.common.io.Resources; -import com.google.common.net.HttpHeaders; - -import javax.net.ssl.HttpsURLConnection; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.Charset; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; - -public class UpdateChecker { - private static final String SPIGOT_URL = "https://api.spigotmc.org/legacy/update.php?resource=70927"; - - private final PluginVersionsBungee plugin; - - private final String currentVersion; - - private final BiConsumer versionResponse; - - public UpdateChecker(PluginVersionsBungee plugin, BiConsumer consumer) { - this.plugin = plugin; - this.currentVersion = plugin.getDescription().getVersion(); - this.versionResponse = consumer; - } - - public void check() { - plugin.getProxy().getScheduler().schedule(this.plugin, () -> { - try { - HttpURLConnection httpURLConnection = (HttpsURLConnection) new URL(SPIGOT_URL).openConnection(); - httpURLConnection.setRequestMethod("GET"); - httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, "Mozilla/5.0"); - - String fetchedVersion = Resources.toString(httpURLConnection.getURL(), Charset.defaultCharset()); - - boolean latestVersion = fetchedVersion.equalsIgnoreCase(this.currentVersion); - - plugin.getProxy().getScheduler().runAsync(this.plugin, () -> this.versionResponse.accept(latestVersion ? VersionResponse.LATEST : VersionResponse.FOUND_NEW, latestVersion ? this.currentVersion : fetchedVersion)); - } catch (IOException exception) { - exception.printStackTrace(); - plugin.getProxy().getScheduler().runAsync(this.plugin, () -> this.versionResponse.accept(VersionResponse.UNAVAILABLE, null)); - } - }, 1L, TimeUnit.MILLISECONDS); - } - - public enum VersionResponse { - LATEST, - FOUND_NEW, - UNAVAILABLE - } - -} \ No newline at end of file diff --git a/src/main/java/com/straight8/rambeau/bungee/YamlConfig.java b/src/main/java/com/straight8/rambeau/bungee/YamlConfig.java deleted file mode 100644 index 740b9ab..0000000 --- a/src/main/java/com/straight8/rambeau/bungee/YamlConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.straight8.rambeau.bungee; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; - -import net.md_5.bungee.config.Configuration; -import net.md_5.bungee.config.ConfigurationProvider; -import net.md_5.bungee.config.YamlConfiguration; - -public class YamlConfig { - - public static void saveConfiguration(Configuration configuration, String file) { - try { - ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, new File(PluginVersionsBungee.getInstance().getDataFolder(), file)); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static void createFiles(String file) { - if (!PluginVersionsBungee.getInstance().getDataFolder().exists()) { - PluginVersionsBungee.getInstance().getDataFolder().mkdir(); - } - File fileconfig = new File(PluginVersionsBungee.getInstance().getDataFolder(), file+".yml"); - if (!fileconfig.exists()) { - try { - InputStream in = PluginVersionsBungee.getInstance().getResourceAsStream(file+".yml"); - Files.copy(in, fileconfig.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static Configuration getConfiguration(String file) { - File configFile = new File(PluginVersionsBungee.getInstance().getDataFolder(), file+".yml"); - if (!configFile.exists()) { - return null; - } - try { - return ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/straight8/rambeau/metrics/BungeeMetrics.java b/src/main/java/com/straight8/rambeau/metrics/BungeeMetrics.java deleted file mode 100644 index 49234ed..0000000 --- a/src/main/java/com/straight8/rambeau/metrics/BungeeMetrics.java +++ /dev/null @@ -1,857 +0,0 @@ -package com.straight8.rambeau.metrics; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; -import javax.net.ssl.HttpsURLConnection; -import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.config.Configuration; -import net.md_5.bungee.config.ConfigurationProvider; -import net.md_5.bungee.config.YamlConfiguration; - -public class BungeeMetrics { - - private final Plugin plugin; - - private final MetricsBase metricsBase; - - private boolean enabled; - - private String serverUUID; - - private boolean logErrors = false; - - private boolean logSentData; - - private boolean logResponseStatusText; - - /** - * Creates a new Metrics instance. - * - * @param plugin Your plugin instance. - * @param serviceId The id of the service. It can be found at What is my plugin id? - */ - public BungeeMetrics(Plugin plugin, int serviceId) { - this.plugin = plugin; - try { - loadConfig(); - } catch (IOException e) { - // Failed to load configuration - plugin.getLogger().log(Level.WARNING, "Failed to load bStats config!", e); - metricsBase = null; - return; - } - metricsBase = - new MetricsBase( - "bungeecord", - serverUUID, - serviceId, - enabled, - this::appendPlatformData, - this::appendServiceData, - null, - () -> true, - (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), - (message) -> this.plugin.getLogger().log(Level.INFO, message), - logErrors, - logSentData, - logResponseStatusText); - } - - /** Loads the bStats configuration. */ - private void loadConfig() throws IOException { - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - bStatsFolder.mkdirs(); - File configFile = new File(bStatsFolder, "config.yml"); - if (!configFile.exists()) { - writeFile( - configFile, - "# bStats (https://bStats.org) collects some basic information for plugin authors, like how", - "# many people use their plugin and their total player count. It's recommended to keep bStats", - "# enabled, but if you're not comfortable with this, you can turn this setting off. There is no", - "# performance penalty associated with having metrics enabled, and data sent to bStats is fully", - "# anonymous.", - "enabled: true", - "serverUuid: \"" + UUID.randomUUID() + "\"", - "logFailedRequests: false", - "logSentData: false", - "logResponseStatusText: false"); - } - Configuration configuration = - ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); - // Load configuration - enabled = configuration.getBoolean("enabled", true); - serverUUID = configuration.getString("serverUuid"); - logErrors = configuration.getBoolean("logFailedRequests", false); - logSentData = configuration.getBoolean("logSentData", false); - logResponseStatusText = configuration.getBoolean("logResponseStatusText", false); - } - - private void writeFile(File file, String... lines) throws IOException { - try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file))) { - for (String line : lines) { - bufferedWriter.write(line); - bufferedWriter.newLine(); - } - } - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - metricsBase.addCustomChart(chart); - } - - private void appendPlatformData(JsonObjectBuilder builder) { - builder.appendField("playerAmount", plugin.getProxy().getOnlineCount()); - builder.appendField("managedServers", plugin.getProxy().getServers().size()); - builder.appendField("onlineMode", plugin.getProxy().getConfig().isOnlineMode() ? 1 : 0); - builder.appendField("bungeecordVersion", plugin.getProxy().getVersion()); - builder.appendField("javaVersion", System.getProperty("java.version")); - builder.appendField("osName", System.getProperty("os.name")); - builder.appendField("osArch", System.getProperty("os.arch")); - builder.appendField("osVersion", System.getProperty("os.version")); - builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); - } - - private void appendServiceData(JsonObjectBuilder builder) { - builder.appendField("pluginVersion", plugin.getDescription().getVersion()); - } - - public static class MetricsBase { - - /** The version of the Metrics class. */ - public static final String METRICS_VERSION = "2.2.1"; - - private static final ScheduledExecutorService scheduler = - Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); - - private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; - - private final String platform; - - private final String serverUuid; - - private final int serviceId; - - private final Consumer appendPlatformDataConsumer; - - private final Consumer appendServiceDataConsumer; - - private final Consumer submitTaskConsumer; - - private final Supplier checkServiceEnabledSupplier; - - private final BiConsumer errorLogger; - - private final Consumer infoLogger; - - private final boolean logErrors; - - private final boolean logSentData; - - private final boolean logResponseStatusText; - - private final Set customCharts = new HashSet<>(); - - private final boolean enabled; - - /** - * Creates a new MetricsBase class instance. - * - * @param platform The platform of the service. - * @param serviceId The id of the service. - * @param serverUuid The server uuid. - * @param enabled Whether or not data sending is enabled. - * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all platform-specific data. - * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all service-specific data. - * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be - * used to delegate the data collection to a another thread to prevent errors caused by - * concurrency. Can be {@code null}. - * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. - * @param errorLogger A consumer that accepts log message and an error. - * @param infoLogger A consumer that accepts info log messages. - * @param logErrors Whether or not errors should be logged. - * @param logSentData Whether or not the sent data should be logged. - * @param logResponseStatusText Whether or not the response status text should be logged. - */ - public MetricsBase( - String platform, - String serverUuid, - int serviceId, - boolean enabled, - Consumer appendPlatformDataConsumer, - Consumer appendServiceDataConsumer, - Consumer submitTaskConsumer, - Supplier checkServiceEnabledSupplier, - BiConsumer errorLogger, - Consumer infoLogger, - boolean logErrors, - boolean logSentData, - boolean logResponseStatusText) { - this.platform = platform; - this.serverUuid = serverUuid; - this.serviceId = serviceId; - this.enabled = enabled; - this.appendPlatformDataConsumer = appendPlatformDataConsumer; - this.appendServiceDataConsumer = appendServiceDataConsumer; - this.submitTaskConsumer = submitTaskConsumer; - this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; - this.errorLogger = errorLogger; - this.infoLogger = infoLogger; - this.logErrors = logErrors; - this.logSentData = logSentData; - this.logResponseStatusText = logResponseStatusText; - checkRelocation(); - if (enabled) { - startSubmitting(); - } - } - - public void addCustomChart(CustomChart chart) { - this.customCharts.add(chart); - } - - private void startSubmitting() { - final Runnable submitTask = - () -> { - if (!enabled || !checkServiceEnabledSupplier.get()) { - // Submitting data or service is disabled - scheduler.shutdown(); - return; - } - if (submitTaskConsumer != null) { - submitTaskConsumer.accept(this::submitData); - } else { - this.submitData(); - } - }; - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution - // of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into the initial - // and second delay. - // WARNING: You must not modify and part of this Metrics class, including the submit delay or - // frequency! - // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! - long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); - long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); - scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); - scheduler.scheduleAtFixedRate( - submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); - } - - private void submitData() { - final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); - appendPlatformDataConsumer.accept(baseJsonBuilder); - final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); - appendServiceDataConsumer.accept(serviceJsonBuilder); - JsonObjectBuilder.JsonObject[] chartData = - customCharts.stream() - .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) - .filter(Objects::nonNull) - .toArray(JsonObjectBuilder.JsonObject[]::new); - serviceJsonBuilder.appendField("id", serviceId); - serviceJsonBuilder.appendField("customCharts", chartData); - baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); - baseJsonBuilder.appendField("serverUUID", serverUuid); - baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); - JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); - scheduler.execute( - () -> { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logErrors) { - errorLogger.accept("Could not submit bStats metrics data", e); - } - } - }); - } - - private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { - if (logSentData) { - infoLogger.accept("Sent bStats metrics data: " + data.toString()); - } - String url = String.format(REPORT_URL, platform); - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", "Metrics-Service/1"); - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - if (logResponseStatusText) { - infoLogger.accept("Sent data to bStats and received response: " + builder); - } - } - - /** Checks that the class was properly relocated. */ - private void checkRelocation() { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null - || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little - // "trick" ... :D - final String defaultPackage = - new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); - final String examplePackage = - new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure no one just copy & pastes the example and uses the wrong package - // names - if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) - || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - /** - * Gzips the given string. - * - * @param str The string to gzip. - * @return The gzipped string. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - } - - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public abstract static class CustomChart { - - private final String chartId; - - protected CustomChart(String chartId) { - if (chartId == null) { - throw new IllegalArgumentException("chartId must not be null"); - } - this.chartId = chartId; - } - - public JsonObjectBuilder.JsonObject getRequestJsonObject( - BiConsumer errorLogger, boolean logErrors) { - JsonObjectBuilder builder = new JsonObjectBuilder(); - builder.appendField("chartId", chartId); - try { - JsonObjectBuilder.JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - builder.appendField("data", data); - } catch (Throwable t) { - if (logErrors) { - errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return builder.build(); - } - - protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; - } - - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - /** - * An extremely simple JSON builder. - * - *

While this class is neither feature-rich nor the most performant one, it's sufficient enough - * for its use-case. - */ - public static class JsonObjectBuilder { - - private StringBuilder builder = new StringBuilder(); - - private boolean hasAtLeastOneField = false; - - public JsonObjectBuilder() { - builder.append("{"); - } - - /** - * Appends a null field to the JSON. - * - * @param key The key of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendNull(String key) { - appendFieldUnescaped(key, "null"); - return this; - } - - /** - * Appends a string field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String value) { - if (value == null) { - throw new IllegalArgumentException("JSON value must not be null"); - } - appendFieldUnescaped(key, "\"" + escape(value) + "\""); - return this; - } - - /** - * Appends an integer field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int value) { - appendFieldUnescaped(key, String.valueOf(value)); - return this; - } - - /** - * Appends an object to the JSON. - * - * @param key The key of the field. - * @param object The object. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject object) { - if (object == null) { - throw new IllegalArgumentException("JSON object must not be null"); - } - appendFieldUnescaped(key, object.toString()); - return this; - } - - /** - * Appends a string array to the JSON. - * - * @param key The key of the field. - * @param values The string array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values) - .map(value -> "\"" + escape(value) + "\"") - .collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an integer array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an object array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends a field to the object. - * - * @param key The key of the field. - * @param escapedValue The escaped value of the field. - */ - private void appendFieldUnescaped(String key, String escapedValue) { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - if (key == null) { - throw new IllegalArgumentException("JSON key must not be null"); - } - if (hasAtLeastOneField) { - builder.append(","); - } - builder.append("\"").append(escape(key)).append("\":").append(escapedValue); - hasAtLeastOneField = true; - } - - /** - * Builds the JSON string and invalidates this builder. - * - * @return The built JSON string. - */ - public JsonObject build() { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - JsonObject object = new JsonObject(builder.append("}").toString()); - builder = null; - return object; - } - - /** - * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. - * - *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. - * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). - * - * @param value The value to escape. - * @return The escaped value. - */ - private static String escape(String value) { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '"') { - builder.append("\\\""); - } else if (c == '\\') { - builder.append("\\\\"); - } else if (c <= '\u000F') { - builder.append("\\u000").append(Integer.toHexString(c)); - } else if (c <= '\u001F') { - builder.append("\\u00").append(Integer.toHexString(c)); - } else { - builder.append(c); - } - } - return builder.toString(); - } - - /** - * A super simple representation of a JSON object. - * - *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not - * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, - * JsonObject)}. - */ - public static class JsonObject { - - private final String value; - - private JsonObject(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/straight8/rambeau/metrics/SpigotMetrics.java b/src/main/java/com/straight8/rambeau/metrics/SpigotMetrics.java deleted file mode 100644 index 09e3b81..0000000 --- a/src/main/java/com/straight8/rambeau/metrics/SpigotMetrics.java +++ /dev/null @@ -1,847 +0,0 @@ -package com.straight8.rambeau.metrics; - -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; -import javax.net.ssl.HttpsURLConnection; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; - -public class SpigotMetrics { - - private final Plugin plugin; - private final MetricsBase metricsBase; - - /** - * Creates a new Metrics instance. - * - * @param plugin Your plugin instance. - * @param serviceId The id of the service. It can be found at What is my plugin id? - */ - public SpigotMetrics(JavaPlugin plugin, int serviceId) { - this.plugin = plugin; - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - if (!config.isSet("serverUuid")) { - config.addDefault("enabled", true); - config.addDefault("serverUuid", UUID.randomUUID().toString()); - config.addDefault("logFailedRequests", false); - config.addDefault("logSentData", false); - config.addDefault("logResponseStatusText", false); - // Inform the server owners about bStats - config - .options() - .header( - "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" - + "many people use their plugin and their total player count. It's recommended to keep bStats\n" - + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" - + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" - + "anonymous.") - .copyDefaults(true); - try { - config.save(configFile); - } catch (IOException ignored) { - } - } - // Load the data - boolean enabled = config.getBoolean("enabled", true); - String serverUUID = config.getString("serverUuid"); - boolean logErrors = config.getBoolean("logFailedRequests", false); - boolean logSentData = config.getBoolean("logSentData", false); - boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); - metricsBase = - new MetricsBase( - "bukkit", - serverUUID, - serviceId, - enabled, - this::appendPlatformData, - this::appendServiceData, - submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), - plugin::isEnabled, - (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), - (message) -> this.plugin.getLogger().log(Level.INFO, message), - logErrors, - logSentData, - logResponseStatusText); - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - metricsBase.addCustomChart(chart); - } - - private void appendPlatformData(JsonObjectBuilder builder) { - builder.appendField("playerAmount", getPlayerAmount()); - builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); - builder.appendField("bukkitVersion", Bukkit.getVersion()); - builder.appendField("bukkitName", Bukkit.getName()); - builder.appendField("javaVersion", System.getProperty("java.version")); - builder.appendField("osName", System.getProperty("os.name")); - builder.appendField("osArch", System.getProperty("os.arch")); - builder.appendField("osVersion", System.getProperty("os.version")); - builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); - } - - private void appendServiceData(JsonObjectBuilder builder) { - builder.appendField("pluginVersion", plugin.getDescription().getVersion()); - } - - private int getPlayerAmount() { - try { - // Around MC 1.8 the return type was changed from an array to a collection, - // This fixes java.lang.NoSuchMethodError: - // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; - Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - return onlinePlayersMethod.getReturnType().equals(Collection.class) - ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() - : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; - } catch (Exception e) { - // Just use the new method if the reflection failed - return Bukkit.getOnlinePlayers().size(); - } - } - - public static class MetricsBase { - - /** The version of the Metrics class. */ - public static final String METRICS_VERSION = "2.2.1"; - - private static final ScheduledExecutorService scheduler = - Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); - - private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; - - private final String platform; - - private final String serverUuid; - - private final int serviceId; - - private final Consumer appendPlatformDataConsumer; - - private final Consumer appendServiceDataConsumer; - - private final Consumer submitTaskConsumer; - - private final Supplier checkServiceEnabledSupplier; - - private final BiConsumer errorLogger; - - private final Consumer infoLogger; - - private final boolean logErrors; - - private final boolean logSentData; - - private final boolean logResponseStatusText; - - private final Set customCharts = new HashSet<>(); - - private final boolean enabled; - - /** - * Creates a new MetricsBase class instance. - * - * @param platform The platform of the service. - * @param serviceId The id of the service. - * @param serverUuid The server uuid. - * @param enabled Whether or not data sending is enabled. - * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all platform-specific data. - * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all service-specific data. - * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be - * used to delegate the data collection to a another thread to prevent errors caused by - * concurrency. Can be {@code null}. - * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. - * @param errorLogger A consumer that accepts log message and an error. - * @param infoLogger A consumer that accepts info log messages. - * @param logErrors Whether or not errors should be logged. - * @param logSentData Whether or not the sent data should be logged. - * @param logResponseStatusText Whether or not the response status text should be logged. - */ - public MetricsBase( - String platform, - String serverUuid, - int serviceId, - boolean enabled, - Consumer appendPlatformDataConsumer, - Consumer appendServiceDataConsumer, - Consumer submitTaskConsumer, - Supplier checkServiceEnabledSupplier, - BiConsumer errorLogger, - Consumer infoLogger, - boolean logErrors, - boolean logSentData, - boolean logResponseStatusText) { - this.platform = platform; - this.serverUuid = serverUuid; - this.serviceId = serviceId; - this.enabled = enabled; - this.appendPlatformDataConsumer = appendPlatformDataConsumer; - this.appendServiceDataConsumer = appendServiceDataConsumer; - this.submitTaskConsumer = submitTaskConsumer; - this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; - this.errorLogger = errorLogger; - this.infoLogger = infoLogger; - this.logErrors = logErrors; - this.logSentData = logSentData; - this.logResponseStatusText = logResponseStatusText; - checkRelocation(); - if (enabled) { - startSubmitting(); - } - } - - public void addCustomChart(CustomChart chart) { - this.customCharts.add(chart); - } - - private void startSubmitting() { - final Runnable submitTask = - () -> { - if (!enabled || !checkServiceEnabledSupplier.get()) { - // Submitting data or service is disabled - scheduler.shutdown(); - return; - } - if (submitTaskConsumer != null) { - submitTaskConsumer.accept(this::submitData); - } else { - this.submitData(); - } - }; - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution - // of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into the initial - // and second delay. - // WARNING: You must not modify and part of this Metrics class, including the submit delay or - // frequency! - // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! - long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); - long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); - scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); - scheduler.scheduleAtFixedRate( - submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); - } - - private void submitData() { - final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); - appendPlatformDataConsumer.accept(baseJsonBuilder); - final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); - appendServiceDataConsumer.accept(serviceJsonBuilder); - JsonObjectBuilder.JsonObject[] chartData = - customCharts.stream() - .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) - .filter(Objects::nonNull) - .toArray(JsonObjectBuilder.JsonObject[]::new); - serviceJsonBuilder.appendField("id", serviceId); - serviceJsonBuilder.appendField("customCharts", chartData); - baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); - baseJsonBuilder.appendField("serverUUID", serverUuid); - baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); - JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); - scheduler.execute( - () -> { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logErrors) { - errorLogger.accept("Could not submit bStats metrics data", e); - } - } - }); - } - - private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { - if (logSentData) { - infoLogger.accept("Sent bStats metrics data: " + data.toString()); - } - String url = String.format(REPORT_URL, platform); - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", "Metrics-Service/1"); - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - if (logResponseStatusText) { - infoLogger.accept("Sent data to bStats and received response: " + builder); - } - } - - /** Checks that the class was properly relocated. */ - private void checkRelocation() { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null - || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little - // "trick" ... :D - final String defaultPackage = - new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); - final String examplePackage = - new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure no one just copy & pastes the example and uses the wrong package - // names - if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) - || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - /** - * Gzips the given string. - * - * @param str The string to gzip. - * @return The gzipped string. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - } - - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public abstract static class CustomChart { - - private final String chartId; - - protected CustomChart(String chartId) { - if (chartId == null) { - throw new IllegalArgumentException("chartId must not be null"); - } - this.chartId = chartId; - } - - public JsonObjectBuilder.JsonObject getRequestJsonObject( - BiConsumer errorLogger, boolean logErrors) { - JsonObjectBuilder builder = new JsonObjectBuilder(); - builder.appendField("chartId", chartId); - try { - JsonObjectBuilder.JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - builder.appendField("data", data); - } catch (Throwable t) { - if (logErrors) { - errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return builder.build(); - } - - protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; - } - - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - /** - * An extremely simple JSON builder. - * - *

While this class is neither feature-rich nor the most performant one, it's sufficient enough - * for its use-case. - */ - public static class JsonObjectBuilder { - - private StringBuilder builder = new StringBuilder(); - - private boolean hasAtLeastOneField = false; - - public JsonObjectBuilder() { - builder.append("{"); - } - - /** - * Appends a null field to the JSON. - * - * @param key The key of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendNull(String key) { - appendFieldUnescaped(key, "null"); - return this; - } - - /** - * Appends a string field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String value) { - if (value == null) { - throw new IllegalArgumentException("JSON value must not be null"); - } - appendFieldUnescaped(key, "\"" + escape(value) + "\""); - return this; - } - - /** - * Appends an integer field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int value) { - appendFieldUnescaped(key, String.valueOf(value)); - return this; - } - - /** - * Appends an object to the JSON. - * - * @param key The key of the field. - * @param object The object. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject object) { - if (object == null) { - throw new IllegalArgumentException("JSON object must not be null"); - } - appendFieldUnescaped(key, object.toString()); - return this; - } - - /** - * Appends a string array to the JSON. - * - * @param key The key of the field. - * @param values The string array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values) - .map(value -> "\"" + escape(value) + "\"") - .collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an integer array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an object array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends a field to the object. - * - * @param key The key of the field. - * @param escapedValue The escaped value of the field. - */ - private void appendFieldUnescaped(String key, String escapedValue) { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - if (key == null) { - throw new IllegalArgumentException("JSON key must not be null"); - } - if (hasAtLeastOneField) { - builder.append(","); - } - builder.append("\"").append(escape(key)).append("\":").append(escapedValue); - hasAtLeastOneField = true; - } - - /** - * Builds the JSON string and invalidates this builder. - * - * @return The built JSON string. - */ - public JsonObject build() { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - JsonObject object = new JsonObject(builder.append("}").toString()); - builder = null; - return object; - } - - /** - * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. - * - *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. - * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). - * - * @param value The value to escape. - * @return The escaped value. - */ - private static String escape(String value) { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '"') { - builder.append("\\\""); - } else if (c == '\\') { - builder.append("\\\\"); - } else if (c <= '\u000F') { - builder.append("\\u000").append(Integer.toHexString(c)); - } else if (c <= '\u001F') { - builder.append("\\u00").append(Integer.toHexString(c)); - } else { - builder.append(c); - } - } - return builder.toString(); - } - - /** - * A super simple representation of a JSON object. - * - *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not - * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, - * JsonObject)}. - */ - public static class JsonObject { - - private final String value; - - private JsonObject(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/straight8/rambeau/metrics/VelocityMetrics.java b/src/main/java/com/straight8/rambeau/metrics/VelocityMetrics.java deleted file mode 100644 index 5c93f8f..0000000 --- a/src/main/java/com/straight8/rambeau/metrics/VelocityMetrics.java +++ /dev/null @@ -1,1026 +0,0 @@ -package com.straight8.rambeau.metrics; - -import com.google.inject.Inject; -import com.velocitypowered.api.plugin.PluginContainer; -import com.velocitypowered.api.plugin.PluginDescription; -import com.velocitypowered.api.plugin.annotation.DataDirectory; -import com.velocitypowered.api.proxy.ProxyServer; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; -import javax.net.ssl.HttpsURLConnection; - -import org.slf4j.Logger; - -public class VelocityMetrics { - - /** A factory to create new Metrics classes. */ - public static class Factory { - - private final ProxyServer server; - - private final Logger logger; - - private final Path dataDirectory; - - // The constructor is not meant to be called by the user. - // The instance is created using Dependency Injection - @Inject - private Factory(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { - this.server = server; - this.logger = logger; - this.dataDirectory = dataDirectory; - } - - /** - * Creates a new Metrics class. - * - * @param plugin The plugin instance. - * @param serviceId The id of the service. It can be found at What is my plugin id? - *

Not to be confused with Velocity's {@link PluginDescription#getId()} method! - * @return A Metrics instance that can be used to register custom charts. - *

The return value can be ignored, when you do not want to register custom charts. - */ - public VelocityMetrics make(Object plugin, int serviceId) { - return new VelocityMetrics(plugin, server, logger, dataDirectory, serviceId); - } - } - - private final PluginContainer pluginContainer; - - private final ProxyServer server; - - private MetricsBase metricsBase; - - private VelocityMetrics(Object plugin, ProxyServer server, Logger logger, Path dataDirectory, int serviceId) { - pluginContainer = - server - .getPluginManager() - .fromInstance(plugin) - .orElseThrow( - () -> new IllegalArgumentException("The provided instance is not a plugin")); - this.server = server; - File configFile = dataDirectory.getParent().resolve("bStats").resolve("config.txt").toFile(); - MetricsConfig config; - try { - config = new MetricsConfig(configFile, true); - } catch (IOException e) { - logger.error("Failed to create bStats config", e); - return; - } - metricsBase = - new MetricsBase( - "velocity", - config.getServerUUID(), - serviceId, - config.isEnabled(), - this::appendPlatformData, - this::appendServiceData, - task -> server.getScheduler().buildTask(plugin, task).schedule(), - () -> true, - logger::warn, - logger::info, - config.isLogErrorsEnabled(), - config.isLogSentDataEnabled(), - config.isLogResponseStatusTextEnabled()); - if (!config.didExistBefore()) { - // Send an info message when the bStats config file gets created for the first time - logger.info( - "Velocity and some of its plugins collect metrics and send them to bStats (https://bStats.org)."); - logger.info( - "bStats collects some basic information for plugin authors, like how many people use"); - logger.info( - "their plugin and their total player count. It's recommend to keep bStats enabled, but"); - logger.info( - "if you're not comfortable with this, you can opt-out by editing the config.txt file in"); - logger.info("the '/plugins/bStats/' folder and setting enabled to false."); - } - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - if (metricsBase != null) { - metricsBase.addCustomChart(chart); - } - } - - private void appendPlatformData(JsonObjectBuilder builder) { - builder.appendField("playerAmount", server.getPlayerCount()); - builder.appendField("managedServers", server.getAllServers().size()); - builder.appendField("onlineMode", server.getConfiguration().isOnlineMode() ? 1 : 0); - builder.appendField("velocityVersionVersion", server.getVersion().getVersion()); - builder.appendField("velocityVersionName", server.getVersion().getName()); - builder.appendField("velocityVersionVendor", server.getVersion().getVendor()); - builder.appendField("javaVersion", System.getProperty("java.version")); - builder.appendField("osName", System.getProperty("os.name")); - builder.appendField("osArch", System.getProperty("os.arch")); - builder.appendField("osVersion", System.getProperty("os.version")); - builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); - } - - private void appendServiceData(JsonObjectBuilder builder) { - builder.appendField( - "pluginVersion", pluginContainer.getDescription().getVersion().orElse("unknown")); - } - - public static class MetricsBase { - - /** The version of the Metrics class. */ - public static final String METRICS_VERSION = "2.2.1"; - - private static final ScheduledExecutorService scheduler = - Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); - - private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; - - private final String platform; - - private final String serverUuid; - - private final int serviceId; - - private final Consumer appendPlatformDataConsumer; - - private final Consumer appendServiceDataConsumer; - - private final Consumer submitTaskConsumer; - - private final Supplier checkServiceEnabledSupplier; - - private final BiConsumer errorLogger; - - private final Consumer infoLogger; - - private final boolean logErrors; - - private final boolean logSentData; - - private final boolean logResponseStatusText; - - private final Set customCharts = new HashSet<>(); - - private final boolean enabled; - - /** - * Creates a new MetricsBase class instance. - * - * @param platform The platform of the service. - * @param serviceId The id of the service. - * @param serverUuid The server uuid. - * @param enabled Whether or not data sending is enabled. - * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all platform-specific data. - * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all service-specific data. - * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be - * used to delegate the data collection to a another thread to prevent errors caused by - * concurrency. Can be {@code null}. - * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. - * @param errorLogger A consumer that accepts log message and an error. - * @param infoLogger A consumer that accepts info log messages. - * @param logErrors Whether or not errors should be logged. - * @param logSentData Whether or not the sent data should be logged. - * @param logResponseStatusText Whether or not the response status text should be logged. - */ - public MetricsBase( - String platform, - String serverUuid, - int serviceId, - boolean enabled, - Consumer appendPlatformDataConsumer, - Consumer appendServiceDataConsumer, - Consumer submitTaskConsumer, - Supplier checkServiceEnabledSupplier, - BiConsumer errorLogger, - Consumer infoLogger, - boolean logErrors, - boolean logSentData, - boolean logResponseStatusText) { - this.platform = platform; - this.serverUuid = serverUuid; - this.serviceId = serviceId; - this.enabled = enabled; - this.appendPlatformDataConsumer = appendPlatformDataConsumer; - this.appendServiceDataConsumer = appendServiceDataConsumer; - this.submitTaskConsumer = submitTaskConsumer; - this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; - this.errorLogger = errorLogger; - this.infoLogger = infoLogger; - this.logErrors = logErrors; - this.logSentData = logSentData; - this.logResponseStatusText = logResponseStatusText; - checkRelocation(); - if (enabled) { - startSubmitting(); - } - } - - public void addCustomChart(CustomChart chart) { - this.customCharts.add(chart); - } - - private void startSubmitting() { - final Runnable submitTask = - () -> { - if (!enabled || !checkServiceEnabledSupplier.get()) { - // Submitting data or service is disabled - scheduler.shutdown(); - return; - } - if (submitTaskConsumer != null) { - submitTaskConsumer.accept(this::submitData); - } else { - this.submitData(); - } - }; - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution - // of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into the initial - // and second delay. - // WARNING: You must not modify and part of this Metrics class, including the submit delay or - // frequency! - // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! - long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); - long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); - scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); - scheduler.scheduleAtFixedRate( - submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); - } - - private void submitData() { - final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); - appendPlatformDataConsumer.accept(baseJsonBuilder); - final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); - appendServiceDataConsumer.accept(serviceJsonBuilder); - JsonObjectBuilder.JsonObject[] chartData = - customCharts.stream() - .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) - .filter(Objects::nonNull) - .toArray(JsonObjectBuilder.JsonObject[]::new); - serviceJsonBuilder.appendField("id", serviceId); - serviceJsonBuilder.appendField("customCharts", chartData); - baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); - baseJsonBuilder.appendField("serverUUID", serverUuid); - baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); - JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); - scheduler.execute( - () -> { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logErrors) { - errorLogger.accept("Could not submit bStats metrics data", e); - } - } - }); - } - - private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { - if (logSentData) { - infoLogger.accept("Sent bStats metrics data: " + data.toString()); - } - String url = String.format(REPORT_URL, platform); - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", "Metrics-Service/1"); - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - if (logResponseStatusText) { - infoLogger.accept("Sent data to bStats and received response: " + builder); - } - } - - /** Checks that the class was properly relocated. */ - private void checkRelocation() { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null - || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little - // "trick" ... :D - final String defaultPackage = - new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); - final String examplePackage = - new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure no one just copy & pastes the example and uses the wrong package - // names - if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) - || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - /** - * Gzips the given string. - * - * @param str The string to gzip. - * @return The gzipped string. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - } - - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public abstract static class CustomChart { - - private final String chartId; - - protected CustomChart(String chartId) { - if (chartId == null) { - throw new IllegalArgumentException("chartId must not be null"); - } - this.chartId = chartId; - } - - public JsonObjectBuilder.JsonObject getRequestJsonObject( - BiConsumer errorLogger, boolean logErrors) { - JsonObjectBuilder builder = new JsonObjectBuilder(); - builder.appendField("chartId", chartId); - try { - JsonObjectBuilder.JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - builder.appendField("data", data); - } catch (Throwable t) { - if (logErrors) { - errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return builder.build(); - } - - protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; - } - - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - /** - * An extremely simple JSON builder. - * - *

While this class is neither feature-rich nor the most performant one, it's sufficient enough - * for its use-case. - */ - public static class JsonObjectBuilder { - - private StringBuilder builder = new StringBuilder(); - - private boolean hasAtLeastOneField = false; - - public JsonObjectBuilder() { - builder.append("{"); - } - - /** - * Appends a null field to the JSON. - * - * @param key The key of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendNull(String key) { - appendFieldUnescaped(key, "null"); - return this; - } - - /** - * Appends a string field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String value) { - if (value == null) { - throw new IllegalArgumentException("JSON value must not be null"); - } - appendFieldUnescaped(key, "\"" + escape(value) + "\""); - return this; - } - - /** - * Appends an integer field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int value) { - appendFieldUnescaped(key, String.valueOf(value)); - return this; - } - - /** - * Appends an object to the JSON. - * - * @param key The key of the field. - * @param object The object. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject object) { - if (object == null) { - throw new IllegalArgumentException("JSON object must not be null"); - } - appendFieldUnescaped(key, object.toString()); - return this; - } - - /** - * Appends a string array to the JSON. - * - * @param key The key of the field. - * @param values The string array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values) - .map(value -> "\"" + escape(value) + "\"") - .collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an integer array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an object array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends a field to the object. - * - * @param key The key of the field. - * @param escapedValue The escaped value of the field. - */ - private void appendFieldUnescaped(String key, String escapedValue) { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - if (key == null) { - throw new IllegalArgumentException("JSON key must not be null"); - } - if (hasAtLeastOneField) { - builder.append(","); - } - builder.append("\"").append(escape(key)).append("\":").append(escapedValue); - hasAtLeastOneField = true; - } - - /** - * Builds the JSON string and invalidates this builder. - * - * @return The built JSON string. - */ - public JsonObject build() { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - JsonObject object = new JsonObject(builder.append("}").toString()); - builder = null; - return object; - } - - /** - * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. - * - *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. - * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). - * - * @param value The value to escape. - * @return The escaped value. - */ - private static String escape(String value) { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '"') { - builder.append("\\\""); - } else if (c == '\\') { - builder.append("\\\\"); - } else if (c <= '\u000F') { - builder.append("\\u000").append(Integer.toHexString(c)); - } else if (c <= '\u001F') { - builder.append("\\u00").append(Integer.toHexString(c)); - } else { - builder.append(c); - } - } - return builder.toString(); - } - - /** - * A super simple representation of a JSON object. - * - *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not - * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, - * JsonObject)}. - */ - public static class JsonObject { - - private final String value; - - private JsonObject(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - } - - /** - * A simple config for bStats. - * - *

This class is not used by every platform. - */ - public static class MetricsConfig { - - private final File file; - - private final boolean defaultEnabled; - - private String serverUUID; - - private boolean enabled; - - private boolean logErrors; - - private boolean logSentData; - - private boolean logResponseStatusText; - - private boolean didExistBefore = true; - - public MetricsConfig(File file, boolean defaultEnabled) throws IOException { - this.file = file; - this.defaultEnabled = defaultEnabled; - setupConfig(); - } - - public String getServerUUID() { - return serverUUID; - } - - public boolean isEnabled() { - return enabled; - } - - public boolean isLogErrorsEnabled() { - return logErrors; - } - - public boolean isLogSentDataEnabled() { - return logSentData; - } - - public boolean isLogResponseStatusTextEnabled() { - return logResponseStatusText; - } - - /** - * Checks whether the config file did exist before or not. - * - * @return If the config did exist before. - */ - public boolean didExistBefore() { - return didExistBefore; - } - - /** Creates the config file if it does not exist and read its content. */ - private void setupConfig() throws IOException { - if (!file.exists()) { - // Looks like it's the first time we create it (or someone deleted it). - didExistBefore = false; - writeConfig(); - } - readConfig(); - if (serverUUID == null) { - // Found a malformed config file with no UUID. Let's recreate it. - writeConfig(); - readConfig(); - } - } - - /** Creates a config file with teh default content. */ - private void writeConfig() throws IOException { - List configContent = new ArrayList<>(); - configContent.add( - "# bStats (https://bStats.org) collects some basic information for plugin authors, like"); - configContent.add( - "# how many people use their plugin and their total player count. It's recommended to keep"); - configContent.add( - "# bStats enabled, but if you're not comfortable with this, you can turn this setting off."); - configContent.add( - "# There is no performance penalty associated with having metrics enabled, and data sent to"); - configContent.add("# bStats is fully anonymous."); - configContent.add("enabled=" + defaultEnabled); - configContent.add("server-uuid=" + UUID.randomUUID()); - configContent.add("log-errors=false"); - configContent.add("log-sent-data=false"); - configContent.add("log-response-status-text=false"); - writeFile(file, configContent); - } - - /** Reads the content of the config file. */ - private void readConfig() throws IOException { - List lines = readFile(file); - if (lines == null) { - throw new AssertionError("Content of newly created file is null"); - } - enabled = getConfigValue("enabled", lines).map("true"::equals).orElse(true); - serverUUID = getConfigValue("server-uuid", lines).orElse(null); - logErrors = getConfigValue("log-errors", lines).map("true"::equals).orElse(false); - logSentData = getConfigValue("log-sent-data", lines).map("true"::equals).orElse(false); - logResponseStatusText = - getConfigValue("log-response-status-text", lines).map("true"::equals).orElse(false); - } - - /** - * Gets a config setting from the given list of lines of the file. - * - * @param key The key for the setting. - * @param lines The lines of the file. - * @return The value of the setting. - */ - private Optional getConfigValue(String key, List lines) { - return lines.stream() - .filter(line -> line.startsWith(key + "=")) - .map(line -> line.replaceFirst(Pattern.quote(key + "="), "")) - .findFirst(); - } - - /** - * Reads the text content of the given file. - * - * @param file The file to read. - * @return The lines of the given file. - */ - private List readFile(File file) throws IOException { - if (!file.exists()) { - return null; - } - try (FileReader fileReader = new FileReader(file); - BufferedReader bufferedReader = new BufferedReader(fileReader)) { - return bufferedReader.lines().collect(Collectors.toList()); - } - } - - /** - * Writes the given lines to the given file. - * - * @param file The file to write to. - * @param lines The lines to write. - */ - private void writeFile(File file, List lines) throws IOException { - if (!file.exists()) { - file.getParentFile().mkdirs(); - file.createNewFile(); - } - try (FileWriter fileWriter = new FileWriter(file); - BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { - for (String line : lines) { - bufferedWriter.write(line); - bufferedWriter.newLine(); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/straight8/rambeau/util/CommandPageUtils.java b/src/main/java/com/straight8/rambeau/util/CommandPageUtils.java index 68c24ec..02430d8 100644 --- a/src/main/java/com/straight8/rambeau/util/CommandPageUtils.java +++ b/src/main/java/com/straight8/rambeau/util/CommandPageUtils.java @@ -3,13 +3,14 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import org.jspecify.annotations.NonNull; public final class CommandPageUtils { private CommandPageUtils() { } - public static final boolean isInteger(String str) { + public static boolean isInteger(String str) { try { Integer.valueOf(str); return true; @@ -18,7 +19,7 @@ public static final boolean isInteger(String str) { } } - public static final List getNextInteger(String argument, int maxPage) { + public static @NonNull List getNextInteger(@NonNull String argument, int maxPage) { List options = new ArrayList<>(); int curNr; if (argument.isEmpty()) { @@ -26,7 +27,7 @@ public static final List getNextInteger(String argument, int maxPage) { } else if (!isInteger(argument)) { return options; } else { - curNr = Integer.valueOf(argument); + curNr = Integer.parseInt(argument); } if (curNr * 10 > maxPage) { return options; @@ -42,7 +43,7 @@ public static final List getNextInteger(String argument, int maxPage) { return options; } - public static List getPage(List items, int page, int perPage) { + public static @NonNull List getPage(@NonNull List items, int page, int perPage) { page = page - 1; // 0-starting page List list = new ArrayList<>(); int startItem = page * perPage; @@ -56,14 +57,14 @@ public static List getPage(List items, int page, int perPage) { return list; } - public static final String getSpacingFor(String name, int maxLength, boolean isPlayer) { + public static @NonNull String getSpacingFor(String name, int maxLength, boolean isPlayer) { if (isPlayer) { return " "; } return " ".repeat(maxLength - name.length()); } - public static final int getMaxNameLength(Function nameGetter, List plugins) { + public static int getMaxNameLength(Function nameGetter, @NonNull List plugins) { List names = new ArrayList<>(); for (T plugin : plugins) { names.add(nameGetter.apply(plugin)); @@ -71,7 +72,7 @@ public static final int getMaxNameLength(Function nameGetter, Lis return getMaxNameLength(names); } - public static final int getMaxNameLength(List pluginNames) { + public static int getMaxNameLength(@NonNull List pluginNames) { int len = 0; for (String name : pluginNames) { if (name.length() > len) { @@ -80,5 +81,4 @@ public static final int getMaxNameLength(List pluginNames) { } return len + 1; } - } diff --git a/src/main/java/com/straight8/rambeau/util/StringUtil.java b/src/main/java/com/straight8/rambeau/util/StringUtil.java deleted file mode 100644 index 791ea70..0000000 --- a/src/main/java/com/straight8/rambeau/util/StringUtil.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.straight8.rambeau.util; - -import java.util.Collection; - -public final class StringUtil { - - private StringUtil() { - } - - // THE FOLLOWING TWO METHODS ARE COPIED FROM bukkit: - // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/util/StringUtil.java - // copied on 24.03.2023 - /** - * Copies all elements from the iterable collection of originals to the - * collection provided. - * - * @param the collection of strings - * @param token String to search for - * @param originals An iterable collection of strings to filter. - * @param collection The collection to add matches to - * @return the collection provided that would have the elements copied - * into - * @throws UnsupportedOperationException if the collection is immutable - * and originals contains a string which - * starts with the specified - * search string. - * @throws IllegalArgumentException if originals contains a null element. - * Note: the collection may be modified - * before this is thrown - */ - public static > T copyPartialMatches(final String token, - final Iterable originals, final T collection) - throws UnsupportedOperationException, IllegalArgumentException { - for (String string : originals) { - if (startsWithIgnoreCase(string, token)) { - collection.add(string); - } - } - - return collection; - } - - /** - * This method uses a region to check case-insensitive equality. This - * means the internal array does not need to be copied like a - * toLowerCase() call would. - * - * @param string String to check - * @param prefix Prefix of string to compare - * @return true if provided string starts with, ignoring case, the prefix - * provided - * @throws IllegalArgumentException if string is null - */ - public static boolean startsWithIgnoreCase(final String string, final String prefix) - throws IllegalArgumentException, NullPointerException { - if (string.length() < prefix.length()) { - return false; - } - return string.regionMatches(true, 0, prefix, 0, prefix.length()); - } - -} diff --git a/src/main/java/com/straight8/rambeau/velocity/PluginComparator.java b/src/main/java/com/straight8/rambeau/velocity/PluginComparator.java deleted file mode 100644 index 3208cc0..0000000 --- a/src/main/java/com/straight8/rambeau/velocity/PluginComparator.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.straight8.rambeau.velocity; - -import com.velocitypowered.api.plugin.PluginContainer; - -import java.util.Comparator; - -public class PluginComparator implements Comparator { - - @Override - public int compare(PluginContainer p1, PluginContainer p2) { - if (!(p1 instanceof PluginContainer) || !(p2 instanceof PluginContainer)) { - throw new ClassCastException(); - } - - String name1; - String name2; - - if(p1.getDescription().getName().isPresent()) { - name1 = p1.getDescription().getName().get(); - } else if(p1.getDescription().getId().equalsIgnoreCase("serverlistplus")) { - name1 = SLPUtils.getSLPName(); - } else { - name1 = ""; - } - - if(p2.getDescription().getName().isPresent()) { - name2 = p2.getDescription().getName().get(); - } else if(p2.getDescription().getId().equalsIgnoreCase("serverlistplus")) { - name2 = SLPUtils.getSLPName(); - } else { - name2 = ""; - } - - if(name1 != null && name2 != null) { - return name1.compareToIgnoreCase(name2); - } - return -1; - } -} diff --git a/src/main/java/com/straight8/rambeau/velocity/PluginVersionsCmd.java b/src/main/java/com/straight8/rambeau/velocity/PluginVersionsCmd.java deleted file mode 100644 index 7624e59..0000000 --- a/src/main/java/com/straight8/rambeau/velocity/PluginVersionsCmd.java +++ /dev/null @@ -1,246 +0,0 @@ -package com.straight8.rambeau.velocity; - -import com.straight8.rambeau.util.CommandPageUtils; -import com.straight8.rambeau.util.StringUtil; -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.command.RawCommand; -import com.velocitypowered.api.plugin.PluginContainer; -import com.velocitypowered.api.proxy.Player; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.JoinConfiguration; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -public class PluginVersionsCmd implements RawCommand { - private static final int LINES_PER_PAGE = 10; - - private final PluginVersionsVelocity plugin; - - public PluginVersionsCmd(PluginVersionsVelocity plugin) { - this.plugin = plugin; - } - - private void showUsage(Invocation invocation) { - CommandSource source = invocation.source(); - StringBuilder sb = new StringBuilder(); - if (source.hasPermission("pluginversions.list")) { - sb.append("/pvv list [page]"); - } - if (source.hasPermission("pluginversions.reload")) { - if (sb.length() > 1) { - sb.append("\n"); - } - sb.append("/pvv reload"); - } - source.sendMessage(Component.text(sb.toString())); - } - - @Override - public void execute(Invocation invocation) { - CommandSource sender = invocation.source(); - // Get the arguments after the command alias - String[] args = invocation.arguments().split(" "); - - if (args.length == 0 || args[0].isEmpty()) { - showUsage(invocation); - return; - } - String cmdLowercase = args[0].toLowerCase(); - - if (sender instanceof Player) { - if (!sender.hasPermission("pluginversions." + cmdLowercase)) { - String senderName = ((Player) sender).getUsername(); - sender.sendMessage(Component.text("You do not have permission to run this command")); - this.plugin - .log(senderName + " attempted to run command pv " + cmdLowercase + ", but lacked permissions"); - return; - } - } - - if (cmdLowercase.equals("list")) { - List pluginList = new ArrayList<>(this.plugin.getServer().getPluginManager().getPlugins()); - - if (pluginList.isEmpty()) { - sender.sendMessage(Component.text("No plugins loaded")); - return; - } - - pluginList.sort(new PluginComparator()); - - // Identify the page to display. Page 0 indicates the entire list. - int page = 0; - if (args.length > 1) { - try { - page = Integer.parseInt(args[1]); - } catch (Exception ignored) { - } - } - // Set page to 0 if illegal page was requested - page = Math.max(page, 0); - - if (page > 0) { - if (((page - 1) * LINES_PER_PAGE) < pluginList.size()) { - String msg = plugin.getConfig().getString("page-header-format", - "PluginVersions ===== page {page} ====="); - sender.sendMessage(Color.color(msg.replace("{page}", String.valueOf(page)))); - } - int maxSpacing = CommandPageUtils.getMaxNameLength(plugin -> { - Optional name = plugin.getDescription().getName(); - return name.isPresent() ? name.get() : "N/A"; - }, CommandPageUtils.getPage(pluginList, page, LINES_PER_PAGE)); - for (int i = ((page - 1) * LINES_PER_PAGE); i < pluginList.size() && i < (page * LINES_PER_PAGE); i++) { - PluginContainer p = pluginList.get(i); - - String msg = plugin.getConfig().getString("enabled-version-format", "&a{name}{spacing}&e{version}"); - String spacing = CommandPageUtils.getSpacingFor(p.getDescription().getName().get(), maxSpacing, - sender instanceof Player); - if (p.getDescription().getName().isPresent() && p.getDescription().getVersion().isPresent()) { - Component comp = Color - .color(msg.replace("{name}", p.getDescription().getName().get()).replace("{version}", - p.getDescription().getVersion().get()).replace("{spacing}", spacing)); - sender.sendMessage(comp); - } else if (p.getDescription().getId().equalsIgnoreCase("serverlistplus")) { - Component comp = Color.color(msg.replace("{name}", SLPUtils.getSLPName()).replace("{version}", - SLPUtils.getSLPVersion()).replace("{spacing}", spacing)); - sender.sendMessage(comp); - } - } - } else { - int maxSpacing = CommandPageUtils.getMaxNameLength(plugin -> { - Optional name = plugin.getDescription().getName(); - return name.isPresent() ? name.get() : "N/A"; - }, pluginList); - for (PluginContainer p : pluginList) { - String msg = plugin.getConfig().getString("enabled-version-format", "&a{name}{spacing}&e{version}"); - String spacing = CommandPageUtils.getSpacingFor(p.getDescription().getName().get(), maxSpacing, - sender instanceof Player); - if (p.getDescription().getName().isPresent() && p.getDescription().getVersion().isPresent()) { - Component comp = Color - .color(msg.replace("{name}", p.getDescription().getName().get()).replace("{version}", - p.getDescription().getVersion().get()).replace("{spacing}", spacing)); - sender.sendMessage(comp); - } else if (p.getDescription().getId().equalsIgnoreCase("serverlistplus")) { - Component comp = Color - .color(msg.replace("{name}", p.getDescription().getName().get()).replace("{version}", - p.getDescription().getVersion().get()).replace("{spacing}", spacing)); - sender.sendMessage(comp); - } - } - } - // break; - } else if (cmdLowercase.equals("reload")) { - YamlConfig.createFiles("config"); - PluginVersionsVelocity.getInstance().ReadConfigValuesFromFile(); - - sender.sendMessage(Component.text("Reloaded §bPluginVersions/config.yml")); - } else { - sender.sendMessage(Component.text("Unrecognized command option " + cmdLowercase)); - } - } - - @Override - public List suggest(Invocation invocation) { - String[] args = invocation.arguments().split(" "); - if ((args.length == 1 && !invocation.arguments().endsWith(" ")) - || (args.length == 0 && invocation.arguments().endsWith(" "))) { - List options = new ArrayList<>(); - if (invocation.source().hasPermission("pluginversions.list")) { - options.add("list"); - } - if (invocation.source().hasPermission("pluginversions.reload")) { - options.add("relaod"); - } - if (args.length < 1) { - return options; - } - return StringUtil.copyPartialMatches(args[0], options, new ArrayList<>()); - } else if (args[0].equalsIgnoreCase("list") && ((args.length == 2 && !invocation.arguments().endsWith(" ")) || - args.length == 1 && invocation.arguments().endsWith(" "))) { - String arg = args.length == 2 ? args[1] : ""; - return CommandPageUtils.getNextInteger(arg, - (plugin.getServer().getPluginManager().getPlugins().size() + LINES_PER_PAGE - 1) / LINES_PER_PAGE); - } else { - return Collections.emptyList(); - } - } - - private static final char COLOR_STR = '&'; - - private static enum Color { - BLACK('0', NamedTextColor.BLACK), - DARK_BLUE('1', NamedTextColor.DARK_BLUE), - DARK_GREEN('2', NamedTextColor.DARK_GREEN), - DARK_AQUA('3', NamedTextColor.DARK_AQUA), - DARK_RED('4', NamedTextColor.DARK_RED), - DARK_PURPLE('5', NamedTextColor.DARK_PURPLE), - GOLD('6', NamedTextColor.GOLD), - GRAY('7', NamedTextColor.GRAY), - DARK_GRAY('8', NamedTextColor.DARK_GRAY), - BLUE('9', NamedTextColor.BLUE), - GREEN('a', NamedTextColor.GREEN), - AQUA('b', NamedTextColor.AQUA), - RED('c', NamedTextColor.RED), - LIGHT_PURPLE('d', NamedTextColor.LIGHT_PURPLE), - YELLOW('e', NamedTextColor.YELLOW), - WHITE('f', NamedTextColor.WHITE), - // MAGIC('k', NamedTextColor.MAGIC), - BOLD('l', NamedTextColor.BLUE), - // UNDERLINE('n', NamedTextColor.UNDERLINE), - // ITALIC('o', NamedTextColor.ITALIC), - // RESET('r', NamedTextColor.RESET), - ; - - private static final String COLOR_CHARS; - - private final char c; - private final TextColor color; - - private Color(char c, TextColor color) { - this.c = c; - this.color = color; - } - - public static Component color(String msg) { - List compList = new ArrayList<>(); - char[] charArray = msg.toCharArray(); - TextColor nextColor = NamedTextColor.WHITE; - boolean prevHadColor = false; - int lastEnd = -2; - for (int i = 0; i < charArray.length - 1; i++) { - if (prevHadColor) { - prevHadColor = false; - continue; - } - char potColorChar = charArray[i]; - char charAfter = charArray[i + 1]; - int index = COLOR_CHARS.indexOf(String.valueOf(charAfter)); - if (potColorChar == COLOR_STR && index >= 0) { - String msgUntil = msg.substring(lastEnd + 2, i); - compList.add(Component.text(msgUntil, nextColor)); - nextColor = values()[index].color; - lastEnd = i; - prevHadColor = true; - } - } - if (lastEnd + 2 < msg.length()) { - compList.add(Component.text(msg.substring(lastEnd + 2, msg.length()), nextColor)); - } - return Component.join(JoinConfiguration.noSeparators(), compList); - } - - static { - StringBuilder colorChars = new StringBuilder(); - for (Color color : values()) { - colorChars.append(color.c); - } - COLOR_CHARS = colorChars.toString(); - } - - } - -} diff --git a/src/main/java/com/straight8/rambeau/velocity/PluginVersionsVelocity.java b/src/main/java/com/straight8/rambeau/velocity/PluginVersionsVelocity.java deleted file mode 100644 index 9a7a7b9..0000000 --- a/src/main/java/com/straight8/rambeau/velocity/PluginVersionsVelocity.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.straight8.rambeau.velocity; - -import com.google.inject.Inject; -import com.straight8.rambeau.metrics.VelocityMetrics; -import com.velocitypowered.api.command.CommandManager; -import com.velocitypowered.api.command.CommandMeta; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; -import com.velocitypowered.api.plugin.Plugin; -import com.velocitypowered.api.plugin.annotation.DataDirectory; -import com.velocitypowered.api.proxy.ProxyServer; -import org.slf4j.Logger; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.nio.file.Path; - -@Plugin(id = "pluginversions", name = "PluginVersions", version = "1.3.5", description = "List installed plugins and versions alphabetically", authors = {"drives_a_ford", "GabrielHD150", "SlimeDog"}) -public class PluginVersionsVelocity { - - private static PluginVersionsVelocity instance; - private YamlConfig yamlConfig; - - private final Path dataDirectory; - private final ProxyServer server; - private final Logger logger; - - private boolean configurationSendMetrics = true; - private boolean checkUpdates = true; - private final VelocityMetrics.Factory metricsFactory; - - @Inject - public PluginVersionsVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, VelocityMetrics.Factory metricsFactory) { - instance = this; - - this.server = server; - this.logger = logger; - - this.dataDirectory = dataDirectory; - this.metricsFactory = metricsFactory; - } - - @Subscribe - public void onProxyInitialization(ProxyInitializeEvent event) { - YamlConfig.createFiles("config"); - - try { - this.yamlConfig = new YamlConfig(new File(this.dataDirectory.toFile(), "config.yml")); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - - this.ReadConfigValuesFromFile(); - - if (configurationSendMetrics) { - - // All you have to do is adding the following two lines in your onProxyInitialization method. - // You can find the plugin ids of your plugins on the page https://bstats.org/what-is-my-plugin-id - metricsFactory.make(this, 13032); - } - - CommandManager commandManager = server.getCommandManager(); - CommandMeta meta = commandManager.metaBuilder("pluginversions") - // Specify other aliases (optional) - .aliases("pluginversionsvelocity") - .aliases("pvv") - .build(); - - commandManager.register(meta, new PluginVersionsCmd(this)); - - if (checkUpdates) { - new UpdateChecker(this, (response, version)-> { - switch (response) { - case LATEST: { - this.logger.info("Running latest version!"); - break; - } - case UNAVAILABLE: { - this.logger.info("Unable to check for new version"); - break; - } - case FOUND_NEW: { - this.logger.warn("Running outdated version! New version available:" + version); - break; - } - } - }).check(); - } - } - - public static PluginVersionsVelocity getInstance() { - return instance; - } - - public YamlConfig getConfig() { - return yamlConfig; - } - - public void ReadConfigValuesFromFile() { - YamlConfig configNode = this.yamlConfig; - - // Optimized the code to read the configuration options - configurationSendMetrics = configNode.getBoolean("enable-metrics", true); - checkUpdates = configNode.getBoolean("check-for-updates", true); - } - - public void log(String logString) { - this.logger.info("[PluginVersions] " + logString); - } - - public ProxyServer getServer() { - return server; - } - - public File getDataFolder() { - return this.dataDirectory.toFile(); - } - - public InputStream getResourceAsStream(String name) { - return this.getClass().getClassLoader().getResourceAsStream(name); - } -} diff --git a/src/main/java/com/straight8/rambeau/velocity/SLPUtils.java b/src/main/java/com/straight8/rambeau/velocity/SLPUtils.java deleted file mode 100644 index 54dfe92..0000000 --- a/src/main/java/com/straight8/rambeau/velocity/SLPUtils.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.straight8.rambeau.velocity; - -import net.minecrell.serverlistplus.core.CoreDescription; -import net.minecrell.serverlistplus.core.ServerListPlusCore; - -public class SLPUtils { - - public static String getSLPName() { - CoreDescription info = CoreDescription.load(ServerListPlusCore.getInstance()); - - return info.getName(); - } - - public static String getSLPVersion() { - CoreDescription info = CoreDescription.load(ServerListPlusCore.getInstance()); - - return info.getVersion(); - } -} diff --git a/src/main/java/com/straight8/rambeau/velocity/UpdateChecker.java b/src/main/java/com/straight8/rambeau/velocity/UpdateChecker.java deleted file mode 100644 index ebdb608..0000000 --- a/src/main/java/com/straight8/rambeau/velocity/UpdateChecker.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.straight8.rambeau.velocity; - -import com.google.common.io.Resources; -import com.google.common.net.HttpHeaders; -import com.straight8.rambeau.bungee.PluginVersionsBungee; - -import javax.net.ssl.HttpsURLConnection; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.Charset; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; - -public class UpdateChecker { - private static final String SPIGOT_URL = "https://api.spigotmc.org/legacy/update.php?resource=70927"; - - private final PluginVersionsVelocity plugin; - - private final String currentVersion; - - private final BiConsumer versionResponse; - - public UpdateChecker(PluginVersionsVelocity plugin, BiConsumer consumer) { - this.plugin = plugin; - this.currentVersion = "1.1.0"; - this.versionResponse = consumer; - } - - public void check() { - plugin.getServer().getScheduler().buildTask(this.plugin, () -> { - try { - HttpURLConnection httpURLConnection = (HttpsURLConnection) new URL(SPIGOT_URL).openConnection(); - httpURLConnection.setRequestMethod("GET"); - httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, "Mozilla/5.0"); - - String fetchedVersion = Resources.toString(httpURLConnection.getURL(), Charset.defaultCharset()); - - boolean latestVersion = fetchedVersion.equalsIgnoreCase(this.currentVersion); - - plugin.getServer().getScheduler().buildTask(this.plugin, () -> this.versionResponse.accept(latestVersion ? VersionResponse.LATEST : VersionResponse.FOUND_NEW, latestVersion ? this.currentVersion : fetchedVersion)).schedule(); - } catch (IOException exception) { - exception.printStackTrace(); - plugin.getServer().getScheduler().buildTask(this.plugin, () -> this.versionResponse.accept(VersionResponse.UNAVAILABLE, null)).schedule(); - } - }).schedule(); - } - - public enum VersionResponse { - LATEST, - FOUND_NEW, - UNAVAILABLE - } - -} \ No newline at end of file diff --git a/src/main/java/com/straight8/rambeau/velocity/YamlConfig.java b/src/main/java/com/straight8/rambeau/velocity/YamlConfig.java deleted file mode 100644 index abd589d..0000000 --- a/src/main/java/com/straight8/rambeau/velocity/YamlConfig.java +++ /dev/null @@ -1,220 +0,0 @@ -package com.straight8.rambeau.velocity; - -import org.yaml.snakeyaml.Yaml; - -import java.io.*; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -public class YamlConfig { - private static final String DELIMITER = "."; - private final String currentPath; - private final String name; - private final Map map; - - public static void createFiles(String file) { - if (!PluginVersionsVelocity.getInstance().getDataFolder().exists()) { - PluginVersionsVelocity.getInstance().getDataFolder().mkdir(); - } - - File fileconfig = new File(PluginVersionsVelocity.getInstance().getDataFolder(), file+".yml"); - if (!fileconfig.exists()) { - try (InputStream in = PluginVersionsVelocity.getInstance().getResourceAsStream(file+".yml")) { - Files.copy(in, fileconfig.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public YamlConfig(File file) throws FileNotFoundException { - this(new Yaml().load(new FileInputStream(file)), ""); - } - - private YamlConfig(Map map, String currentPath) { - this.map = map; - this.currentPath = currentPath; - String[] split = currentPath.split(Pattern.quote(DELIMITER)); - this.name = split[split.length - 1]; // last part - } - - public Collection getKeys(boolean deep) { - if (deep) { - throw new IllegalStateException("Deep keys are not supported at this time"); - } - return map.keySet(); - } - - public Map getValues(boolean deep) { - if (!deep) { - throw new IllegalStateException("Shallow values are not supported at this time"); - } - return map; - } - - public boolean contains(String path) { - Map curMap = map; - Object result; - String[] split = path.split(Pattern.quote(DELIMITER)); - int counter = 0; - for (String curPath : split) { - counter++; - result = curMap.get(curPath); - if (!(result instanceof Map)) { - return counter >= split.length; // not found -// contains - } - curMap = (Map) result; // unchecked - } - return true; // result is a map - } - - public boolean contains(String path, boolean ignoreDefault) { - return contains(path); // TODO - better - } - - public boolean isSet(String path) { - return contains(path); // TODO - better - } - - public String getCurrentPath() { - return currentPath; - } - - public String getName() { - return name; - } - - public Object get(String path) { - Map curMap = map; - Object result = null; - String[] split = path.split(Pattern.quote(DELIMITER)); - int counter = 0; - for (String curPath : split) { - counter++; - result = curMap.get(curPath); - if (!(result instanceof Map)) { - if (counter < split.length) { - return null; // not found - } - return result; - } - curMap = (Map) result; // unchecked - } - return result; - } - - public Object get(String path, Object def) { - Object o = get(path); - return o == null ? def : o; - } - - public String getString(String path) { - Object o = get(path); - return o == null ? null : String.valueOf(o); - } - - public String getString(String path, String def) { - String str = getString(path); - return str == null ? def : str; - } - - public int getInt(String path) { - return getInt(path, 0); - } - - public int getInt(String path, int def) { - if (!contains(path) && !isSet(path)) { - return def; - } - try { - return Integer.parseInt(getString(path)); - } catch (NumberFormatException e) { - return def; - } - } - - public boolean getBoolean(String path) { - return getBoolean(path, false); - } - - public boolean getBoolean(String path, boolean def) { - if (!contains(path) && !isSet(path)) { - return def; - } - try { - return Boolean.parseBoolean(getString(path)); - } catch (NumberFormatException e) { - return def; - } - } - - public double getDouble(String path) { - return getDouble(path, 0.0D); - } - - public double getDouble(String path, double def) { - if (!contains(path) && !isSet(path)) { - return def; - } - try { - return Double.parseDouble(getString(path)); - } catch (NumberFormatException e) { - return def; - } - } - - public long getLong(String path) { - return getLong(path, 0L); - } - - public long getLong(String path, long def) { - if (!contains(path) && !isSet(path)) { - return def; - } - try { - return Long.parseLong(getString(path)); - } catch (NumberFormatException e) { - return def; - } - } - - public List getList(String path) { - if (!contains(path)) { - return null; - } - Object o = get(path); - if (o instanceof List) { - return (List) o; - } - return null; - } - - public List getList(String path, List def) { - List list = getList(path); - return list == null ? def : list; - } - - public List getStringList(String path) { - List list = getList(path); - if (list == null || list.isEmpty()) { - return new ArrayList<>(); // empty list - } - if (list.get(0) instanceof String) { - return (List) list; // unchecked - } - return new ArrayList<>(); - } - - public YamlConfig getConfigurationSection(String path) { - Object res = get(path); - if (res instanceof Map) { - return new YamlConfig((Map) res, currentPath + DELIMITER + path); // unchecked - } - return null; - } -} \ No newline at end of file diff --git a/src/main/resources/bungee.yml b/src/main/resources/bungee.yml deleted file mode 100644 index 153762b..0000000 --- a/src/main/resources/bungee.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: PluginVersions -version: ${project.version} -main: com.straight8.rambeau.bungee.PluginVersionsBungee -description: List installed plugins and versions alphabetically -authors: [drives_a_ford, GabrielHD150, SlimeDog] \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 389e898..3f982b0 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -11,12 +11,6 @@ # enable-metrics: true -# Check for updates. -# -check-for-updates: true -# Update source may be either Hangar (default) or SpigotMC. -update-source: Hangar - # When executed at the console, # {spacing} will enable tabular output (in-game does not support tabular output). # To disable tabular output at the console, replace "{spacing}" with " " in the format strings. diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 8723466..01e2f8b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,16 +4,16 @@ name: PluginVersions version: ${project.version} -api-version: 1.13 +api-version: 1.21.11 main: com.straight8.rambeau.bukkit.PluginVersionsBukkit description: List installed plugins and versions alphabetically -authors: [mart-r, GabrielHD150, SlimeDog] +authors: [ mart-r, GabrielHD150, SlimeDog ] commands: pluginversions: description: Commands for PluginVersions usage: list | reload - aliases: [pv] + aliases: [ pv ] pluginversions list: description: List loaded plugins and versions usage: [page]