1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

First part of news

This commit is contained in:
Tim203
2021-06-08 14:48:30 +02:00
parent 0773c7d37b
commit a65d4e821d
26 changed files with 517 additions and 86 deletions

View File

@@ -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 <b>only</b> 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

View File

@@ -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,16 +136,39 @@ 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<Object> getOnlinePlayersWithPermission(String permission) {
List<Object> 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,
public BaseComponent[] translateAndTransform(
String locale,
TranslatableMessage message,
Object... args) {
return TextComponent.fromLegacyText(message.translateMessage(manager, locale, args));
}

View File

@@ -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,
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);
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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);

View File

@@ -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<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
Command.Builder<UserAudience> 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"),

View File

@@ -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();

View File

@@ -90,6 +90,11 @@ final class DisabledPlayerLink implements PlayerLink {
return failedFuture();
}
@Override
public String getName() {
return null;
}
@Override
public boolean isEnabled() {
return false;

View File

@@ -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<LinkedPlayer> 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<Boolean> 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

View File

@@ -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")

View File

@@ -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<Integer, NewsItem> 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<JsonArray> 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<Object> 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<NewsItem> getActiveNews() {
return activeNewsItems.values();
}
public Collection<NewsItem> getActiveNews(NewsItemAction action) {
List<NewsItem> 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();
}
}

View File

@@ -50,25 +50,51 @@ public interface CommandUtil {
@NonNull Collection<String> 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<Object> 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.

View File

@@ -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
*/

View File

@@ -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<String> getOnlineUsernames(PlayerType limitTo);

View File

@@ -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() {

View File

@@ -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 =

View File

@@ -48,13 +48,21 @@ public class HttpUtils {
private static final String BOUNDARY = "******";
private static final String END = "\r\n";
public static CompletableFuture<HttpResponse> asyncGet(String urlString) {
public static CompletableFuture<DefaultHttpResponse> 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 <T> HttpResponse<T> getSilent(String urlString, Class<T> 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 <T> HttpResponse<T> readResponseSilent(HttpURLConnection connection, Class<T> 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<T> {
private final int httpCode;
private final JsonObject response;
private final T response;
}
public static final class DefaultHttpResponse extends HttpResponse<JsonObject> {
private DefaultHttpResponse(int httpCode, JsonObject response) {
super(httpCode, response);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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());

View File

@@ -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<Object> getOnlinePlayersWithPermission(String permission) {
List<Object> 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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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,16 +135,39 @@ 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<Object> getOnlinePlayersWithPermission(String permission) {
List<Object> 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,
public Component translateAndTransform(
String locale,
TranslatableMessage message,
Object... args) {
return Component.text(message.translateMessage(manager, locale, args));
}