diff --git a/docs/commands.md b/docs/commands.md index 00f4f8c6..4dd070fd 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,5 +1,5 @@ - + ## AuthMe Commands You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >` @@ -45,6 +45,8 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`). - **/authme version**: Show detailed information about the installed AuthMeReloaded version, the developers, contributors, and license. - **/authme converter** <job>: Converter command for AuthMeReloaded.
Requires `authme.admin.converter` +- **/authme messages**: Adds missing messages to the current messages file. +
Requires `authme.admin.updatemessages` - **/authme help** [query]: View detailed help for /authme commands. - **/login** <password>: Command to log in using AuthMeReloaded.
Requires `authme.player.login` @@ -76,4 +78,4 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`). --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Oct 01 23:33:39 CEST 2016 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Oct 16 21:39:08 CEST 2016 diff --git a/docs/permission_nodes.md b/docs/permission_nodes.md index 61f61c3e..60d39a15 100644 --- a/docs/permission_nodes.md +++ b/docs/permission_nodes.md @@ -1,5 +1,5 @@ - + ## AuthMe Permission Nodes The following are the permission nodes that are currently supported by the latest dev builds. @@ -26,14 +26,16 @@ The following are the permission nodes that are currently supported by the lates - **authme.admin.spawn** – Administrator command to teleport to the AuthMe spawn. - **authme.admin.switchantibot** – Administrator command to toggle the AntiBot protection status. - **authme.admin.unregister** – Administrator command to unregister an existing user. +- **authme.admin.updatemessages** – Permission to use the update messages command. - **authme.allowmultipleaccounts** – Permission to be able to register multiple accounts. - **authme.bypassantibot** – Permission node to bypass AntiBot protection. - **authme.bypassforcesurvival** – Permission for users to bypass force-survival mode. -- **authme.bypasspurge** – Permission to bypass the purging process +- **authme.bypasspurge** – Permission to bypass the purging process. - **authme.player.*** – Permission to use all player (non-admin) commands. - **authme.player.canbeforced** – Permission for users a login can be forced to. - **authme.player.captcha** – Command permission to use captcha. - **authme.player.changepassword** – Command permission to change the password. +- **authme.player.email** – Grants all email permissions. - **authme.player.email.add** – Command permission to add an email address. - **authme.player.email.change** – Command permission to change the email address. - **authme.player.email.recover** – Command permission to recover an account using it's email address. @@ -47,4 +49,4 @@ The following are the permission nodes that are currently supported by the lates --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Oct 02 10:47:16 CEST 2016 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Oct 16 21:39:10 CEST 2016 diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index a6d7b767..808bbdbf 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -11,6 +11,7 @@ import fr.xephi.authme.command.executable.authme.ForceLoginCommand; import fr.xephi.authme.command.executable.authme.GetEmailCommand; import fr.xephi.authme.command.executable.authme.GetIpCommand; import fr.xephi.authme.command.executable.authme.LastLoginCommand; +import fr.xephi.authme.command.executable.authme.MessagesCommand; import fr.xephi.authme.command.executable.authme.PurgeBannedPlayersCommand; import fr.xephi.authme.command.executable.authme.PurgeCommand; import fr.xephi.authme.command.executable.authme.PurgeLastPositionCommand; @@ -289,6 +290,15 @@ public class CommandInitializer { .executableCommand(ConverterCommand.class) .build(); + CommandDescription.builder() + .parent(AUTHME_BASE) + .labels("messages", "msg") + .description("Add missing messages") + .detailedDescription("Adds missing messages to the current messages file.") + .permission(AdminPermission.UPDATE_MESSAGES) + .executableCommand(MessagesCommand.class) + .build(); + // Register the base login command final CommandDescription LOGIN_BASE = CommandDescription.builder() .parent(null) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/MessagesCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/MessagesCommand.java new file mode 100644 index 00000000..a7d38d5a --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/MessagesCommand.java @@ -0,0 +1,54 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.MessageUpdater; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.PluginSettings; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.io.File; +import java.util.List; + +/** + * Messages command, updates the user's messages file with any missing files + * from the provided file in the JAR. + */ +public class MessagesCommand implements ExecutableCommand { + + private static final String DEFAULT_LANGUAGE = "en"; + + @Inject + private Settings settings; + @Inject + @DataFolder + private File dataFolder; + @Inject + private Messages messages; + + @Override + public void executeCommand(CommandSender sender, List arguments) { + final String language = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE); + + try { + boolean isFileUpdated = new MessageUpdater( + new File(dataFolder, getMessagePath(language)), + getMessagePath(language), + getMessagePath(DEFAULT_LANGUAGE)) + .executeCopy(sender); + if (isFileUpdated) { + messages.reload(); + } + } catch (Exception e) { + sender.sendMessage("Could not update messages: " + e.getMessage()); + ConsoleLogger.logException("Could not update messages:", e); + } + } + + private static String getMessagePath(String code) { + return "messages/messages_" + code + ".yml"; + } +} diff --git a/src/main/java/fr/xephi/authme/permission/AdminPermission.java b/src/main/java/fr/xephi/authme/permission/AdminPermission.java index ed5ab9f4..46d58072 100644 --- a/src/main/java/fr/xephi/authme/permission/AdminPermission.java +++ b/src/main/java/fr/xephi/authme/permission/AdminPermission.java @@ -8,126 +8,125 @@ public enum AdminPermission implements PermissionNode { /** * Administrator command to register a new user. */ - REGISTER("authme.admin.register", DefaultPermission.OP_ONLY), + REGISTER("authme.admin.register"), /** * Administrator command to unregister an existing user. */ - UNREGISTER("authme.admin.unregister", DefaultPermission.OP_ONLY), + UNREGISTER("authme.admin.unregister"), /** * Administrator command to force-login an existing user. */ - FORCE_LOGIN("authme.admin.forcelogin", DefaultPermission.OP_ONLY), + FORCE_LOGIN("authme.admin.forcelogin"), /** * Administrator command to change the password of a user. */ - CHANGE_PASSWORD("authme.admin.changepassword", DefaultPermission.OP_ONLY), + CHANGE_PASSWORD("authme.admin.changepassword"), /** * Administrator command to see the last login date and time of a user. */ - LAST_LOGIN("authme.admin.lastlogin", DefaultPermission.OP_ONLY), + LAST_LOGIN("authme.admin.lastlogin"), /** * Administrator command to see all accounts associated with a user. */ - ACCOUNTS("authme.admin.accounts", DefaultPermission.OP_ONLY), + ACCOUNTS("authme.admin.accounts"), /** * Administrator command to get the email address of a user, if set. */ - GET_EMAIL("authme.admin.getemail", DefaultPermission.OP_ONLY), + GET_EMAIL("authme.admin.getemail"), /** * Administrator command to set or change the email address of a user. */ - CHANGE_EMAIL("authme.admin.changemail", DefaultPermission.OP_ONLY), + CHANGE_EMAIL("authme.admin.changemail"), /** * Administrator command to get the last known IP of a user. */ - GET_IP("authme.admin.getip", DefaultPermission.OP_ONLY), + GET_IP("authme.admin.getip"), /** * Administrator command to teleport to the AuthMe spawn. */ - SPAWN("authme.admin.spawn", DefaultPermission.OP_ONLY), + SPAWN("authme.admin.spawn"), /** * Administrator command to set the AuthMe spawn. */ - SET_SPAWN("authme.admin.setspawn", DefaultPermission.OP_ONLY), + SET_SPAWN("authme.admin.setspawn"), /** * Administrator command to teleport to the first AuthMe spawn. */ - FIRST_SPAWN("authme.admin.firstspawn", DefaultPermission.OP_ONLY), + FIRST_SPAWN("authme.admin.firstspawn"), /** * Administrator command to set the first AuthMe spawn. */ - SET_FIRST_SPAWN("authme.admin.setfirstspawn", DefaultPermission.OP_ONLY), + SET_FIRST_SPAWN("authme.admin.setfirstspawn"), /** * Administrator command to purge old user data. */ - PURGE("authme.admin.purge", DefaultPermission.OP_ONLY), + PURGE("authme.admin.purge"), /** * Administrator command to purge the last position of a user. */ - PURGE_LAST_POSITION("authme.admin.purgelastpos", DefaultPermission.OP_ONLY), + PURGE_LAST_POSITION("authme.admin.purgelastpos"), /** * Administrator command to purge all data associated with banned players. */ - PURGE_BANNED_PLAYERS("authme.admin.purgebannedplayers", DefaultPermission.OP_ONLY), + PURGE_BANNED_PLAYERS("authme.admin.purgebannedplayers"), /** * Administrator command to toggle the AntiBot protection status. */ - SWITCH_ANTIBOT("authme.admin.switchantibot", DefaultPermission.OP_ONLY), + SWITCH_ANTIBOT("authme.admin.switchantibot"), /** * Administrator command to convert old or other data to AuthMe data. */ - CONVERTER("authme.admin.converter", DefaultPermission.OP_ONLY), + CONVERTER("authme.admin.converter"), /** * Administrator command to reload the plugin configuration. */ - RELOAD("authme.admin.reload", DefaultPermission.OP_ONLY), + RELOAD("authme.admin.reload"), /** * Permission to see Antibot messages. */ - ANTIBOT_MESSAGES("authme.admin.antibotmessages", DefaultPermission.OP_ONLY), + ANTIBOT_MESSAGES("authme.admin.antibotmessages"), + + /** + * Permission to use the update messages command. + */ + UPDATE_MESSAGES("authme.admin.updatemessages"), /** * Permission to see the other accounts of the players that log in. */ - SEE_OTHER_ACCOUNTS("authme.admin.seeotheraccounts", DefaultPermission.OP_ONLY); + SEE_OTHER_ACCOUNTS("authme.admin.seeotheraccounts"); /** * The permission node. */ private String node; - /** - * The default permission level - */ - private DefaultPermission defaultPermission; - /** * Constructor. * * @param node Permission node. */ - AdminPermission(String node, DefaultPermission defaultPermission) { + AdminPermission(String node) { this.node = node; - this.defaultPermission = defaultPermission; } @Override @@ -137,6 +136,6 @@ public enum AdminPermission implements PermissionNode { @Override public DefaultPermission getDefaultPermission() { - return defaultPermission; + return DefaultPermission.OP_ONLY; } } diff --git a/src/main/java/fr/xephi/authme/permission/PlayerPermission.java b/src/main/java/fr/xephi/authme/permission/PlayerPermission.java index e2932e93..48e2a4bc 100644 --- a/src/main/java/fr/xephi/authme/permission/PlayerPermission.java +++ b/src/main/java/fr/xephi/authme/permission/PlayerPermission.java @@ -8,76 +8,70 @@ public enum PlayerPermission implements PermissionNode { /** * Command permission to login. */ - LOGIN("authme.player.login", DefaultPermission.ALLOWED), + LOGIN("authme.player.login"), /** * Command permission to logout. */ - LOGOUT("authme.player.logout", DefaultPermission.ALLOWED), + LOGOUT("authme.player.logout"), /** * Command permission to register. */ - REGISTER("authme.player.register", DefaultPermission.ALLOWED), + REGISTER("authme.player.register"), /** * Command permission to unregister. */ - UNREGISTER("authme.player.unregister", DefaultPermission.ALLOWED), + UNREGISTER("authme.player.unregister"), /** * Command permission to change the password. */ - CHANGE_PASSWORD("authme.player.changepassword", DefaultPermission.ALLOWED), + CHANGE_PASSWORD("authme.player.changepassword"), /** * Command permission to add an email address. */ - ADD_EMAIL("authme.player.email.add", DefaultPermission.ALLOWED), + ADD_EMAIL("authme.player.email.add"), /** * Command permission to change the email address. */ - CHANGE_EMAIL("authme.player.email.change", DefaultPermission.ALLOWED), + CHANGE_EMAIL("authme.player.email.change"), /** * Command permission to recover an account using it's email address. */ - RECOVER_EMAIL("authme.player.email.recover", DefaultPermission.ALLOWED), + RECOVER_EMAIL("authme.player.email.recover"), /** * Command permission to use captcha. */ - CAPTCHA("authme.player.captcha", DefaultPermission.ALLOWED), + CAPTCHA("authme.player.captcha"), /** * Permission for users a login can be forced to. */ - CAN_LOGIN_BE_FORCED("authme.player.canbeforced", DefaultPermission.ALLOWED), + CAN_LOGIN_BE_FORCED("authme.player.canbeforced"), /** * Permission to use to see own other accounts. */ - SEE_OWN_ACCOUNTS("authme.player.seeownaccounts", DefaultPermission.ALLOWED); + SEE_OWN_ACCOUNTS("authme.player.seeownaccounts"); /** * The permission node. */ private String node; - /** - * The default permission level - */ - private DefaultPermission defaultPermission; - /** * Constructor. * * @param node Permission node. */ - PlayerPermission(String node, DefaultPermission defaultPermission) { + PlayerPermission(String node) { this.node = node; - this.defaultPermission = defaultPermission; } @Override @@ -87,7 +81,7 @@ public enum PlayerPermission implements PermissionNode { @Override public DefaultPermission getDefaultPermission() { - return defaultPermission; + return DefaultPermission.ALLOWED; } } diff --git a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java index d733814e..0820be01 100644 --- a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java +++ b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java @@ -27,7 +27,7 @@ public enum PlayerStatePermission implements PermissionNode { ALLOW_MULTIPLE_ACCOUNTS("authme.allowmultipleaccounts", DefaultPermission.OP_ONLY), /** - * Permission to bypass the purging process + * Permission to bypass the purging process. */ BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED); @@ -44,7 +44,8 @@ public enum PlayerStatePermission implements PermissionNode { /** * Constructor. * - * @param node Permission node. + * @param node Permission node + * @param defaultPermission The default permission */ PlayerStatePermission(String node, DefaultPermission defaultPermission) { this.node = node; diff --git a/src/main/java/fr/xephi/authme/service/MessageUpdater.java b/src/main/java/fr/xephi/authme/service/MessageUpdater.java new file mode 100644 index 00000000..129a47b8 --- /dev/null +++ b/src/main/java/fr/xephi/authme/service/MessageUpdater.java @@ -0,0 +1,139 @@ +package fr.xephi.authme.service; + +import com.github.authme.configme.SettingsManager; +import com.github.authme.configme.knownproperties.ConfigurationData; +import com.github.authme.configme.properties.Property; +import com.github.authme.configme.properties.StringProperty; +import com.github.authme.configme.resource.YamlFileResource; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.util.FileUtils; +import fr.xephi.authme.util.StringUtils; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Updates a user's messages file with messages from the JAR files. + */ +public class MessageUpdater { + + private final FileConfiguration userConfiguration; + private final FileConfiguration localJarConfiguration; + private final FileConfiguration defaultJarConfiguration; + + private final List> properties; + private final SettingsManager settingsManager; + private boolean hasMissingMessages = false; + + /** + * Constructor. + * + * @param userFile messages file in the data folder + * @param localJarFile path to messages file in JAR in local language + * @param defaultJarFile path to messages file in JAR for default language + * @throws Exception if userFile does not exist or no JAR messages file can be loaded + */ + public MessageUpdater(File userFile, String localJarFile, String defaultJarFile) throws Exception { + if (!userFile.exists()) { + throw new Exception("Local messages file does not exist"); + } + + userConfiguration = YamlConfiguration.loadConfiguration(userFile); + localJarConfiguration = loadJarFileOrSendError(localJarFile); + defaultJarConfiguration = localJarFile.equals(defaultJarFile) ? null : loadJarFileOrSendError(defaultJarFile); + if (localJarConfiguration == null && defaultJarConfiguration == null) { + throw new Exception("Could not load any JAR messages file to copy from"); + } + + properties = buildPropertyEntriesForMessageKeys(); + settingsManager = new SettingsManager( + new YamlFileResource(userFile), (r, p) -> true, new ConfigurationData((List) properties)); + } + + /** + * Copies missing messages to the messages file. + * + * @param sender sender starting the copy process + * @return true if the messages file was updated, false otherwise + * @throws Exception if an error occurs during saving + */ + public boolean executeCopy(CommandSender sender) throws Exception { + copyMissingMessages(); + + if (!hasMissingMessages) { + sender.sendMessage("No new messages to add"); + return false; + } + + // Save user configuration file + try { + settingsManager.save(); + sender.sendMessage("Message file updated with new messages"); + return true; + } catch (Exception e) { + throw new Exception("Could not save to messages file: " + StringUtils.formatException(e)); + } + } + + @SuppressWarnings("unchecked") + private void copyMissingMessages() { + for (Property property : properties) { + String message = userConfiguration.getString(property.getPath()); + if (message == null) { + hasMissingMessages = true; + message = getMessageFromJar(property.getPath()); + } + settingsManager.setProperty(property, message); + } + } + + private String getMessageFromJar(String key) { + String message = (localJarConfiguration == null ? null : localJarConfiguration.getString(key)); + if (message != null) { + return message; + } + return (defaultJarConfiguration == null) ? null : defaultJarConfiguration.getString(key); + } + + private static FileConfiguration loadJarFileOrSendError(String jarPath) { + try (InputStream stream = FileUtils.getResourceFromJar(jarPath)) { + if (stream == null) { + ConsoleLogger.info("Could not load '" + jarPath + "' from JAR"); + return null; + } + InputStreamReader isr = new InputStreamReader(stream); + FileConfiguration configuration = YamlConfiguration.loadConfiguration(isr); + close(isr); + return configuration; + } catch (IOException e) { + ConsoleLogger.logException("Exception while handling JAR path '" + jarPath + "'", e); + } + return null; + } + + private static List> buildPropertyEntriesForMessageKeys() { + return Arrays.stream(MessageKey.values()) + .map(key -> new StringProperty(key.getKey(), "")) + .collect(Collectors.toList()); + } + + private static void close(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + ConsoleLogger.info("Cannot close '" + closeable + "': " + StringUtils.formatException(e)); + } + } + } +} diff --git a/src/main/java/fr/xephi/authme/util/FileUtils.java b/src/main/java/fr/xephi/authme/util/FileUtils.java index 831beafd..86c8cae4 100644 --- a/src/main/java/fr/xephi/authme/util/FileUtils.java +++ b/src/main/java/fr/xephi/authme/util/FileUtils.java @@ -23,7 +23,7 @@ public final class FileUtils { * Copy a resource file (from the JAR) to the given file if it doesn't exist. * * @param destinationFile The file to check and copy to (outside of JAR) - * @param resourcePath Absolute path to the resource file (path to file within JAR) + * @param resourcePath Local path to the resource file (path to file within JAR) * * @return False if the file does not exist and could not be copied, true otherwise */ @@ -35,9 +35,7 @@ public final class FileUtils { return false; } - // ClassLoader#getResourceAsStream does not deal with the '\' path separator: replace to '/' - final String normalizedPath = resourcePath.replace("\\", "/"); - try (InputStream is = AuthMe.class.getClassLoader().getResourceAsStream(normalizedPath)) { + try (InputStream is = getResourceFromJar(resourcePath)) { if (is == null) { ConsoleLogger.warning(format("Cannot copy resource '%s' to file '%s': cannot load resource", resourcePath, destinationFile.getPath())); @@ -52,6 +50,18 @@ public final class FileUtils { return false; } + /** + * Returns a JAR file as stream. Returns null if it doesn't exist. + * + * @param path the local path (starting from resources project, e.g. "config.yml" for 'resources/config.yml') + * @return the stream if the file exists, or false otherwise + */ + public static InputStream getResourceFromJar(String path) { + // ClassLoader#getResourceAsStream does not deal with the '\' path separator: replace to '/' + final String normalizedPath = path.replace("\\", "/"); + return AuthMe.class.getClassLoader().getResourceAsStream(normalizedPath); + } + /** * Delete a given directory and all its content. * diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 2b950e3a..90ee29bf 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -48,6 +48,7 @@ permissions: description: Give access to all admin commands. children: authme.admin.accounts: true + authme.admin.antibotmessages: true authme.admin.changemail: true authme.admin.changepassword: true authme.admin.converter: true @@ -66,6 +67,7 @@ permissions: authme.admin.spawn: true authme.admin.switchantibot: true authme.admin.unregister: true + authme.admin.updatemessages: true authme.admin.register: description: Administrator command to register a new user. default: op @@ -127,7 +129,10 @@ permissions: description: Administrator command to reload the plugin configuration. default: op authme.admin.antibotmessages: - description: Permission to see Antibot messages + description: Permission to see Antibot messages. + default: op + authme.admin.updatemessages: + description: Permission to use the update messages command. default: op authme.player.*: description: Permission to use all player (non-admin) commands. diff --git a/src/test/java/fr/xephi/authme/util/FileUtilsTest.java b/src/test/java/fr/xephi/authme/util/FileUtilsTest.java index f36acf75..e7a8d873 100644 --- a/src/test/java/fr/xephi/authme/util/FileUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/FileUtilsTest.java @@ -11,6 +11,8 @@ import java.io.File; import java.io.IOException; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; /** @@ -119,6 +121,13 @@ public class FileUtilsTest { // Nothing happens } + @Test + public void shouldGetResourceFromJar() { + // given / when / then + assertThat(FileUtils.getResourceFromJar("config.yml"), not(nullValue())); + assertThat(FileUtils.getResourceFromJar("does-not-exist"), nullValue()); + } + @Test public void shouldConstructPath() { // given/when