commit 6de67cce13d0d68ae3d0ec9a8469d401cbbdd98b
Author: Taah
Date: Sun Apr 10 22:06:13 2022 -0700
update to paper 1.18.2 and switch to gradle + fix up command sender to handle perms
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c37caf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,118 @@
+# User-specific stuff
+.idea/
+
+*.iml
+*.ipr
+*.iws
+
+# IntelliJ
+out/
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+# General
+.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 thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+.gradle
+build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Cache of project
+.gradletasknamecache
+
+**/build/
+
+# Common working directory
+run/
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..3e6bcbd
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,78 @@
+import java.nio.charset.StandardCharsets
+
+plugins {
+ id "java"
+ id "net.minecrell.plugin-yml.bukkit" version "0.6.1-SNAPSHOT"
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+ id "maven-publish"
+}
+
+group = "me.totalfreedom"
+version = "4.8-SNAPSHOT"
+
+repositories {
+ mavenCentral()
+ maven {
+ url = uri("https://jitpack.io")
+ }
+ maven {
+ url = uri("https://papermc.io/repo/repository/maven-public/")
+ }
+}
+
+dependencies {
+ compileOnly "io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT"
+ library "org.projectlombok:lombok:1.18.22"
+ library "org.json:json:20220320"
+ compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
+ exclude group: "org.bukkit", module: "bukkit"
+ }
+ library 'org.apache.logging.log4j:log4j-api:2.17.2'
+ library 'org.apache.logging.log4j:log4j-core:2.17.2'
+ annotationProcessor "org.projectlombok:lombok:1.18.22"
+}
+tasks {
+ jar {
+ dependsOn(shadowJar)
+ }
+ compileJava {
+ options.encoding = StandardCharsets.UTF_8.name() // We want UTF-8 for everything
+
+ // Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable.
+ // See https://openjdk.java.net/jeps/247 for more information.
+ options.release.set(17)
+ }
+ javadoc {
+ options.encoding = StandardCharsets.UTF_8.name() // We want UTF-8 for everything
+ }
+ processResources {
+ filteringCharset = StandardCharsets.UTF_8.name() // We want UTF-8 for everything
+ }
+}
+
+bukkit {
+ name = "BukkitTelnet"
+ main = "me.totalfreedom.bukkittelnet.BukkitTelnet"
+ apiVersion = "1.18"
+ version = project.version
+ description = "Telnet console access plugin."
+ authors = ["bekvon", "Madgeek1450", "Prozza", "Taahh", "Telesphoreo"]
+ softDepend = ["Vault"]
+}
+
+shadowJar {
+ archiveBaseName.set("BukkitTelnet")
+ archiveVersion.set("")
+ archiveClassifier.set("")
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ groupId = project.group
+ artifactId = "bukkittelnet"
+ version = project.version
+ artifacts = [shadowJar]
+ }
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..e69de29
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7454180
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e750102
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..c53aefa
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e5732d0
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,9 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ maven {
+ url = uri("https://nexus.telesphoreo.me/repository/gradle-plugins-snapshots/")
+ }
+ }
+}
+rootProject.name = 'bukkittelnet'
diff --git a/src/main/java/dev/plex/PermissionHandler.java b/src/main/java/dev/plex/PermissionHandler.java
new file mode 100644
index 0000000..71ff701
--- /dev/null
+++ b/src/main/java/dev/plex/PermissionHandler.java
@@ -0,0 +1,10 @@
+package dev.plex;
+
+import java.util.UUID;
+
+public interface PermissionHandler
+{
+
+ boolean hasPermission(String username, String permission);
+
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/BukkitTelnet.java b/src/main/java/me/totalfreedom/bukkittelnet/BukkitTelnet.java
new file mode 100644
index 0000000..6c90657
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/BukkitTelnet.java
@@ -0,0 +1,79 @@
+package me.totalfreedom.bukkittelnet;
+
+import dev.plex.PermissionHandler;
+import lombok.Getter;
+import me.totalfreedom.bukkittelnet.api.Server;
+import net.milkbowl.vault.permission.Permission;
+import org.bukkit.Bukkit;
+import org.bukkit.event.HandlerList;
+import org.bukkit.plugin.RegisteredServiceProvider;
+import org.bukkit.plugin.ServicePriority;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class BukkitTelnet extends JavaPlugin
+{
+ @Getter
+ private static BukkitTelnet plugin;
+ public TelnetConfigLoader config;
+ public TelnetServer telnet;
+ public TelnetLogAppender appender;
+ public PlayerEventListener listener;
+ public Permission permissions;
+
+ public PermissionHandler handler;
+ @Override
+ public void onLoad()
+ {
+ plugin = this;
+ config = new TelnetConfigLoader(plugin);
+ telnet = new TelnetServer(plugin, config.getConfig());
+ appender = new TelnetLogAppender();
+ listener = new PlayerEventListener(plugin);
+ handler = new PermissionHandlerImpl();
+
+ TelnetLogger.setPluginLogger(plugin.getLogger());
+ TelnetLogger.setServerLogger(Bukkit.getLogger());
+ System.setProperty("log4j2.formatMsgNoLookups", "true");
+ }
+
+ @Override
+ public void onEnable()
+ {
+ setupPermissions();
+ config.load();
+
+ appender.attach();
+
+ telnet.startServer();
+
+ getServer().getPluginManager().registerEvents(listener, plugin);
+
+ getServer().getServicesManager().register(Server.class, telnet, this, ServicePriority.Normal);
+
+ TelnetLogger.info(plugin.getName() + " v" + plugin.getDescription().getVersion() + " enabled");
+ }
+
+ @Override
+ public void onDisable()
+ {
+ HandlerList.unregisterAll(plugin);
+
+ appender.deattach();
+ appender.removeAllSesssions();
+
+ telnet.stopServer();
+
+ TelnetLogger.info(plugin.getName() + " disabled");
+ }
+
+ private boolean setupPermissions() {
+ if (!getServer().getPluginManager().isPluginEnabled("Vault"))
+ {
+ return false;
+ }
+ RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Permission.class);
+ permissions = rsp.getProvider();
+ return permissions != null;
+ }
+
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/PermissionHandlerImpl.java b/src/main/java/me/totalfreedom/bukkittelnet/PermissionHandlerImpl.java
new file mode 100644
index 0000000..a8065e8
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/PermissionHandlerImpl.java
@@ -0,0 +1,13 @@
+package me.totalfreedom.bukkittelnet;
+
+import dev.plex.PermissionHandler;
+import org.bukkit.World;
+
+public class PermissionHandlerImpl implements PermissionHandler
+{
+ @Override
+ public boolean hasPermission(String username, String permission)
+ {
+ return BukkitTelnet.getPlugin().permissions.has((World) null, username, permission);
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/PlayerEventListener.java b/src/main/java/me/totalfreedom/bukkittelnet/PlayerEventListener.java
new file mode 100644
index 0000000..9f0429a
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/PlayerEventListener.java
@@ -0,0 +1,140 @@
+package me.totalfreedom.bukkittelnet;
+
+import me.totalfreedom.bukkittelnet.api.TelnetRequestDataTagsEvent;
+import me.totalfreedom.bukkittelnet.api.TelnetRequestUsageEvent;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitTask;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.text.DecimalFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+public class PlayerEventListener implements Listener
+{
+ private final BukkitTelnet plugin;
+
+ public PlayerEventListener(BukkitTelnet plugin)
+ {
+ this.plugin = plugin;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPlayerJoin(PlayerJoinEvent event)
+ {
+ triggerPlayerListUpdates();
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPlayerQuit(PlayerQuitEvent event)
+ {
+ triggerPlayerListUpdates();
+ }
+
+
+ private static BukkitTask updateTask = null;
+
+ public void triggerPlayerListUpdates()
+ {
+ if (updateTask != null)
+ {
+ updateTask.cancel();
+ }
+
+ updateTask = new BukkitRunnable()
+ {
+ @Override
+ public void run()
+ {
+ final SocketListener socketListener = plugin.telnet.getSocketListener();
+ if (socketListener != null)
+ {
+ final TelnetRequestDataTagsEvent event = new TelnetRequestDataTagsEvent();
+ Bukkit.getServer().getPluginManager().callEvent(event);
+ socketListener.triggerPlayerListUpdates(generatePlayerList(event.getDataTags()));
+ }
+ }
+ }.runTaskLater(plugin, 20L * 2L);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static String generatePlayerList(final Map> dataTags)
+ {
+ final JSONArray players = new JSONArray();
+
+ for (Map.Entry> playerMapEntry : dataTags.entrySet())
+ {
+ final HashMap info = new HashMap<>();
+
+ final Player player = playerMapEntry.getKey();
+ final Map playerTags = playerMapEntry.getValue();
+
+ info.put("name", player.getName());
+ info.put("ip", player.getAddress().getAddress().getHostAddress());
+ info.put("displayName", StringUtils.trimToEmpty(player.getDisplayName()));
+ info.put("uuid", player.getUniqueId().toString());
+
+ for (Map.Entry playerTagsEntry : playerTags.entrySet())
+ {
+ final Object value = playerTagsEntry.getValue();
+ info.put(playerTagsEntry.getKey(), value != null ? value.toString() : "null");
+ }
+
+ players.put(info);
+ }
+
+ final JSONObject response = new JSONObject();
+ response.put("players", players);
+ return response.toString();
+ }
+
+ private static BukkitTask usageUpdateTask = null;
+
+ // Just putting this stuff here
+ public void triggerUsageUpdates()
+ {
+ if (usageUpdateTask != null)
+ {
+ return;
+ }
+
+ usageUpdateTask = new BukkitRunnable()
+ {
+ @Override
+ public void run()
+ {
+ final SocketListener socketListener = plugin.telnet.getSocketListener();
+ if (socketListener != null)
+ {
+ final TelnetRequestUsageEvent event = new TelnetRequestUsageEvent();
+ Bukkit.getServer().getPluginManager().callEvent(event);
+ socketListener.triggerDataUsageUpdates(generateUsageStats());
+ }
+ }
+ }.runTaskTimer(plugin, 100L, 100L); // every 5 seconds
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private static String generateUsageStats()
+ {
+ final JSONObject info = new JSONObject();
+
+ String cpuUsage = null;
+ String ramUsage = null;
+ String tps = null;
+
+ tps = String.valueOf(new DecimalFormat("#.##").format(Bukkit.getServer().getTPS()[0]));
+ info.put("tps", tps);
+ return info.toString();
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/SocketListener.java b/src/main/java/me/totalfreedom/bukkittelnet/SocketListener.java
new file mode 100644
index 0000000..2eccaf5
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/SocketListener.java
@@ -0,0 +1,143 @@
+package me.totalfreedom.bukkittelnet;
+
+import me.totalfreedom.bukkittelnet.session.ClientSession;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.*;
+
+public class SocketListener extends Thread
+{
+
+ public static long LISTEN_THRESHOLD_MILLIS = 10000;
+ //
+ private final TelnetServer telnet;
+ private final ServerSocket serverSocket;
+ private final List clientSessions = new ArrayList<>();
+ private final Map recentIPs = new HashMap<>();
+
+ public SocketListener(TelnetServer telnet, ServerSocket serverSocket)
+ {
+ this.telnet = telnet;
+ this.serverSocket = serverSocket;
+ }
+
+ @Override
+ public void run()
+ {
+ while (!serverSocket.isClosed())
+ {
+ final Socket clientSocket;
+
+ try
+ {
+ clientSocket = serverSocket.accept();
+ }
+ catch (IOException ex)
+ {
+ continue;
+ }
+
+ // Remove old entries
+ recentIPs.entrySet().removeIf(inetAddressLongEntry -> inetAddressLongEntry.getValue() + LISTEN_THRESHOLD_MILLIS < System.currentTimeMillis());
+
+ final InetAddress addr = clientSocket.getInetAddress();
+ if (addr == null)
+ {
+ return; // Socket is not connected
+ }
+
+ // Connect Threshold
+ if (recentIPs.containsKey(addr))
+ {
+ recentIPs.put(addr, System.currentTimeMillis());
+
+ try
+ {
+ final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
+ writer.write("Connection throttled. Please wait a minute and try again.\r\n");
+ writer.flush();
+ }
+ catch (IOException ignored)
+ {
+ }
+
+ try
+ {
+ clientSocket.close();
+ }
+ catch (IOException ignored)
+ {
+ }
+
+ continue;
+ }
+
+ recentIPs.put(addr, System.currentTimeMillis());
+
+ final ClientSession clientSession = new ClientSession(telnet, clientSocket);
+ clientSessions.add(clientSession);
+ clientSession.start();
+ removeDisconnected();
+ }
+
+ TelnetLogger.info("Server closed");
+ }
+
+ private void removeDisconnected()
+ {
+ final Iterator it = clientSessions.iterator();
+
+ while (it.hasNext())
+ {
+ final ClientSession session = it.next();
+
+ if (!session.syncIsConnected())
+ {
+ telnet.getPlugin().appender.removeSession(session);
+ it.remove();
+ }
+ }
+ }
+
+ public void triggerPlayerListUpdates(final String playerListData)
+ {
+ clientSessions.forEach(session ->
+ session.syncTriggerPlayerListUpdate(playerListData));
+ }
+
+ public void triggerDataUsageUpdates(final String usageData)
+ {
+ clientSessions.forEach(session ->
+ session.syncUsageUpdate(usageData));
+ }
+
+ public void stopServer()
+ {
+ try
+ {
+ serverSocket.close();
+ }
+ catch (IOException ex)
+ {
+ TelnetLogger.severe(ex);
+ }
+
+ for (ClientSession session : clientSessions)
+ {
+ session.syncTerminateSession();
+ }
+
+ clientSessions.clear();
+
+ }
+
+ public List getSessions()
+ {
+ return Collections.unmodifiableList(clientSessions);
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/TelnetConfigLoader.java b/src/main/java/me/totalfreedom/bukkittelnet/TelnetConfigLoader.java
new file mode 100644
index 0000000..5119fe1
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/TelnetConfigLoader.java
@@ -0,0 +1,117 @@
+package me.totalfreedom.bukkittelnet;
+
+import me.totalfreedom.bukkittelnet.thirdparty.YamlConfig;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TelnetConfigLoader
+{
+
+ private final YamlConfig config;
+ private final TelnetConfig configEntries;
+
+ public TelnetConfigLoader(BukkitTelnet plugin)
+ {
+ configEntries = new TelnetConfig();
+ config = new YamlConfig(plugin, "config.yml", true);
+ }
+
+ public void load()
+ {
+ config.load();
+
+ configEntries.setAddress(config.getString("address"));
+ configEntries.setPort(config.getInt("port"));
+ configEntries.setPassword(config.getString("password"));
+
+ configEntries.clearAdmins();
+ if (config.isConfigurationSection("admins"))
+ {
+ for (String admin : config.getConfigurationSection("admins").getKeys(false))
+ {
+
+ if (!config.isList("admins." + admin))
+ {
+ continue;
+ }
+
+ configEntries.addAdmin(admin, config.getStringList("admins." + admin));
+ }
+ }
+
+ if (configEntries.getPassword().isEmpty())
+ {
+ configEntries.setPassword(config.getDefaultConfig().getString("password"));
+ TelnetLogger.warning("Password is undefined in config!");
+ TelnetLogger.warning("Defaulting to " + configEntries.getPassword());
+ }
+ }
+
+ public TelnetConfig getConfig()
+ {
+ return configEntries;
+ }
+
+ public static final class TelnetConfig
+ {
+
+ private int port;
+ private String address;
+ private String password;
+ private final Map> admins;
+
+ private TelnetConfig()
+ {
+ admins = new HashMap<>();
+ }
+
+ public int getPort()
+ {
+ return port;
+ }
+
+ public void setPort(int port)
+ {
+ this.port = port;
+ }
+
+ public String getAddress()
+ {
+ return address;
+ }
+
+ public void setAddress(String address)
+ {
+ this.address = address;
+ }
+
+ public String getPassword()
+ {
+ return password;
+ }
+
+ public void setPassword(String password)
+ {
+ this.password = password;
+ }
+
+ public Map> getAdmins()
+ {
+ return Collections.unmodifiableMap(admins);
+ }
+
+ private void clearAdmins()
+ {
+ admins.clear();
+ }
+
+ private void addAdmin(String name, List ips)
+ {
+ admins.put(name, ips);
+ }
+ }
+
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/TelnetLogAppender.java b/src/main/java/me/totalfreedom/bukkittelnet/TelnetLogAppender.java
new file mode 100644
index 0000000..fdb01f8
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/TelnetLogAppender.java
@@ -0,0 +1,125 @@
+package me.totalfreedom.bukkittelnet;
+
+import me.totalfreedom.bukkittelnet.session.ClientSession;
+import me.totalfreedom.bukkittelnet.session.FilterMode;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+public class TelnetLogAppender extends AbstractAppender
+{
+ private static final long serialVersionUID = 234234L;
+
+ private final Set sessions;
+ private final SimpleDateFormat dateFormat;
+
+ public TelnetLogAppender()
+ {
+ super("BukkitTelnet", null, null, true, Property.EMPTY_ARRAY);
+
+ this.sessions = new HashSet<>();
+ this.dateFormat = new SimpleDateFormat("HH:mm:ss");
+
+ super.start();
+ }
+
+ public Set getSessions()
+ {
+ return Collections.unmodifiableSet(sessions);
+ }
+
+ public boolean addSession(ClientSession session)
+ {
+ return sessions.add(session);
+ }
+
+ public boolean removeSession(ClientSession session)
+ {
+ return sessions.remove(session);
+ }
+
+ public void removeAllSesssions()
+ {
+ sessions.clear();
+ }
+
+ @Override
+ public void append(LogEvent event)
+ {
+ final String message = event.getMessage().getFormattedMessage();
+
+ for (ClientSession session : sessions)
+ {
+ try
+ {
+ if (!session.syncIsConnected())
+ {
+ continue;
+ }
+
+ boolean chat = message.startsWith("<")
+ || message.startsWith("[Server")
+ || message.startsWith("[CONSOLE") || message.startsWith("[TotalFreedomMod] [ADMIN]");
+
+ if (session.getFilterMode() == FilterMode.CHAT_ONLY && !chat)
+ {
+ continue;
+ }
+
+ if (session.getFilterMode() == FilterMode.NON_CHAT_ONLY && chat)
+ {
+ continue;
+ }
+
+ session.writeRawLine(formatMessage(message, event));
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ private String formatMessage(String message, LogEvent event)
+ {
+ final StringBuilder builder = new StringBuilder();
+ final Throwable ex = event.getThrown();
+
+ builder.append("[");
+ builder.append(dateFormat.format(new Date()));
+ builder.append(" ");
+ builder.append(event.getLevel().name().toUpperCase());
+ builder.append("]: ");
+ builder.append(message);
+
+ if (ex != null)
+ {
+ StringWriter writer = new StringWriter();
+ ex.printStackTrace(new PrintWriter(writer));
+ builder.append(writer);
+ }
+
+ return builder.toString();
+ }
+
+ public void attach()
+ {
+ ((Logger) LogManager.getRootLogger()).addAppender(this);
+ }
+
+ public void deattach()
+ {
+ ((Logger) LogManager.getRootLogger()).removeAppender(this);
+ }
+
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/TelnetLogger.java b/src/main/java/me/totalfreedom/bukkittelnet/TelnetLogger.java
new file mode 100644
index 0000000..33b59f5
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/TelnetLogger.java
@@ -0,0 +1,78 @@
+package me.totalfreedom.bukkittelnet;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+
+import java.util.logging.Logger;
+
+public final class TelnetLogger
+{
+
+ private static Logger serverLogger = null;
+ private static Logger pluginLogger = null;
+
+ private TelnetLogger()
+ {
+ }
+
+ public static void setServerLogger(Logger serverLogger)
+ {
+ TelnetLogger.serverLogger = serverLogger;
+ }
+
+ public static void setPluginLogger(Logger pluginLogger)
+ {
+ TelnetLogger.pluginLogger = pluginLogger;
+ }
+
+ public static void rawInfo(String message)
+ {
+ serverLogger.info(message);
+ }
+
+ public static void info(String message)
+ {
+ pluginLogger.info(message);
+ }
+
+ public static void rawWarning(String message)
+ {
+ serverLogger.warning(message);
+ }
+
+ public static void warning(String message)
+ {
+ pluginLogger.warning(message);
+ }
+
+ public static void rawSevere(Object message)
+ {
+ final String line;
+
+ if (message instanceof Throwable)
+ {
+ line = ExceptionUtils.getStackTrace((Throwable) message);
+ }
+ else
+ {
+ line = String.valueOf(message);
+ }
+
+ serverLogger.severe(line);
+ }
+
+ public static void severe(Object message)
+ {
+ final String line;
+
+ if (message instanceof Throwable)
+ {
+ line = ExceptionUtils.getStackTrace((Throwable) message);
+ }
+ else
+ {
+ line = String.valueOf(message);
+ }
+
+ pluginLogger.severe(line);
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/TelnetServer.java b/src/main/java/me/totalfreedom/bukkittelnet/TelnetServer.java
new file mode 100644
index 0000000..b5af674
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/TelnetServer.java
@@ -0,0 +1,103 @@
+package me.totalfreedom.bukkittelnet;
+
+import lombok.Getter;
+import me.totalfreedom.bukkittelnet.TelnetConfigLoader.TelnetConfig;
+import me.totalfreedom.bukkittelnet.api.Server;
+import me.totalfreedom.bukkittelnet.session.ClientSession;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.UnknownHostException;
+import java.util.List;
+
+public class TelnetServer implements Server
+{
+
+ @Getter
+ private final BukkitTelnet plugin;
+ @Getter
+ private final TelnetConfig config;
+ @Getter
+ private SocketListener socketListener;
+
+ public TelnetServer(BukkitTelnet plugin, TelnetConfig config)
+ {
+ this.plugin = plugin;
+ this.config = config;
+ }
+
+ @Override
+ public void startServer()
+ {
+ // If the server is running, stop it
+ stopServer();
+
+ // Server address, optional.
+ final InetAddress hostAddress;
+
+ final String address = config.getAddress();
+ if (address != null)
+ {
+ try
+ {
+ hostAddress = InetAddress.getByName(address);
+ }
+ catch (UnknownHostException ex)
+ {
+ TelnetLogger.severe("Cannot start server - Invalid address: " + config.getAddress());
+ TelnetLogger.severe(ex);
+ return;
+ }
+ }
+ else
+ {
+ hostAddress = null;
+ }
+
+ // Server socket
+ final ServerSocket serversocket;
+
+ try
+ {
+ if (hostAddress == null)
+ {
+ serversocket = new ServerSocket(config.getPort());
+ }
+ else
+ {
+ serversocket = new ServerSocket(config.getPort(), 50, hostAddress);
+ }
+ }
+ catch (IOException ex)
+ {
+ TelnetLogger.severe("Cannot start server - " + "Cant bind to " + (hostAddress == null ? "*" : hostAddress) + ":" + config.getPort());
+ TelnetLogger.severe(ex);
+ return;
+ }
+
+ socketListener = new SocketListener(this, serversocket);
+ socketListener.start();
+
+ final String host = serversocket.getInetAddress().getHostAddress().replace("0.0.0.0", "*");
+ TelnetLogger.info("Server started on " + host + ":" + serversocket.getLocalPort());
+ }
+
+ @Override
+ public void stopServer()
+ {
+ if (socketListener == null)
+ {
+ return;
+ }
+
+ socketListener.stopServer();
+ }
+
+ @Override
+ public List getSessions()
+ {
+ return socketListener.getSessions();
+ }
+
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/Util.java b/src/main/java/me/totalfreedom/bukkittelnet/Util.java
new file mode 100644
index 0000000..b89cae3
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/Util.java
@@ -0,0 +1,43 @@
+package me.totalfreedom.bukkittelnet;
+
+public class Util
+{
+
+ public static boolean fuzzyIpMatch(String a, String b, int octets)
+ {
+ boolean match = true;
+
+ String[] aParts = a.split("\\.");
+ String[] bParts = b.split("\\.");
+
+ if (aParts.length != 4 || bParts.length != 4)
+ {
+ return false;
+ }
+
+ if (octets > 4)
+ {
+ octets = 4;
+ }
+ else if (octets < 1)
+ {
+ octets = 1;
+ }
+
+ for (int i = 0; i < octets && i < 4; i++)
+ {
+ if (aParts[i].equals("*") || bParts[i].equals("*"))
+ {
+ continue;
+ }
+
+ if (!aParts[i].equals(bParts[i]))
+ {
+ match = false;
+ break;
+ }
+ }
+
+ return match;
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/api/Server.java b/src/main/java/me/totalfreedom/bukkittelnet/api/Server.java
new file mode 100644
index 0000000..1fda918
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/api/Server.java
@@ -0,0 +1,23 @@
+package me.totalfreedom.bukkittelnet.api;
+
+import me.totalfreedom.bukkittelnet.SocketListener;
+import me.totalfreedom.bukkittelnet.TelnetConfigLoader.TelnetConfig;
+import me.totalfreedom.bukkittelnet.session.ClientSession;
+
+import java.util.List;
+
+public interface Server
+{
+
+ void startServer();
+
+ void stopServer();
+
+ @Deprecated
+ SocketListener getSocketListener();
+
+ TelnetConfig getConfig();
+
+ List getSessions();
+
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetCommandEvent.java b/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetCommandEvent.java
new file mode 100644
index 0000000..892d9ec
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetCommandEvent.java
@@ -0,0 +1,74 @@
+package me.totalfreedom.bukkittelnet.api;
+
+import me.totalfreedom.bukkittelnet.session.SessionCommandSender;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+public class TelnetCommandEvent extends Event implements Cancellable
+{
+
+ private static final HandlerList handlers = new HandlerList();
+
+ private boolean cancelled;
+ private SessionCommandSender sender;
+ private String command;
+
+ public TelnetCommandEvent(SessionCommandSender sender, String command)
+ {
+ super(!Bukkit.getServer().isPrimaryThread());
+ this.cancelled = false;
+ this.sender = sender;
+ this.command = command;
+ }
+
+ @Override
+ public boolean isCancelled()
+ {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel)
+ {
+ cancelled = cancel;
+ }
+
+ public SessionCommandSender getSender()
+ {
+ return sender;
+ }
+
+ public void setSender(SessionCommandSender sender)
+ {
+ this.sender = sender;
+ }
+
+ public String getCommand()
+ {
+ return command;
+ }
+
+ public void setCommand(String command)
+ {
+ if (command == null)
+ {
+ command = "";
+ }
+
+ this.command = command;
+ }
+
+ @Override
+ public HandlerList getHandlers()
+ {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList()
+ {
+ return handlers;
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetPreLoginEvent.java b/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetPreLoginEvent.java
new file mode 100644
index 0000000..2ccce6b
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetPreLoginEvent.java
@@ -0,0 +1,74 @@
+package me.totalfreedom.bukkittelnet.api;
+
+import org.bukkit.Bukkit;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+public class TelnetPreLoginEvent extends Event implements Cancellable
+{
+
+ private static final HandlerList handlers = new HandlerList();
+
+ private boolean cancelled = false;
+ private String name;
+ private final String ip;
+ private boolean bypassPassword;
+
+ public TelnetPreLoginEvent(String ip, String name, boolean bypassPassword)
+ {
+ super(!Bukkit.getServer().isPrimaryThread());
+ this.ip = ip;
+ this.name = name;
+ this.bypassPassword = bypassPassword;
+ }
+
+ @Override
+ public boolean isCancelled()
+ {
+ return cancelled;
+ }
+
+
+ @Override
+ public void setCancelled(boolean cancel)
+ {
+ cancelled = cancel;
+ }
+
+ public String getIp()
+ {
+ return ip;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public boolean canBypassPassword()
+ {
+ return bypassPassword;
+ }
+
+ public void setBypassPassword(boolean bypassPassword)
+ {
+ this.bypassPassword = bypassPassword;
+ }
+
+ @Override
+ public HandlerList getHandlers()
+ {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList()
+ {
+ return handlers;
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetRequestDataTagsEvent.java b/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetRequestDataTagsEvent.java
new file mode 100644
index 0000000..f99b9a2
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetRequestDataTagsEvent.java
@@ -0,0 +1,41 @@
+package me.totalfreedom.bukkittelnet.api;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TelnetRequestDataTagsEvent extends Event
+{
+
+ private static final HandlerList handlers = new HandlerList();
+ private final Map> dataTags = new HashMap<>();
+
+ public TelnetRequestDataTagsEvent()
+ {
+ super(!Bukkit.getServer().isPrimaryThread());
+ for (final Player player : Bukkit.getServer().getOnlinePlayers())
+ {
+ dataTags.put(player, new HashMap<>());
+ }
+ }
+
+ public Map> getDataTags()
+ {
+ return dataTags;
+ }
+
+ @Override
+ public HandlerList getHandlers()
+ {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList()
+ {
+ return handlers;
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetRequestUsageEvent.java b/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetRequestUsageEvent.java
new file mode 100644
index 0000000..55187ea
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/api/TelnetRequestUsageEvent.java
@@ -0,0 +1,22 @@
+package me.totalfreedom.bukkittelnet.api;
+
+import org.bukkit.Bukkit;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+public class TelnetRequestUsageEvent extends Event
+{
+
+ private static final HandlerList handlers = new HandlerList();
+
+ public TelnetRequestUsageEvent()
+ {
+ super(!Bukkit.getServer().isPrimaryThread());
+ }
+
+ @Override
+ public HandlerList getHandlers()
+ {
+ return handlers;
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/session/ClientSession.java b/src/main/java/me/totalfreedom/bukkittelnet/session/ClientSession.java
new file mode 100644
index 0000000..6c5f55e
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/session/ClientSession.java
@@ -0,0 +1,526 @@
+package me.totalfreedom.bukkittelnet.session;
+
+//import dev.plex.SimulatedPlayer;
+
+import me.totalfreedom.bukkittelnet.TelnetLogger;
+import me.totalfreedom.bukkittelnet.TelnetServer;
+import me.totalfreedom.bukkittelnet.Util;
+import me.totalfreedom.bukkittelnet.api.TelnetCommandEvent;
+import me.totalfreedom.bukkittelnet.api.TelnetPreLoginEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Server;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.io.*;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public final class ClientSession extends Thread
+{
+
+ public static final Pattern NON_ASCII_FILTER = Pattern.compile("[^\\x20-\\x7E]");
+ public static final Pattern AUTH_INPUT_FILTER = Pattern.compile("[^_a-zA-Z0-9]");
+ public static final Pattern COMMAND_INPUT_FILTER = Pattern.compile("^[^_a-zA-Z0-9/?!.]+");
+ //
+ private final TelnetServer telnet;
+ private final Socket clientSocket;
+ private final String clientAddress;
+ private final SessionCommandSender commandSender;
+ //
+ private FilterMode filterMode = FilterMode.NONE;
+ //
+ private BufferedWriter writer;
+ private BufferedReader reader;
+ private String username = "";
+ private volatile boolean terminated = false;
+ private volatile boolean authenticated = false;
+ private boolean enhancedMode = false;
+ private boolean enhancedPlusMode = false;
+
+ public ClientSession(TelnetServer telnet, Socket clientSocket)
+ {
+ this.telnet = telnet;
+ this.clientSocket = clientSocket;
+ this.clientAddress = clientSocket.getInetAddress().getHostAddress();
+ this.commandSender = new SessionCommandSender(this);
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ synchronized (clientSocket)
+ {
+ reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
+ writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
+ }
+ }
+ catch (IOException ex)
+ {
+ TelnetLogger.severe(ex);
+ syncTerminateSession();
+ return;
+ }
+
+ writeLine("Session Started.");
+
+ if (!authenticate())
+ {
+ writeLine("Authentication failed.");
+ syncTerminateSession();
+ }
+
+ writeLine("Logged in as " + username + ".");
+ TelnetLogger.info(clientAddress + " logged in as \"" + username + "\".");
+// this.commandSender.setPlayer(setPlayernew SimulatedPlayer(Bukkit.getOfflinePlayer(this.getUserName()).getUniqueId(), Bukkit.getServer(), Bukkit.getWorlds().get(0)));
+// this.commandSender.getPlayer().getPlayer().connection.connection.channel.pipeline().addBefore("packet_handler", this.username, new ProtocolHandler(this));
+
+ // Start feeding data to the client.
+ telnet.getPlugin().appender.addSession(this);
+
+ mainLoop();
+
+ syncTerminateSession();
+
+ telnet.getPlugin().listener.triggerUsageUpdates();
+ }
+
+ public boolean syncIsAuthenticated()
+ {
+ return authenticated;
+ }
+
+ public boolean syncIsConnected()
+ {
+ synchronized (clientSocket)
+ {
+ return !clientSocket.isClosed();
+ }
+ }
+
+ public synchronized void syncTerminateSession()
+ {
+ if (terminated)
+ {
+ return;
+ }
+
+ terminated = true;
+
+ if (authenticated)
+ {
+ TelnetLogger.info("Closing connection: " + clientAddress + (username.isEmpty() ? "" : " (" + username + ")"));
+ }
+
+ // Stop feeding the client with data
+// Channel channel = this.commandSender.getPlayer().getPlayer().connection.connection.channel;
+// channel.eventLoop().submit(() -> {
+// channel.pipeline().remove(this.username);
+// });
+// this.commandSender.getPlayer(getPlayer).getPlayer().remove(Entity.RemovalReason.DISCARDED);
+ telnet.getPlugin().appender.removeSession(this);
+
+ synchronized (clientSocket)
+ {
+ writeLine("Closing connection...");
+ try
+ {
+ clientSocket.close();
+ }
+ catch (IOException ignored)
+ {
+ }
+ }
+ }
+
+ public String getUserName()
+ {
+ return username;
+ }
+
+ public SessionCommandSender getCommandSender()
+ {
+ return commandSender;
+ }
+
+ public FilterMode getFilterMode()
+ {
+ return filterMode;
+ }
+
+ public void setFilterMode(FilterMode filterMode)
+ {
+ this.filterMode = filterMode;
+ }
+
+ public void writeLine(String message)
+ {
+ writeRawLine("[" + (username.isEmpty() ? "" : username + "@") + "BukkitTelnet]$ " + message);
+ }
+
+ public void writeRawLine(String message)
+ {
+ if (writer == null || !syncIsConnected())
+ {
+ return;
+ }
+
+ try
+ {
+ writer.write(":" + ChatColor.stripColor(message) + "\r\n");
+ writer.flush();
+ }
+ catch (IOException ignored)
+ {
+ }
+ }
+
+ public void flush()
+ {
+ if (writer == null || !syncIsConnected())
+ {
+ return;
+ }
+
+ try
+ {
+ writer.flush();
+ }
+ catch (IOException ignored)
+ {
+ }
+ }
+
+ public void syncExecuteCommand(final String command)
+ {
+ new BukkitRunnable()
+ {
+ @Override
+ public void run()
+ {
+ final Server server = Bukkit.getServer();
+
+ final TelnetCommandEvent event = new TelnetCommandEvent(commandSender, command);
+ server.getPluginManager().callEvent(event);
+
+ if (event.isCancelled() || event.getCommand().isEmpty())
+ {
+ return;
+ }
+ Bukkit.getServer().dispatchCommand(event.getSender(), event.getCommand());
+
+// commandSender.getPlayer().runCommand(event.getCommand());
+ }
+ }.runTask(telnet.getPlugin());
+ }
+
+ private boolean authenticate()
+ {
+ if (terminated)
+ {
+ return false;
+ }
+
+ boolean passAuth = false;
+
+ // Pre-authenticate IP addresses
+ if (clientAddress != null)
+ {
+ final Map> admins = telnet.getConfig().getAdmins();
+
+ // For every admin
+ for (String name : admins.keySet())
+ {
+
+ // For every IP of each admin
+ for (String ip : admins.get(name))
+ {
+ if (Util.fuzzyIpMatch(ip, clientAddress, 3))
+ {
+ passAuth = true;
+ this.username = name;
+ break;
+ }
+ }
+ }
+ }
+
+ // TelnetPreLoginEvent authentication
+ final TelnetPreLoginEvent event = new TelnetPreLoginEvent(clientAddress, username, passAuth);
+ Bukkit.getServer().getPluginManager().callEvent(event);
+
+ if (event.isCancelled())
+ {
+ return false;
+ }
+
+ if (event.canBypassPassword())
+ {
+ if (!event.getName().isEmpty()) // If the name hasn't been set, we'll ask for it.
+ {
+ this.username = event.getName();
+ return true;
+ }
+
+ passAuth = true;
+ }
+
+ // Username
+ boolean validUsername = false;
+
+ int tries = 0;
+ while (tries++ < 3)
+ {
+ writeLine("Username: ");
+
+ String input;
+ try
+ {
+ input = reader.readLine();
+ }
+ catch (IOException ex)
+ {
+ break;
+ }
+
+ if (input == null)
+ {
+ break;
+ }
+
+ if (input.isEmpty())
+ {
+ continue;
+ }
+
+ input = AUTH_INPUT_FILTER.matcher(input).replaceAll("").trim();
+
+ if (input.isEmpty() || input.length() > 20)
+ {
+ writeLine("Invalid username.");
+ continue;
+ }
+
+ this.username = input;
+ validUsername = true;
+ break;
+ }
+
+ if (!validUsername)
+ {
+ return false;
+ }
+
+ // If the TelnetPreLoginEvent authenticates the password,
+ // don't ask for it.
+ if (passAuth)
+ {
+ authenticated = true;
+ return true;
+ }
+
+ // Password
+ tries = 0;
+ while (tries++ < 3)
+ {
+ writeLine("Password: ");
+
+ String input;
+
+ try
+ {
+ input = reader.readLine();
+ }
+ catch (IOException ex)
+ {
+ return false;
+ }
+
+ if (input == null)
+ {
+ break;
+ }
+ if (input.isEmpty())
+ {
+ continue;
+ }
+
+ input = AUTH_INPUT_FILTER.matcher(input).replaceAll("").trim();
+
+ if (telnet.getConfig().getPassword().equals(input))
+ {
+ authenticated = true;
+ return true;
+ }
+
+ writeLine("Invalid password.");
+ try
+ {
+ Thread.sleep(2000);
+ }
+ catch (InterruptedException ignored)
+ {
+ }
+ }
+
+ return false;
+ }
+
+ private void mainLoop()
+ {
+ if (terminated)
+ {
+ return;
+ }
+
+ // Process commands
+ while (syncIsConnected())
+ {
+ // Read a command
+ String command;
+ try
+ {
+ command = reader.readLine();
+ }
+ catch (SocketException ex)
+ {
+ break;
+ }
+ catch (IOException ex)
+ {
+ TelnetLogger.severe(ex);
+ break;
+ }
+
+ if (command == null)
+ {
+ break;
+ }
+ else if (command.isEmpty())
+ {
+ continue;
+ }
+
+ command = COMMAND_INPUT_FILTER.matcher(NON_ASCII_FILTER.matcher(command).replaceAll("")).replaceFirst("").trim();
+ if (command.isEmpty())
+ {
+ continue;
+ }
+
+ if (command.toLowerCase().startsWith("telnet"))
+ {
+ executeTelnetCommand(command);
+ continue;
+ }
+
+ syncExecuteCommand(command);
+ }
+ }
+
+ private void executeTelnetCommand(final String command)
+ {
+ if ("telnet.help".equalsIgnoreCase(command))
+ {
+ writeLine("--- Telnet commands ---");
+ writeLine("telnet.help - See all of the telnet commands.");
+ writeLine("telnet.stop - Shut the server down.");
+ writeLine("telnet.log - Change your logging settings.");
+ writeLine("telnet.exit - Quit the telnet session.");
+ }
+ else if ("telnet.stop".equalsIgnoreCase(command))
+ {
+ writeLine("Shutting down the server...");
+ TelnetLogger.warning(username + ": Shutting down the server...");
+ Bukkit.shutdown();
+ System.exit(0);
+ }
+ else if ("telnet.log".equalsIgnoreCase(command))
+ {
+ switch (filterMode)
+ {
+ case NONE:
+ {
+ filterMode = FilterMode.CHAT_ONLY;
+ writeLine("Showing only chat logs.");
+ break;
+ }
+ case CHAT_ONLY:
+ {
+ filterMode = FilterMode.NON_CHAT_ONLY;
+ writeLine("Showing only non-chat logs.");
+ break;
+ }
+ case NON_CHAT_ONLY:
+ {
+ filterMode = FilterMode.NONE;
+ writeLine("Showing all logs.");
+ break;
+ }
+ }
+ }
+ else if ("telnet.exit".equalsIgnoreCase(command))
+ {
+ writeLine("Goodbye. <3");
+ syncTerminateSession();
+ }
+ else if ("telnet.enhanced".equalsIgnoreCase(command))
+ {
+ enhancedMode = !enhancedMode;
+ writeLine((enhancedMode ? "A" : "Dea") + "ctivated enhanced mode.");
+ if (enhancedMode)
+ {
+ telnet.getPlugin().listener.triggerPlayerListUpdates();
+ }
+ }
+ else if ("telnet.enhancedplus".equalsIgnoreCase(command))
+ {
+ enhancedPlusMode = !enhancedPlusMode;
+ writeLine((enhancedPlusMode ? "A" : "Dea") + "ctivated enhanced+ mode.");
+ }
+ else
+ {
+ writeLine("Invalid telnet command, use \"telnet.help\" to view help.");
+ }
+ }
+
+ public void syncTriggerPlayerListUpdate(String playerListData)
+ {
+ if (!enhancedMode)
+ {
+ return;
+ }
+
+ synchronized (clientSocket)
+ {
+ if (clientSocket.isClosed())
+ {
+ return;
+ }
+
+ writeLine("playerList~" + playerListData);
+ }
+ }
+
+ public void syncUsageUpdate(String usageData)
+ {
+ if (!enhancedPlusMode)
+ {
+ return;
+ }
+
+ synchronized (clientSocket)
+ {
+ if (clientSocket.isClosed())
+ {
+ return;
+ }
+
+ writeLine("usage~" + usageData);
+ }
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/session/FilterMode.java b/src/main/java/me/totalfreedom/bukkittelnet/session/FilterMode.java
new file mode 100644
index 0000000..9adbdfb
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/session/FilterMode.java
@@ -0,0 +1,9 @@
+package me.totalfreedom.bukkittelnet.session;
+
+public enum FilterMode
+{
+
+ NONE,
+ NON_CHAT_ONLY,
+ CHAT_ONLY;
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/session/SessionCommandSender.java b/src/main/java/me/totalfreedom/bukkittelnet/session/SessionCommandSender.java
new file mode 100644
index 0000000..78f01aa
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/session/SessionCommandSender.java
@@ -0,0 +1,162 @@
+package me.totalfreedom.bukkittelnet.session;
+
+//import dev.plex.SimulatedPlayer;
+
+import lombok.Getter;
+import lombok.Setter;
+import me.totalfreedom.bukkittelnet.BukkitTelnet;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Bukkit;
+import org.bukkit.Server;
+import org.bukkit.command.CommandSender;
+import org.bukkit.permissions.Permission;
+import org.bukkit.permissions.PermissionAttachment;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Set;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class SessionCommandSender implements CommandSender
+{
+ private final ClientSession session;
+ private boolean opped = true;
+
+ public SessionCommandSender(ClientSession session)
+ {
+ this.session = session;
+ }
+
+ @Override
+ public void sendMessage(String message)
+ {
+ session.writeRawLine(message);
+ }
+
+ @Override
+ public void sendMessage(String[] messages)
+ {
+ for (String message : messages)
+ {
+ sendMessage(message);
+ }
+ }
+
+ @Override
+ public void sendMessage(@Nullable UUID sender, @NotNull String message)
+ {
+ this.sendMessage(message);
+ }
+
+ @Override
+ public void sendMessage(@Nullable UUID sender, @NotNull String... messages)
+ {
+ this.sendMessage(messages);
+ }
+
+ @Override
+ public String getName()
+ {
+ return this.session.getUserName();
+ }
+
+ @Override
+ public @NotNull Spigot spigot()
+ {
+ return new Spigot();
+ }
+
+ @Override
+ public @NotNull Component name()
+ {
+ return Component.text(this.getSession().getUserName());
+ }
+
+ @Override
+ public Server getServer()
+ {
+ return Bukkit.getServer();
+ }
+
+ @Override
+ public boolean isPermissionSet(String name)
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isPermissionSet(Permission perm)
+ {
+ return true;
+ }
+
+ @Override
+ public boolean hasPermission(String name)
+ {
+ return BukkitTelnet.getPlugin().handler.hasPermission(this.getSession().getUserName(), name);
+ }
+
+ @Override
+ public boolean hasPermission(Permission perm)
+ {
+ return hasPermission(perm.getName());
+ }
+
+ @Override
+ public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value)
+ {
+ return null;
+ }
+
+ @Override
+ public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin)
+ {
+ return null;
+ }
+
+ @Override
+ public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks)
+ {
+ return null;
+ }
+
+ @Override
+ public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks)
+ {
+ return null;
+ }
+
+ @Override
+ public void removeAttachment(@NotNull PermissionAttachment attachment)
+ {
+
+ }
+
+ @Override
+ public void recalculatePermissions()
+ {
+
+ }
+
+ @Override
+ public @NotNull Set getEffectivePermissions()
+ {
+ return null;
+ }
+
+ @Override
+ public boolean isOp()
+ {
+ return opped;
+ }
+
+ @Override
+ public void setOp(boolean value)
+ {
+ this.opped = false;
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/thirdparty/FileUtils.java b/src/main/java/me/totalfreedom/bukkittelnet/thirdparty/FileUtils.java
new file mode 100644
index 0000000..6ce2737
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/thirdparty/FileUtils.java
@@ -0,0 +1,159 @@
+package me.totalfreedom.bukkittelnet.thirdparty;
+
+import org.bukkit.plugin.Plugin;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+
+/**
+ * Represents all File-related utilities.
+ */
+public class FileUtils
+{
+
+ /**
+ * Downloads a file from the specified URIL and saves it at the specified location.
+ *
+ * @param url The URL from where to download the file from.
+ * @param output The file where the file will be stored.
+ * @throws MalformedURLException
+ * @throws IOException
+ */
+ public static void downloadFile(String url, File output) throws MalformedURLException, IOException
+ {
+ final URL website = new URL(url);
+ final ReadableByteChannel rbc = Channels.newChannel(website.openStream());
+ final FileOutputStream fos = new FileOutputStream(output);
+ fos.getChannel().transferFrom(rbc, 0, 1 << 24);
+ fos.close();
+ }
+
+ /**
+ * Saves a raw Object to a file.
+ *
+ * @param object The object to save.
+ * @param file The file where the object will be stored.
+ * @throws IOException
+ */
+ public static void saveObject(Object object, File file) throws IOException
+ {
+ if (!file.exists())
+ {
+ file.getParentFile().mkdirs();
+ }
+
+ final ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
+ oos.writeObject(object);
+ oos.close();
+ }
+
+ /**
+ * Attempts to load a raw Object from a file.
+ *
+ * @param file The file where the object is stored.
+ * @throws IOException
+ */
+ public static Object loadObject(File file) throws IOException, ClassNotFoundException
+ {
+ if (!file.exists())
+ {
+ throw new IllegalStateException();
+ }
+
+ final ObjectInputStream oos = new ObjectInputStream(new FileInputStream(file));
+ final Object object = oos.readObject();
+ oos.close();
+
+ return object;
+ }
+
+ /**
+ * Returns a file at located at the Plugins Data folder.
+ *
+ * @param plugin The plugin to use
+ * @param name The name of the file.
+ * @return The requested file.
+ */
+ public static File getPluginFile(Plugin plugin, String name)
+ {
+ return new File(plugin.getDataFolder(), name);
+ }
+
+ /**
+ * Returns the root location of the CraftBukkit server.
+ *
+ * @return The current working directory.
+ */
+ public static File getRoot()
+ {
+ return new File(".");
+ }
+
+ /**
+ * Returns the folder where all plugins are stored.
+ *
+ * @return The plugins folder.
+ */
+ public static File getPluginsFolder()
+ {
+ return new File(getRoot(), "plugins");
+ }
+
+ /**
+ * Returns a file at the root of the CraftBukkit server.
+ *
+ * @param name The name of the file.
+ * @return The requested file.
+ */
+ public static File getRootFile(String name)
+ {
+ return new File(getRoot(), name);
+ }
+
+ /**
+ * Delete a specified folder and all contents quietly.
+ *
+ *
+ * Warning: This method will delete files, only folders!
+ *
+ * @param file The folder to delete.
+ * @return true if the delete was successful.
+ * @deprecated Not in use; Relies on CraftBukkit source
+ */
+ public static boolean deleteFolder(File file)
+ {
+ if (file.exists() && file.isDirectory())
+ {
+ //return net.minecraft.util.org.apache.commons.io.FileUtils.deleteQuietly(file);
+ }
+ return false;
+ }
+
+ /**
+ * Write the specified InputStream to a file.
+ *
+ * @param in The InputStream from which to read.
+ * @param file The File to write to.
+ * @throws IOException
+ */
+ public static void copy(InputStream in, File file) throws IOException
+ {
+ if (!file.exists())
+ {
+ file.getParentFile().mkdirs();
+ }
+
+ OutputStream out = new FileOutputStream(file);
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0)
+ {
+ out.write(buf, 0, len);
+ }
+ out.close();
+ in.close();
+ }
+}
diff --git a/src/main/java/me/totalfreedom/bukkittelnet/thirdparty/YamlConfig.java b/src/main/java/me/totalfreedom/bukkittelnet/thirdparty/YamlConfig.java
new file mode 100644
index 0000000..4799ddc
--- /dev/null
+++ b/src/main/java/me/totalfreedom/bukkittelnet/thirdparty/YamlConfig.java
@@ -0,0 +1,155 @@
+package me.totalfreedom.bukkittelnet.thirdparty;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.logging.log4j.core.util.IOUtils;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.plugin.Plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Represents a definable YAML configuration.
+ *
+ * @see YamlConfiguration
+ */
+public class YamlConfig extends YamlConfiguration
+{
+
+ private final Plugin PLUGIN;
+ private final File CONFIG_FILE;
+ private final boolean COPY_DEFAULTS;
+
+ /**
+ * Creates a new YamlConfig instance.
+ *
+ *
+ * Example:
+ *
+ * YamlConfig config = new YamlConfig(this, "config.yml", true);
+ * config.load();
+ *
+ *
+ * @param plugin The plugin to which the config belongs.
+ * @param fileName The filename of the config file.
+ * @param copyDefaults If the defaults should be copied and/loaded from a config in the plugin jar-file.
+ */
+ public YamlConfig(Plugin plugin, String fileName, boolean copyDefaults)
+ {
+ this(plugin, FileUtils.getPluginFile(plugin, fileName), copyDefaults);
+ }
+
+ /**
+ * Creates a new YamlConfig instance.
+ *
+ *
+ * Example:
+ *
+ * YamlConfig config = new YamlConfig(this, new File(plugin.getDataFolder() + "/players", "DarthSalamon.yml"), false);
+ * config.load();
+ *
+ *
+ * @param plugin The plugin to which the config belongs.
+ * @param file The file of the config file.
+ * @param copyDefaults If the defaults should be copied and/loaded from a config in the plugin jar-file.
+ */
+ public YamlConfig(Plugin plugin, File file, boolean copyDefaults)
+ {
+ this.PLUGIN = plugin;
+ this.CONFIG_FILE = file;
+ this.COPY_DEFAULTS = copyDefaults;
+ }
+
+ /**
+ * Saves the configuration to the predefined file.
+ *
+ * @see #YamlConfig(Plugin, String, boolean)
+ */
+ public void save()
+ {
+ try
+ {
+ super.save(CONFIG_FILE);
+ }
+ catch (Exception ex)
+ {
+ PLUGIN.getLogger().severe("Could not save configuration file: " + CONFIG_FILE.getName());
+ PLUGIN.getLogger().severe(ExceptionUtils.getStackTrace(ex));
+ }
+ }
+
+ /**
+ * Loads the configuration from the predefined file.
+ *
+ *
+ * Optionally, if loadDefaults has been set to true, the file will be copied over from the default inside the jar-file of the owning plugin.
+ *
+ * @see #YamlConfig(Plugin, String, boolean)
+ */
+ public void load()
+ {
+ try
+ {
+ if (COPY_DEFAULTS)
+ {
+ if (!CONFIG_FILE.exists())
+ {
+ CONFIG_FILE.getParentFile().mkdirs();
+ try
+ {
+ FileUtils.copy(PLUGIN.getResource(CONFIG_FILE.getName()), CONFIG_FILE);
+ }
+ catch (IOException ex)
+ {
+ PLUGIN.getLogger().severe("Could not write default configuration file: " + CONFIG_FILE.getName());
+ PLUGIN.getLogger().severe(ExceptionUtils.getStackTrace(ex));
+ }
+ PLUGIN.getLogger().info("Installed default configuration " + CONFIG_FILE.getName());
+ }
+
+ super.addDefaults(getDefaultConfig());
+ }
+
+ super.load(CONFIG_FILE);
+ }
+ catch (Exception ex)
+ {
+ PLUGIN.getLogger().severe("Could not load configuration file: " + CONFIG_FILE.getName());
+ PLUGIN.getLogger().severe(ExceptionUtils.getStackTrace(ex));
+ }
+ }
+
+ /**
+ * Returns the raw YamlConfiguration this config is based on.
+ *
+ * @return The YamlConfiguration.
+ * @see YamlConfiguration
+ */
+ public YamlConfiguration getConfig()
+ {
+ return this;
+ }
+
+ /**
+ * Returns the default configuration as been stored in the jar-file of the owning plugin.
+ *
+ * @return The default configuration.
+ */
+ public YamlConfiguration getDefaultConfig()
+ {
+ final YamlConfiguration DEFAULT_CONFIG = new YamlConfiguration();
+ try
+ {
+ final String configString = IOUtils.toString(new InputStreamReader(PLUGIN.getResource(CONFIG_FILE.getName())));
+ DEFAULT_CONFIG.loadFromString(configString);
+ }
+ catch (Throwable ex)
+ {
+ PLUGIN.getLogger().severe("Could not load default configuration: " + CONFIG_FILE.getName());
+ PLUGIN.getLogger().severe(ExceptionUtils.getStackTrace(ex));
+ return null;
+ }
+ return DEFAULT_CONFIG;
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..0426760
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,19 @@
+# BukkitTelnet v4.6.1 Configuration File
+
+# Address to listen on, leave blank for all
+address: ''
+
+# Port to bind to:
+port: 8765
+
+# Main connection password, must be defined
+password: 'walrus'
+
+# List of admins / IPs that don't have to use the password
+admins:
+ madgeek1450:
+ - 74.131.135.3
+ prozza:
+ - 176.56.237.227
+ markbyron:
+ - 71.47.67.103