diff --git a/pom.xml b/pom.xml index 3ec71875..7bf92c8f 100644 --- a/pom.xml +++ b/pom.xml @@ -126,7 +126,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.2.1 + 3.5.0 enforce-environment @@ -216,6 +216,13 @@ org.apache.maven.plugins maven-jar-plugin 3.4.1 + + + + mojang + + + @@ -463,6 +470,18 @@ com.alessiodp.libby fr.xephi.authme.libs.com.alessiodp.libby + + net.kyori.adventure + fr.xephi.authme.libs.net.kyori.adventure + + + net.kyori.examination + fr.xephi.authme.libs.net.kyori.examination + + + net.kyori.option + fr.xephi.authme.libs.net.kyori.option + @@ -475,7 +494,6 @@ META-INF/*.DSA META-INF/*.RSA META-INF/*.RSA - META-INF/*.MF META-INF/DEPENDENCIES META-INF/**/module-info.class @@ -495,13 +513,13 @@ org.apache.maven.plugins maven-install-plugin - 3.1.0 + 3.1.2 org.apache.maven.plugins maven-deploy-plugin - 3.0.0 + 3.1.2 @@ -539,6 +557,12 @@ https://repo.opencollab.dev/maven-snapshots/ + + + sonatype-oss-snapshots1 + https://s01.oss.sonatype.org/content/repositories/snapshots/ + + apache-snapshots @@ -807,14 +831,14 @@ junit junit - - bungeecord-chat - net.md-5 - com.googlecode.json-simple json-simple + + bungeecord-chat + net.md-5 + @@ -882,6 +906,23 @@ + + + net.kyori + adventure-text-minimessage + 4.17.0 + + + net.kyori + adventure-platform-bukkit + 4.3.2 + + + net.kyori + adventure-text-serializer-gson + 4.17.0 + + net.luckperms @@ -1152,7 +1193,7 @@ org.xerial sqlite-jdbc - 3.45.3.0 + 3.46.0.0 test diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 3bec3edb..8d079e8e 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -44,6 +44,7 @@ import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.task.CleanupTask; +import fr.xephi.authme.task.Updater; import fr.xephi.authme.task.purge.PurgeService; import fr.xephi.authme.util.ExceptionUtils; import org.bukkit.Server; @@ -55,15 +56,10 @@ import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.io.File; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Objects; -import java.util.Scanner; import java.util.function.Consumer; -import java.util.logging.Level; import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE; import static fr.xephi.authme.util.Utils.isClassLoaded; @@ -218,14 +214,15 @@ public class AuthMe extends JavaPlugin { } //detect server brand with classloader checkServerType(); - Objects.requireNonNull(getCommand("register")).setTabCompleter(new TabCompleteHandler()); - Objects.requireNonNull(getCommand("login")).setTabCompleter(new TabCompleteHandler()); + try { + Objects.requireNonNull(getCommand("register")).setTabCompleter(new TabCompleteHandler()); + Objects.requireNonNull(getCommand("login")).setTabCompleter(new TabCompleteHandler()); + } catch (NullPointerException ignored) { + } logger.info("AuthMeReReloaded is enabled successfully!"); // Purge on start if enabled PurgeService purgeService = injector.getSingleton(PurgeService.class); purgeService.runAutoPurge(); - // 注册玩家加入事件监听 -// register3rdPartyListeners(); logger.info("GitHub: https://github.com/HaHaWTH/AuthMeReReloaded/"); if (settings.getProperty(SecuritySettings.CHECK_FOR_UPDATES)) { checkForUpdates(); @@ -233,8 +230,6 @@ public class AuthMe extends JavaPlugin { } - //Migrated - /** * Load the version and build number of the plugin from the description file. * @@ -402,7 +397,7 @@ public class AuthMe extends JavaPlugin { if (onShutdownPlayerSaver != null) { onShutdownPlayerSaver.saveAllPlayers(); } - if (settings.getProperty(EmailSettings.SHUTDOWN_MAIL)){ + if (settings != null && settings.getProperty(EmailSettings.SHUTDOWN_MAIL)) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy'.'MM'.'dd'.' HH:mm:ss"); Date date = new Date(System.currentTimeMillis()); emailService.sendShutDown(settings.getProperty(EmailSettings.SHUTDOWN_MAIL_ADDRESS),dateFormat.format(date)); @@ -422,55 +417,19 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.closeFileWriter(); } - private static final String owner = "HaHaWTH"; -// private static final String owner_gitee = "Shixuehan114514"; - private static final String repo = "AuthMeReReloaded"; - private void checkForUpdates() { logger.info("Checking for updates..."); + Updater updater = new Updater(pluginBuild + pluginBuildNumber); bukkitService.runTaskAsynchronously(() -> { - try { - // 从南通集线器获取最新版本号 - URL url = new URL("https://api.github.com/repos/" + owner + "/" + repo + "/releases/latest"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(10000); // 设置连接超时为10秒 - conn.setReadTimeout(10000); // 设置读取超时为10秒 - Scanner scanner = new Scanner(conn.getInputStream()); - String response = scanner.useDelimiter("\\Z").next(); - scanner.close(); - - // 处理JSON响应 - String latestVersion = response.substring(response.indexOf("tag_name") + 11); - latestVersion = latestVersion.substring(0, latestVersion.indexOf("\"")); - if (isUpdateAvailable(latestVersion)) { - String message = "New version available! Latest:" + latestVersion + " Current:" + pluginBuild + pluginBuildNumber; - getLogger().log(Level.WARNING, message); - getLogger().log(Level.WARNING, "Download from here: https://github.com/HaHaWTH/AuthMeReReloaded/releases/latest"); - } else { - getLogger().log(Level.INFO, "You are running the latest version."); - } - } catch (IOException ignored) { + if (updater.isUpdateAvailable()) { + String message = "New version available! Latest:" + updater.getLatestVersion() + " Current:" + pluginBuild + pluginBuildNumber; + logger.warning(message); + logger.warning("Download from here: https://github.com/HaHaWTH/AuthMeReReloaded/releases/latest"); + } else { + logger.info("You are running the latest version."); } }); } - private boolean isUpdateAvailable(String latestVersion) { - // Extract the first character and the remaining digits from the version string - char latestChar = latestVersion.charAt(0); - int latestNumber = Integer.parseInt(latestVersion.substring(1)); - - char currentChar = pluginBuild.charAt(0); - int currentNumber = Integer.parseInt(pluginBuildNumber); - - // Compare the characters first - if (latestChar > currentChar) { - return true; - } else if (latestChar < currentChar) { - return false; - } else { - // If the characters are the same, compare the numbers - return latestNumber > currentNumber; - } - } private void checkServerType() { diff --git a/src/main/java/fr/xephi/authme/api/v3/AuthMeApi.java b/src/main/java/fr/xephi/authme/api/v3/AuthMeApi.java index 3d8ab194..fd487d66 100644 --- a/src/main/java/fr/xephi/authme/api/v3/AuthMeApi.java +++ b/src/main/java/fr/xephi/authme/api/v3/AuthMeApi.java @@ -265,6 +265,16 @@ public class AuthMeApi { management.forceLogin(player); } + /** + * Force a player to login, i.e. the player is logged in without needing his password. + * + * @param player The player to log in + * @param quiet Whether to suppress the login message + */ + public void forceLogin(Player player, boolean quiet) { + management.forceLogin(player, quiet); + } + /** * Force a player to logout. * diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java index 7bd7d7cc..9eba3d15 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java @@ -1,9 +1,9 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; -import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.TeleportUtils; import org.bukkit.entity.Player; @@ -18,17 +18,17 @@ public class FirstSpawnCommand extends PlayerCommand { private Settings settings; @Inject private SpawnLoader spawnLoader; + @Inject + private BukkitService bukkitService; @Override public void runCommand(Player player, List arguments) { if (spawnLoader.getFirstSpawn() == null) { player.sendMessage("[AuthMe] First spawn has failed, please try to define the first spawn"); } else { //String name= player.getName(); - if(settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { + bukkitService.runTaskIfFolia(player, () -> { TeleportUtils.teleport(player, spawnLoader.getFirstSpawn()); - } else { - player.teleport(spawnLoader.getFirstSpawn()); - } + }); //player.teleport(spawnLoader.getFirstSpawn()); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SpawnCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SpawnCommand.java index 3c011e6d..92ad0a30 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SpawnCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SpawnCommand.java @@ -1,7 +1,9 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.util.TeleportUtils; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -11,13 +13,15 @@ public class SpawnCommand extends PlayerCommand { @Inject private SpawnLoader spawnLoader; + @Inject + private BukkitService bukkitService; @Override public void runCommand(Player player, List arguments) { if (spawnLoader.getSpawn() == null) { player.sendMessage("[AuthMe] Spawn has failed, please try to define the spawn"); } else { - player.teleport(spawnLoader.getSpawn()); + bukkitService.runTaskIfFolia(player, () -> TeleportUtils.teleport(player, spawnLoader.getSpawn())); } } } diff --git a/src/main/java/fr/xephi/authme/listener/BedrockAutoLoginListener.java b/src/main/java/fr/xephi/authme/listener/BedrockAutoLoginListener.java index 9a0adfb3..4f63e610 100644 --- a/src/main/java/fr/xephi/authme/listener/BedrockAutoLoginListener.java +++ b/src/main/java/fr/xephi/authme/listener/BedrockAutoLoginListener.java @@ -46,7 +46,7 @@ public class BedrockAutoLoginListener implements Listener { UUID uuid = event.getPlayer().getUniqueId(); bukkitService.runTaskLater(player, () -> { if (isBedrockPlayer(uuid) && !authmeApi.isAuthenticated(player) && authmeApi.isRegistered(name)) { - authmeApi.forceLogin(player); + authmeApi.forceLogin(player, true); messages.send(player, MessageKey.BEDROCK_AUTO_LOGGED_IN); } },20L); diff --git a/src/main/java/fr/xephi/authme/listener/LoginLocationFixListener.java b/src/main/java/fr/xephi/authme/listener/LoginLocationFixListener.java index 344290ac..1c64b68b 100644 --- a/src/main/java/fr/xephi/authme/listener/LoginLocationFixListener.java +++ b/src/main/java/fr/xephi/authme/listener/LoginLocationFixListener.java @@ -75,11 +75,7 @@ public class LoginLocationFixListener implements Listener { boolean solved = false; for (BlockFace face : faces) { if (JoinBlock.getRelative(face).getType().equals(Material.AIR) && JoinBlock.getRelative(face).getRelative(BlockFace.UP).getType().equals(Material.AIR)) { - if (settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { - TeleportUtils.teleport(player, JoinBlock.getRelative(face).getLocation().add(0.5, 0.1, 0.5)); - } else { - player.teleport(JoinBlock.getRelative(face).getLocation().add(0.5, 0.1, 0.5)); - } + TeleportUtils.teleport(player, JoinBlock.getRelative(face).getLocation().add(0.5, 0.1, 0.5)); solved = true; break; } @@ -107,20 +103,12 @@ public class LoginLocationFixListener implements Listener { if (JoinBlock.getRelative(BlockFace.DOWN).getType().equals(Material.LAVA)) { JoinBlock.getRelative(BlockFace.DOWN).setType(Material.DIRT); } - if (settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { - TeleportUtils.teleport(player, JoinBlock.getLocation().add(0.5, 0.1, 0.5)); - } else { - player.teleport(JoinBlock.getLocation().add(0.5, 0.1, 0.5)); - } + TeleportUtils.teleport(player, JoinBlock.getLocation().add(0.5, 0.1, 0.5)); messages.send(player, MessageKey.LOCATION_FIX_UNDERGROUND); break; } if (i == MaxHeight) { - if (settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { - TeleportUtils.teleport(player, JoinBlock.getLocation().add(0.5, 1.1, 0.5)); - } else { - player.teleport(JoinBlock.getLocation().add(0.5, 1.1, 0.5)); - } + TeleportUtils.teleport(player, JoinBlock.getLocation().add(0.5, 1.1, 0.5)); messages.send(player, MessageKey.LOCATION_FIX_UNDERGROUND_CANT_FIX); } } diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener.java b/src/main/java/fr/xephi/authme/listener/PlayerListener.java index 85056448..d79620b3 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener.java @@ -19,8 +19,8 @@ import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.TeleportUtils; +import fr.xephi.authme.util.message.MiniMessageUtils; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.entity.HumanEntity; @@ -220,7 +220,7 @@ public class PlayerListener implements Listener{ String customJoinMessage = settings.getProperty(RegistrationSettings.CUSTOM_JOIN_MESSAGE); if (!customJoinMessage.isEmpty()) { - customJoinMessage = ChatColor.translateAlternateColorCodes('&', customJoinMessage); + customJoinMessage = ChatColor.translateAlternateColorCodes('&', MiniMessageUtils.parseMiniMessageToLegacy(customJoinMessage)); event.setJoinMessage(customJoinMessage .replace("{PLAYERNAME}", player.getName()) .replace("{DISPLAYNAME}", player.getDisplayName()) @@ -375,17 +375,9 @@ public class PlayerListener implements Listener{ Location spawn = spawnLoader.getSpawnLocation(player); if (spawn != null && spawn.getWorld() != null) { if (!player.getWorld().equals(spawn.getWorld())) { - if(settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { - TeleportUtils.teleport(player,spawn); - } else { - player.teleport(spawn); - } + TeleportUtils.teleport(player,spawn); } else if (spawn.distance(player.getLocation()) > settings.getProperty(ALLOWED_MOVEMENT_RADIUS)) { - if(settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { - TeleportUtils.teleport(player,spawn); - } else { - player.teleport(spawn); - } + TeleportUtils.teleport(player,spawn); } } } diff --git a/src/main/java/fr/xephi/authme/message/Messages.java b/src/main/java/fr/xephi/authme/message/Messages.java index 2877e484..0d7ec317 100644 --- a/src/main/java/fr/xephi/authme/message/Messages.java +++ b/src/main/java/fr/xephi/authme/message/Messages.java @@ -6,6 +6,7 @@ import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.util.expiring.Duration; import fr.xephi.authme.util.message.I18NUtils; +import fr.xephi.authme.util.message.MiniMessageUtils; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -129,8 +130,8 @@ public class Messages { if (sender instanceof Player) { displayName = ((Player) sender).getDisplayName(); } - - return ChatColor.translateAlternateColorCodes('&', message) + + return ChatColor.translateAlternateColorCodes('&', MiniMessageUtils.parseMiniMessageToLegacy(message)) .replace(NEWLINE_TAG, "\n") .replace(USERNAME_TAG, sender.getName()) .replace(DISPLAYNAME_TAG, displayName); @@ -146,7 +147,7 @@ public class Messages { private String retrieveMessage(MessageKey key, String name) { String message = messagesFileHandler.getMessage(key.getKey()); - return ChatColor.translateAlternateColorCodes('&', message) + return ChatColor.translateAlternateColorCodes('&', MiniMessageUtils.parseMiniMessageToLegacy(message)) .replace(NEWLINE_TAG, "\n") .replace(USERNAME_TAG, name) .replace(DISPLAYNAME_TAG, name); diff --git a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java index 5e6f953f..4b6af1e5 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java @@ -13,7 +13,6 @@ import fr.xephi.authme.service.bungeecord.BungeeSender; import fr.xephi.authme.service.bungeecord.MessageType; import fr.xephi.authme.service.velocity.VMessageType; import fr.xephi.authme.service.velocity.VelocitySender; -import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -65,11 +64,11 @@ public class AsynchronousLogout implements AsynchronousProcess { PlayerAuth auth = playerCache.getAuth(name); database.updateSession(auth); // TODO: send an update when a messaging service will be implemented (SESSION) - if (service.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)) { - auth.setQuitLocation(player.getLocation()); - database.updateQuitLoc(auth); + //if (service.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)) { + auth.setQuitLocation(player.getLocation()); + database.updateQuitLoc(auth); // TODO: send an update when a messaging service will be implemented (QUITLOC) - } + //} AuthMeReReloaded - Always save quit location playerCache.removePlayer(name); codeManager.unverify(name); diff --git a/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java b/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java index f4b1d325..31bb7078 100644 --- a/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java @@ -12,7 +12,6 @@ import fr.xephi.authme.service.SessionService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.PluginSettings; -import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -68,13 +67,16 @@ public class AsynchronousQuit implements AsynchronousProcess { boolean wasLoggedIn = playerCache.isAuthenticated(name); if (wasLoggedIn) { - if (service.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)) { - Location loc = spawnLoader.getPlayerLocationOrSpawn(player); - PlayerAuth auth = PlayerAuth.builder() - .name(name).location(loc) - .realName(player.getName()).build(); - database.updateQuitLoc(auth); - } + //if (service.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)) { + // AuthMeReReloaded - Always save quit location on quit + Location loc = spawnLoader.getPlayerLocationOrSpawn(player); + PlayerAuth authLoc = PlayerAuth.builder() + .name(name).location(loc) + .realName(player.getName()).build(); + database.updateQuitLoc(authLoc); + // AuthMeReReloaded - Fix AuthMe#2769 -1 + //} + String ip = PlayerUtils.getPlayerIp(player); PlayerAuth auth = PlayerAuth.builder() diff --git a/src/main/java/fr/xephi/authme/service/TeleportationService.java b/src/main/java/fr/xephi/authme/service/TeleportationService.java index bbc204cd..c50b0129 100644 --- a/src/main/java/fr/xephi/authme/service/TeleportationService.java +++ b/src/main/java/fr/xephi/authme/service/TeleportationService.java @@ -14,7 +14,6 @@ import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.TeleportUtils; import org.bukkit.Location; import org.bukkit.World; @@ -189,10 +188,8 @@ public class TeleportationService implements Reloadable { private void performTeleportation(final Player player, final AbstractTeleportEvent event) { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { bukkitService.callEvent(event); - if (player.isOnline() && isEventValid(event) && settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { + if (player.isOnline() && isEventValid(event)) { TeleportUtils.teleport(player, event.getTo()); - } else if (player.isOnline() && isEventValid(event)) { - player.teleport(event.getTo()); } }); } diff --git a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java index a5e62c02..7becd390 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java @@ -58,7 +58,7 @@ public final class ProtectionSettings implements SettingsHolder { @Comment("Kicks the player that issued a command before the defined time after the join process") public static final Property QUICK_COMMANDS_DENIED_BEFORE_MILLISECONDS = - newProperty("Protection.quickCommands.denyCommandsBeforeMilliseconds", 3000); + newProperty("Protection.quickCommands.denyCommandsBeforeMilliseconds", 1000); private ProtectionSettings() { } diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index ed938518..c8469e86 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -48,11 +48,6 @@ public final class SecuritySettings implements SettingsHolder { public static final Property ADVANCED_SHULKER_FIX = newProperty("3rdPartyFeature.fixes.advancedShulkerFix", false); - @Comment({"Choose the best teleport method by server brand?", - "(Enable this if you are using Paper/Folia)"}) - public static final Property SMART_ASYNC_TELEPORT = - newProperty("3rdPartyFeature.optimizes.smartAsyncTeleport", true); - @Comment("Send a GUI captcha to unregistered players?(Requires ProtocolLib)") public static final Property GUI_CAPTCHA = newProperty("3rdPartyFeature.features.captcha.guiCaptcha", false); diff --git a/src/main/java/fr/xephi/authme/task/Updater.java b/src/main/java/fr/xephi/authme/task/Updater.java new file mode 100644 index 00000000..5616d88a --- /dev/null +++ b/src/main/java/fr/xephi/authme/task/Updater.java @@ -0,0 +1,67 @@ +package fr.xephi.authme.task; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.Scanner; + +public class Updater { + private final String currentVersion; + private String latestVersion; + private static boolean isUpdateAvailable = false; + private static final String owner = "HaHaWTH"; + private static final String repo = "AuthMeReReloaded"; + private static final String UPDATE_URL = "https://api.github.com/repos/" + owner + "/" + repo + "/releases/latest"; + + public Updater(String currentVersion) { + this.currentVersion = currentVersion; + } + + + /** + * Check if there is an update available + * Note: This method will perform a network request! + * + * @return true if there is an update available, false otherwise + */ + public boolean isUpdateAvailable() { + URI uri = URI.create(UPDATE_URL); + try { + URL url = uri.toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(10000); + conn.setReadTimeout(10000); + Scanner scanner = new Scanner(conn.getInputStream()); + String response = scanner.useDelimiter("\\Z").next(); + scanner.close(); + String latestVersion = response.substring(response.indexOf("tag_name") + 11); + latestVersion = latestVersion.substring(0, latestVersion.indexOf("\"")); + this.latestVersion = latestVersion; + isUpdateAvailable = !currentVersion.equals(latestVersion); + return isUpdateAvailable; + } catch (IOException ignored) { + this.latestVersion = null; + isUpdateAvailable = false; + return false; + } + } + + public String getLatestVersion() { + return latestVersion; + } + + public String getCurrentVersion() { + return currentVersion; + } + + /** + * Returns true if there is an update available, false otherwise + * Must be called after {@link Updater#isUpdateAvailable()} + * + * @return A boolean indicating whether there is an update available + */ + public static boolean hasUpdate() { + return isUpdateAvailable; + } +} diff --git a/src/main/java/fr/xephi/authme/util/PlayerUtils.java b/src/main/java/fr/xephi/authme/util/PlayerUtils.java index d9b867d7..3c9b6f4f 100644 --- a/src/main/java/fr/xephi/authme/util/PlayerUtils.java +++ b/src/main/java/fr/xephi/authme/util/PlayerUtils.java @@ -10,7 +10,7 @@ public final class PlayerUtils { // Utility class private PlayerUtils() { } - private static final boolean IS_LEAVES_SERVER = Utils.isClassLoaded("top.leavesmc.leaves.LeavesConfig"); + private static final boolean isLeavesServer = Utils.isClassLoaded("top.leavesmc.leaves.LeavesConfig") || Utils.isClassLoaded("org.leavesmc.leaves.LeavesConfig"); /** * Returns the IP of the given player. @@ -29,7 +29,7 @@ public final class PlayerUtils { * @return True if the player is an NPC, false otherwise */ public static boolean isNpc(Player player) { - if (IS_LEAVES_SERVER) { + if (isLeavesServer) { return player.hasMetadata("NPC") || player.getAddress() == null; } else { return player.hasMetadata("NPC"); diff --git a/src/main/java/fr/xephi/authme/util/TeleportUtils.java b/src/main/java/fr/xephi/authme/util/TeleportUtils.java index a6594447..4a043b17 100644 --- a/src/main/java/fr/xephi/authme/util/TeleportUtils.java +++ b/src/main/java/fr/xephi/authme/util/TeleportUtils.java @@ -3,24 +3,24 @@ package fr.xephi.authme.util; import org.bukkit.Location; import org.bukkit.entity.Player; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.concurrent.CompletableFuture; /** * This class is a utility class for handling async teleportation of players in game. */ public class TeleportUtils { - private static Method teleportAsyncMethod; + private static MethodHandle teleportAsyncMethodHandle; static { - try {//Detect Paper class - Class paperClass = Class.forName("com.destroystokyo.paper.PaperConfig"); - teleportAsyncMethod = Player.class.getMethod("teleportAsync", Location.class); - teleportAsyncMethod.setAccessible(true); - // if detected,use teleportAsync() - } catch (ClassNotFoundException | NoSuchMethodException e) { - teleportAsyncMethod = null; - //if not, set method to null + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + teleportAsyncMethodHandle = lookup.findVirtual(Player.class, "teleportAsync", MethodType.methodType(CompletableFuture.class, Location.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + teleportAsyncMethodHandle = null; + // if not, set method handle to null } } @@ -31,10 +31,10 @@ public class TeleportUtils { * @param location Where should the player be teleported */ public static void teleport(Player player, Location location) { - if (teleportAsyncMethod != null) { + if (teleportAsyncMethodHandle != null) { try { - teleportAsyncMethod.invoke(player, location); - } catch (IllegalAccessException | InvocationTargetException e) { + teleportAsyncMethodHandle.invoke(player, location); + } catch (Throwable throwable) { player.teleport(location); } } else { diff --git a/src/main/java/fr/xephi/authme/util/message/MiniMessageUtils.java b/src/main/java/fr/xephi/authme/util/message/MiniMessageUtils.java new file mode 100644 index 00000000..c8f59afd --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/message/MiniMessageUtils.java @@ -0,0 +1,23 @@ +package fr.xephi.authme.util.message; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class MiniMessageUtils { + private static final MiniMessage miniMessage = MiniMessage.miniMessage(); + + /** + * Parse a MiniMessage string into a legacy string. + * + * @param message The message to parse. + * @return The parsed message. + */ + public static String parseMiniMessageToLegacy(String message) { + Component component = miniMessage.deserialize(message); + return LegacyComponentSerializer.legacyAmpersand().serialize(component); + } + + private MiniMessageUtils() { + } +} diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index 6d9b6860..cf822763 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -1,152 +1,181 @@ +# List of global tags: +# %nl% - Goes to new line. +# %username% - Replaces the username of the player receiving the message. +# %displayname% - Replaces the nickname (and colors) of the player receiving the message. + # Registration registration: - disabled: '&cРегистрация отключена.' - name_taken: '&cИгрок с таким никнеймом уже зарегистрирован.' - register_request: '&3Регистрация: /reg <пароль> <повтор пароля>' - command_usage: '&cИспользование: /reg <пароль> <повтор пароля>' - reg_only: '&4Вход только для зарегистрированных! Посетите http://сайт_сервера.ru для регистрации.' - success: '&2Вы успешно зарегистрировались!' - kicked_admin_registered: 'Администратор зарегистрировал вас. Авторизуйтесь снова.' + register_request: '&3Регистрация: /reg <пароль> <повтор пароля>' + command_usage: '&cИспользование: /reg <пароль> <повтор пароля>' + reg_only: '&4Вход только для зарегистрированных! Посетите http://сайт_сервера.ru для регистрации.' + kicked_admin_registered: 'Администратор зарегистрировал вас. Авторизуйтесь снова.' + success: '&2Вы успешно зарегистрировались!' + disabled: '&cРегистрация отключена.' + name_taken: '&cИгрок с таким никнеймом уже зарегистрирован.' # Password errors on registration password: - match_error: '&cПароли не совпадают.' - name_in_password: '&cНельзя использовать свой никнейм в качестве пароля.' - unsafe_password: '&cТакой пароль небезопасен.' - forbidden_characters: '&4Пароль содержит запрещённые символы. Разрешённые: %valid_chars' - wrong_length: '&cПароль слишком длинный/короткий.' + match_error: '&cПароли не совпадают.' + name_in_password: '&cНельзя использовать свой никнейм в качестве пароля.' + unsafe_password: '&cТакой пароль небезопасен.' + forbidden_characters: '&4Пароль содержит запрещённые символы. Разрешённые: %valid_chars' + wrong_length: '&cПароль слишком длинный/короткий.' + pwned_password: '&cВыбранный вами пароль небезопасен. Он уже был использован %pwned_count раз! Пожалуйста, используйте надежный пароль...' # Login login: - command_usage: '&cИспользование: /login <пароль>' - wrong_password: '&cНеправильный пароль!' - success: '&2Вы успешно вошли!' - login_request: '&3Авторизация: /login <Пароль>' - timeout_error: '&4Время авторизации истекло.' + command_usage: '&cИспользование: /login <пароль>' + wrong_password: '&cНеправильный пароль!' + success: '&2Вы успешно вошли!' + login_request: '&3Авторизация: /login <Пароль>' + timeout_error: '&4Время авторизации истекло.' # Errors error: - denied_command: '&cНеобходимо авторизоваться для использования этой команды!' - denied_chat: '&cНеобходимо авторизоваться, чтобы писать в чат!' - unregistered_user: '&cИгрок с таким именем не зарегистрирован.' - not_logged_in: '&cВы ещё не вошли!' - no_permission: '&4Недостаточно прав.' - unexpected_error: '&cПроизошла ошибка. Свяжитесь с администратором.' - max_registration: '&cПревышено максимальное количество регистраций на сервере! (%reg_count/%max_acc %reg_names)' - logged_in: '&cВы уже авторизированы!' - kick_for_vip: '&3VIP-игрок зашёл на переполненный сервер.' - kick_unresolved_hostname: '&cПроизошла ошибка: неразрешенное имя узла игрока!' - tempban_max_logins: '&cВы временно заблокированы из-за большого количества неудачных попыток авторизоваться.' + unregistered_user: '&cИгрок с таким именем не зарегистрирован.' + denied_command: '&cНеобходимо авторизоваться для использования этой команды!' + denied_chat: '&cНеобходимо авторизоваться, чтобы писать в чат!' + not_logged_in: '&cВы ещё не вошли!' + tempban_max_logins: '&cВы временно заблокированы из-за большого количества неудачных попыток авторизоваться.' + max_registration: '&cПревышено максимальное количество регистраций на сервере! (%reg_count/%max_acc %reg_names)' + no_permission: '&4Недостаточно прав.' + unexpected_error: '&cПроизошла ошибка. Свяжитесь с администратором.' + kick_for_vip: '&3VIP-игрок зашёл на переполненный сервер.' + logged_in: '&cВы уже авторизированы!' + kick_unresolved_hostname: '&cПроизошла ошибка: неразрешенное имя узла игрока!' # AntiBot antibot: - kick_antibot: 'Сработала защита против ботов! Необходимо подождать перед следующим входом на сервер.' - auto_enabled: '&4[AuthMe] AntiBot-режим включился из-за большого количества входов!' - auto_disabled: '&2[AuthMe] AntiBot-режим отключился спустя %m мин.' + kick_antibot: 'Сработала защита против ботов! Необходимо подождать перед следующим входом на сервер.' + auto_enabled: '&4[AuthMe] AntiBot-режим включился из-за большого количества входов!' + auto_disabled: '&2[AuthMe] AntiBot-режим отключился спустя %m мин.' -# Unregister unregister: - success: '&cУчётная запись успешно удалена!' - command_usage: '&cИспользование: /unregister <пароль>' + success: '&cУчётная запись успешно удалена!' + command_usage: '&cИспользование: /unregister <пароль>' # Other messages misc: - account_not_activated: '&cВаша уч. запись ещё не активирована. Проверьте электронную почту!' - password_changed: '&2Ваш пароль изменён!' - logout: '&2Вы успешно вышли.' - reload: '&6Конфигурация и база данных перезагружены.' - usage_change_password: '&cИспользование: /changepassword <пароль> <новый пароль>' - accounts_owned_self: 'У вас %count уч. записей:' - accounts_owned_other: 'У игрока %name %count уч. записей:' + accounts_owned_self: 'У вас %count уч. записей:' + accounts_owned_other: 'У игрока %name %count уч. записей:' + account_not_activated: '&cВаша уч. запись ещё не активирована. Проверьте электронную почту!' + password_changed: '&2Ваш пароль изменён!' + reload: '&6Конфигурация и база данных перезагружены.' + usage_change_password: '&cИспользование: /changepassword <пароль> <новый пароль>' # Session messages session: - valid_session: '&2Вы автоматически авторизовались!' - invalid_session: '&cСессия некорректна. Дождитесь, пока она закончится.' + invalid_session: '&cСессия некорректна. Дождитесь, пока она закончится.' + valid_session: '&2Вы автоматически авторизовались!' # Error messages when joining on_join_validation: - same_ip_online: 'Игрок с данным IP-адресом уже играет на сервере!' - same_nick_online: '&4Игрок с данным никнеймом уже играет на сервере!' - name_length: '&4Ваш никнейм слишком длинный/короткий.' - characters_in_name: '&4Ваш никнейм содержит запрещённые символы. Разрешённые: %valid_chars' - kick_full_server: '&4Сервер полон. Попробуйте зайти позже!' - country_banned: '&4Вход с IP-адресов вашей страны запрещён на этом сервере.' - not_owner_error: 'Вы не являетесь владельцем данной уч. записи. Выберите себе другой никнейм!' - invalid_name_case: 'Неверный никнейм! Зайдите под никнеймом %valid, а не %invalid.' - quick_command: 'Вы вводили команды слишком часто! Пожалуйста, переподключитесь и вводите команды медленнее.' + name_length: '&4Ваш никнейм слишком длинный/короткий.' + characters_in_name: '&4Ваш никнейм содержит запрещённые символы. Разрешённые: %valid_chars' + country_banned: '&4Вход с IP-адресов вашей страны запрещён на этом сервере.' + not_owner_error: 'Вы не являетесь владельцем данной уч. записи. Выберите себе другой никнейм!' + kick_full_server: '&4Сервер полон. Попробуйте зайти позже!' + same_nick_online: '&4Игрок с данным никнеймом уже играет на сервере!' + invalid_name_case: 'Неверный никнейм! Зайдите под никнеймом %valid, а не %invalid.' + same_ip_online: 'Игрок с данным IP-адресом уже играет на сервере!' + quick_command: 'Вы вводили команды слишком часто! Пожалуйста, переподключитесь и вводите команды медленнее.' # Email email: - add_email_request: '&3Добавьте электронную почту: /email add <эл. почта> <повтор эл. почты>' - usage_email_add: '&cИспользование: /email add <эл. почта> <повтор эл. почты>' - usage_email_change: '&cИспользование: /email change <эл. почта> <новая эл. почта>' - new_email_invalid: '&cНедействительная новая электронная почта!' - old_email_invalid: '&cНедействительная старая электронная почта!' - invalid: '&cНедействительный адрес электронной почты!' - added: '&2Электронная почта успешно добавлена!' - add_not_allowed: '&cДобавление электронной почты не было разрешено.' - request_confirmation: '&cПодтвердите свою электронную почту!' - changed: '&2Адрес электронной почты изменён!' - change_not_allowed: '&cИзменение электронной почты не было разрешено.' - email_show: '&2Текущий адрес электронной почты — &f%email' - no_email_for_account: '&2К вашей уч. записи не привязана электронная почта.' - already_used: '&4Эта электронная почта уже используется.' - incomplete_settings: 'Ошибка: не все необходимые параметры установлены для отправки электронной почты. Свяжитесь с администратором.' - send_failure: 'Письмо не может быть отправлено. Свяжитесь в администратором.' - change_password_expired: 'Больше нельзя сменить свой пароль, используя эту команду.' - email_cooldown_error: '&cПисьмо было отправлено недавно. Подождите %time, прежде чем отправить новое.' + usage_email_add: '&cИспользование: /email add <эл. почта> <повтор эл. почты>' + usage_email_change: '&cИспользование: /email change <эл. почта> <новая эл. почта>' + new_email_invalid: '&cНедействительная новая электронная почта!' + old_email_invalid: '&cНедействительная старая электронная почта!' + invalid: '&cНедействительный адрес электронной почты!' + added: '&2Электронная почта успешно добавлена!' + request_confirmation: '&cПодтвердите свою электронную почту!' + changed: '&2Адрес электронной почты изменён!' + email_show: '&2Текущий адрес электронной почты — &f%email' + incomplete_settings: 'Ошибка: не все необходимые параметры установлены для отправки электронной почты. Свяжитесь с администратором.' + already_used: '&4Эта электронная почта уже используется.' + send_failure: 'Письмо не может быть отправлено. Свяжитесь в администратором.' + no_email_for_account: '&2К вашей уч. записи не привязана электронная почта.' + add_email_request: '&3Добавьте электронную почту: /email add <эл. почта> <повтор эл. почты>' + change_password_expired: 'Больше нельзя сменить свой пароль, используя эту команду.' + email_cooldown_error: '&cПисьмо было отправлено недавно. Подождите %time, прежде чем отправить новое.' + add_not_allowed: '&cДобавление электронной почты не было разрешено.' + change_not_allowed: '&cИзменение электронной почты не было разрешено.' # Password recovery by email recovery: - forgot_password_hint: '&Забыли пароль? Используйте «/email recovery <эл. почта>».' - command_usage: '&cИспользование: /email recovery <эл. почта>' - email_sent: '&2Письмо с инструкциями для восстановления было отправлено на вашу электронную почту!' - code: - code_sent: 'Код восстановления для сброса пароля был отправлен на электронную почту.' - incorrect: 'Неверный код восстановления! Попыток осталось: %count.' - tries_exceeded: 'Вы слишком много раз неверно ввели код восстановления. Используйте «/email recovery [эл. почта]», чтобы получить новый код.' - correct: 'Код восстановления введён верно!' - change_password: 'Используйте «/email setpassword <новый пароль>», чтобы сменить свой пароль.' + forgot_password_hint: '&Забыли пароль? Используйте «/email recovery <эл. почта>».' + command_usage: '&cИспользование: /email recovery <эл. почта>' + email_sent: '&2Письмо с инструкциями для восстановления было отправлено на вашу электронную почту!' + code: + code_sent: 'Код восстановления для сброса пароля был отправлен на электронную почту.' + incorrect: 'Неверный код восстановления! Попыток осталось: %count.' + tries_exceeded: 'Вы слишком много раз неверно ввели код восстановления. Используйте «/email recovery [эл. почта]», чтобы получить новый код.' + correct: 'Код восстановления введён верно!' + change_password: 'Используйте «/email setpassword <новый пароль>», чтобы сменить свой пароль.' # Captcha captcha: - usage_captcha: '&3Необходимо ввести текст с каптчи. Используйте «/captcha %captcha_code»' - wrong_captcha: '&cНеверно! Используйте «/captcha %captcha_code».' - valid_captcha: '&2Вы успешно решили каптчу!' - captcha_for_registration: 'Чтобы зарегистрироваться, решите каптчу используя команду: «/captcha %captcha_code»' - register_captcha_valid: '&2Вы успешно решили каптчу! Теперь вы можете зарегистрироваться командой «/register»' + usage_captcha: '&3Необходимо ввести текст с каптчи. Используйте «/captcha %captcha_code»' + wrong_captcha: '&cНеверно! Используйте «/captcha %captcha_code».' + valid_captcha: '&2Вы успешно решили каптчу!' + captcha_for_registration: 'Чтобы зарегистрироваться, решите каптчу используя команду: «/captcha %captcha_code»' + register_captcha_valid: '&2Вы успешно решили каптчу! Теперь вы можете зарегистрироваться командой «/register»' # Verification code verification: - code_required: '&3Эта команда чувствительна и требует подтверждения электронной почты! Проверьте свою почту и следуйте инструкциям в письме.' - command_usage: '&cИспользование: /verification <код>' - incorrect_code: '&cНеверный код, используйте «/verification <код>», подставив код из полученного письма.' - success: '&2Ваша личность подтверждена! Теперь можно выполнять все чувствительные команды в текущем сеансе!' - already_verified: '&2Вы уже можете выполнять все чувствительные команды в текущем сеансе!' - code_expired: '&3Срок действия кода истёк! Выполните чувствительную команду, чтобы получить новый код!' - email_needed: '&3Чтобы подтвердить вашу личность, необходимо привязать электронную почту к учётной записи!!' - -# Time units -time: - second: 'с.' - seconds: 'с.' - minute: 'мин.' - minutes: 'мин.' - hour: 'ч.' - hours: 'ч.' - day: 'дн.' - days: 'дн.' + code_required: '&3Эта команда чувствительна и требует подтверждения электронной почты! Проверьте свою почту и следуйте инструкциям в письме.' + command_usage: '&cИспользование: /verification <код>' + incorrect_code: '&cНеверный код, используйте «/verification <код>», подставив код из полученного письма.' + success: '&2Ваша личность подтверждена! Теперь можно выполнять все чувствительные команды в текущем сеансе!' + already_verified: '&2Вы уже можете выполнять все чувствительные команды в текущем сеансе!' + code_expired: '&3Срок действия кода истёк! Выполните чувствительную команду, чтобы получить новый код!' + email_needed: '&3Чтобы подтвердить вашу личность, необходимо привязать электронную почту к учётной записи!!' # Two-factor authentication two_factor: - code_created: '&2Ваш секретный код — %code. Просканируйте его здесь: %url' - confirmation_required: 'Пожалуйста, подтвердите ваш код с помощью /2fa confirm <код>' - code_required: 'Пожалуйста, введите ваш код двухфакторной аутентификации используя команду /2fa code <код>' - already_enabled: 'Двухфакторная аутентификация уже активирована для вашего аккаунта!' - enable_error_no_code: 'Код двухфакторной аутентификации не был сгенерирован или истек. Пожалуйста, введите /2fa add' - enable_success: 'Двухфакторная аутентификация для вашего аккаунта успешно подключена' - enable_error_wrong_code: 'Срок действия кода истек или код неверный. Введите /2fa add' - not_enabled_error: 'Двухфакторная аутентификация не включена для вашего аккаунта. Введите /2fa add' - removed_success: 'Двухфакторная аутентификация успешно удалена с вашего аккаунта!' - invalid_code: 'Неверный код!' + code_created: '&2Ваш секретный код — %code. Просканируйте его здесь: %url' + confirmation_required: 'Пожалуйста, подтвердите ваш код с помощью /2fa confirm <код>' + code_required: 'Пожалуйста, введите ваш код двухфакторной аутентификации используя команду /2fa code <код>' + already_enabled: 'Двухфакторная аутентификация уже активирована для вашего аккаунта!' + enable_error_no_code: 'Код двухфакторной аутентификации не был сгенерирован или истек. Пожалуйста, введите /2fa add' + enable_success: 'Двухфакторная аутентификация для вашего аккаунта успешно подключена' + enable_error_wrong_code: 'Срок действия кода истек или код неверный. Введите /2fa add' + not_enabled_error: 'Двухфакторная аутентификация не включена для вашего аккаунта. Введите /2fa add' + removed_success: 'Двухфакторная аутентификация успешно удалена с вашего аккаунта!' + invalid_code: 'Неверный код!' + +# Time units +time: + second: 'с.' + seconds: 'с.' + minute: 'мин.' + minutes: 'мин.' + hour: 'ч.' + hours: 'ч.' + day: 'дн.' + days: 'дн.' + +# 3rd party features: GUI Captcha +gui_captcha: + success: '&aВы успешно прошли проверку на робота!' + bedrock_auto_verify_success: '&aВы обошли проверку на робота потому что вы играете с Bedrock!' + captcha_window_name: '%random Верификация' + captcha_clickable_name: '%random Я человек' + message_on_retry: '&cНеверно! %times попыток осталось' + denied_message_sending: '&cНеобходимо верифицироваться, чтобы писать в чат!' + kick_on_failed: '&cВы провалили верификацию!' + kick_on_timeout: '&cВремя верификации истекло!' + +# 3rd party features: Bedrock Auto Login +bedrock_auto_login: + success: '&aВы автоматически авторизовались потому что вы играете с Bedrock!' + +# 3rd party features: Login Location Fix +login_location_fix: + fix_portal: '&aВы застряли в портале во время авторизации.' + fix_underground: '&aВы застряли под землей во время авторизации.' + cannot_fix_underground: '&aВы застряли под землей во время авторизации, но мы не можем это исправить.' + +# 3rd party features: Double Login Fix +double_login_fix: + fix_message: '&cВы были отключены из-за двойного входа.'