diff --git a/pom.xml b/pom.xml index 37e5f505..622af6bd 100644 --- a/pom.xml +++ b/pom.xml @@ -108,10 +108,6 @@ junit junit - - json-simple - com.googlecode.json-simple - persistence-api javax.persistence @@ -146,10 +142,6 @@ junit junit - - json-simple - com.googlecode.json-simple - persistence-api javax.persistence @@ -276,11 +268,6 @@ javax.inject fr.xephi.authme.libs.javax.inject - - - org.mcstats - fr.xephi.authme - target/${project.finalName}-spigot.jar @@ -331,11 +318,6 @@ javax.inject fr.xephi.authme.libs.javax.inject - - - org.mcstats - fr.xephi.authme - target/${project.finalName}-legacy.jar @@ -544,21 +526,6 @@ - - - org.mcstats.bukkit - metrics - R8-SNAPSHOT - compile - - - org.bukkit - bukkit - - - true - - com.comphenix.protocol diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java index 893aba22..622ed481 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java @@ -1,6 +1,5 @@ package fr.xephi.authme.data.limbo; -import fr.xephi.authme.data.backup.LimboPlayerStorage; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; diff --git a/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java similarity index 98% rename from src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java rename to src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java index 1b227028..dd2790bc 100644 --- a/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.data.backup; +package fr.xephi.authme.data.limbo; import com.google.common.io.Files; import com.google.gson.Gson; @@ -10,11 +10,10 @@ import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.Location; diff --git a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java index 1e786244..11e9d6af 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java +++ b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java @@ -2,7 +2,7 @@ package fr.xephi.authme.initialization; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.backup.LimboPlayerStorage; +import fr.xephi.authme.data.limbo.LimboPlayerStorage; import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.service.PluginHookService; diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index 0dd42181..e939d11f 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -6,6 +6,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.metrics.Metrics; import fr.xephi.authme.output.ConsoleFilter; import fr.xephi.authme.output.Log4JFilter; import fr.xephi.authme.service.BukkitService; @@ -18,10 +19,8 @@ import fr.xephi.authme.util.StringUtils; import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.mcstats.Metrics; import javax.inject.Inject; -import java.io.IOException; import java.util.logging.Logger; import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE; @@ -45,40 +44,28 @@ public class OnStartupTasks { } public static void sendMetrics(AuthMe plugin, Settings settings) { - try { - final Metrics metrics = new Metrics(plugin); + final Metrics metrics = new Metrics(plugin); - final Metrics.Graph languageGraph = metrics.createGraph("Messages Language"); - final String messagesLanguage = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE); - languageGraph.addPlotter(new Metrics.Plotter(messagesLanguage) { - @Override - public int getValue() { - return 1; - } - }); + metrics.addCustomChart(new Metrics.SimplePie("messages_language") { + @Override + public String getValue() { + return settings.getProperty(PluginSettings.MESSAGES_LANGUAGE); + } + }); - final Metrics.Graph databaseBackend = metrics.createGraph("Database Backend"); - final String dataSource = settings.getProperty(DatabaseSettings.BACKEND).toString(); - databaseBackend.addPlotter(new Metrics.Plotter(dataSource) { - @Override - public int getValue() { - return 1; - } - }); - - // Submit metrics - metrics.start(); - } catch (IOException e) { - // Failed to submit the metrics data - ConsoleLogger.logException("Can't send Metrics data! The plugin will work anyway...", e); - } + metrics.addCustomChart(new Metrics.SimplePie("database_backend") { + @Override + public String getValue() { + return settings.getProperty(DatabaseSettings.BACKEND).toString(); + } + }); } /** * Sets up the console filter if enabled. * * @param settings the settings - * @param logger the plugin logger + * @param logger the plugin logger */ public static void setupConsoleFilter(Settings settings, Logger logger) { if (!settings.getProperty(SecuritySettings.REMOVE_PASSWORD_FROM_CONSOLE)) { diff --git a/src/main/java/fr/xephi/authme/message/MessageFileHandler.java b/src/main/java/fr/xephi/authme/message/MessageFileHandler.java index a0d4e947..5d19e8f4 100644 --- a/src/main/java/fr/xephi/authme/message/MessageFileHandler.java +++ b/src/main/java/fr/xephi/authme/message/MessageFileHandler.java @@ -1,6 +1,7 @@ package fr.xephi.authme.message; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.util.FileUtils; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -16,6 +17,7 @@ public class MessageFileHandler { // regular file private final String filename; private final FileConfiguration configuration; + private final String updateAddition; // default file private final String defaultFile; private FileConfiguration defaultConfiguration; @@ -25,11 +27,15 @@ public class MessageFileHandler { * * @param file the file to use for messages * @param defaultFile the default file from the JAR to use if no message is found + * @param updateCommand command to update the messages file (nullable) to show in error messages */ - public MessageFileHandler(File file, String defaultFile) { + public MessageFileHandler(File file, String defaultFile, String updateCommand) { this.filename = file.getName(); this.configuration = YamlConfiguration.loadConfiguration(file); this.defaultFile = defaultFile; + this.updateAddition = updateCommand == null + ? "" + : " (or run " + updateCommand + ")"; } /** @@ -53,7 +59,7 @@ public class MessageFileHandler { if (message == null) { ConsoleLogger.warning("Error getting message with key '" + key + "'. " - + "Please update your config file '" + filename + "' (or run /authme messages)"); + + "Please update your config file '" + filename + "'" + updateAddition); return getDefault(key); } return message; @@ -78,7 +84,7 @@ public class MessageFileHandler { */ private String getDefault(String key) { if (defaultConfiguration == null) { - InputStream stream = MessageFileHandler.class.getClassLoader().getResourceAsStream(defaultFile); + InputStream stream = FileUtils.getResourceFromJar(defaultFile); defaultConfiguration = YamlConfiguration.loadConfiguration(new InputStreamReader(stream)); } String message = defaultConfiguration.getString(key); diff --git a/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java b/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java index 987caecc..5b809b2c 100644 --- a/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java +++ b/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java @@ -36,10 +36,23 @@ public class MessageFileHandlerProvider { * @return the message file handler */ public MessageFileHandler initializeHandler(Function pathBuilder) { + return initializeHandler(pathBuilder, null); + } + + /** + * Initializes a message file handler with the messages file of the configured language. + * Ensures beforehand that the messages file exists or creates it otherwise. + * + * @param pathBuilder function taking the configured language code as argument and returning the messages file + * @param updateCommand command to run to update the languages file (nullable) + * @return the message file handler + */ + public MessageFileHandler initializeHandler(Function pathBuilder, String updateCommand) { String language = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE); return new MessageFileHandler( initializeFile(language, pathBuilder), - pathBuilder.apply(DEFAULT_LANGUAGE)); + pathBuilder.apply(DEFAULT_LANGUAGE), + updateCommand); } /** @@ -53,7 +66,8 @@ public class MessageFileHandlerProvider { File initializeFile(String language, Function pathBuilder) { String filePath = pathBuilder.apply(language); File file = new File(dataFolder, filePath); - if (FileUtils.copyFileFromResource(file, filePath)) { + // Check that JAR file exists to avoid logging an error + if (FileUtils.getResourceFromJar(filePath) != null && FileUtils.copyFileFromResource(file, filePath)) { return file; } diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index 6cc57480..7546a89c 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -17,7 +17,7 @@ public enum MessageKey { /** AntiBot protection mode is enabled! You have to wait some minutes before joining the server. */ KICK_ANTIBOT("kick_antibot"), - /** Can't find the requested user in the database! */ + /** This user isn't registered! */ UNKNOWN_USER("unknown_user"), /** Your quit location was unsafe, you have been teleported to the world's spawnpoint. */ @@ -26,7 +26,7 @@ public enum MessageKey { /** You're not logged in! */ NOT_LOGGED_IN("not_logged_in"), - /** Usage: /login <password> */ + /** Usage: /login <password> */ USAGE_LOGIN("usage_log"), /** Wrong password! */ @@ -56,19 +56,19 @@ public enum MessageKey { /** An unexpected error occurred, please contact an administrator! */ ERROR("error"), - /** Please, login with the command "/login <password>" */ + /** Please, login with the command: /login <password> */ LOGIN_MESSAGE("login_msg"), - /** Please, register to the server with the command "/register <password> <ConfirmPassword>" */ + /** Please, register to the server with the command: /register <password> <ConfirmPassword> */ REGISTER_MESSAGE("reg_msg"), /** You have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection! */ MAX_REGISTER_EXCEEDED("max_reg", "%max_acc", "%reg_count", "%reg_names"), - /** Usage: /register <password> <ConfirmPassword> */ + /** Usage: /register <password> <ConfirmPassword> */ USAGE_REGISTER("usage_reg"), - /** Usage: /unregister <password> */ + /** Usage: /unregister <password> */ USAGE_UNREGISTER("usage_unreg"), /** Password changed successfully! */ @@ -95,7 +95,7 @@ public enum MessageKey { /** You're already logged in! */ ALREADY_LOGGED_IN_ERROR("logged_in"), - /** Logged-out successfully! */ + /** Logged out successfully! */ LOGOUT_SUCCESS("logout"), /** The same username is already playing on the server! */ @@ -113,7 +113,7 @@ public enum MessageKey { /** Login timeout exceeded, you have been kicked from the server, please try again! */ LOGIN_TIMEOUT_ERROR("timeout"), - /** Usage: /changepassword <oldPassword> <newPassword> */ + /** Usage: /changepassword <oldPassword> <newPassword> */ USAGE_CHANGE_PASSWORD("usage_changepassword"), /** Your username is either too short or too long! */ @@ -122,13 +122,13 @@ public enum MessageKey { /** Your username contains illegal characters. Allowed chars: REG_EX */ INVALID_NAME_CHARACTERS("regex", "REG_EX"), - /** Please add your email to your account with the command "/email add <yourEmail> <confirmEmail>" */ + /** Please add your email to your account with the command: /email add <yourEmail> <confirmEmail> */ ADD_EMAIL_MESSAGE("add_email"), - /** Forgot your password? Please use the command "/email recovery <yourEmail>" */ + /** Forgot your password? Please use the command: /email recovery <yourEmail> */ FORGOT_PASSWORD_MESSAGE("recovery_email"), - /** To login you have to solve a captcha code, please use the command "/captcha <theCaptcha>" */ + /** To login you have to solve a captcha code, please use the command: /captcha <theCaptcha> */ USAGE_CAPTCHA("usage_captcha", ""), /** Wrong captcha, please type "/captcha THE_CAPTCHA" into the chat! */ @@ -143,13 +143,13 @@ public enum MessageKey { /** The server is full, try again later! */ KICK_FULL_SERVER("kick_fullserver"), - /** Usage: /email add <email> <confirmEmail> */ + /** Usage: /email add <email> <confirmEmail> */ USAGE_ADD_EMAIL("usage_email_add"), - /** Usage: /email change <oldEmail> <newEmail> */ + /** Usage: /email change <oldEmail> <newEmail> */ USAGE_CHANGE_EMAIL("usage_email_change"), - /** Usage: /email recovery <Email> */ + /** Usage: /email recovery <Email> */ USAGE_RECOVER_EMAIL("usage_email_recovery"), /** Invalid new email, try again! */ @@ -224,7 +224,7 @@ public enum MessageKey { /** A recovery code to reset your password has been sent to your email. */ RECOVERY_CODE_SENT("recovery_code_sent"), - /** The recovery code is not correct! Use /email recovery [email] to generate a new one */ + /** The recovery code is not correct! Use "/email recovery [email]" to generate a new one */ INCORRECT_RECOVERY_CODE("recovery_code_incorrect"); private String key; diff --git a/src/main/java/fr/xephi/authme/message/Messages.java b/src/main/java/fr/xephi/authme/message/Messages.java index 86fa2db6..5d777ab8 100644 --- a/src/main/java/fr/xephi/authme/message/Messages.java +++ b/src/main/java/fr/xephi/authme/message/Messages.java @@ -107,7 +107,7 @@ public class Messages implements Reloadable { @Override public void reload() { this.messageFileHandler = messageFileHandlerProvider - .initializeHandler(lang -> "messages/messages_" + lang + ".yml"); + .initializeHandler(lang -> "messages/messages_" + lang + ".yml", "/authme messages"); } private static String formatMessage(String message) { diff --git a/src/main/java/fr/xephi/authme/metrics/Metrics.java b/src/main/java/fr/xephi/authme/metrics/Metrics.java new file mode 100644 index 00000000..714036ce --- /dev/null +++ b/src/main/java/fr/xephi/authme/metrics/Metrics.java @@ -0,0 +1,1031 @@ +package fr.xephi.authme.metrics; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import javax.net.ssl.HttpsURLConnection; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +/** + * bStats collects some data for plugin authors. + *

+ * Check out https://bStats.org/ to learn more about bStats! + */ +public class Metrics { + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData"; + + // Should failed requests be logged? + private static boolean logFailedRequests; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private final JavaPlugin plugin; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param plugin The plugin which stats should be submitted. + */ + public Metrics(JavaPlugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + 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); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + + // Inform the server owners about bStats + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "This has nearly no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)" + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + + // Load the data + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + if (config.getBoolean("enabled", true)) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { + } + } + // Register our service + Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, new Runnable() { + @Override + public void run() { + submitData(); + } + }); + } + }, 1000 * 60 * 5, 1000 * 60 * 30); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JSONObject getPluginData() { + JSONObject data = new JSONObject(); + + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.put("pluginName", pluginName); // Append the name of the plugin + data.put("pluginVersion", pluginVersion); // Append the version of the plugin + JSONArray customCharts = new JSONArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JSONObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.put("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JSONObject getServerData() { + // Minecraft specific data + int playerAmount = Bukkit.getOnlinePlayers().size(); + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = org.bukkit.Bukkit.getVersion(); + bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JSONObject data = new JSONObject(); + + data.put("serverUUID", serverUUID); + + data.put("playerAmount", playerAmount); + data.put("onlineMode", onlineMode); + data.put("bukkitVersion", bukkitVersion); + + data.put("javaVersion", javaVersion); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JSONObject data = getServerData(); + + JSONArray pluginData = new JSONArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + } catch (NoSuchFieldException ignored) { + continue; // Continue "searching" + } + // Found one! + try { + pluginData.add(service.getMethod("getPluginData").invoke(Bukkit.getServicesManager().load(service))); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { + } + } + + data.put("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + + } + + /** + * Sends the data to the bStats server. + * + * @param data The data to send. + * + * @throws Exception If the request failed. + */ + private static void sendData(JSONObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + connection.getInputStream().close(); // We don't care about the response - Just send our data :) + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * + * @return The gzipped String. + * + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes("UTF-8")); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + protected final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + protected JSONObject getRequestJsonObject() { + JSONObject chart = new JSONObject(); + chart.put("chartId", chartId); + try { + JSONObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.put("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JSONObject getChartData(); + + } + + /** + * Represents a custom simple pie. + */ + public static abstract class SimplePie extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public SimplePie(String chartId) { + super(chartId); + } + + /** + * Gets the value of the pie. + * + * @return The value of the pie. + */ + public abstract String getValue(); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + String value = getValue(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static abstract class AdvancedPie extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public AdvancedPie(String chartId) { + super(chartId); + } + + /** + * Gets the values of the pie. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The values of the pie. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static abstract class SingleLineChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public SingleLineChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @return The value of the chart. + */ + public abstract int getValue(); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + int value = getValue(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static abstract class MultiLineChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public MultiLineChart(String chartId) { + super(chartId); + } + + /** + * Gets the values of the chart. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The values of the chart. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static abstract class SimpleBarChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public SimpleBarChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The value of the chart. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JSONArray categoryValues = new JSONArray(); + categoryValues.add(entry.getValue()); + values.put(entry.getKey(), categoryValues); + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static abstract class AdvancedBarChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public AdvancedBarChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The value of the chart. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + 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) { + continue; // Skip this invalid + } + allSkipped = false; + JSONArray categoryValues = new JSONArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.put(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple map chart. + */ + public static abstract class SimpleMapChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public SimpleMapChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @return The value of the chart. + */ + public abstract Country getValue(); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + Country value = getValue(); + + if (value == null) { + // Null = skip the chart + return null; + } + data.put("value", value.getCountryIsoTag()); + return data; + } + + } + + /** + * Represents a custom advanced map chart. + */ + public static abstract class AdvancedMapChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public AdvancedMapChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The value of the chart. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey().getCountryIsoTag(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * A enum which is used for custom maps. + */ + public enum Country { + + /** + * bStats will use the country of the server. + */ + AUTO_DETECT("AUTO", "Auto Detected"), + + ANDORRA("AD", "Andorra"), + UNITED_ARAB_EMIRATES("AE", "United Arab Emirates"), + AFGHANISTAN("AF", "Afghanistan"), + ANTIGUA_AND_BARBUDA("AG", "Antigua and Barbuda"), + ANGUILLA("AI", "Anguilla"), + ALBANIA("AL", "Albania"), + ARMENIA("AM", "Armenia"), + NETHERLANDS_ANTILLES("AN", "Netherlands Antilles"), + ANGOLA("AO", "Angola"), + ANTARCTICA("AQ", "Antarctica"), + ARGENTINA("AR", "Argentina"), + AMERICAN_SAMOA("AS", "American Samoa"), + AUSTRIA("AT", "Austria"), + AUSTRALIA("AU", "Australia"), + ARUBA("AW", "Aruba"), + ÅLAND_ISLANDS("AX", "Åland Islands"), + AZERBAIJAN("AZ", "Azerbaijan"), + BOSNIA_AND_HERZEGOVINA("BA", "Bosnia and Herzegovina"), + BARBADOS("BB", "Barbados"), + BANGLADESH("BD", "Bangladesh"), + BELGIUM("BE", "Belgium"), + BURKINA_FASO("BF", "Burkina Faso"), + BULGARIA("BG", "Bulgaria"), + BAHRAIN("BH", "Bahrain"), + BURUNDI("BI", "Burundi"), + BENIN("BJ", "Benin"), + SAINT_BARTHÉLEMY("BL", "Saint Barthélemy"), + BERMUDA("BM", "Bermuda"), + BRUNEI("BN", "Brunei"), + BOLIVIA("BO", "Bolivia"), + BONAIRE_SINT_EUSTATIUS_AND_SABA("BQ", "Bonaire, Sint Eustatius and Saba"), + BRAZIL("BR", "Brazil"), + BAHAMAS("BS", "Bahamas"), + BHUTAN("BT", "Bhutan"), + BOUVET_ISLAND("BV", "Bouvet Island"), + BOTSWANA("BW", "Botswana"), + BELARUS("BY", "Belarus"), + BELIZE("BZ", "Belize"), + CANADA("CA", "Canada"), + COCOS_ISLANDS("CC", "Cocos Islands"), + THE_DEMOCRATIC_REPUBLIC_OF_CONGO("CD", "The Democratic Republic Of Congo"), + CENTRAL_AFRICAN_REPUBLIC("CF", "Central African Republic"), + CONGO("CG", "Congo"), + SWITZERLAND("CH", "Switzerland"), + CÔTE_D_IVOIRE("CI", "Côte d'Ivoire"), + COOK_ISLANDS("CK", "Cook Islands"), + CHILE("CL", "Chile"), + CAMEROON("CM", "Cameroon"), + CHINA("CN", "China"), + COLOMBIA("CO", "Colombia"), + COSTA_RICA("CR", "Costa Rica"), + CUBA("CU", "Cuba"), + CAPE_VERDE("CV", "Cape Verde"), + CURAÇAO("CW", "Curaçao"), + CHRISTMAS_ISLAND("CX", "Christmas Island"), + CYPRUS("CY", "Cyprus"), + CZECH_REPUBLIC("CZ", "Czech Republic"), + GERMANY("DE", "Germany"), + DJIBOUTI("DJ", "Djibouti"), + DENMARK("DK", "Denmark"), + DOMINICA("DM", "Dominica"), + DOMINICAN_REPUBLIC("DO", "Dominican Republic"), + ALGERIA("DZ", "Algeria"), + ECUADOR("EC", "Ecuador"), + ESTONIA("EE", "Estonia"), + EGYPT("EG", "Egypt"), + WESTERN_SAHARA("EH", "Western Sahara"), + ERITREA("ER", "Eritrea"), + SPAIN("ES", "Spain"), + ETHIOPIA("ET", "Ethiopia"), + FINLAND("FI", "Finland"), + FIJI("FJ", "Fiji"), + FALKLAND_ISLANDS("FK", "Falkland Islands"), + MICRONESIA("FM", "Micronesia"), + FAROE_ISLANDS("FO", "Faroe Islands"), + FRANCE("FR", "France"), + GABON("GA", "Gabon"), + UNITED_KINGDOM("GB", "United Kingdom"), + GRENADA("GD", "Grenada"), + GEORGIA("GE", "Georgia"), + FRENCH_GUIANA("GF", "French Guiana"), + GUERNSEY("GG", "Guernsey"), + GHANA("GH", "Ghana"), + GIBRALTAR("GI", "Gibraltar"), + GREENLAND("GL", "Greenland"), + GAMBIA("GM", "Gambia"), + GUINEA("GN", "Guinea"), + GUADELOUPE("GP", "Guadeloupe"), + EQUATORIAL_GUINEA("GQ", "Equatorial Guinea"), + GREECE("GR", "Greece"), + SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS("GS", "South Georgia And The South Sandwich Islands"), + GUATEMALA("GT", "Guatemala"), + GUAM("GU", "Guam"), + GUINEA_BISSAU("GW", "Guinea-Bissau"), + GUYANA("GY", "Guyana"), + HONG_KONG("HK", "Hong Kong"), + HEARD_ISLAND_AND_MCDONALD_ISLANDS("HM", "Heard Island And McDonald Islands"), + HONDURAS("HN", "Honduras"), + CROATIA("HR", "Croatia"), + HAITI("HT", "Haiti"), + HUNGARY("HU", "Hungary"), + INDONESIA("ID", "Indonesia"), + IRELAND("IE", "Ireland"), + ISRAEL("IL", "Israel"), + ISLE_OF_MAN("IM", "Isle Of Man"), + INDIA("IN", "India"), + BRITISH_INDIAN_OCEAN_TERRITORY("IO", "British Indian Ocean Territory"), + IRAQ("IQ", "Iraq"), + IRAN("IR", "Iran"), + ICELAND("IS", "Iceland"), + ITALY("IT", "Italy"), + JERSEY("JE", "Jersey"), + JAMAICA("JM", "Jamaica"), + JORDAN("JO", "Jordan"), + JAPAN("JP", "Japan"), + KENYA("KE", "Kenya"), + KYRGYZSTAN("KG", "Kyrgyzstan"), + CAMBODIA("KH", "Cambodia"), + KIRIBATI("KI", "Kiribati"), + COMOROS("KM", "Comoros"), + SAINT_KITTS_AND_NEVIS("KN", "Saint Kitts And Nevis"), + NORTH_KOREA("KP", "North Korea"), + SOUTH_KOREA("KR", "South Korea"), + KUWAIT("KW", "Kuwait"), + CAYMAN_ISLANDS("KY", "Cayman Islands"), + KAZAKHSTAN("KZ", "Kazakhstan"), + LAOS("LA", "Laos"), + LEBANON("LB", "Lebanon"), + SAINT_LUCIA("LC", "Saint Lucia"), + LIECHTENSTEIN("LI", "Liechtenstein"), + SRI_LANKA("LK", "Sri Lanka"), + LIBERIA("LR", "Liberia"), + LESOTHO("LS", "Lesotho"), + LITHUANIA("LT", "Lithuania"), + LUXEMBOURG("LU", "Luxembourg"), + LATVIA("LV", "Latvia"), + LIBYA("LY", "Libya"), + MOROCCO("MA", "Morocco"), + MONACO("MC", "Monaco"), + MOLDOVA("MD", "Moldova"), + MONTENEGRO("ME", "Montenegro"), + SAINT_MARTIN("MF", "Saint Martin"), + MADAGASCAR("MG", "Madagascar"), + MARSHALL_ISLANDS("MH", "Marshall Islands"), + MACEDONIA("MK", "Macedonia"), + MALI("ML", "Mali"), + MYANMAR("MM", "Myanmar"), + MONGOLIA("MN", "Mongolia"), + MACAO("MO", "Macao"), + NORTHERN_MARIANA_ISLANDS("MP", "Northern Mariana Islands"), + MARTINIQUE("MQ", "Martinique"), + MAURITANIA("MR", "Mauritania"), + MONTSERRAT("MS", "Montserrat"), + MALTA("MT", "Malta"), + MAURITIUS("MU", "Mauritius"), + MALDIVES("MV", "Maldives"), + MALAWI("MW", "Malawi"), + MEXICO("MX", "Mexico"), + MALAYSIA("MY", "Malaysia"), + MOZAMBIQUE("MZ", "Mozambique"), + NAMIBIA("NA", "Namibia"), + NEW_CALEDONIA("NC", "New Caledonia"), + NIGER("NE", "Niger"), + NORFOLK_ISLAND("NF", "Norfolk Island"), + NIGERIA("NG", "Nigeria"), + NICARAGUA("NI", "Nicaragua"), + NETHERLANDS("NL", "Netherlands"), + NORWAY("NO", "Norway"), + NEPAL("NP", "Nepal"), + NAURU("NR", "Nauru"), + NIUE("NU", "Niue"), + NEW_ZEALAND("NZ", "New Zealand"), + OMAN("OM", "Oman"), + PANAMA("PA", "Panama"), + PERU("PE", "Peru"), + FRENCH_POLYNESIA("PF", "French Polynesia"), + PAPUA_NEW_GUINEA("PG", "Papua New Guinea"), + PHILIPPINES("PH", "Philippines"), + PAKISTAN("PK", "Pakistan"), + POLAND("PL", "Poland"), + SAINT_PIERRE_AND_MIQUELON("PM", "Saint Pierre And Miquelon"), + PITCAIRN("PN", "Pitcairn"), + PUERTO_RICO("PR", "Puerto Rico"), + PALESTINE("PS", "Palestine"), + PORTUGAL("PT", "Portugal"), + PALAU("PW", "Palau"), + PARAGUAY("PY", "Paraguay"), + QATAR("QA", "Qatar"), + REUNION("RE", "Reunion"), + ROMANIA("RO", "Romania"), + SERBIA("RS", "Serbia"), + RUSSIA("RU", "Russia"), + RWANDA("RW", "Rwanda"), + SAUDI_ARABIA("SA", "Saudi Arabia"), + SOLOMON_ISLANDS("SB", "Solomon Islands"), + SEYCHELLES("SC", "Seychelles"), + SUDAN("SD", "Sudan"), + SWEDEN("SE", "Sweden"), + SINGAPORE("SG", "Singapore"), + SAINT_HELENA("SH", "Saint Helena"), + SLOVENIA("SI", "Slovenia"), + SVALBARD_AND_JAN_MAYEN("SJ", "Svalbard And Jan Mayen"), + SLOVAKIA("SK", "Slovakia"), + SIERRA_LEONE("SL", "Sierra Leone"), + SAN_MARINO("SM", "San Marino"), + SENEGAL("SN", "Senegal"), + SOMALIA("SO", "Somalia"), + SURINAME("SR", "Suriname"), + SOUTH_SUDAN("SS", "South Sudan"), + SAO_TOME_AND_PRINCIPE("ST", "Sao Tome And Principe"), + EL_SALVADOR("SV", "El Salvador"), + SINT_MAARTEN_DUTCH_PART("SX", "Sint Maarten (Dutch part)"), + SYRIA("SY", "Syria"), + SWAZILAND("SZ", "Swaziland"), + TURKS_AND_CAICOS_ISLANDS("TC", "Turks And Caicos Islands"), + CHAD("TD", "Chad"), + FRENCH_SOUTHERN_TERRITORIES("TF", "French Southern Territories"), + TOGO("TG", "Togo"), + THAILAND("TH", "Thailand"), + TAJIKISTAN("TJ", "Tajikistan"), + TOKELAU("TK", "Tokelau"), + TIMOR_LESTE("TL", "Timor-Leste"), + TURKMENISTAN("TM", "Turkmenistan"), + TUNISIA("TN", "Tunisia"), + TONGA("TO", "Tonga"), + TURKEY("TR", "Turkey"), + TRINIDAD_AND_TOBAGO("TT", "Trinidad and Tobago"), + TUVALU("TV", "Tuvalu"), + TAIWAN("TW", "Taiwan"), + TANZANIA("TZ", "Tanzania"), + UKRAINE("UA", "Ukraine"), + UGANDA("UG", "Uganda"), + UNITED_STATES_MINOR_OUTLYING_ISLANDS("UM", "United States Minor Outlying Islands"), + UNITED_STATES("US", "United States"), + URUGUAY("UY", "Uruguay"), + UZBEKISTAN("UZ", "Uzbekistan"), + VATICAN("VA", "Vatican"), + SAINT_VINCENT_AND_THE_GRENADINES("VC", "Saint Vincent And The Grenadines"), + VENEZUELA("VE", "Venezuela"), + BRITISH_VIRGIN_ISLANDS("VG", "British Virgin Islands"), + U_S__VIRGIN_ISLANDS("VI", "U.S. Virgin Islands"), + VIETNAM("VN", "Vietnam"), + VANUATU("VU", "Vanuatu"), + WALLIS_AND_FUTUNA("WF", "Wallis And Futuna"), + SAMOA("WS", "Samoa"), + YEMEN("YE", "Yemen"), + MAYOTTE("YT", "Mayotte"), + SOUTH_AFRICA("ZA", "South Africa"), + ZAMBIA("ZM", "Zambia"), + ZIMBABWE("ZW", "Zimbabwe"); + + private String isoTag; + private String name; + + Country(String isoTag, String name) { + this.isoTag = isoTag; + this.name = name; + } + + /** + * Gets the name of the country. + * + * @return The name of the country. + */ + public String getCountryName() { + return name; + } + + /** + * Gets the iso tag of the country. + * + * @return The iso tag of the country. + */ + public String getCountryIsoTag() { + return isoTag; + } + + /** + * Gets a country by it's iso tag. + * + * @param isoTag The iso tag of the county. + * + * @return The country with the given iso tag or null if unknown. + */ + public static Country byIsoTag(String isoTag) { + for (Country country : Country.values()) { + if (country.getCountryIsoTag().equals(isoTag)) { + return country; + } + } + return null; + } + + /** + * Gets a country by a locale. + * + * @param locale The locale. + * + * @return The country from the giben locale or null if unknown country or + * if the locale does not contain a country. + */ + public static Country byLocale(Locale locale) { + return byIsoTag(locale.getCountry()); + } + } +} diff --git a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java index 73db67f8..47bfb912 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java @@ -1,6 +1,6 @@ package fr.xephi.authme.process.quit; -import fr.xephi.authme.data.backup.LimboPlayerStorage; +import fr.xephi.authme.data.limbo.LimboPlayerStorage; import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.process.SynchronousProcess; import org.bukkit.entity.Player; diff --git a/src/main/java/fr/xephi/authme/service/AntiBotService.java b/src/main/java/fr/xephi/authme/service/AntiBotService.java index 91f80041..90c62a24 100644 --- a/src/main/java/fr/xephi/authme/service/AntiBotService.java +++ b/src/main/java/fr/xephi/authme/service/AntiBotService.java @@ -30,7 +30,6 @@ public class AntiBotService implements SettingsDependent { // Settings private int duration; private int sensibility; - private int delay; private int interval; // Service status private AntiBotStatus antiBotStatus; @@ -60,7 +59,6 @@ public class AntiBotService implements SettingsDependent { // Load settings duration = settings.getProperty(ProtectionSettings.ANTIBOT_DURATION); sensibility = settings.getProperty(ProtectionSettings.ANTIBOT_SENSIBILITY); - delay = settings.getProperty(ProtectionSettings.ANTIBOT_DELAY); interval = settings.getProperty(ProtectionSettings.ANTIBOT_INTERVAL); // Stop existing protection @@ -77,6 +75,7 @@ public class AntiBotService implements SettingsDependent { // Delay the schedule on first start if (startup) { + int delay = settings.getProperty(ProtectionSettings.ANTIBOT_DELAY); bukkitService.scheduleSyncDelayedTask(enableTask, delay * TICKS_PER_SECOND); startup = false; } else { diff --git a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java index ac1742fa..26f7d03b 100644 --- a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java +++ b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java @@ -1,10 +1,9 @@ package fr.xephi.authme.settings; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.FileUtils; @@ -43,11 +42,9 @@ public class SpawnLoader implements Reloadable { * @param pluginFolder The AuthMe data folder * @param settings The setting instance * @param pluginHookService The plugin hooks instance - * @param dataSource The plugin auth database instance */ @Inject - SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService, - DataSource dataSource) { + SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService) { // TODO ljacqu 20160312: Check if resource could be copied and handle the case if not File spawnFile = new File(pluginFolder, "spawn.yml"); FileUtils.copyFileFromResource(spawnFile, "spawn.yml"); diff --git a/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java b/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java index 92c3f70d..ce9487e2 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java @@ -28,7 +28,7 @@ public class WrappedTagReplacer { * @param allTags all available tags * @param items the items to apply the replacements on * @param stringGetter getter of the String property to adapt on the items - * @param itemCreator a function of signature (T, String) -> T: the original item and the adapted String are passed + * @param itemCreator a function taking (T, String): the original item and the adapted String, returning a new item */ public WrappedTagReplacer(Collection> allTags, Collection items, diff --git a/src/main/resources/messages/help_br.yml b/src/main/resources/messages/help_br.yml index 1a143c8e..be4983ab 100644 --- a/src/main/resources/messages/help_br.yml +++ b/src/main/resources/messages/help_br.yml @@ -4,6 +4,7 @@ # ------------------------------------------------------- # Lista de textos usados na seção de ajuda: common: + header: '==========[ Ajuda AuthMeReloaded ]==========' optional: 'Opcional' hasPermission: 'Você tem permissão' noPermission: 'Sem Permissão' diff --git a/src/main/resources/messages/help_de.yml b/src/main/resources/messages/help_de.yml index 46292e16..b1841ef4 100644 --- a/src/main/resources/messages/help_de.yml +++ b/src/main/resources/messages/help_de.yml @@ -1,4 +1,5 @@ common: + header: '==========[ AuthMeReloaded Hilfe ]==========' optional: 'Optional' hasPermission: 'Du hast Berechtigung' noPermission: 'Keine Berechtigung' diff --git a/src/main/resources/messages/help_ru.yml b/src/main/resources/messages/help_ru.yml new file mode 100644 index 00000000..20d6bd9a --- /dev/null +++ b/src/main/resources/messages/help_ru.yml @@ -0,0 +1,45 @@ +# Translation config for the AuthMe help, e.g. when /authme help or /authme help register is called + +# ------------------------------------------------------- +# List of texts used in the help section +common: + header: '==========[ AuthMeReloaded Справка ]==========' + optional: 'Опционально' + hasPermission: 'У вас есть такие права' + noPermission: 'Нет прав' + default: 'По-умолчанию' + result: 'Результат' + defaultPermissions: + notAllowed: 'Нет прав' + opOnly: 'Только Операторы' + allowed: 'Разрешено всем' + +# ------------------------------------------------------- +# Titles of the individual help sections +# Set the translation text to empty text to disable the section, e.g. to hide alternatives: +# alternatives: '' +section: + command: 'Команда' + description: 'Краткое описание' + detailedDescription: 'Детальное описание' + arguments: 'Аргументы' + permissions: 'Разрешения' + alternatives: 'Альтернативы' + children: 'Команды' + +# ------------------------------------------------------- +# You can translate the data for all commands using the below pattern. +# For example to translate /authme reload, create a section "authme.reload", or "login" for /login +# If the command has arguments, you can use arg1 as below to translate the first argument, and so forth +# Translations don't need to be complete; any missing section will be taken from the default silently +# Important: Put main commands like "authme" before their children (e.g. "authme.reload") +commands: + authme.register: + description: 'Регистрация новго игрока' + detailedDescription: 'Регистрация игрока с указанным именем и паролем.' + arg1: + label: 'player' + description: 'Имя игрока' + arg2: + label: 'password' + description: 'Пароль' diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index f52f9b41..e6c5e755 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -2,7 +2,7 @@ reg_msg: '&8[&6玩家系统&8] &c请输入“/register <密码> <再输入一次以确定密码>”以注册' usage_reg: '&8[&6玩家系统&8] &c正确用法:“/register <密码> <再输入一次以确定密码>”' reg_only: '&8[&6玩家系统&8] &f只允许注册过的玩家进服!请到 https://example.cn 注册' -# TODO kicked_admin_registered: 'An admin just registered you; please log in again' +kicked_admin_registered: '有一位管理员刚刚为您完成了注册,请重新登录' registered: '&8[&6玩家系统&8] &c已成功注册!' reg_disabled: '&8[&6玩家系统&8] &c目前服务器暂时禁止注册,请到服务器论坛以得到更多资讯' user_regged: '&8[&6玩家系统&8] &c此用户已经在此服务器注册过' @@ -11,7 +11,7 @@ user_regged: '&8[&6玩家系统&8] &c此用户已经在此服务器注册过' password_error: '&8[&6玩家系统&8] &f密码不相同' password_error_nick: '&8[&6玩家系统&8] &f你不能使用你的名字作为密码。 ' password_error_unsafe: '&8[&6玩家系统&8] &f你不能使用安全性过低的码。 ' -# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +password_error_chars: '&4您的密码包含了非法字符。可使用的字符: REG_EX' pass_len: '&8[&6玩家系统&8] &你的密码没有达到要求!' # Login @@ -23,10 +23,10 @@ timeout: '&8[&6玩家系统&8] &f给你登录的时间已经过了' # Errors unknown_user: '&8[&6玩家系统&8] &c此用户名还未注册过' -# TODO denied_command: '&cIn order to use this command you must be authenticated!' -# TODO denied_chat: '&cIn order to chat you must be authenticated!' +denied_command: '&c您需要先通过验证才能使用该命令!' +denied_chat: '&c您需要先通过验证才能聊天!' not_logged_in: '&8[&6玩家系统&8] &c你还未登录!' -# TODO tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' +tempban_max_logins: '&c由于您登录失败次数过多,已被暂时禁止登录。' # TODO: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&8[&6玩家系统&8] &f你不允许再为你的IP在服务器注册更多用户了!' no_perm: '&8[&6玩家系统&8] &c没有权限' @@ -41,11 +41,11 @@ antibot_auto_disabled: '&8[&6玩家系统&8] &f防机器人程序由于异常连 # Other messages unregistered: '&8[&6玩家系统&8] &c成功删除此用户!' -# TODO accounts_owned_self: 'You own %count accounts:' -# TODO accounts_owned_other: 'The player %name has %count accounts:' +accounts_owned_self: '您拥有 %count 个账户:' +accounts_owned_other: '玩家 %name 拥有 %count 个账户:' two_factor_create: '&8[&6玩家系统&8] &2你的代码是 %code,你可以使用 %url 来扫描' -# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +recovery_code_sent: '一个用于重置您的密码的验证码已发到您的邮箱' +recovery_code_incorrect: '验证码不正确! 使用 /email recovery [email] 以生成新的验证码' vb_nonActiv: '&8[&6玩家系统&8] &f你的帐号还未激活,请查看你的邮箱!' usage_unreg: '&8[&6玩家系统&8] &c正确用法:“/unregister <密码>”' pwd_changed: '&8[&6玩家系统&8] &c密码已成功修改!' @@ -66,7 +66,7 @@ not_owner_error: '&8[&6玩家系统&8] &4警告! &c你并不是此帐户持有 kick_fullserver: '&8[&6玩家系统&8] &c抱歉,服务器已满!' same_nick: '&8[&6玩家系统&8] &f同样的用户名现在在线且已经登录了!' invalid_name_case: '&8[&6玩家系统&8] &c你应该使用「%valid」而并非「%invalid」登入游戏。 ' -# TODO same_ip_online: 'A player with the same IP is already in game!' +same_ip_online: '已有一个同IP玩家在游戏中了!' # Email usage_email_add: '&8[&6玩家系统&8] &f用法: /email add <邮箱> <确认电子邮件> ' @@ -80,11 +80,11 @@ email_confirm: '&8[&6玩家系统&8] &f确认你的邮箱 !' email_changed: '&8[&6玩家系统&8] &f邮箱已改变 !' email_send: '&8[&6玩家系统&8] &f恢复邮件已发送 !' email_exists: '&8[&6玩家系统&8] &c恢复邮件已发送 ! 你可以丢弃它然後使用以下的指令来发送新的邮件:' -# TODO email_show: '&2Your current email address is: &f%email' -# TODO incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' +email_show: '&2您当前的电子邮件地址为: &f%email' +incomplete_email_settings: '错误:并非所有发送邮件需要的设置都已被设置,请联系管理员' email_already_used: '&8[&6玩家系统&8] &4邮箱已被使用' -# TODO email_send_failure: 'The email could not be sent. Please contact an administrator.' -# TODO show_no_email: '&2You currently don''t have email address associated with this account.' +email_send_failure: '邮件发送失败,请联系管理员' +show_no_email: '&2您当前并没有任何邮箱与该账号绑定' add_email: '&8[&6玩家系统&8] &c请输入“/email add <你的邮箱> <再输入一次以确认>”以把你的邮箱添加到此帐号' recovery_email: '&8[&6玩家系统&8] &c忘了你的密码?请输入:“/email recovery <你的邮箱>”' diff --git a/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java b/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java index 292b0ec9..ea0f372a 100644 --- a/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java +++ b/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java @@ -39,10 +39,10 @@ public class HelpMessagesServiceTest { @Mock private MessageFileHandlerProvider messageFileHandlerProvider; - @SuppressWarnings("unchecked") @BeforeInjecting + @SuppressWarnings("unchecked") public void initializeHandler() { - MessageFileHandler handler = new MessageFileHandler(getJarFile(TEST_FILE), "messages/messages_en.yml"); + MessageFileHandler handler = new MessageFileHandler(getJarFile(TEST_FILE), "messages/messages_en.yml", null); given(messageFileHandlerProvider.initializeHandler(any(Function.class))).willReturn(handler); } diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java index 8eb8e959..fcc45451 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java @@ -1,7 +1,6 @@ package fr.xephi.authme.data.limbo; import fr.xephi.authme.ReflectionTestUtils; -import fr.xephi.authme.data.backup.LimboPlayerStorage; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; diff --git a/src/test/java/fr/xephi/authme/data/backup/LimboPlayerStorageTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java similarity index 98% rename from src/test/java/fr/xephi/authme/data/backup/LimboPlayerStorageTest.java rename to src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java index f9c5880c..6d346854 100644 --- a/src/test/java/fr/xephi/authme/data/backup/LimboPlayerStorageTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java @@ -1,14 +1,13 @@ -package fr.xephi.authme.data.backup; +package fr.xephi.authme.data.limbo; import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.util.FileUtils; import org.bukkit.Location; import org.bukkit.World; diff --git a/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java b/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java new file mode 100644 index 00000000..399fd93a --- /dev/null +++ b/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java @@ -0,0 +1,93 @@ +package fr.xephi.authme.message; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.command.help.HelpMessage; +import fr.xephi.authme.command.help.HelpSection; +import fr.xephi.authme.permission.DefaultPermission; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Tests that all help_xx.yml files contain all entries for + * {@link HelpSection}, {@link HelpMessage} and {@link DefaultPermission}. + */ +public class HelpMessageConsistencyTest { + + private static final String MESSAGES_FOLDER = "/messages"; + private static final Pattern HELP_MESSAGES_FILE = Pattern.compile("help_[a-z]+\\.yml"); + + private List helpFiles; + + @Before + public void findHelpMessagesFiles() { + File folder = TestHelper.getJarFile(MESSAGES_FOLDER); + File[] files = folder.listFiles(); + if (files == null || files.length == 0) { + throw new IllegalStateException("Could not get files from '" + MESSAGES_FOLDER + "'"); + } + helpFiles = Arrays.stream(files) + .filter(file -> HELP_MESSAGES_FILE.matcher(file.getName()).matches()) + .collect(Collectors.toList()); + } + + @Test + public void shouldHaveRequiredEntries() { + for (File file : helpFiles) { + // given + FileConfiguration configuration = YamlConfiguration.loadConfiguration(file); + + // when / then + assertHasAllHelpSectionEntries(file.getName(), configuration); + } + } + + private void assertHasAllHelpSectionEntries(String filename, FileConfiguration configuration) { + for (HelpSection section : HelpSection.values()) { + assertThat(filename + " should have entry for HelpSection '" + section + "'", + configuration.getString(section.getKey()), notEmptyString()); + } + + for (HelpMessage message : HelpMessage.values()) { + assertThat(filename + " should have entry for HelpMessage '" + message + "'", + configuration.getString(message.getKey()), notEmptyString()); + } + + for (DefaultPermission defaultPermission : DefaultPermission.values()) { + assertThat(filename + " should have entry for DefaultPermission '" + defaultPermission + "'", + configuration.getString(getPathForDefaultPermission(defaultPermission)), notEmptyString()); + } + } + + private static String getPathForDefaultPermission(DefaultPermission defaultPermission) { + String path = "common.defaultPermissions."; + switch (defaultPermission) { + case ALLOWED: + return path + "allowed"; + case NOT_ALLOWED: + return path + "notAllowed"; + case OP_ONLY: + return path + "opOnly"; + default: + throw new IllegalStateException("Unknown default permission '" + defaultPermission + "'"); + } + } + + private static Matcher notEmptyString() { + return both(not(emptyString())).and(not(nullValue())); + } +} diff --git a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java index 12949c5a..94fe9a0d 100644 --- a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java @@ -233,8 +233,8 @@ public class MessagesIntegrationTest { @SuppressWarnings("unchecked") private static MessageFileHandlerProvider providerReturning(File file, String defaultFile) { MessageFileHandlerProvider handler = mock(MessageFileHandlerProvider.class); - given(handler.initializeHandler(any(Function.class))) - .willReturn(new MessageFileHandler(file, defaultFile)); + given(handler.initializeHandler(any(Function.class), anyString())) + .willReturn(new MessageFileHandler(file, defaultFile, "/authme messages")); return handler; } } diff --git a/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java b/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java index 1ca06911..edd56192 100644 --- a/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java +++ b/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java @@ -5,9 +5,8 @@ import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import com.google.common.io.Files; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.Location; import org.bukkit.World; @@ -38,9 +37,6 @@ public class SpawnLoaderTest { @Mock private Settings settings; - @Mock - private DataSource dataSource; - @Mock private PluginHookService pluginHookService; diff --git a/src/test/java/tools/messages/AddJavaDocToMessageEnumTask.java b/src/test/java/tools/messages/AddJavaDocToMessageEnumTask.java index 30a63244..44306dc8 100644 --- a/src/test/java/tools/messages/AddJavaDocToMessageEnumTask.java +++ b/src/test/java/tools/messages/AddJavaDocToMessageEnumTask.java @@ -45,6 +45,7 @@ public class AddJavaDocToMessageEnumTask implements AutoToolTask { return configuration.getString(key.getKey()) .replaceAll("&[0-9a-f]", "") .replace("&", "&") - .replace("<", "<"); + .replace("<", "<") + .replace(">", ">"); } }