diff --git a/api/src/main/java/org/geysermc/floodgate/api/link/PlayerLink.java b/api/src/main/java/org/geysermc/floodgate/api/link/PlayerLink.java index 95403ee3..757b086a 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/link/PlayerLink.java +++ b/api/src/main/java/org/geysermc/floodgate/api/link/PlayerLink.java @@ -118,6 +118,12 @@ public interface PlayerLink { @NonNull String code ); + /** + * Returns the name of this database implementation. This will return null when Player Linking + * is disabled or when only Global Linking is used. + */ + String getName(); + /** * Return if account linking is enabled. The difference between enabled and allowed is that * 'enabled' still allows already linked people to join with their linked account while 'allow diff --git a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java index 2dc31d41..20b373af 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java +++ b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java @@ -28,6 +28,7 @@ package org.geysermc.floodgate.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -40,8 +41,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; import org.geysermc.floodgate.util.BungeeUserAudience.BungeeConsoleAudience; @@ -135,17 +136,40 @@ public final class BungeeCommandUtil implements CommandUtil { } @Override - public void sendMessage(Object target, String locale, CommandMessage message, Object... args) { + public boolean hasPermission(Object player, String permission) { + return cast(player).hasPermission(permission); + } + + @Override + public Collection getOnlinePlayersWithPermission(String permission) { + List players = new ArrayList<>(); + for (ProxiedPlayer player : ProxyServer.getInstance().getPlayers()) { + if (hasPermission(player, permission)) { + players.add(player); + } + } + return players; + } + + @Override + public void sendMessage(Object target, String locale, TranslatableMessage message, Object... args) { ((CommandSender) target).sendMessage(translateAndTransform(locale, message, args)); } @Override - public void kickPlayer(Object player, String locale, CommandMessage message, Object... args) { + public void sendMessage(Object target, String message) { + ((CommandSender) target).sendMessage(message); + } + + @Override + public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { cast(player).disconnect(translateAndTransform(locale, message, args)); } - public BaseComponent[] translateAndTransform(String locale, CommandMessage message, - Object... args) { + public BaseComponent[] translateAndTransform( + String locale, + TranslatableMessage message, + Object... args) { return TextComponent.fromLegacyText(message.translateMessage(manager, locale, args)); } diff --git a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeUserAudience.java b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeUserAudience.java index 90488740..e8f9e005 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeUserAudience.java +++ b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeUserAudience.java @@ -37,8 +37,8 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.connection.ProxiedPlayer; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; @RequiredArgsConstructor @@ -74,13 +74,15 @@ public class BungeeUserAudience implements UserAudience, ForwardingAudience.Sing } @Override - public void sendMessage(@NonNull Identity source, @NonNull Component message, - @NonNull MessageType type) { + public void sendMessage( + @NonNull Identity source, + @NonNull Component message, + @NonNull MessageType type) { this.source.sendMessage(GsonComponentSerializer.gson().serialize(message)); } @Override - public void sendMessage(CommandMessage message, Object... args) { + public void sendMessage(TranslatableMessage message, Object... args) { commandUtil.sendMessage(source(), locale(), message, args); } @@ -92,7 +94,7 @@ public class BungeeUserAudience implements UserAudience, ForwardingAudience.Sing } @Override - public void disconnect(CommandMessage message, Object... args) { + public void disconnect(TranslatableMessage message, Object... args) { commandUtil.kickPlayer(source(), locale(), message, args); } diff --git a/common/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java b/common/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java index 61fd0b0b..051c37dd 100644 --- a/common/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java +++ b/common/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java @@ -46,6 +46,7 @@ import org.geysermc.floodgate.config.loader.ConfigLoader; import org.geysermc.floodgate.link.PlayerLinkLoader; import org.geysermc.floodgate.module.ConfigLoadedModule; import org.geysermc.floodgate.module.PostInitializeModule; +import org.geysermc.floodgate.news.NewsChecker; import org.geysermc.floodgate.util.PrefixCheckTask; import org.geysermc.floodgate.util.TimeSyncerHolder; @@ -123,6 +124,7 @@ public class FloodgatePlatform { PrefixCheckTask.checkAndExecuteDelayed(config, logger); + guice.getInstance(NewsChecker.class).start(); return true; } diff --git a/common/src/main/java/org/geysermc/floodgate/addon/debug/State.java b/common/src/main/java/org/geysermc/floodgate/addon/debug/State.java index 5df0639f..e40fd243 100644 --- a/common/src/main/java/org/geysermc/floodgate/addon/debug/State.java +++ b/common/src/main/java/org/geysermc/floodgate/addon/debug/State.java @@ -28,7 +28,7 @@ package org.geysermc.floodgate.addon.debug; public enum State { HANDSHAKE, STATUS, LOGIN, PLAY; - public static final State[] VALUES = values(); + private static final State[] VALUES = values(); public static State getById(int id) { return id < VALUES.length ? VALUES[id] : null; diff --git a/common/src/main/java/org/geysermc/floodgate/command/CommonCommandMessage.java b/common/src/main/java/org/geysermc/floodgate/command/CommonCommandMessage.java index 213dae4f..31d97d1a 100644 --- a/common/src/main/java/org/geysermc/floodgate/command/CommonCommandMessage.java +++ b/common/src/main/java/org/geysermc/floodgate/command/CommonCommandMessage.java @@ -26,14 +26,14 @@ package org.geysermc.floodgate.command; import lombok.Getter; -import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.TranslatableMessage; /** * Messages (or part of messages) that are used in two or more commands and thus are 'commonly * used' */ @Getter -public enum CommonCommandMessage implements CommandMessage { +public enum CommonCommandMessage implements TranslatableMessage { LINKING_DISABLED("floodgate.commands.linking_disabled"), NOT_A_PLAYER("floodgate.commands.not_a_player"), CHECK_CONSOLE("floodgate.commands.check_console"), diff --git a/common/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java b/common/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java index bf1ba5e5..6fdab4e3 100644 --- a/common/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java +++ b/common/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java @@ -42,12 +42,13 @@ import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.link.GlobalPlayerLinking; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.FloodgateCommand; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience.PlayerAudience; import org.geysermc.floodgate.player.UserAudienceArgument; import org.geysermc.floodgate.util.Constants; +import org.geysermc.floodgate.util.Permissions; @NoArgsConstructor public final class LinkAccountCommand implements FloodgateCommand { @@ -59,7 +60,7 @@ public final class LinkAccountCommand implements FloodgateCommand { return commandManager.commandBuilder("linkaccount", ArgumentDescription.of("Link your Java account with your Bedrock account")) .senderType(PlayerAudience.class) - .permission("floodgate.command.linkaccount") + .permission(Permissions.COMMAND_LINK.get()) .argument(UserAudienceArgument.of("player", true)) .argument(StringArgument.optional("code")) .handler(this::execute) @@ -165,7 +166,7 @@ public final class LinkAccountCommand implements FloodgateCommand { } @Getter - public enum Message implements CommandMessage { + public enum Message implements TranslatableMessage { ALREADY_LINKED("floodgate.command.link_account.already_linked"), JAVA_USAGE("floodgate.command.link_account.java_usage"), LINK_REQUEST_CREATED("floodgate.command.link_account.link_request_created"), diff --git a/common/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java b/common/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java index 70c0bcf8..bdad5786 100644 --- a/common/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java +++ b/common/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java @@ -38,11 +38,12 @@ import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.link.GlobalPlayerLinking; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.FloodgateCommand; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience.PlayerAudience; import org.geysermc.floodgate.util.Constants; +import org.geysermc.floodgate.util.Permissions; @NoArgsConstructor public final class UnlinkAccountCommand implements FloodgateCommand { @@ -53,7 +54,7 @@ public final class UnlinkAccountCommand implements FloodgateCommand { return commandManager.commandBuilder("unlinkaccount", ArgumentDescription.of("Unlink your Java account from your Bedrock account")) .senderType(PlayerAudience.class) - .permission("floodgate.command.unlinkaccount") + .permission(Permissions.COMMAND_UNLINK.get()) .handler(this::execute) .build(); } @@ -113,7 +114,7 @@ public final class UnlinkAccountCommand implements FloodgateCommand { } @Getter - public enum Message implements CommandMessage { + public enum Message implements TranslatableMessage { NOT_LINKED("floodgate.command.unlink_account.not_linked"), UNLINK_SUCCESS("floodgate.command.unlink_account.unlink_success"), UNLINK_ERROR("floodgate.command.unlink_account.error " + CHECK_CONSOLE); diff --git a/common/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java b/common/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java index 4b9b2122..02678380 100644 --- a/common/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java +++ b/common/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java @@ -37,13 +37,14 @@ import lombok.Getter; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.command.FloodgateCommand; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudienceArgument; import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.HttpUtils; +import org.geysermc.floodgate.util.Permissions; public class WhitelistCommand implements FloodgateCommand { @Inject private FloodgateConfig config; @@ -53,7 +54,7 @@ public class WhitelistCommand implements FloodgateCommand { public Command buildCommand(CommandManager commandManager) { Command.Builder builder = commandManager.commandBuilder("fwhitelist", ArgumentDescription.of("Easy way to whitelist Bedrock players")) - .permission("floodgate.command.fwhitelist"); + .permission(Permissions.COMMAND_WHITELIST.get()); commandManager.command(builder .literal("add", "a") @@ -153,7 +154,7 @@ public class WhitelistCommand implements FloodgateCommand { } @Getter - public enum Message implements CommandMessage { + public enum Message implements TranslatableMessage { INVALID_USERNAME("floodgate.command.fwhitelist.invalid_username"), API_UNAVAILABLE("floodgate.command.fwhitelist.api_unavailable " + CHECK_CONSOLE), USER_NOT_FOUND("floodgate.command.fwhitelist.user_not_found"), diff --git a/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java b/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java index 556629f7..594d366c 100644 --- a/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java +++ b/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java @@ -26,6 +26,8 @@ package org.geysermc.floodgate.link; import com.google.inject.Inject; +import com.google.inject.Key; +import com.google.inject.name.Names; import java.util.Random; import java.util.UUID; import java.util.concurrent.ExecutorService; @@ -91,6 +93,11 @@ public abstract class CommonPlayerLink implements PlayerLink { return injectorHolder.get().getInstance(DatabaseConfigLoader.class).loadAs(configClass); } + @Override + public String getName() { + return injectorHolder.get().getInstance(Key.get(String.class, Names.named("databaseName"))); + } + @Override public void stop() { executorService.shutdown(); diff --git a/common/src/main/java/org/geysermc/floodgate/link/DisabledPlayerLink.java b/common/src/main/java/org/geysermc/floodgate/link/DisabledPlayerLink.java index c6ffa14a..2ace6caf 100644 --- a/common/src/main/java/org/geysermc/floodgate/link/DisabledPlayerLink.java +++ b/common/src/main/java/org/geysermc/floodgate/link/DisabledPlayerLink.java @@ -90,6 +90,11 @@ final class DisabledPlayerLink implements PlayerLink { return failedFuture(); } + @Override + public String getName() { + return null; + } + @Override public boolean isEnabled() { return false; diff --git a/common/src/main/java/org/geysermc/floodgate/link/GlobalPlayerLinking.java b/common/src/main/java/org/geysermc/floodgate/link/GlobalPlayerLinking.java index 7cd3a2db..5aadb171 100644 --- a/common/src/main/java/org/geysermc/floodgate/link/GlobalPlayerLinking.java +++ b/common/src/main/java/org/geysermc/floodgate/link/GlobalPlayerLinking.java @@ -33,7 +33,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.api.link.LinkRequestResult; import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.util.HttpUtils; -import org.geysermc.floodgate.util.HttpUtils.HttpResponse; +import org.geysermc.floodgate.util.HttpUtils.DefaultHttpResponse; import org.geysermc.floodgate.util.LinkedPlayer; import org.geysermc.floodgate.util.Utils; @@ -55,6 +55,15 @@ public class GlobalPlayerLinking extends CommonPlayerLink { } } + @Override + public String getName() { + if (databaseImpl != null) { + return databaseImpl.getName(); + } + // Global Linking is integrated + return null; + } + @Override public void stop() { super.stop(); @@ -82,7 +91,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink { private CompletableFuture getLinkedPlayer0(@NonNull UUID bedrockId) { return CompletableFuture.supplyAsync( () -> { - HttpResponse response = + DefaultHttpResponse response = HttpUtils.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits()); // both on code != 200 and fails with 200 'success' will be false @@ -128,7 +137,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink { private CompletableFuture isLinkedPlayer0(@NonNull UUID bedrockId) { return CompletableFuture.supplyAsync( () -> { - HttpResponse response = + DefaultHttpResponse response = HttpUtils.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits()); // both on http != 200 and fails with 200 success will be false diff --git a/common/src/main/java/org/geysermc/floodgate/module/CommonModule.java b/common/src/main/java/org/geysermc/floodgate/module/CommonModule.java index a4e6fae2..344cbe1b 100644 --- a/common/src/main/java/org/geysermc/floodgate/module/CommonModule.java +++ b/common/src/main/java/org/geysermc/floodgate/module/CommonModule.java @@ -34,9 +34,11 @@ import java.nio.file.Path; import lombok.RequiredArgsConstructor; import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl; import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.InstanceHolder; import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.inject.PlatformInjector; +import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.packet.PacketHandlers; import org.geysermc.floodgate.api.player.FloodgatePlayer; @@ -52,7 +54,9 @@ import org.geysermc.floodgate.crypto.Base64Topping; import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.crypto.KeyProducer; import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.news.NewsChecker; import org.geysermc.floodgate.packet.PacketHandlersImpl; +import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.skin.SkinApplier; @@ -169,6 +173,15 @@ public class CommonModule extends AbstractModule { return new SkinUploadManager(api, skinApplier, logger); } + @Provides + @Singleton + public NewsChecker newsChecker(CommandUtil commandUtil, FloodgateLogger logger) { + // will be loaded after enabling, so we can use the link instance in InstanceHolder + PlayerLink link = InstanceHolder.getPlayerLink(); + logger.info(link.getName()); + return new NewsChecker(link, commandUtil, logger, null, -1); + } + @Provides @Singleton @Named("kickMessageAttribute") diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsChecker.java b/common/src/main/java/org/geysermc/floodgate/news/NewsChecker.java new file mode 100644 index 00000000..ceb61c35 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsChecker.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.geysermc.floodgate.api.link.PlayerLink; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.news.data.BuildSpecificData; +import org.geysermc.floodgate.news.data.CheckAfterData; +import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.util.Constants; +import org.geysermc.floodgate.util.HttpUtils; +import org.geysermc.floodgate.util.HttpUtils.HttpResponse; +import org.geysermc.floodgate.util.Permissions; + +public class NewsChecker { + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); + private final PlayerLink link; + private final CommandUtil commandUtil; + private final FloodgateLogger logger; + + private final Map activeNewsItems = new HashMap<>(); + private final String branch; + private final int build; + + private boolean firstCheck; + + public NewsChecker( + PlayerLink link, + CommandUtil commandUtil, + FloodgateLogger logger, + String branch, + int build) { + this.link = link; + this.commandUtil = commandUtil; + this.logger = logger; + this.branch = branch; + this.build = build; + } + + public void start() { + executorService.scheduleWithFixedDelay(this::checkNews, 0, 30, TimeUnit.MINUTES); + } + + private void schedule(long delayMs) { + executorService.schedule(this::checkNews, delayMs, TimeUnit.MILLISECONDS); + } + + private void checkNews() { + HttpResponse response = + HttpUtils.getSilent(Constants.NEWS_OVERVIEW_URL, JsonArray.class); + JsonArray array = response.getResponse(); + + // silent mode doesn't throw exceptions, so array will be null on failure + if (array == null) { + return; + } + + try { + for (JsonElement newsItemElement : array) { + NewsItem newsItem = NewsItem.readItem(newsItemElement.getAsJsonObject()); + if (newsItem != null) { + addNews(newsItem); + } + } + firstCheck = false; + } catch (Exception e) { + if (logger.isDebug()) { + logger.error("Error while reading news item", e); + } + } + } + + public void handleNews(Object player, NewsItemAction action) { + for (NewsItem news : getActiveNews(action)) { + handleNewsItem(player, news, action); + } + } + + private void handleNewsItem(Object player, NewsItem news, NewsItemAction action) { + switch (action) { + case ON_SERVER_STARTED: + if (!firstCheck) { + return; + } + case BROADCAST_TO_CONSOLE: + logger.info(news.getMessage()); + break; + case ON_OPERATOR_JOIN: + if (player == null) { + return; + } + + if (commandUtil.hasPermission(player, Permissions.NEWS_RECEIVE.get())) { + String message = Constants.COLOR_CHAR + "a " + news.getMessage(); + commandUtil.sendMessage(player, message); + } + break; + case BROADCAST_TO_OPERATORS: + Collection onlinePlayers = commandUtil.getOnlinePlayersWithPermission( + Permissions.NEWS_RECEIVE.get() + ); + + for (Object onlinePlayer : onlinePlayers) { + String message = Constants.COLOR_CHAR + "a " + news.getMessage(); + commandUtil.sendMessage(onlinePlayer, message); + } + break; + } + } + + public Collection getActiveNews() { + return activeNewsItems.values(); + } + + public Collection getActiveNews(NewsItemAction action) { + List news = new ArrayList<>(); + for (NewsItem item : getActiveNews()) { + if (item.getActions().contains(action)) { + news.add(item); + } + } + return news; + } + + public void addNews(NewsItem item) { + if (activeNewsItems.containsKey(item.getId())) { + if (!item.isActive()) { + activeNewsItems.remove(item.getId()); + } + return; + } + + if (!item.isActive()) { + return; + } + + if (!item.isGlobal() && !Constants.NEWS_PROJECT_NAME.equals(item.getProject())) { + // only non-integrated database types have a name + if (link.getName() == null) { + return; + } + + String fullDatabaseName = Constants.NEWS_PROJECT_NAME + link.getName(); + if (!fullDatabaseName.equals(item.getProject())) { + return; + } + } + + switch (item.getType()) { + case BUILD_SPECIFIC: + if (!item.getDataAs(BuildSpecificData.class).isAffected(branch, build)) { + return; + } + break; + case CHECK_AFTER: + long checkAfter = item.getDataAs(CheckAfterData.class).getCheckAfter(); + long delayMs = System.currentTimeMillis() - checkAfter; + schedule(delayMs > 0 ? delayMs : 0); + break; + } + + activeNewsItems.put(item.getId(), item); + activateNews(item); + } + + private void activateNews(NewsItem item) { + for (NewsItemAction action : item.getActions()) { + handleNewsItem(null, item, action); + } + } + + public void shutdown() { + executorService.shutdown(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/platform/command/CommandUtil.java b/common/src/main/java/org/geysermc/floodgate/platform/command/CommandUtil.java index 4161f6f4..e8ef6922 100644 --- a/common/src/main/java/org/geysermc/floodgate/platform/command/CommandUtil.java +++ b/common/src/main/java/org/geysermc/floodgate/platform/command/CommandUtil.java @@ -50,25 +50,51 @@ public interface CommandUtil { @NonNull Collection getOnlineUsernames(final @NonNull PlayerType limitTo); /** - * Send a message to the specified target, no matter what platform Floodgate is running on. + * Checks if the given player has the given permission. * - * @param target the target to send the message to - * @param message the command message - * @param locale the locale of the player - * @param args the arguments + * @param player the player to check + * @param permission the permission to check + * @return true or false depending on if the player has the permission */ - void sendMessage(Object target, String locale, CommandMessage message, Object... args); + boolean hasPermission(Object player, String permission); /** - * Same as {@link CommandUtil#sendMessage(Object, String, CommandMessage, Object...)} except it - * kicks the player. + * Get all online players with the given permission. * - * @param player the player to send the message to + * @param permission the permission to check + * @return a list of online players that have the given permission + */ + Collection getOnlinePlayersWithPermission(String permission); + + /** + * Send a message to the specified target, no matter what platform Floodgate is running on. + * + * @param target the player that should receive the message * @param message the command message * @param locale the locale of the player * @param args the arguments */ - void kickPlayer(Object player, String locale, CommandMessage message, Object... args); + void sendMessage(Object target, String locale, TranslatableMessage message, Object... args); + + /** + * Sends a raw message to the specified target, no matter what platform Floodgate is running + * on. + * + * @param target the player that should receive the message + * @param message the message + */ + void sendMessage(Object target, String message); + + /** + * Same as {@link CommandUtil#sendMessage(Object, String, TranslatableMessage, Object...)} + * except it kicks the player using the given message as the kick reason. + * + * @param player the player that should be kicked + * @param message the command message + * @param locale the locale of the player + * @param args the arguments + */ + void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args); /** * Whitelist the given Bedrock player. diff --git a/common/src/main/java/org/geysermc/floodgate/platform/command/CommandMessage.java b/common/src/main/java/org/geysermc/floodgate/platform/command/TranslatableMessage.java similarity index 91% rename from common/src/main/java/org/geysermc/floodgate/platform/command/CommandMessage.java rename to common/src/main/java/org/geysermc/floodgate/platform/command/TranslatableMessage.java index ef6a06c8..56ecc1a8 100644 --- a/common/src/main/java/org/geysermc/floodgate/platform/command/CommandMessage.java +++ b/common/src/main/java/org/geysermc/floodgate/platform/command/TranslatableMessage.java @@ -28,10 +28,10 @@ package org.geysermc.floodgate.platform.command; import org.geysermc.floodgate.util.LanguageManager; /** - * CommandMessage is the interface of a message that can be send to a command source after executing - * a command. Messages are generally implemented using enums. + * TranslatableMessage is the interface for a message that can be translated. + * Messages are generally implemented using enums. */ -public interface CommandMessage { +public interface TranslatableMessage { /** * Returns the message attached to the enum identifier */ diff --git a/common/src/main/java/org/geysermc/floodgate/platform/util/PlatformUtils.java b/common/src/main/java/org/geysermc/floodgate/platform/util/PlatformUtils.java index 7eaf12b2..7f8fdd49 100644 --- a/common/src/main/java/org/geysermc/floodgate/platform/util/PlatformUtils.java +++ b/common/src/main/java/org/geysermc/floodgate/platform/util/PlatformUtils.java @@ -26,8 +26,8 @@ package org.geysermc.floodgate.platform.util; import java.util.Collection; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.platform.command.TranslatableMessage; public interface PlatformUtils { /** @@ -38,10 +38,10 @@ public interface PlatformUtils { * @param locale the locale of the player * @param args the arguments */ - void sendMessage(Object player, String locale, CommandMessage message, Object... args); + void sendMessage(Object player, String locale, TranslatableMessage message, Object... args); /** - * Same as {@link CommandUtil#sendMessage(Object, String, CommandMessage, Object...)} except it + * Same as {@link CommandUtil#sendMessage(Object, String, TranslatableMessage, Object...)} except it * kicks the player. * * @param player the player to send the message to @@ -49,7 +49,7 @@ public interface PlatformUtils { * @param locale the locale of the player * @param args the arguments */ - void kickPlayer(Object player, String locale, CommandMessage message, Object... args); + void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args); Collection getOnlineUsernames(PlayerType limitTo); diff --git a/common/src/main/java/org/geysermc/floodgate/player/UserAudience.java b/common/src/main/java/org/geysermc/floodgate/player/UserAudience.java index 3ca9c208..04fc3e74 100644 --- a/common/src/main/java/org/geysermc/floodgate/player/UserAudience.java +++ b/common/src/main/java/org/geysermc/floodgate/player/UserAudience.java @@ -32,7 +32,7 @@ import net.kyori.adventure.identity.Identified; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.TranslatableMessage; public interface UserAudience extends Identified, Identity, Audience { @Override @@ -51,11 +51,11 @@ public interface UserAudience extends Identified, Identity, Audience { final @NonNull Component message, final @NonNull MessageType type); - void sendMessage(CommandMessage message, Object... args); + void sendMessage(TranslatableMessage message, Object... args); void disconnect(@NonNull final Component reason); - void disconnect(CommandMessage message, Object... args); + void disconnect(TranslatableMessage message, Object... args); @Override default @NonNull Identity identity() { diff --git a/common/src/main/java/org/geysermc/floodgate/util/Constants.java b/common/src/main/java/org/geysermc/floodgate/util/Constants.java index e81ab7d8..3b4dd5da 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/Constants.java +++ b/common/src/main/java/org/geysermc/floodgate/util/Constants.java @@ -26,6 +26,8 @@ package org.geysermc.floodgate.util; public final class Constants { + public static final char COLOR_CHAR = 'ยง'; + public static final boolean DEBUG_MODE = true; public static final boolean PRINT_ALL_PACKETS = false; @@ -36,9 +38,12 @@ public final class Constants { public static final String WEBSOCKET_URL = "ws" + API_BASE_URL + "/ws"; public static final String GET_XUID_URL = "http" + API_BASE_URL + "/v1/xbox/xuid/"; public static final String GET_GAMERTAG_URL = "http" + API_BASE_URL + "/v1/xbox/gamertag/"; + public static final String NEWS_OVERVIEW_URL = "http" + API_BASE_URL + "/v1/news"; public static final String LINK_INFO_URL = "https://link.geysermc.org/"; + public static final String NEWS_PROJECT_NAME = "floodgate"; + public static final String NTP_SERVER = "time.cloudflare.com"; public static final String TIMESTAMP_DENIED_MESSAGE = diff --git a/common/src/main/java/org/geysermc/floodgate/util/HttpUtils.java b/common/src/main/java/org/geysermc/floodgate/util/HttpUtils.java index 9e4c5760..99980542 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/HttpUtils.java +++ b/common/src/main/java/org/geysermc/floodgate/util/HttpUtils.java @@ -48,13 +48,21 @@ public class HttpUtils { private static final String BOUNDARY = "******"; private static final String END = "\r\n"; - public static CompletableFuture asyncGet(String urlString) { + public static CompletableFuture asyncGet(String urlString) { return CompletableFuture.supplyAsync(() -> { return get(urlString); }, EXECUTOR_SERVICE); } - public static HttpResponse get(String urlString) { + public static DefaultHttpResponse get(String urlString) { + return readDefaultResponse(request(urlString)); + } + + public static HttpResponse getSilent(String urlString, Class clazz) { + return readResponseSilent(request(urlString), clazz); + } + + private static HttpURLConnection request(String urlString) { HttpURLConnection connection; try { @@ -68,34 +76,37 @@ public class HttpUtils { connection.setRequestMethod("GET"); connection.setUseCaches(false); connection.setRequestProperty("User-Agent", USER_AGENT); - connection.setRequestProperty("ContentType", "application/json"); } catch (Exception exception) { throw new RuntimeException("Failed to create request", exception); } - return readResponse(connection); + return connection; } - private static HttpResponse readResponse(HttpURLConnection connection) { - InputStream stream = null; - try { - stream = connection.getInputStream(); - } catch (Exception exception) { - try { - stream = connection.getErrorStream(); - } catch (Exception exception1) { - throw new RuntimeException("Both the input and the error stream failed?!"); - } - } - - InputStreamReader streamReader = new InputStreamReader(stream); + private static HttpResponse readResponseSilent(HttpURLConnection connection, Class clazz) { + InputStreamReader streamReader = createReader(connection); try { int responseCode = connection.getResponseCode(); + T response = GSON.fromJson(streamReader, clazz); + return new HttpResponse<>(responseCode, response); + } catch (Exception ignored) { + return new HttpResponse<>(-1, null); + } finally { + try { + streamReader.close(); + } catch (Exception ignored) { + } + } + } + private static DefaultHttpResponse readDefaultResponse(HttpURLConnection connection) { + InputStreamReader streamReader = createReader(connection); + + try { + int responseCode = connection.getResponseCode(); JsonObject response = GSON.fromJson(streamReader, JsonObject.class); - - return new HttpResponse(responseCode, response); + return new DefaultHttpResponse(responseCode, response); } catch (Exception exception) { throw new RuntimeException("Failed to read response", exception); } finally { @@ -106,10 +117,30 @@ public class HttpUtils { } } + private static InputStreamReader createReader(HttpURLConnection connection) { + InputStream stream = null; + try { + stream = connection.getInputStream(); + } catch (Exception exception) { + try { + stream = connection.getErrorStream(); + } catch (Exception exception1) { + throw new RuntimeException("Both the input and the error stream failed?!"); + } + } + return new InputStreamReader(stream); + } + @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) - public static final class HttpResponse { + public static class HttpResponse { private final int httpCode; - private final JsonObject response; + private final T response; + } + + public static final class DefaultHttpResponse extends HttpResponse { + private DefaultHttpResponse(int httpCode, JsonObject response) { + super(httpCode, response); + } } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/Permissions.java b/common/src/main/java/org/geysermc/floodgate/util/Permissions.java new file mode 100644 index 00000000..f7550d27 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/Permissions.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.util; + +public enum Permissions { + COMMAND_LINK("floodgate.command.linkaccount"), + COMMAND_UNLINK("floodgate.command.unlinkaccount"), + COMMAND_WHITELIST("floodgate.command.fwhitelist"), + + NEWS_RECEIVE("floodgate.news.receive"); + + private final String permission; + + Permissions(String permission) { + this.permission = permission; + } + + public String get() { + return permission; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/PrefixCheckTask.java b/common/src/main/java/org/geysermc/floodgate/util/PrefixCheckTask.java index 1601c454..70490dd8 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/PrefixCheckTask.java +++ b/common/src/main/java/org/geysermc/floodgate/util/PrefixCheckTask.java @@ -46,7 +46,7 @@ public final class PrefixCheckTask { "**********************************\n" + "* You specified an empty prefix in your Floodgate config for Bedrock players!\n" + "* Should a Java player join and a Bedrock player join with the same username, unwanted results and conflicts will happen!\n" + - "* We strongly recommend using . as the prefix, but other alternatives that will not conflict include: *, - and +\n" + + "* We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *\n" + "**********************************"); return; } @@ -54,9 +54,9 @@ public final class PrefixCheckTask { logger.warn( "\n" + "**********************************\n" + - "The prefix you entered in your Floodgate config ({}) could lead to username conflicts!\n" + - "Should a Java player join with the username {}Notch, and a Bedrock player join as Notch (who will be given the name {}Notch), unwanted results will happen!\n" + - "We strongly recommend using . as the prefix, but other alternatives that will not conflict include: *, - and +\n" + + "* The prefix you entered in your Floodgate config ({}) could lead to username conflicts!\n" + + "* Should a Java player join with the username {}Notch, and a Bedrock player join as Notch (who will be given the name {}Notch), unwanted results will happen!\n" + + "* We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *\n" + "**********************************", config.getUsernamePrefix(), config.getUsernamePrefix(), config.getUsernamePrefix(), config.getUsernamePrefix()); diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java index 4349b462..35cef933 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java @@ -28,6 +28,7 @@ package org.geysermc.floodgate.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -40,8 +41,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; import org.geysermc.floodgate.util.SpigotUserAudience.SpigotConsoleAudience; @@ -136,12 +137,33 @@ public final class SpigotCommandUtil implements CommandUtil { } @Override - public void sendMessage(Object target, String locale, CommandMessage message, Object... args) { - ((CommandSender) target).sendMessage(translateAndTransform(locale, message, args)); + public boolean hasPermission(Object player, String permission) { + return cast(player).hasPermission(permission); } @Override - public void kickPlayer(Object player, String locale, CommandMessage message, Object... args) { + public Collection getOnlinePlayersWithPermission(String permission) { + List players = new ArrayList<>(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (hasPermission(player, permission)) { + players.add(player); + } + } + return players; + } + + @Override + public void sendMessage(Object target, String locale, TranslatableMessage message, Object... args) { + sendMessage(target, translateAndTransform(locale, message, args)); + } + + @Override + public void sendMessage(Object target, String message) { + ((CommandSender) target).sendMessage(message); + } + + @Override + public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { // Have to run this in the main thread so we don't get a `Asynchronous player kick!` error Bukkit.getScheduler().runTask(plugin, () -> cast(player).kickPlayer(translateAndTransform(locale, message, args))); @@ -157,7 +179,7 @@ public final class SpigotCommandUtil implements CommandUtil { return WhitelistUtils.removePlayer(xuid, username); } - public String translateAndTransform(String locale, CommandMessage message, Object... args) { + public String translateAndTransform(String locale, TranslatableMessage message, Object... args) { // unlike others, Bukkit doesn't have to transform a message into another class. return message.translateMessage(manager, locale, args); } diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotUserAudience.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotUserAudience.java index 986a3b00..ebb6a2a5 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotUserAudience.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotUserAudience.java @@ -37,8 +37,8 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; @RequiredArgsConstructor @@ -82,7 +82,7 @@ public class SpigotUserAudience implements UserAudience, ForwardingAudience.Sing } @Override - public void sendMessage(CommandMessage message, Object... args) { + public void sendMessage(TranslatableMessage message, Object... args) { commandUtil.sendMessage(source(), locale(), message, args); } @@ -94,7 +94,7 @@ public class SpigotUserAudience implements UserAudience, ForwardingAudience.Sing } @Override - public void disconnect(CommandMessage message, Object... args) { + public void disconnect(TranslatableMessage message, Object... args) { commandUtil.kickPlayer(source(), locale(), message, args); } diff --git a/velocity/src/main/java/org/geysermc/floodgate/player/VelocityUserAudience.java b/velocity/src/main/java/org/geysermc/floodgate/player/VelocityUserAudience.java index 14c914ec..4e0b4313 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/player/VelocityUserAudience.java +++ b/velocity/src/main/java/org/geysermc/floodgate/player/VelocityUserAudience.java @@ -35,8 +35,8 @@ import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.platform.command.TranslatableMessage; @RequiredArgsConstructor public abstract class VelocityUserAudience implements UserAudience, ForwardingAudience.Single { @@ -78,7 +78,7 @@ public abstract class VelocityUserAudience implements UserAudience, ForwardingAu } @Override - public void sendMessage(CommandMessage message, Object... args) { + public void sendMessage(TranslatableMessage message, Object... args) { commandUtil.sendMessage(source(), locale(), message, args); } @@ -90,7 +90,7 @@ public abstract class VelocityUserAudience implements UserAudience, ForwardingAu } @Override - public void disconnect(CommandMessage message, Object... args) { + public void disconnect(TranslatableMessage message, Object... args) { commandUtil.kickPlayer(source(), locale(), message, args); } diff --git a/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java b/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java index 65e6cd6f..b7e521bd 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java +++ b/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java @@ -32,6 +32,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import net.kyori.adventure.text.Component; @@ -39,8 +40,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; import org.geysermc.floodgate.player.VelocityUserAudience.VelocityConsoleAudience; @@ -134,17 +135,40 @@ public final class VelocityCommandUtil implements CommandUtil { } @Override - public void sendMessage(Object target, String locale, CommandMessage message, Object... args) { + public boolean hasPermission(Object player, String permission) { + return cast(player).hasPermission(permission); + } + + @Override + public Collection getOnlinePlayersWithPermission(String permission) { + List players = new ArrayList<>(); + for (Player player : server.getAllPlayers()) { + if (hasPermission(player, permission)) { + players.add(player); + } + } + return players; + } + + @Override + public void sendMessage(Object target, String locale, TranslatableMessage message, Object... args) { ((CommandSource) target).sendMessage(translateAndTransform(locale, message, args)); } @Override - public void kickPlayer(Object player, String locale, CommandMessage message, Object... args) { + public void sendMessage(Object target, String message) { + ((CommandSource) target).sendMessage(Component.text(message)); + } + + @Override + public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { cast(player).disconnect(translateAndTransform(locale, message, args)); } - public Component translateAndTransform(String locale, CommandMessage message, - Object... args) { + public Component translateAndTransform( + String locale, + TranslatableMessage message, + Object... args) { return Component.text(message.translateMessage(manager, locale, args)); }