reupload files
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package fr.xephi.authme.message;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.initialization.DataFolder;
|
||||
import fr.xephi.authme.initialization.Reloadable;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
|
||||
import static fr.xephi.authme.message.MessagePathHelper.DEFAULT_LANGUAGE;
|
||||
|
||||
/**
|
||||
* Handles a YAML message file with a default file fallback.
|
||||
*/
|
||||
public abstract class AbstractMessageFileHandler implements Reloadable {
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(AbstractMessageFileHandler.class);
|
||||
|
||||
@DataFolder
|
||||
@Inject
|
||||
private File dataFolder;
|
||||
|
||||
@Inject
|
||||
private Settings settings;
|
||||
|
||||
private String filename;
|
||||
private FileConfiguration configuration;
|
||||
private final String defaultFile;
|
||||
|
||||
protected AbstractMessageFileHandler() {
|
||||
this.defaultFile = createFilePath(DEFAULT_LANGUAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void reload() {
|
||||
String language = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
|
||||
filename = createFilePath(language);
|
||||
File messagesFile = initializeFile(filename);
|
||||
configuration = YamlConfiguration.loadConfiguration(messagesFile);
|
||||
}
|
||||
|
||||
protected String getLanguage() {
|
||||
return settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
|
||||
}
|
||||
|
||||
protected File getUserLanguageFile() {
|
||||
return new File(dataFolder, filename);
|
||||
}
|
||||
|
||||
protected String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the message file configuration has an entry at the given path.
|
||||
*
|
||||
* @param path the path to verify
|
||||
* @return true if an entry exists for the path in the messages file, false otherwise
|
||||
*/
|
||||
public boolean hasSection(String path) {
|
||||
return configuration.get(path) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message for the given key.
|
||||
*
|
||||
* @param key the key to retrieve the message for
|
||||
* @return the message
|
||||
*/
|
||||
public String getMessage(String key) {
|
||||
String message = configuration.getString(key);
|
||||
return message == null
|
||||
? "Error retrieving message '" + key + "'"
|
||||
: message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message for the given key only if it exists,
|
||||
* i.e. without falling back to the default file.
|
||||
*
|
||||
* @param key the key to retrieve the message for
|
||||
* @return the message, or {@code null} if not available
|
||||
*/
|
||||
public String getMessageIfExists(String key) {
|
||||
return configuration.getString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the path to the messages file for the given language code.
|
||||
*
|
||||
* @param language the language code
|
||||
* @return path to the message file for the given language
|
||||
*/
|
||||
protected abstract String createFilePath(String language);
|
||||
|
||||
/**
|
||||
* Copies the messages file from the JAR to the local messages/ folder if it doesn't exist.
|
||||
*
|
||||
* @param filePath path to the messages file to use
|
||||
* @return the messages file to use
|
||||
*/
|
||||
@VisibleForTesting
|
||||
File initializeFile(String filePath) {
|
||||
File file = new File(dataFolder, filePath);
|
||||
// Check that JAR file exists to avoid logging an error
|
||||
if (FileUtils.getResourceFromJar(filePath) != null && FileUtils.copyFileFromResource(file, filePath)) {
|
||||
return file;
|
||||
}
|
||||
|
||||
if (FileUtils.copyFileFromResource(file, defaultFile)) {
|
||||
return file;
|
||||
} else {
|
||||
logger.warning("Wanted to copy default messages file '" + defaultFile + "' from JAR but it didn't exist");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package fr.xephi.authme.message;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import static fr.xephi.authme.message.MessagePathHelper.DEFAULT_LANGUAGE;
|
||||
|
||||
/**
|
||||
* File handler for the help_xx.yml resource.
|
||||
*/
|
||||
public class HelpMessagesFileHandler extends AbstractMessageFileHandler {
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(HelpMessagesFileHandler.class);
|
||||
|
||||
private FileConfiguration defaultConfiguration;
|
||||
|
||||
@Inject // Trigger injection in the superclass
|
||||
HelpMessagesFileHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message for the given key.
|
||||
*
|
||||
* @param key the key to retrieve the message for
|
||||
* @return the message
|
||||
*/
|
||||
@Override
|
||||
public String getMessage(String key) {
|
||||
String message = getMessageIfExists(key);
|
||||
|
||||
if (message == null) {
|
||||
logger.warning("Error getting message with key '" + key + "'. "
|
||||
+ "Please update your config file '" + getFilename() + "' or run /authme messages help");
|
||||
return getDefault(key);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message from the default file.
|
||||
*
|
||||
* @param key the key to retrieve the message for
|
||||
* @return the message from the default file
|
||||
*/
|
||||
private String getDefault(String key) {
|
||||
if (defaultConfiguration == null) {
|
||||
InputStream stream = FileUtils.getResourceFromJar(createFilePath(DEFAULT_LANGUAGE));
|
||||
defaultConfiguration = YamlConfiguration.loadConfiguration(new InputStreamReader(stream));
|
||||
}
|
||||
String message = defaultConfiguration.getString(key);
|
||||
return message == null
|
||||
? "Error retrieving message '" + key + "'"
|
||||
: message;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createFilePath(String language) {
|
||||
return MessagePathHelper.createHelpMessageFilePath(language);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
package fr.xephi.authme.message;
|
||||
|
||||
/**
|
||||
* Keys for translatable messages managed by {@link Messages}.
|
||||
*/
|
||||
public enum MessageKey {
|
||||
|
||||
/** In order to use this command you must be authenticated! */
|
||||
DENIED_COMMAND("error.denied_command"),
|
||||
|
||||
/** A player with the same IP is already in game! */
|
||||
SAME_IP_ONLINE("on_join_validation.same_ip_online"),
|
||||
|
||||
/** In order to chat you must be authenticated! */
|
||||
DENIED_CHAT("error.denied_chat"),
|
||||
|
||||
/** AntiBot protection mode is enabled! You have to wait some minutes before joining the server. */
|
||||
KICK_ANTIBOT("antibot.kick_antibot"),
|
||||
|
||||
/** This user isn't registered! */
|
||||
UNKNOWN_USER("error.unregistered_user"),
|
||||
|
||||
/** You're not logged in! */
|
||||
NOT_LOGGED_IN("error.not_logged_in"),
|
||||
|
||||
/** Usage: /login <password> */
|
||||
USAGE_LOGIN("login.command_usage"),
|
||||
|
||||
/** Wrong password! */
|
||||
WRONG_PASSWORD("login.wrong_password"),
|
||||
|
||||
/** Successfully unregistered! */
|
||||
UNREGISTERED_SUCCESS("unregister.success"),
|
||||
|
||||
/** In-game registration is disabled! */
|
||||
REGISTRATION_DISABLED("registration.disabled"),
|
||||
|
||||
/** Logged-in due to Session Reconnection. */
|
||||
SESSION_RECONNECTION("session.valid_session"),
|
||||
|
||||
/** Successful login! */
|
||||
LOGIN_SUCCESS("login.success"),
|
||||
|
||||
/** Your account isn't activated yet, please check your emails! */
|
||||
ACCOUNT_NOT_ACTIVATED("misc.account_not_activated"),
|
||||
|
||||
/** You already have registered this username! */
|
||||
NAME_ALREADY_REGISTERED("registration.name_taken"),
|
||||
|
||||
/** You don't have the permission to perform this action! */
|
||||
NO_PERMISSION("error.no_permission"),
|
||||
|
||||
/** An unexpected error occurred, please contact an administrator! */
|
||||
ERROR("error.unexpected_error"),
|
||||
|
||||
/** Please, login with the command: /login <password> */
|
||||
LOGIN_MESSAGE("login.login_request"),
|
||||
|
||||
/** Please, register to the server with the command: /register <password> <ConfirmPassword> */
|
||||
REGISTER_MESSAGE("registration.register_request"),
|
||||
|
||||
/** You have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection! */
|
||||
MAX_REGISTER_EXCEEDED("error.max_registration", "%max_acc", "%reg_count", "%reg_names"),
|
||||
|
||||
/** Usage: /register <password> <ConfirmPassword> */
|
||||
USAGE_REGISTER("registration.command_usage"),
|
||||
|
||||
/** Usage: /unregister <password> */
|
||||
USAGE_UNREGISTER("unregister.command_usage"),
|
||||
|
||||
/** Password changed successfully! */
|
||||
PASSWORD_CHANGED_SUCCESS("misc.password_changed"),
|
||||
|
||||
/** Passwords didn't match, check them again! */
|
||||
PASSWORD_MATCH_ERROR("password.match_error"),
|
||||
|
||||
/** You can't use your name as password, please choose another one... */
|
||||
PASSWORD_IS_USERNAME_ERROR("password.name_in_password"),
|
||||
|
||||
/** The chosen password isn't safe, please choose another one... */
|
||||
PASSWORD_UNSAFE_ERROR("password.unsafe_password"),
|
||||
|
||||
/** Your chosen password is not secure. It was used %pwned_count times already! Please use a stronger password... */
|
||||
PASSWORD_PWNED_ERROR("password.pwned_password", "%pwned_count"),
|
||||
|
||||
/** Your password contains illegal characters. Allowed chars: %valid_chars */
|
||||
PASSWORD_CHARACTERS_ERROR("password.forbidden_characters", "%valid_chars"),
|
||||
|
||||
/** Your IP has been changed and your session data has expired! */
|
||||
SESSION_EXPIRED("session.invalid_session"),
|
||||
|
||||
/** Only registered users can join the server! Please visit http://example.com to register yourself! */
|
||||
MUST_REGISTER_MESSAGE("registration.reg_only"),
|
||||
|
||||
/** You're already logged in! */
|
||||
ALREADY_LOGGED_IN_ERROR("error.logged_in"),
|
||||
|
||||
/** Logged out successfully! */
|
||||
LOGOUT_SUCCESS("misc.logout"),
|
||||
|
||||
/** The same username is already playing on the server! */
|
||||
USERNAME_ALREADY_ONLINE_ERROR("on_join_validation.same_nick_online"),
|
||||
|
||||
/** Successfully registered! */
|
||||
REGISTER_SUCCESS("registration.success"),
|
||||
|
||||
/** Your password is too short or too long! Please try with another one! */
|
||||
INVALID_PASSWORD_LENGTH("password.wrong_length"),
|
||||
|
||||
/** Configuration and database have been reloaded correctly! */
|
||||
CONFIG_RELOAD_SUCCESS("misc.reload"),
|
||||
|
||||
/** Login timeout exceeded, you have been kicked from the server, please try again! */
|
||||
LOGIN_TIMEOUT_ERROR("login.timeout_error"),
|
||||
|
||||
/** Usage: /changepassword <oldPassword> <newPassword> */
|
||||
USAGE_CHANGE_PASSWORD("misc.usage_change_password"),
|
||||
|
||||
/** Your username is either too short or too long! */
|
||||
INVALID_NAME_LENGTH("on_join_validation.name_length"),
|
||||
|
||||
/** Your username contains illegal characters. Allowed chars: %valid_chars */
|
||||
INVALID_NAME_CHARACTERS("on_join_validation.characters_in_name", "%valid_chars"),
|
||||
|
||||
/** Please add your email to your account with the command: /email add <yourEmail> <confirmEmail> */
|
||||
ADD_EMAIL_MESSAGE("email.add_email_request"),
|
||||
|
||||
/** Forgot your password? Please use the command: /email recovery <yourEmail> */
|
||||
FORGOT_PASSWORD_MESSAGE("recovery.forgot_password_hint"),
|
||||
|
||||
/** To log in you have to solve a captcha code, please use the command: /captcha %captcha_code */
|
||||
USAGE_CAPTCHA("captcha.usage_captcha", "%captcha_code"),
|
||||
|
||||
/** Wrong captcha, please type "/captcha %captcha_code" into the chat! */
|
||||
CAPTCHA_WRONG_ERROR("captcha.wrong_captcha", "%captcha_code"),
|
||||
|
||||
/** Captcha code solved correctly! */
|
||||
CAPTCHA_SUCCESS("captcha.valid_captcha"),
|
||||
|
||||
/** To register you have to solve a captcha first, please use the command: /captcha %captcha_code */
|
||||
CAPTCHA_FOR_REGISTRATION_REQUIRED("captcha.captcha_for_registration", "%captcha_code"),
|
||||
|
||||
/** Valid captcha! You may now register with /register */
|
||||
REGISTER_CAPTCHA_SUCCESS("captcha.register_captcha_valid"),
|
||||
|
||||
/** A VIP player has joined the server when it was full! */
|
||||
KICK_FOR_VIP("error.kick_for_vip"),
|
||||
|
||||
/** The server is full, try again later! */
|
||||
KICK_FULL_SERVER("on_join_validation.kick_full_server"),
|
||||
|
||||
/** An error occurred: unresolved player hostname! **/
|
||||
KICK_UNRESOLVED_HOSTNAME("error.kick_unresolved_hostname"),
|
||||
|
||||
/** Usage: /email add <email> <confirmEmail> */
|
||||
USAGE_ADD_EMAIL("email.usage_email_add"),
|
||||
|
||||
/** Usage: /email change <oldEmail> <newEmail> */
|
||||
USAGE_CHANGE_EMAIL("email.usage_email_change"),
|
||||
|
||||
/** Usage: /email recovery <Email> */
|
||||
USAGE_RECOVER_EMAIL("recovery.command_usage"),
|
||||
|
||||
/** Invalid new email, try again! */
|
||||
INVALID_NEW_EMAIL("email.new_email_invalid"),
|
||||
|
||||
/** Invalid old email, try again! */
|
||||
INVALID_OLD_EMAIL("email.old_email_invalid"),
|
||||
|
||||
/** Invalid email address, try again! */
|
||||
INVALID_EMAIL("email.invalid"),
|
||||
|
||||
/** Email address successfully added to your account! */
|
||||
EMAIL_ADDED_SUCCESS("email.added"),
|
||||
|
||||
/** Adding email was not allowed */
|
||||
EMAIL_ADD_NOT_ALLOWED("email.add_not_allowed"),
|
||||
|
||||
/** Please confirm your email address! */
|
||||
CONFIRM_EMAIL_MESSAGE("email.request_confirmation"),
|
||||
|
||||
/** Email address changed correctly! */
|
||||
EMAIL_CHANGED_SUCCESS("email.changed"),
|
||||
|
||||
/** Changing email was not allowed */
|
||||
EMAIL_CHANGE_NOT_ALLOWED("email.change_not_allowed"),
|
||||
|
||||
/** Your current email address is: %email */
|
||||
EMAIL_SHOW("email.email_show", "%email"),
|
||||
|
||||
/** You currently don't have email address associated with this account. */
|
||||
SHOW_NO_EMAIL("email.no_email_for_account"),
|
||||
|
||||
/** Recovery email sent successfully! Please check your email inbox! */
|
||||
RECOVERY_EMAIL_SENT_MESSAGE("recovery.email_sent"),
|
||||
|
||||
/** Your country is banned from this server! */
|
||||
COUNTRY_BANNED_ERROR("on_join_validation.country_banned"),
|
||||
|
||||
/** [AntiBotService] AntiBot enabled due to the huge number of connections! */
|
||||
ANTIBOT_AUTO_ENABLED_MESSAGE("antibot.auto_enabled"),
|
||||
|
||||
/** [AntiBotService] AntiBot disabled after %m minutes! */
|
||||
ANTIBOT_AUTO_DISABLED_MESSAGE("antibot.auto_disabled", "%m"),
|
||||
|
||||
/** The email address is already being used */
|
||||
EMAIL_ALREADY_USED_ERROR("email.already_used"),
|
||||
|
||||
/** Your secret code is %code. You can scan it from here %url */
|
||||
TWO_FACTOR_CREATE("two_factor.code_created", "%code", "%url"),
|
||||
|
||||
/** Please confirm your code with /2fa confirm <code> */
|
||||
TWO_FACTOR_CREATE_CONFIRMATION_REQUIRED("two_factor.confirmation_required"),
|
||||
|
||||
/** Please submit your two-factor authentication code with /2fa code <code> */
|
||||
TWO_FACTOR_CODE_REQUIRED("two_factor.code_required"),
|
||||
|
||||
/** Two-factor authentication is already enabled for your account! */
|
||||
TWO_FACTOR_ALREADY_ENABLED("two_factor.already_enabled"),
|
||||
|
||||
/** No 2fa key has been generated for you or it has expired. Please run /2fa add */
|
||||
TWO_FACTOR_ENABLE_ERROR_NO_CODE("two_factor.enable_error_no_code"),
|
||||
|
||||
/** Successfully enabled two-factor authentication for your account */
|
||||
TWO_FACTOR_ENABLE_SUCCESS("two_factor.enable_success"),
|
||||
|
||||
/** Wrong code or code has expired. Please run /2fa add */
|
||||
TWO_FACTOR_ENABLE_ERROR_WRONG_CODE("two_factor.enable_error_wrong_code"),
|
||||
|
||||
/** Two-factor authentication is not enabled for your account. Run /2fa add */
|
||||
TWO_FACTOR_NOT_ENABLED_ERROR("two_factor.not_enabled_error"),
|
||||
|
||||
/** Successfully removed two-factor auth from your account */
|
||||
TWO_FACTOR_REMOVED_SUCCESS("two_factor.removed_success"),
|
||||
|
||||
/** Invalid code! */
|
||||
TWO_FACTOR_INVALID_CODE("two_factor.invalid_code"),
|
||||
|
||||
/** You are not the owner of this account. Please choose another name! */
|
||||
NOT_OWNER_ERROR("on_join_validation.not_owner_error"),
|
||||
|
||||
/** You should join using username %valid, not %invalid. */
|
||||
INVALID_NAME_CASE("on_join_validation.invalid_name_case", "%valid", "%invalid"),
|
||||
|
||||
/** You have been temporarily banned for failing to log in too many times. */
|
||||
TEMPBAN_MAX_LOGINS("error.tempban_max_logins"),
|
||||
|
||||
/** You own %count accounts: */
|
||||
ACCOUNTS_OWNED_SELF("misc.accounts_owned_self", "%count"),
|
||||
|
||||
/** The player %name has %count accounts: */
|
||||
ACCOUNTS_OWNED_OTHER("misc.accounts_owned_other", "%name", "%count"),
|
||||
|
||||
/** An admin just registered you; please log in again */
|
||||
KICK_FOR_ADMIN_REGISTER("registration.kicked_admin_registered"),
|
||||
|
||||
/** Error: not all required settings are set for sending emails. Please contact an admin. */
|
||||
INCOMPLETE_EMAIL_SETTINGS("email.incomplete_settings"),
|
||||
|
||||
/** The email could not be sent. Please contact an administrator. */
|
||||
EMAIL_SEND_FAILURE("email.send_failure"),
|
||||
|
||||
/** A recovery code to reset your password has been sent to your email. */
|
||||
RECOVERY_CODE_SENT("recovery.code.code_sent"),
|
||||
|
||||
/** The recovery code is not correct! You have %count tries remaining. */
|
||||
INCORRECT_RECOVERY_CODE("recovery.code.incorrect", "%count"),
|
||||
|
||||
/**
|
||||
* You have exceeded the maximum number of attempts to enter the recovery code.
|
||||
* Use "/email recovery [email]" to generate a new one.
|
||||
*/
|
||||
RECOVERY_TRIES_EXCEEDED("recovery.code.tries_exceeded"),
|
||||
|
||||
/** Recovery code entered correctly! */
|
||||
RECOVERY_CODE_CORRECT("recovery.code.correct"),
|
||||
|
||||
/** Please use the command /email setpassword to change your password immediately. */
|
||||
RECOVERY_CHANGE_PASSWORD("recovery.code.change_password"),
|
||||
|
||||
/** You cannot change your password using this command anymore. */
|
||||
CHANGE_PASSWORD_EXPIRED("email.change_password_expired"),
|
||||
|
||||
/** An email was already sent recently. You must wait %time before you can send a new one. */
|
||||
EMAIL_COOLDOWN_ERROR("email.email_cooldown_error", "%time"),
|
||||
|
||||
/**
|
||||
* This command is sensitive and requires an email verification!
|
||||
* Check your inbox and follow the email's instructions.
|
||||
*/
|
||||
VERIFICATION_CODE_REQUIRED("verification.code_required"),
|
||||
|
||||
/** Usage: /verification <code> */
|
||||
USAGE_VERIFICATION_CODE("verification.command_usage"),
|
||||
|
||||
/** Incorrect code, please type "/verification <code>" into the chat, using the code you received by email */
|
||||
INCORRECT_VERIFICATION_CODE("verification.incorrect_code"),
|
||||
|
||||
/** Your identity has been verified! You can now execute all commands within the current session! */
|
||||
VERIFICATION_CODE_VERIFIED("verification.success"),
|
||||
|
||||
/** You can already execute every sensitive command within the current session! */
|
||||
VERIFICATION_CODE_ALREADY_VERIFIED("verification.already_verified"),
|
||||
|
||||
/** Your code has expired! Execute another sensitive command to get a new code! */
|
||||
VERIFICATION_CODE_EXPIRED("verification.code_expired"),
|
||||
|
||||
/** To verify your identity you need to link an email address with your account! */
|
||||
VERIFICATION_CODE_EMAIL_NEEDED("verification.email_needed"),
|
||||
|
||||
/** You used a command too fast! Please, join the server again and wait more before using any command. */
|
||||
QUICK_COMMAND_PROTECTION_KICK("on_join_validation.quick_command"),
|
||||
|
||||
/** second */
|
||||
SECOND("time.second"),
|
||||
|
||||
/** seconds */
|
||||
SECONDS("time.seconds"),
|
||||
|
||||
/** minute */
|
||||
MINUTE("time.minute"),
|
||||
|
||||
/** minutes */
|
||||
MINUTES("time.minutes"),
|
||||
|
||||
/** hour */
|
||||
HOUR("time.hour"),
|
||||
|
||||
/** hours */
|
||||
HOURS("time.hours"),
|
||||
|
||||
/** day */
|
||||
DAY("time.day"),
|
||||
|
||||
/** days */
|
||||
DAYS("time.days");
|
||||
|
||||
|
||||
private String key;
|
||||
private String[] tags;
|
||||
|
||||
MessageKey(String key, String... tags) {
|
||||
this.key = key;
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key used in the messages file.
|
||||
*
|
||||
* @return The key
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of tags (texts) that are replaced with actual content in AuthMe.
|
||||
*
|
||||
* @return List of tags
|
||||
*/
|
||||
public String[] getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package fr.xephi.authme.message;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Helper for creating and processing paths to message files.
|
||||
*/
|
||||
public final class MessagePathHelper {
|
||||
|
||||
/** The default language (used as fallback, assumed to be complete, etc.). */
|
||||
public static final String DEFAULT_LANGUAGE = "en";
|
||||
/** Local path to the folder containing the message files. */
|
||||
public static final String MESSAGES_FOLDER = "messages/";
|
||||
/** Local path to the default messages file (messages/messages_en.yml). */
|
||||
public static final String DEFAULT_MESSAGES_FILE = createMessageFilePath(DEFAULT_LANGUAGE);
|
||||
|
||||
private static final Pattern MESSAGE_FILE_PATTERN = Pattern.compile("messages_([a-z]+)\\.yml");
|
||||
private static final Pattern HELP_MESSAGES_FILE = Pattern.compile("help_[a-z]+\\.yml");
|
||||
|
||||
private MessagePathHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the local path to the messages file for the provided language code.
|
||||
*
|
||||
* @param languageCode the language code
|
||||
* @return local path to the messages file of the given language
|
||||
*/
|
||||
public static String createMessageFilePath(String languageCode) {
|
||||
return "messages/messages_" + languageCode + ".yml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the local path to the help messages file for the provided language code.
|
||||
*
|
||||
* @param languageCode the language code
|
||||
* @return local path to the help messages file of the given language
|
||||
*/
|
||||
public static String createHelpMessageFilePath(String languageCode) {
|
||||
return "messages/help_" + languageCode + ".yml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given file name is a messages file.
|
||||
*
|
||||
* @param filename the file name to test
|
||||
* @return true if it is a messages file, false otherwise
|
||||
*/
|
||||
public static boolean isMessagesFile(String filename) {
|
||||
return MESSAGE_FILE_PATTERN.matcher(filename).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language code the given file name is for if it is a messages file, otherwise null is returned.
|
||||
*
|
||||
* @param filename the file name to process
|
||||
* @return the language code the file name is a messages file for, or null if not applicable
|
||||
*/
|
||||
public static String getLanguageIfIsMessagesFile(String filename) {
|
||||
Matcher matcher = MESSAGE_FILE_PATTERN.matcher(filename);
|
||||
if (matcher.matches()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given file name is a help messages file.
|
||||
*
|
||||
* @param filename the file name to test
|
||||
* @return true if it is a help messages file, false otherwise
|
||||
*/
|
||||
public static boolean isHelpFile(String filename) {
|
||||
return HELP_MESSAGES_FILE.matcher(filename).matches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package fr.xephi.authme.message;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.mail.EmailService;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.util.expiring.Duration;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Class for retrieving and sending translatable messages to players.
|
||||
*/
|
||||
public class Messages {
|
||||
|
||||
// Custom Authme tag replaced to new line
|
||||
private static final String NEWLINE_TAG = "%nl%";
|
||||
|
||||
// Global tag replacements
|
||||
private static final String USERNAME_TAG = "%username%";
|
||||
private static final String DISPLAYNAME_TAG = "%displayname%";
|
||||
|
||||
/** Contains the keys of the singular messages for time units. */
|
||||
private static final Map<TimeUnit, MessageKey> TIME_UNIT_SINGULARS = ImmutableMap.<TimeUnit, MessageKey>builder()
|
||||
.put(TimeUnit.SECONDS, MessageKey.SECOND)
|
||||
.put(TimeUnit.MINUTES, MessageKey.MINUTE)
|
||||
.put(TimeUnit.HOURS, MessageKey.HOUR)
|
||||
.put(TimeUnit.DAYS, MessageKey.DAY).build();
|
||||
|
||||
/** Contains the keys of the plural messages for time units. */
|
||||
private static final Map<TimeUnit, MessageKey> TIME_UNIT_PLURALS = ImmutableMap.<TimeUnit, MessageKey>builder()
|
||||
.put(TimeUnit.SECONDS, MessageKey.SECONDS)
|
||||
.put(TimeUnit.MINUTES, MessageKey.MINUTES)
|
||||
.put(TimeUnit.HOURS, MessageKey.HOURS)
|
||||
.put(TimeUnit.DAYS, MessageKey.DAYS).build();
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(EmailService.class);
|
||||
|
||||
private MessagesFileHandler messagesFileHandler;
|
||||
|
||||
/*
|
||||
* Constructor.
|
||||
*/
|
||||
@Inject
|
||||
Messages(MessagesFileHandler messagesFileHandler) {
|
||||
this.messagesFileHandler = messagesFileHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given message code to the player.
|
||||
*
|
||||
* @param sender The entity to send the message to
|
||||
* @param key The key of the message to send
|
||||
*/
|
||||
public void send(CommandSender sender, MessageKey key) {
|
||||
String[] lines = retrieve(key, sender);
|
||||
for (String line : lines) {
|
||||
sender.sendMessage(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given message code to the player with the given tag replacements. Note that this method
|
||||
* logs an error if the number of supplied replacements doesn't correspond to the number of tags
|
||||
* the message key contains.
|
||||
*
|
||||
* @param sender The entity to send the message to
|
||||
* @param key The key of the message to send
|
||||
* @param replacements The replacements to apply for the tags
|
||||
*/
|
||||
public void send(CommandSender sender, MessageKey key, String... replacements) {
|
||||
String message = retrieveSingle(sender, key, replacements);
|
||||
for (String line : message.split("\n")) {
|
||||
sender.sendMessage(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message from the text file and return it split by new line as an array.
|
||||
*
|
||||
* @param key The message key to retrieve
|
||||
* @param sender The entity to send the message to
|
||||
* @return The message split by new lines
|
||||
*/
|
||||
public String[] retrieve(MessageKey key, CommandSender sender) {
|
||||
String message = retrieveMessage(key, sender);
|
||||
if (message.isEmpty()) {
|
||||
// Return empty array instead of array with 1 empty string as entry
|
||||
return new String[0];
|
||||
}
|
||||
return message.split("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the textual representation for the given duration.
|
||||
* Note that this class only supports the time units days, hour, minutes and seconds.
|
||||
*
|
||||
* @param duration the duration to build a text of
|
||||
* @return text of the duration
|
||||
*/
|
||||
public String formatDuration(Duration duration) {
|
||||
long value = duration.getDuration();
|
||||
MessageKey timeUnitKey = value == 1
|
||||
? TIME_UNIT_SINGULARS.get(duration.getTimeUnit())
|
||||
: TIME_UNIT_PLURALS.get(duration.getTimeUnit());
|
||||
|
||||
return value + " " + retrieveMessage(timeUnitKey, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message from the text file.
|
||||
*
|
||||
* @param key The message key to retrieve
|
||||
* @param sender The entity to send the message to
|
||||
* @return The message from the file
|
||||
*/
|
||||
private String retrieveMessage(MessageKey key, CommandSender sender) {
|
||||
String message = messagesFileHandler.getMessage(key.getKey());
|
||||
String displayName = sender.getName();
|
||||
if (sender instanceof Player) {
|
||||
displayName = ((Player) sender).getDisplayName();
|
||||
}
|
||||
|
||||
return ChatColor.translateAlternateColorCodes('&', message)
|
||||
.replace(NEWLINE_TAG, "\n")
|
||||
.replace(USERNAME_TAG, sender.getName())
|
||||
.replace(DISPLAYNAME_TAG, displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message from the text file.
|
||||
*
|
||||
* @param key The message key to retrieve
|
||||
* @param name The name of the entity to send the message to
|
||||
* @return The message from the file
|
||||
*/
|
||||
private String retrieveMessage(MessageKey key, String name) {
|
||||
String message = messagesFileHandler.getMessage(key.getKey());
|
||||
|
||||
return ChatColor.translateAlternateColorCodes('&', message)
|
||||
.replace(NEWLINE_TAG, "\n")
|
||||
.replace(USERNAME_TAG, name)
|
||||
.replace(DISPLAYNAME_TAG, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given message code with the given tag replacements. Note that this method
|
||||
* logs an error if the number of supplied replacements doesn't correspond to the number of tags
|
||||
* the message key contains.
|
||||
*
|
||||
* @param sender The entity to send the message to
|
||||
* @param key The key of the message to send
|
||||
* @param replacements The replacements to apply for the tags
|
||||
* @return The message from the file with replacements
|
||||
*/
|
||||
public String retrieveSingle(CommandSender sender, MessageKey key, String... replacements) {
|
||||
String message = retrieveMessage(key, sender);
|
||||
String[] tags = key.getTags();
|
||||
if (replacements.length == tags.length) {
|
||||
for (int i = 0; i < tags.length; ++i) {
|
||||
message = message.replace(tags[i], replacements[i]);
|
||||
}
|
||||
} else {
|
||||
logger.warning("Invalid number of replacements for message key '" + key + "'");
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given message code with the given tag replacements. Note that this method
|
||||
* logs an error if the number of supplied replacements doesn't correspond to the number of tags
|
||||
* the message key contains.
|
||||
*
|
||||
* @param name The name of the entity to send the message to
|
||||
* @param key The key of the message to send
|
||||
* @param replacements The replacements to apply for the tags
|
||||
* @return The message from the file with replacements
|
||||
*/
|
||||
public String retrieveSingle(String name, MessageKey key, String... replacements) {
|
||||
String message = retrieveMessage(key, name);
|
||||
String[] tags = key.getTags();
|
||||
if (replacements.length == tags.length) {
|
||||
for (int i = 0; i < tags.length; ++i) {
|
||||
message = message.replace(tags[i], replacements[i]);
|
||||
}
|
||||
} else {
|
||||
logger.warning("Invalid number of replacements for message key '" + key + "'");
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package fr.xephi.authme.message;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.message.updater.MessageUpdater;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static fr.xephi.authme.message.MessagePathHelper.DEFAULT_LANGUAGE;
|
||||
|
||||
/**
|
||||
* File handler for the messages_xx.yml resource.
|
||||
*/
|
||||
public class MessagesFileHandler extends AbstractMessageFileHandler {
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(MessagesFileHandler.class);
|
||||
|
||||
@Inject
|
||||
private MessageUpdater messageUpdater;
|
||||
|
||||
MessagesFileHandler() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
reloadInternal(false);
|
||||
}
|
||||
|
||||
private void reloadInternal(boolean isFromReload) {
|
||||
super.reload();
|
||||
|
||||
String language = getLanguage();
|
||||
boolean hasChange = messageUpdater.migrateAndSave(
|
||||
getUserLanguageFile(), createFilePath(language), createFilePath(DEFAULT_LANGUAGE));
|
||||
if (hasChange) {
|
||||
if (isFromReload) {
|
||||
logger.warning("Migration after reload attempt");
|
||||
} else {
|
||||
reloadInternal(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createFilePath(String language) {
|
||||
return MessagePathHelper.createMessageFilePath(language);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package fr.xephi.authme.message.updater;
|
||||
|
||||
import ch.jalu.configme.properties.Property;
|
||||
import ch.jalu.configme.resource.PropertyReader;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Returns messages from the JAR's message files. Favors a local JAR (e.g. messages_nl.yml)
|
||||
* before falling back to the default language (messages_en.yml).
|
||||
*/
|
||||
public class JarMessageSource {
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(JarMessageSource.class);
|
||||
private final PropertyReader localJarMessages;
|
||||
private final PropertyReader defaultJarMessages;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param localJarPath path to the messages file of the language the plugin is configured to use (may not exist)
|
||||
* @param defaultJarPath path to the default messages file in the JAR (must exist)
|
||||
*/
|
||||
public JarMessageSource(String localJarPath, String defaultJarPath) {
|
||||
localJarMessages = localJarPath.equals(defaultJarPath) ? null : loadJarFile(localJarPath);
|
||||
defaultJarMessages = loadJarFile(defaultJarPath);
|
||||
|
||||
if (defaultJarMessages == null) {
|
||||
throw new IllegalStateException("Default JAR file '" + defaultJarPath + "' could not be loaded");
|
||||
}
|
||||
}
|
||||
|
||||
public String getMessageFromJar(Property<?> property) {
|
||||
String key = property.getPath();
|
||||
String message = getString(key, localJarMessages);
|
||||
return message == null ? getString(key, defaultJarMessages) : message;
|
||||
}
|
||||
|
||||
private static String getString(String path, PropertyReader reader) {
|
||||
return reader == null ? null : reader.getString(path);
|
||||
}
|
||||
|
||||
private MessageMigraterPropertyReader loadJarFile(String jarPath) {
|
||||
try (InputStream stream = FileUtils.getResourceFromJar(jarPath)) {
|
||||
if (stream == null) {
|
||||
logger.debug("Could not load '" + jarPath + "' from JAR");
|
||||
return null;
|
||||
}
|
||||
return MessageMigraterPropertyReader.loadFromStream(stream);
|
||||
} catch (IOException e) {
|
||||
logger.logException("Exception while handling JAR path '" + jarPath + "'", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package fr.xephi.authme.message.updater;
|
||||
|
||||
import ch.jalu.configme.configurationdata.ConfigurationDataImpl;
|
||||
import ch.jalu.configme.properties.Property;
|
||||
import ch.jalu.configme.properties.convertresult.PropertyValue;
|
||||
import ch.jalu.configme.resource.PropertyReader;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MessageKeyConfigurationData extends ConfigurationDataImpl {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param propertyListBuilder property list builder for message key properties
|
||||
* @param allComments registered comments
|
||||
*/
|
||||
public MessageKeyConfigurationData(MessageUpdater.MessageKeyPropertyListBuilder propertyListBuilder,
|
||||
Map<String, List<String>> allComments) {
|
||||
super(propertyListBuilder.getAllProperties(), allComments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeValues(PropertyReader reader) {
|
||||
for (Property<String> property : getAllMessageProperties()) {
|
||||
PropertyValue<String> value = property.determineValue(reader);
|
||||
if (value.isValidInResource()) {
|
||||
setValue(property, value.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(Property<T> property) {
|
||||
// Override to silently return null if property is unknown
|
||||
return (T) getValues().get(property.getPath());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Property<String>> getAllMessageProperties() {
|
||||
return (List) getProperties();
|
||||
}
|
||||
|
||||
public String getMessage(MessageKey messageKey) {
|
||||
return getValue(new MessageUpdater.MessageKeyProperty(messageKey));
|
||||
}
|
||||
|
||||
public void setMessage(MessageKey messageKey, String message) {
|
||||
setValue(new MessageUpdater.MessageKeyProperty(messageKey), message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package fr.xephi.authme.message.updater;
|
||||
|
||||
import ch.jalu.configme.exception.ConfigMeException;
|
||||
import ch.jalu.configme.resource.PropertyReader;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PropertyReader} which can read a file or a stream with
|
||||
* a specified charset.
|
||||
*/
|
||||
final class MessageMigraterPropertyReader implements PropertyReader {
|
||||
|
||||
private static final Charset CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
private Map<String, Object> root;
|
||||
|
||||
private MessageMigraterPropertyReader(Map<String, Object> valuesMap) {
|
||||
root = valuesMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new property reader for the given file.
|
||||
*
|
||||
* @param file the file to load
|
||||
* @return the created property reader
|
||||
*/
|
||||
public static MessageMigraterPropertyReader loadFromFile(File file) {
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
return loadFromStream(is);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Error while reading file '" + file + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static MessageMigraterPropertyReader loadFromStream(InputStream inputStream) {
|
||||
Map<String, Object> valuesMap = readStreamToMap(inputStream);
|
||||
return new MessageMigraterPropertyReader(valuesMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String path) {
|
||||
return getObject(path) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getKeys(boolean b) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getChildKeys(String s) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getObject(String path) {
|
||||
if (path.isEmpty()) {
|
||||
return root.get("");
|
||||
}
|
||||
Object node = root;
|
||||
String[] keys = path.split("\\.");
|
||||
for (String key : keys) {
|
||||
node = getIfIsMap(key, node);
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String path) {
|
||||
Object o = getObject(path);
|
||||
return o instanceof String ? (String) o : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getInt(String path) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDouble(String path) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getBoolean(String path) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> getList(String path) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private static Map<String, Object> readStreamToMap(InputStream inputStream) {
|
||||
try (InputStreamReader isr = new InputStreamReader(inputStream, CHARSET)) {
|
||||
Object obj = new Yaml().load(isr);
|
||||
return obj == null ? new HashMap<>() : (Map<String, Object>) obj;
|
||||
} catch (IOException e) {
|
||||
throw new ConfigMeException("Could not read stream", e);
|
||||
} catch (ClassCastException e) {
|
||||
throw new ConfigMeException("Top-level is not a map", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getIfIsMap(String key, Object value) {
|
||||
if (value instanceof Map<?, ?>) {
|
||||
return ((Map<?, ?>) value).get(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package fr.xephi.authme.message.updater;
|
||||
|
||||
import ch.jalu.configme.configurationdata.ConfigurationData;
|
||||
import ch.jalu.configme.configurationdata.PropertyListBuilder;
|
||||
import ch.jalu.configme.properties.Property;
|
||||
import ch.jalu.configme.properties.StringProperty;
|
||||
import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
|
||||
import ch.jalu.configme.resource.PropertyReader;
|
||||
import ch.jalu.configme.resource.PropertyResource;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Files;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
/**
|
||||
* Migrates the used messages file to a complete, up-to-date version when necessary.
|
||||
*/
|
||||
public class MessageUpdater {
|
||||
|
||||
private ConsoleLogger logger = ConsoleLoggerFactory.get(MessageUpdater.class);
|
||||
|
||||
/**
|
||||
* Applies any necessary migrations to the user's messages file and saves it if it has been modified.
|
||||
*
|
||||
* @param userFile the user's messages file (yml file in the plugin's folder)
|
||||
* @param localJarPath path to the messages file in the JAR for the same language (may not exist)
|
||||
* @param defaultJarPath path to the messages file in the JAR for the default language
|
||||
* @return true if the file has been migrated and saved, false if it is up-to-date
|
||||
*/
|
||||
public boolean migrateAndSave(File userFile, String localJarPath, String defaultJarPath) {
|
||||
JarMessageSource jarMessageSource = new JarMessageSource(localJarPath, defaultJarPath);
|
||||
return migrateAndSave(userFile, jarMessageSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the migration.
|
||||
*
|
||||
* @param userFile the file to verify and migrate
|
||||
* @param jarMessageSource jar message source to get texts from if missing
|
||||
* @return true if the file has been migrated and saved, false if it is up-to-date
|
||||
*/
|
||||
private boolean migrateAndSave(File userFile, JarMessageSource jarMessageSource) {
|
||||
// YamlConfiguration escapes all special characters when saving, making the file hard to use, so use ConfigMe
|
||||
MessageKeyConfigurationData configurationData = createConfigurationData();
|
||||
PropertyResource userResource = new MigraterYamlFileResource(userFile);
|
||||
|
||||
PropertyReader reader = userResource.createReader();
|
||||
configurationData.initializeValues(reader);
|
||||
|
||||
// Step 1: Migrate any old keys in the file to the new paths
|
||||
boolean movedOldKeys = migrateOldKeys(reader, configurationData);
|
||||
// Step 2: Perform newer migrations
|
||||
boolean movedNewerKeys = migrateKeys(reader, configurationData);
|
||||
// Step 3: Take any missing messages from the message files shipped in the AuthMe JAR
|
||||
boolean addedMissingKeys = addMissingKeys(jarMessageSource, configurationData);
|
||||
|
||||
if (movedOldKeys || movedNewerKeys || addedMissingKeys) {
|
||||
backupMessagesFile(userFile);
|
||||
|
||||
userResource.exportProperties(configurationData);
|
||||
logger.debug("Successfully saved {0}", userFile);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean migrateKeys(PropertyReader propertyReader, MessageKeyConfigurationData configurationData) {
|
||||
return moveIfApplicable(propertyReader, configurationData,
|
||||
"misc.two_factor_create", MessageKey.TWO_FACTOR_CREATE);
|
||||
}
|
||||
|
||||
private static boolean moveIfApplicable(PropertyReader reader, MessageKeyConfigurationData configurationData,
|
||||
String oldPath, MessageKey messageKey) {
|
||||
if (configurationData.getMessage(messageKey) == null && reader.getString(oldPath) != null) {
|
||||
configurationData.setMessage(messageKey, reader.getString(oldPath));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean migrateOldKeys(PropertyReader propertyReader, MessageKeyConfigurationData configurationData) {
|
||||
boolean hasChange = OldMessageKeysMigrater.migrateOldPaths(propertyReader, configurationData);
|
||||
if (hasChange) {
|
||||
logger.info("Old keys have been moved to the new ones in your messages_xx.yml file");
|
||||
}
|
||||
return hasChange;
|
||||
}
|
||||
|
||||
private boolean addMissingKeys(JarMessageSource jarMessageSource, MessageKeyConfigurationData configurationData) {
|
||||
List<String> addedKeys = new ArrayList<>();
|
||||
for (Property<String> property : configurationData.getAllMessageProperties()) {
|
||||
final String key = property.getPath();
|
||||
if (configurationData.getValue(property) == null) {
|
||||
configurationData.setValue(property, jarMessageSource.getMessageFromJar(property));
|
||||
addedKeys.add(key);
|
||||
}
|
||||
}
|
||||
if (!addedKeys.isEmpty()) {
|
||||
logger.info(
|
||||
"Added " + addedKeys.size() + " missing keys to your messages_xx.yml file: " + addedKeys);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void backupMessagesFile(File messagesFile) {
|
||||
String backupName = FileUtils.createBackupFilePath(messagesFile);
|
||||
File backupFile = new File(backupName);
|
||||
try {
|
||||
Files.copy(messagesFile, backupFile);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Could not back up '" + messagesFile + "' to '" + backupFile + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the {@link ConfigurationData} for exporting a messages file in its entirety.
|
||||
*
|
||||
* @return the configuration data to export with
|
||||
*/
|
||||
public static MessageKeyConfigurationData createConfigurationData() {
|
||||
Map<String, String> comments = ImmutableMap.<String, String>builder()
|
||||
.put("registration", "Registration")
|
||||
.put("password", "Password errors on registration")
|
||||
.put("login", "Login")
|
||||
.put("error", "Errors")
|
||||
.put("antibot", "AntiBot")
|
||||
.put("unregister", "Unregister")
|
||||
.put("misc", "Other messages")
|
||||
.put("session", "Session messages")
|
||||
.put("on_join_validation", "Error messages when joining")
|
||||
.put("email", "Email")
|
||||
.put("recovery", "Password recovery by email")
|
||||
.put("captcha", "Captcha")
|
||||
.put("verification", "Verification code")
|
||||
.put("time", "Time units")
|
||||
.put("two_factor", "Two-factor authentication")
|
||||
.build();
|
||||
|
||||
Set<String> addedKeys = new HashSet<>();
|
||||
MessageKeyPropertyListBuilder builder = new MessageKeyPropertyListBuilder();
|
||||
// Add one key per section based on the comments map above so that the order is clear
|
||||
for (String path : comments.keySet()) {
|
||||
MessageKey key = Arrays.stream(MessageKey.values()).filter(p -> p.getKey().startsWith(path + "."))
|
||||
.findFirst().orElseThrow(() -> new IllegalStateException(path));
|
||||
builder.addMessageKey(key);
|
||||
addedKeys.add(key.getKey());
|
||||
}
|
||||
// Add all remaining keys to the property list builder
|
||||
Arrays.stream(MessageKey.values())
|
||||
.filter(key -> !addedKeys.contains(key.getKey()))
|
||||
.forEach(builder::addMessageKey);
|
||||
|
||||
// Create ConfigurationData instance
|
||||
Map<String, List<String>> commentsMap = comments.entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> e.getKey(), e -> singletonList(e.getValue())));
|
||||
return new MessageKeyConfigurationData(builder, commentsMap);
|
||||
}
|
||||
|
||||
static final class MessageKeyProperty extends StringProperty {
|
||||
|
||||
MessageKeyProperty(MessageKey messageKey) {
|
||||
super(messageKey.getKey(), "");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFromReader(PropertyReader reader, ConvertErrorRecorder errorRecorder) {
|
||||
return reader.getString(getPath());
|
||||
}
|
||||
}
|
||||
|
||||
static final class MessageKeyPropertyListBuilder {
|
||||
|
||||
private PropertyListBuilder propertyListBuilder = new PropertyListBuilder();
|
||||
|
||||
void addMessageKey(MessageKey key) {
|
||||
propertyListBuilder.add(new MessageKeyProperty(key));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<MessageKeyProperty> getAllProperties() {
|
||||
return (List) propertyListBuilder.create();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package fr.xephi.authme.message.updater;
|
||||
|
||||
import ch.jalu.configme.resource.PropertyReader;
|
||||
import ch.jalu.configme.resource.YamlFileResource;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Extension of {@link YamlFileResource} to fine-tune the export style.
|
||||
*/
|
||||
public class MigraterYamlFileResource extends YamlFileResource {
|
||||
|
||||
private Yaml singleQuoteYaml;
|
||||
|
||||
public MigraterYamlFileResource(File file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyReader createReader() {
|
||||
return MessageMigraterPropertyReader.loadFromFile(getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Yaml createNewYaml() {
|
||||
if (singleQuoteYaml == null) {
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
options.setAllowUnicode(true);
|
||||
options.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED);
|
||||
// Overridden setting: don't split lines
|
||||
options.setSplitLines(false);
|
||||
singleQuoteYaml = new Yaml(options);
|
||||
}
|
||||
return singleQuoteYaml;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package fr.xephi.authme.message.updater;
|
||||
|
||||
import ch.jalu.configme.resource.PropertyReader;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.of;
|
||||
|
||||
/**
|
||||
* Migrates message files from the old keys (before 5.5) to the new ones.
|
||||
*
|
||||
* @see <a href="https://github.com/AuthMe/AuthMeReloaded/issues/1467">Issue #1467</a>
|
||||
*/
|
||||
final class OldMessageKeysMigrater {
|
||||
|
||||
@VisibleForTesting
|
||||
static final Map<MessageKey, String> KEYS_TO_OLD_PATH = ImmutableMap.<MessageKey, String>builder()
|
||||
.put(MessageKey.LOGIN_SUCCESS, "login")
|
||||
.put(MessageKey.ERROR, "error")
|
||||
.put(MessageKey.DENIED_COMMAND, "denied_command")
|
||||
.put(MessageKey.SAME_IP_ONLINE, "same_ip_online")
|
||||
.put(MessageKey.DENIED_CHAT, "denied_chat")
|
||||
.put(MessageKey.KICK_ANTIBOT, "kick_antibot")
|
||||
.put(MessageKey.UNKNOWN_USER, "unknown_user")
|
||||
.put(MessageKey.NOT_LOGGED_IN, "not_logged_in")
|
||||
.put(MessageKey.USAGE_LOGIN, "usage_log")
|
||||
.put(MessageKey.WRONG_PASSWORD, "wrong_pwd")
|
||||
.put(MessageKey.UNREGISTERED_SUCCESS, "unregistered")
|
||||
.put(MessageKey.REGISTRATION_DISABLED, "reg_disabled")
|
||||
.put(MessageKey.SESSION_RECONNECTION, "valid_session")
|
||||
.put(MessageKey.ACCOUNT_NOT_ACTIVATED, "vb_nonActiv")
|
||||
.put(MessageKey.NAME_ALREADY_REGISTERED, "user_regged")
|
||||
.put(MessageKey.NO_PERMISSION, "no_perm")
|
||||
.put(MessageKey.LOGIN_MESSAGE, "login_msg")
|
||||
.put(MessageKey.REGISTER_MESSAGE, "reg_msg")
|
||||
.put(MessageKey.MAX_REGISTER_EXCEEDED, "max_reg")
|
||||
.put(MessageKey.USAGE_REGISTER, "usage_reg")
|
||||
.put(MessageKey.USAGE_UNREGISTER, "usage_unreg")
|
||||
.put(MessageKey.PASSWORD_CHANGED_SUCCESS, "pwd_changed")
|
||||
.put(MessageKey.PASSWORD_MATCH_ERROR, "password_error")
|
||||
.put(MessageKey.PASSWORD_IS_USERNAME_ERROR, "password_error_nick")
|
||||
.put(MessageKey.PASSWORD_UNSAFE_ERROR, "password_error_unsafe")
|
||||
.put(MessageKey.PASSWORD_CHARACTERS_ERROR, "password_error_chars")
|
||||
.put(MessageKey.SESSION_EXPIRED, "invalid_session")
|
||||
.put(MessageKey.MUST_REGISTER_MESSAGE, "reg_only")
|
||||
.put(MessageKey.ALREADY_LOGGED_IN_ERROR, "logged_in")
|
||||
.put(MessageKey.LOGOUT_SUCCESS, "logout")
|
||||
.put(MessageKey.USERNAME_ALREADY_ONLINE_ERROR, "same_nick")
|
||||
.put(MessageKey.REGISTER_SUCCESS, "registered")
|
||||
.put(MessageKey.INVALID_PASSWORD_LENGTH, "pass_len")
|
||||
.put(MessageKey.CONFIG_RELOAD_SUCCESS, "reload")
|
||||
.put(MessageKey.LOGIN_TIMEOUT_ERROR, "timeout")
|
||||
.put(MessageKey.USAGE_CHANGE_PASSWORD, "usage_changepassword")
|
||||
.put(MessageKey.INVALID_NAME_LENGTH, "name_len")
|
||||
.put(MessageKey.INVALID_NAME_CHARACTERS, "regex")
|
||||
.put(MessageKey.ADD_EMAIL_MESSAGE, "add_email")
|
||||
.put(MessageKey.FORGOT_PASSWORD_MESSAGE, "recovery_email")
|
||||
.put(MessageKey.USAGE_CAPTCHA, "usage_captcha")
|
||||
.put(MessageKey.CAPTCHA_WRONG_ERROR, "wrong_captcha")
|
||||
.put(MessageKey.CAPTCHA_SUCCESS, "valid_captcha")
|
||||
.put(MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, "captcha_for_registration")
|
||||
.put(MessageKey.REGISTER_CAPTCHA_SUCCESS, "register_captcha_valid")
|
||||
.put(MessageKey.KICK_FOR_VIP, "kick_forvip")
|
||||
.put(MessageKey.KICK_FULL_SERVER, "kick_fullserver")
|
||||
.put(MessageKey.USAGE_ADD_EMAIL, "usage_email_add")
|
||||
.put(MessageKey.USAGE_CHANGE_EMAIL, "usage_email_change")
|
||||
.put(MessageKey.USAGE_RECOVER_EMAIL, "usage_email_recovery")
|
||||
.put(MessageKey.INVALID_NEW_EMAIL, "new_email_invalid")
|
||||
.put(MessageKey.INVALID_OLD_EMAIL, "old_email_invalid")
|
||||
.put(MessageKey.INVALID_EMAIL, "email_invalid")
|
||||
.put(MessageKey.EMAIL_ADDED_SUCCESS, "email_added")
|
||||
.put(MessageKey.CONFIRM_EMAIL_MESSAGE, "email_confirm")
|
||||
.put(MessageKey.EMAIL_CHANGED_SUCCESS, "email_changed")
|
||||
.put(MessageKey.EMAIL_SHOW, "email_show")
|
||||
.put(MessageKey.SHOW_NO_EMAIL, "show_no_email")
|
||||
.put(MessageKey.RECOVERY_EMAIL_SENT_MESSAGE, "email_send")
|
||||
.put(MessageKey.COUNTRY_BANNED_ERROR, "country_banned")
|
||||
.put(MessageKey.ANTIBOT_AUTO_ENABLED_MESSAGE, "antibot_auto_enabled")
|
||||
.put(MessageKey.ANTIBOT_AUTO_DISABLED_MESSAGE, "antibot_auto_disabled")
|
||||
.put(MessageKey.EMAIL_ALREADY_USED_ERROR, "email_already_used")
|
||||
.put(MessageKey.TWO_FACTOR_CREATE, "two_factor_create")
|
||||
.put(MessageKey.NOT_OWNER_ERROR, "not_owner_error")
|
||||
.put(MessageKey.INVALID_NAME_CASE, "invalid_name_case")
|
||||
.put(MessageKey.TEMPBAN_MAX_LOGINS, "tempban_max_logins")
|
||||
.put(MessageKey.ACCOUNTS_OWNED_SELF, "accounts_owned_self")
|
||||
.put(MessageKey.ACCOUNTS_OWNED_OTHER, "accounts_owned_other")
|
||||
.put(MessageKey.KICK_FOR_ADMIN_REGISTER, "kicked_admin_registered")
|
||||
.put(MessageKey.INCOMPLETE_EMAIL_SETTINGS, "incomplete_email_settings")
|
||||
.put(MessageKey.EMAIL_SEND_FAILURE, "email_send_failure")
|
||||
.put(MessageKey.RECOVERY_CODE_SENT, "recovery_code_sent")
|
||||
.put(MessageKey.INCORRECT_RECOVERY_CODE, "recovery_code_incorrect")
|
||||
.put(MessageKey.RECOVERY_TRIES_EXCEEDED, "recovery_tries_exceeded")
|
||||
.put(MessageKey.RECOVERY_CODE_CORRECT, "recovery_code_correct")
|
||||
.put(MessageKey.RECOVERY_CHANGE_PASSWORD, "recovery_change_password")
|
||||
.put(MessageKey.CHANGE_PASSWORD_EXPIRED, "change_password_expired")
|
||||
.put(MessageKey.EMAIL_COOLDOWN_ERROR, "email_cooldown_error")
|
||||
.put(MessageKey.VERIFICATION_CODE_REQUIRED, "verification_code_required")
|
||||
.put(MessageKey.USAGE_VERIFICATION_CODE, "usage_verification_code")
|
||||
.put(MessageKey.INCORRECT_VERIFICATION_CODE, "incorrect_verification_code")
|
||||
.put(MessageKey.VERIFICATION_CODE_VERIFIED, "verification_code_verified")
|
||||
.put(MessageKey.VERIFICATION_CODE_ALREADY_VERIFIED, "verification_code_already_verified")
|
||||
.put(MessageKey.VERIFICATION_CODE_EXPIRED, "verification_code_expired")
|
||||
.put(MessageKey.VERIFICATION_CODE_EMAIL_NEEDED, "verification_code_email_needed")
|
||||
.put(MessageKey.SECOND, "second")
|
||||
.put(MessageKey.SECONDS, "seconds")
|
||||
.put(MessageKey.MINUTE, "minute")
|
||||
.put(MessageKey.MINUTES, "minutes")
|
||||
.put(MessageKey.HOUR, "hour")
|
||||
.put(MessageKey.HOURS, "hours")
|
||||
.put(MessageKey.DAY, "day")
|
||||
.put(MessageKey.DAYS, "days")
|
||||
.build();
|
||||
|
||||
private static final Map<MessageKey, Map<String, String>> PLACEHOLDER_REPLACEMENTS =
|
||||
ImmutableMap.<MessageKey, Map<String, String>>builder()
|
||||
.put(MessageKey.PASSWORD_CHARACTERS_ERROR, of("REG_EX", "%valid_chars"))
|
||||
.put(MessageKey.INVALID_NAME_CHARACTERS, of("REG_EX", "%valid_chars"))
|
||||
.put(MessageKey.USAGE_CAPTCHA, of("<theCaptcha>", "%captcha_code"))
|
||||
.put(MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, of("<theCaptcha>", "%captcha_code"))
|
||||
.put(MessageKey.CAPTCHA_WRONG_ERROR, of("THE_CAPTCHA", "%captcha_code"))
|
||||
.build();
|
||||
|
||||
private OldMessageKeysMigrater() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates any existing old key paths to their new paths if no text has been defined for the new key.
|
||||
*
|
||||
* @param reader the property reader to get values from
|
||||
* @param configurationData the configuration data to write to
|
||||
* @return true if at least one message could be migrated, false otherwise
|
||||
*/
|
||||
static boolean migrateOldPaths(PropertyReader reader, MessageKeyConfigurationData configurationData) {
|
||||
boolean wasPropertyMoved = false;
|
||||
for (Map.Entry<MessageKey, String> migrationEntry : KEYS_TO_OLD_PATH.entrySet()) {
|
||||
wasPropertyMoved |= moveIfApplicable(reader, configurationData,
|
||||
migrationEntry.getKey(), migrationEntry.getValue());
|
||||
}
|
||||
return wasPropertyMoved;
|
||||
}
|
||||
|
||||
private static boolean moveIfApplicable(PropertyReader reader, MessageKeyConfigurationData configurationData,
|
||||
MessageKey messageKey, String oldPath) {
|
||||
if (configurationData.getMessage(messageKey) == null) {
|
||||
String textAtOldPath = reader.getString(oldPath);
|
||||
if (textAtOldPath != null) {
|
||||
textAtOldPath = replaceOldPlaceholders(messageKey, textAtOldPath);
|
||||
configurationData.setMessage(messageKey, textAtOldPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String replaceOldPlaceholders(MessageKey key, String text) {
|
||||
Map<String, String> replacements = PLACEHOLDER_REPLACEMENTS.get(key);
|
||||
if (replacements == null) {
|
||||
return text;
|
||||
}
|
||||
|
||||
String newText = text;
|
||||
for (Map.Entry<String, String> replacement : replacements.entrySet()) {
|
||||
newText = newText.replace(replacement.getKey(), replacement.getValue());
|
||||
}
|
||||
return newText;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user