diff --git a/README.md b/README.md index 9df742775..f0d6fea6b 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ Free software and high quality, LibertyBans is the best-designed punishment plug Supported platforms: -* Spigot / Paper, including Folia -* BungeeCord +* Spigot / Paper (+Folia) +* BungeeCord / Waterfall * Sponge * Velocity @@ -98,3 +98,5 @@ The developer API is extensive. LibertyBans does not recommend developers mess w ### License LibertyBans is licensed under the GNU AGPL v3. See the license file for more information. + +[![GNU AGPL Logo](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html) diff --git a/bans-core/src/main/java/space/arim/libertybans/core/PillarOneBindModuleMinusConfigs.java b/bans-core/src/main/java/space/arim/libertybans/core/PillarOneBindModuleMinusConfigs.java index cb4ad9264..4bac77b71 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/PillarOneBindModuleMinusConfigs.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/PillarOneBindModuleMinusConfigs.java @@ -55,7 +55,7 @@ public GlobalEnforcement enforcement(StandardGlobalEnforcement enforcement) { return enforcement; } - public LocalEnforcer enforcer(StandardLocalEnforcer enforcer) { + public LocalEnforcer enforcer(StandardLocalEnforcer enforcer) { return enforcer; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java index 576632415..791138837 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java @@ -193,16 +193,20 @@ interface Sponge { @ConfHeader("Related to game servers such as Spigot, Paper, and Sponge") interface GameServers { - @ConfKey("kick-via-plugin-messaging") + @ConfKey("use-plugin-messaging") @ConfComments({ "This option is relevant for backend servers running within a network (BungeeCord or Velocity).", - "It instructs the proxy to kick the player from the network via plugin messaging.", + "It enables the use of plugin messaging, such as for:", + " - Kicking the player from the entire network", + " - Detecting the name of the backend server for use with server scopes", + " - Synchronizing punishments across instances, depending on the mode in the sql.yml", "", - "If enabled, the player will NOT be kicked by the backend server, so you MUST use a proxy", - "otherwise players will not be kicked at all." + "DO NOT enable this option if you do not run a network. Otherwise, you create a security vulnerability", + "whereby players can pretend to be coming from a proxy, evading kicks and sending sync messages.", + "After changing this option, please perform a restart (/libertybans restart)." }) @DefaultBoolean(false) - boolean kickViaPluginMessaging(); + boolean usePluginMessaging(); } @@ -212,6 +216,16 @@ interface GameServers { @ConfHeader("Related to proxies such as BungeeCord and Velocity") interface Proxies { + @ConfKey("multiple-proxy-instances") + @ConfComments({ + "Set this to true to indicate that you are running multiple proxy instances.", + "", + "It will instruct LibertyBans to perform additional synchronization measures, where applicable." + }) + @DefaultBoolean(false) + // Currently unused, but may be utilized later + boolean multipleProxyInstances(); + @ConfKey("enforce-server-switch") @ConfComments({ "Server-scoped punishments will be enforced by preventing server switches for players connecting ", diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java index a140ac971..e7119bc53 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java @@ -80,12 +80,15 @@ interface ServerName { @ConfKey("auto-detect") @ConfComments({ "By default, we try to detect the name of this backend server using plugin messaging.", - "On a proxy, the detected name becomes 'proxy'.", + "Make sure 'use-plugin-messaging' is enabled in the config.yml for this detection to succeed.", + "", + "If running a proxy, the detected name becomes 'proxy'.", "", "Plugin messaging requires at least one player to have logged into the backend server.", "Auto detection may fail, for example, if you ban someone through the console but no one has joined yet.", "", - "To disable auto detection, set this to false then configure 'override-value' to the server name you wish to use." + "To disable auto detection, set this to false then configure 'override-value' to the server name you wish to use.", + "Re-enabling this option may require a restart (/libertybans restart)" }) @ConfDefault.DefaultBoolean(true) boolean autoDetect(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java index fe4f31b4b..c503fed8e 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java @@ -22,8 +22,6 @@ import org.jooq.Condition; import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.scope.InternalScopeManager; -import space.arim.libertybans.core.scope.ScopeParsing; -import space.arim.libertybans.core.scope.ScopeType; import static org.jooq.impl.DSL.noCondition; @@ -32,7 +30,7 @@ public record ScopeCondition(ScopeFields scopeFields, InternalScopeManager scope @Override public Condition matchesValue(ServerScope scope) { - return new ScopeParsing().deconstruct(scope, (type, value) -> { + return scopeManager.deconstruct(scope, (type, value) -> { Condition typeMatches = scopeFields.scopeType().eq(type); Condition valueMatches = switch (type) { case GLOBAL -> noCondition(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java index 1e06e6b68..4b08306a6 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java @@ -21,11 +21,17 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import space.arim.api.env.AudienceRepresenter; import space.arim.libertybans.core.config.InternalFormatter; +import space.arim.libertybans.core.env.message.PluginMessage; +import space.arim.omnibus.util.ThisClass; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -36,6 +42,8 @@ public abstract class AbstractEnvEnforcer

implements EnvEnforcer

{ private final Interlocutor interlocutor; private final AudienceRepresenter audienceRepresenter; + private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); + protected AbstractEnvEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, Interlocutor interlocutor, AudienceRepresenter audienceRepresenter) { this.futuresFactory = Objects.requireNonNull(futuresFactory, "futuresFactory"); @@ -83,10 +91,30 @@ private CentralisedFuture sendToThoseWithPermissionNoPrefix(String permiss } }; } - return doForAllPlayers(callback); + return doForAllPlayers((players) -> players.forEach(callback)); + } + + @Override + public final void sendPluginMessage(P player, PluginMessage pluginMessage, D data) { + if (!sendPluginMessageIfListening(player, pluginMessage, data)) { + logger.error( + "Attempted to send plugin message to {}, but the appropriate channel is not accepted. " + + "This suggests you enabled use-plugin-messaging in the config.yml, but the player " + + "is not connected to a network. Please address this critical security flaw immediately. " + + "It leaves your server vulnerable to clients spoofing the plugin messaging channel", + player + ); + } } - protected abstract CentralisedFuture doForAllPlayers(Consumer

callback); + /** + * Sends a plugin message to the given player if it is accepted by their client + * + * @param player the player to whom to send the message + * @param pluginMessage the plugin message + * @return true if sent, false if unsupported + */ + public abstract boolean sendPluginMessageIfListening(P player, PluginMessage pluginMessage, D data); @Override public final void sendMessageNoPrefix(P player, ComponentLike message) { @@ -95,10 +123,16 @@ public final void sendMessageNoPrefix(P player, ComponentLike message) { @Override public final CentralisedFuture enforceMatcher(TargetMatcher

matcher) { - return doForAllPlayers((player) -> { - if (matcher.matches(getUniqueIdFor(player), getAddressFor(player))) { - matcher.callback().accept(player); + return doForAllPlayers((players) -> { + List

matchedPlayers = new ArrayList<>(); + // Some platforms do not provide guarantees about concurrent iteration in presence of kicks + // Proxies effectively must, but game server APIs like Bukkit and Sponge need not + for (P player : players) { + if (matcher.matches(getUniqueIdFor(player), getAddressFor(player))) { + matchedPlayers.add(player); + } } + matchedPlayers.forEach(matcher.callback()); }); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java index c899b1bba..eabae3a9f 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java @@ -20,6 +20,7 @@ package space.arim.libertybans.core.env; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -27,6 +28,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; import space.arim.api.env.annote.PlatformPlayer; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; /** @@ -45,6 +47,7 @@ public interface EnvEnforcer<@PlatformPlayer P> { * * @param permission the permission * @param message the message + * @return a future completed when the operation is done */ CentralisedFuture sendToThoseWithPermission(String permission, ComponentLike message); @@ -53,9 +56,18 @@ public interface EnvEnforcer<@PlatformPlayer P> { * * @param uuid the uuid * @param callback the callback + * @return a future completed when the operation is done */ CentralisedFuture doForPlayerIfOnline(UUID uuid, Consumer

callback); + /** + * Completes an action for all players online + * + * @param action the action + * @return a future completed when the operation is done + */ + CentralisedFuture doForAllPlayers(Consumer> action); + /** * Kicks the given player.
*
@@ -66,6 +78,14 @@ public interface EnvEnforcer<@PlatformPlayer P> { */ void kickPlayer(P player, Component message); + /** + * Sends a plugin message to the given player. Must be used only for proxies. + * + * @param player the player to whom to send the message + * @param pluginMessage the plugin message + */ + void sendPluginMessage(P player, PluginMessage pluginMessage, D data); + /** * Sends a message to the given player. Does not include a prefix.
*
@@ -103,6 +123,16 @@ public interface EnvEnforcer<@PlatformPlayer P> { */ InetAddress getAddressFor(P player); + /** + * Gets the name of a player.
+ *
+ * Must be used within a callback. + * + * @param player the player + * @return the name + */ + String getNameFor(P player); + /** * Determines whether the player has a permission * diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvMessageChannel.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvMessageChannel.java new file mode 100644 index 000000000..b0375dada --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvMessageChannel.java @@ -0,0 +1,74 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.env; + +import space.arim.libertybans.core.env.message.PluginMessage; + +import java.util.function.Consumer; + +public interface EnvMessageChannel { + + /** + * Installs a platform specific handler + * + * @param handler the handler + */ + void installHandler(H handler); + + /** + * Uninstalls a platform specific handler + * + * @param handler the handler + */ + void uninstallHandler(H handler); + + /** + * Wraps an acceptor as a platform specific handler. Should be called once. + * + * @param acceptor the acceptor + * @param pluginMessage the plugin message it handles + * @return the handler + * @param the type of the handler + */ + H createHandler(Consumer acceptor, PluginMessage pluginMessage); + + static void parameterize(EnvMessageChannel messageChannel, Consumer> action) { + action.accept(messageChannel); + } + + final class NoOp implements EnvMessageChannel { + + @Override + public void installHandler(Void handler) { + + } + + @Override + public void uninstallHandler(Void handler) { + + } + + @Override + public Void createHandler(Consumer acceptor, PluginMessage pluginMessage) { + return null; + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/InstanceType.java b/bans-core/src/main/java/space/arim/libertybans/core/env/InstanceType.java new file mode 100644 index 000000000..3f541d830 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/InstanceType.java @@ -0,0 +1,26 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.env; + +public enum InstanceType { + GAME_SERVER, + PROXY, + STANDALONE +} diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessageData.java b/bans-core/src/main/java/space/arim/libertybans/core/env/PluginMessageAsBytes.java similarity index 91% rename from bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessageData.java rename to bans-core/src/main/java/space/arim/libertybans/core/env/PluginMessageAsBytes.java index ed61d78fa..390ad6e82 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessageData.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/PluginMessageAsBytes.java @@ -17,7 +17,7 @@ * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.env.spigot; +package space.arim.libertybans.core.env; import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.libertybans.core.env.message.PluginMessageInput; @@ -33,9 +33,9 @@ import java.io.UncheckedIOException; import java.util.Optional; -record PluginMessageData(PluginMessage pluginMessage) { +public record PluginMessageAsBytes(PluginMessage pluginMessage) { - byte[] generateBytes(D data) { + public byte[] generateBytes(D data) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); DataOutputStream dataOutput = new DataOutputStream(outputStream)) { @@ -47,7 +47,7 @@ byte[] generateBytes(D data) { } } - Optional readBytes(byte[] data) { + public Optional readBytes(byte[] data) { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data); DataInputStream dataInput = new DataInputStream(inputStream)) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/StandardLocalEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/StandardLocalEnforcer.java index 1f81e6a56..a8413fc01 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/StandardLocalEnforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/StandardLocalEnforcer.java @@ -25,6 +25,7 @@ import net.kyori.adventure.text.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import space.arim.api.env.annote.PlatformPlayer; import space.arim.api.jsonchat.adventure.util.ComponentText; import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.CompositeVictim; @@ -47,8 +48,10 @@ import space.arim.libertybans.core.env.AdditionalUUIDTargetMatcher; import space.arim.libertybans.core.env.EnvEnforcer; import space.arim.libertybans.core.env.ExactTargetMatcher; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.core.env.TargetMatcher; import space.arim.libertybans.core.env.UUIDTargetMatcher; +import space.arim.libertybans.core.env.message.KickPlayer; import space.arim.libertybans.core.punish.permission.PunishmentPermission; import space.arim.libertybans.core.selector.cache.MuteCache; import space.arim.omnibus.util.ThisClass; @@ -63,22 +66,24 @@ import static space.arim.libertybans.core.schema.tables.StrictLinks.STRICT_LINKS; @Singleton -public final class StandardLocalEnforcer implements LocalEnforcer { +public final class StandardLocalEnforcer<@PlatformPlayer P> implements LocalEnforcer { + private final InstanceType instanceType; private final Configs configs; private final FactoryOfTheFuture futuresFactory; private final Provider queryExecutor; private final PunishmentSelector selector; private final InternalFormatter formatter; + private final EnvEnforcer

envEnforcer; private final MuteCache muteCache; - private final EnvEnforcer envEnforcer; private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); @Inject - public StandardLocalEnforcer(Configs configs, FactoryOfTheFuture futuresFactory, + public StandardLocalEnforcer(InstanceType instanceType, Configs configs, FactoryOfTheFuture futuresFactory, Provider queryExecutor, PunishmentSelector selector, - InternalFormatter formatter, EnvEnforcer envEnforcer, MuteCache muteCache) { + InternalFormatter formatter, EnvEnforcer

envEnforcer, MuteCache muteCache) { + this.instanceType = instanceType; this.configs = configs; this.futuresFactory = futuresFactory; this.queryExecutor = queryExecutor; @@ -95,7 +100,7 @@ public CentralisedFuture enforceWithoutSynchronization(Punishment punishme PunishmentAdditionSection section = configs.getMessagesConfig().additions().forType(punishment.getType()); - var arrestsAndNotices = new Parameterized<>(envEnforcer).enforceArrestsAndNotices(punishment); + var arrestsAndNotices = enforceArrestsAndNotices(punishment); if (enforcementOptions.broadcasting() == Broadcasting.NONE) { return arrestsAndNotices; } @@ -196,123 +201,120 @@ public CentralisedFuture updateDetailsWithoutSynchronization(long id) { if (optPunishment.isEmpty()) { // Possible race condition if punishment is expunged logger.debug("Tried to update details of non-existent punishment with id {}", id); - return futuresFactory.completedFuture(null); + return completedFuture(null); } return updateDetailsWithoutSynchronization(optPunishment.get()); }).toCompletableFuture(); } - // Enforcement of enacted punishments + private CentralisedFuture enforceArrestsAndNotices(Punishment punishment) { - private class Parameterized

{ + return formatter.getPunishmentMessage(punishment).thenCompose((message) -> { - private final EnvEnforcer

envEnforcer; + Victim victim = punishment.getVictim(); + Consumer

enforcementCallback = enforcementCallback(punishment, message); + AddressStrictness strictness = configs.getMainConfig().enforcement().addressStrictness(); - Parameterized(EnvEnforcer

envEnforcer) { - this.envEnforcer = envEnforcer; - } - - CentralisedFuture enforceArrestsAndNotices(Punishment punishment) { - - return formatter.getPunishmentMessage(punishment).thenCompose((message) -> { - - Victim victim = punishment.getVictim(); - Consumer

enforcementCallback = enforcementCallback(punishment, message); - AddressStrictness strictness = configs.getMainConfig().enforcement().addressStrictness(); + if (victim instanceof PlayerVictim playerVictim) { + UUID uuid = playerVictim.getUUID(); + if (strictness == AddressStrictness.STRICT) { + return matchUserPunishmentStrict(uuid, enforcementCallback) + .thenCompose(envEnforcer::enforceMatcher); + } + return envEnforcer.doForPlayerIfOnline(uuid, enforcementCallback); - if (victim instanceof PlayerVictim playerVictim) { - UUID uuid = playerVictim.getUUID(); - if (strictness == AddressStrictness.STRICT) { - return matchUserPunishmentStrict(uuid, enforcementCallback) - .thenCompose(envEnforcer::enforceMatcher); - } - return envEnforcer.doForPlayerIfOnline(uuid, enforcementCallback); + } else if (victim instanceof AddressVictim addressVictim) { + NetworkAddress address = addressVictim.getAddress(); + return matchAddressPunishment(strictness, enforcementCallback, address) + .thenCompose(envEnforcer::enforceMatcher); - } else if (victim instanceof AddressVictim addressVictim) { - NetworkAddress address = addressVictim.getAddress(); - return matchAddressPunishment(strictness, enforcementCallback, address) - .thenCompose(envEnforcer::enforceMatcher); + } else if (victim instanceof CompositeVictim compositeVictim) { + UUID uuid = compositeVictim.getUUID(); + NetworkAddress address = compositeVictim.getAddress(); + return matchAddressPunishment(strictness, enforcementCallback, address) + .thenApply((addressMatcher) -> new AdditionalUUIDTargetMatcher<>(uuid, addressMatcher)) + .thenCompose(envEnforcer::enforceMatcher); - } else if (victim instanceof CompositeVictim compositeVictim) { - UUID uuid = compositeVictim.getUUID(); - NetworkAddress address = compositeVictim.getAddress(); - return matchAddressPunishment(strictness, enforcementCallback, address) - .thenApply((addressMatcher) -> new AdditionalUUIDTargetMatcher<>(uuid, addressMatcher)) - .thenCompose(envEnforcer::enforceMatcher); + } else { + throw MiscUtil.unknownVictimType(victim.getType()); + } + }); + } + private Consumer

enforcementCallback(Punishment punishment, Component message) { + return switch (punishment.getType()) { + case BAN, KICK -> (player) -> { + if (instanceType == InstanceType.GAME_SERVER + && configs.getMainConfig().platforms().gameServers().usePluginMessaging()) { + envEnforcer.sendPluginMessage( + player, new KickPlayer(), new KickPlayer.Data(envEnforcer.getNameFor(player), message) + ); } else { - throw MiscUtil.unknownVictimType(victim.getType()); + envEnforcer.kickPlayer(player, message); } - }); - } - - private Consumer

enforcementCallback(Punishment punishment, Component message) { - return switch (punishment.getType()) { - case BAN, KICK -> (player) -> envEnforcer.kickPlayer(player, message); - case MUTE -> (player) -> { - /* - * Mute enforcement must additionally take into account the mute cache - */ - UUID uuid = envEnforcer.getUniqueIdFor(player); - NetworkAddress address = NetworkAddress.of(envEnforcer.getAddressFor(player)); - muteCache.setCachedMute(uuid, address, punishment); - - envEnforcer.sendMessageNoPrefix(player, message); - }; - case WARN -> (player) -> envEnforcer.sendMessageNoPrefix(player, message); }; - } - - private CentralisedFuture> matchAddressPunishment( - AddressStrictness strictness, Consumer

enforcementCallback, NetworkAddress address) { - return switch (strictness) { - case LENIENT -> completedFuture(new ExactTargetMatcher<>(address, enforcementCallback)); - case NORMAL -> matchAddressPunishmentNormal(address, enforcementCallback); - case STERN, STRICT -> matchAddressPunishmentSternOrStrict(address, enforcementCallback); + case MUTE -> (player) -> { + /* + * Mute enforcement must additionally take into account the mute cache + */ + UUID uuid = envEnforcer.getUniqueIdFor(player); + NetworkAddress address = NetworkAddress.of(envEnforcer.getAddressFor(player)); + muteCache.setCachedMute(uuid, address, punishment); + + envEnforcer.sendMessageNoPrefix(player, message); }; - } + case WARN -> (player) -> envEnforcer.sendMessageNoPrefix(player, message); + }; + } - private CentralisedFuture> matchAddressPunishmentNormal( - NetworkAddress address, Consumer

enforcementCallback) { - return queryExecutor.get().query(SQLFunction.readOnly((context) -> { - return context - .select(ADDRESSES.UUID) - .from(ADDRESSES) - .where(ADDRESSES.ADDRESS.eq(address)) - .fetchSet(ADDRESSES.UUID); - })).thenApply((uuids) -> { - return new UUIDTargetMatcher<>(uuids, enforcementCallback); - }); - } + private CentralisedFuture> matchAddressPunishment( + AddressStrictness strictness, Consumer

enforcementCallback, NetworkAddress address) { + return switch (strictness) { + case LENIENT -> completedFuture(new ExactTargetMatcher<>(address, enforcementCallback)); + case NORMAL -> matchAddressPunishmentNormal(address, enforcementCallback); + case STERN, STRICT -> matchAddressPunishmentSternOrStrict(address, enforcementCallback); + }; + } - private CentralisedFuture> matchAddressPunishmentSternOrStrict( - NetworkAddress address, Consumer

enforcementCallback) { - return queryExecutor.get().query(SQLFunction.readOnly((context) -> { - return context - .select(STRICT_LINKS.UUID2) - .from(STRICT_LINKS) - .innerJoin(ADDRESSES) - .on(STRICT_LINKS.UUID1.eq(ADDRESSES.UUID)) - .where(ADDRESSES.ADDRESS.eq(address)) - .fetchSet(STRICT_LINKS.UUID2); - })).thenApply((uuids) -> { - return new UUIDTargetMatcher<>(uuids, enforcementCallback); - }); - } + private CentralisedFuture> matchAddressPunishmentNormal( + NetworkAddress address, Consumer

enforcementCallback) { + return queryExecutor.get().query(SQLFunction.readOnly((context) -> { + return context + .select(ADDRESSES.UUID) + .from(ADDRESSES) + .where(ADDRESSES.ADDRESS.eq(address)) + .fetchSet(ADDRESSES.UUID); + })).thenApply((uuids) -> { + return new UUIDTargetMatcher<>(uuids, enforcementCallback); + }); + } - private CentralisedFuture> matchUserPunishmentStrict( - UUID uuid, Consumer

enforcementCallback) { - return queryExecutor.get().query(SQLFunction.readOnly((context) -> { - return context - .select(STRICT_LINKS.UUID2) - .from(STRICT_LINKS) - .where(STRICT_LINKS.UUID1.eq(uuid)) - .fetchSet(STRICT_LINKS.UUID2); - })).thenApply((uuids) -> { - return new UUIDTargetMatcher<>(uuids, enforcementCallback); - }); - } + private CentralisedFuture> matchAddressPunishmentSternOrStrict( + NetworkAddress address, Consumer

enforcementCallback) { + return queryExecutor.get().query(SQLFunction.readOnly((context) -> { + return context + .select(STRICT_LINKS.UUID2) + .from(STRICT_LINKS) + .innerJoin(ADDRESSES) + .on(STRICT_LINKS.UUID1.eq(ADDRESSES.UUID)) + .where(ADDRESSES.ADDRESS.eq(address)) + .fetchSet(STRICT_LINKS.UUID2); + })).thenApply((uuids) -> { + return new UUIDTargetMatcher<>(uuids, enforcementCallback); + }); + } + private CentralisedFuture> matchUserPunishmentStrict( + UUID uuid, Consumer

enforcementCallback) { + return queryExecutor.get().query(SQLFunction.readOnly((context) -> { + return context + .select(STRICT_LINKS.UUID2) + .from(STRICT_LINKS) + .where(STRICT_LINKS.UUID1.eq(uuid)) + .fetchSet(STRICT_LINKS.UUID2); + })).thenApply((uuids) -> { + return new UUIDTargetMatcher<>(uuids, enforcementCallback); + }); } private CentralisedFuture completedFuture(T value) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/InternalScopeManager.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/InternalScopeManager.java index f7342cb85..b54f76eaf 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/scope/InternalScopeManager.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/InternalScopeManager.java @@ -50,7 +50,7 @@ public interface InternalScopeManager extends ScopeManager { * * @return whether to detect the server name */ - boolean shouldDetectServerName(); + boolean serverNameUndetected(); /** * Sets the automatically detected name of this server instance diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ServerNameListenerBase.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ServerNameListenerBase.java new file mode 100644 index 000000000..f7ae3bb6a --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/ServerNameListenerBase.java @@ -0,0 +1,86 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.core.scope; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import space.arim.api.env.annote.PlatformPlayer; +import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.InstanceType; +import space.arim.libertybans.core.env.PlatformListener; +import space.arim.libertybans.core.env.message.GetServer; + +import java.util.function.Consumer; + +@Singleton +public final class ServerNameListenerBase<@PlatformPlayer P, H> implements PlatformListener { + + private final Configs configs; + private final InternalScopeManager scopeManager; + private final EnvEnforcer

envEnforcer; + private final EnvMessageChannel envMessageChannel; + + private final H handler; + + @Inject + public ServerNameListenerBase(InstanceType instanceType, Configs configs, InternalScopeManager scopeManager, + EnvEnforcer

envEnforcer, EnvMessageChannel envMessageChannel) { + if (instanceType != InstanceType.GAME_SERVER) { + throw new IllegalStateException("Cannot use server name listener except for backend game servers"); + } + this.configs = configs; + this.scopeManager = scopeManager; + this.envEnforcer = envEnforcer; + this.envMessageChannel = envMessageChannel; + handler = envMessageChannel.createHandler(new ResponseHandler(), new GetServer()); + } + + @Override + public void register() { + if (configs.getMainConfig().platforms().gameServers().usePluginMessaging()) { + envMessageChannel.installHandler(handler); + } + } + + @Override + public void unregister() { + // This should not throw even if the handler is not registered + envMessageChannel.uninstallHandler(handler); + } + + public void onJoin(P player) { + if (configs.getMainConfig().platforms().gameServers().usePluginMessaging() + && configs.getScopeConfig().serverName().autoDetect() + && scopeManager.serverNameUndetected()) { + envEnforcer.sendPluginMessage(player, new GetServer(), null); + } + } + + final class ResponseHandler implements Consumer { + + @Override + public void accept(GetServer.Response response) { + scopeManager.detectServerName(response.server()); + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java index fbdd8192c..42e1957d8 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java @@ -149,8 +149,8 @@ public ServerScope checkScope(ServerScope scope) { } @Override - public boolean shouldDetectServerName() { - return serverName == null && config().serverName().autoDetect(); + public boolean serverNameUndetected() { + return serverName == null; } @Override diff --git a/bans-core/src/test/java/space/arim/libertybans/core/PillarOneReplacementModule.java b/bans-core/src/test/java/space/arim/libertybans/core/PillarOneReplacementModule.java index be6af5d12..658f7283e 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/PillarOneReplacementModule.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/PillarOneReplacementModule.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,6 +16,7 @@ * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ + package space.arim.libertybans.core; import jakarta.inject.Singleton; @@ -34,7 +35,7 @@ public Configs configs(SpecifiedConfigs configs) { @Singleton public SettableTime time(ConfigSpec configSpec) { - return new SettableTimeImpl(configSpec.unixTime()); + return new SettableTimeImpl(configSpec.unixTimestamp()); } public Time time(SettableTime time) { diff --git a/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTime.java b/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTime.java index 8b1c68352..e8e471478 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTime.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTime.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -37,4 +37,11 @@ public interface SettableTime extends Time { * @param progression the progression */ void advanceBy(Duration progression); + + /** + * Sets the time back to the original test timestamp + * + */ + void reset(); + } diff --git a/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTimeImpl.java b/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTimeImpl.java index 010118069..a2c7d42d1 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTimeImpl.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/service/SettableTimeImpl.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -25,10 +25,12 @@ public final class SettableTimeImpl implements SettableTime { + private final Instant original; /** Milliseconds from the epoch */ private final AtomicLong timestamp; public SettableTimeImpl(Instant timestamp) { + original = timestamp; this.timestamp = new AtomicLong(timestamp.toEpochMilli()); } @@ -51,4 +53,10 @@ public long currentTime() { public long arbitraryNanoTime() { return timestamp.get() * 1_000_000L; // nanoseconds } + + @Override + public void reset() { + setTimestamp(original); + } + } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpec.java b/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpec.java index 2d7eb01f7..b9460ae06 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpec.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpec.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,71 +16,29 @@ * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ + package space.arim.libertybans.it; import space.arim.libertybans.core.database.Vendor; import space.arim.libertybans.api.select.AddressStrictness; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.core.uuid.ServerType; import java.time.Instant; import java.util.Objects; -public final class ConfigSpec { - - private final Vendor vendor; - private final AddressStrictness addressStrictness; - private final ServerType serverType; - private final long unixTime; - - ConfigSpec(Vendor vendor, AddressStrictness addressStrictness, ServerType serverType, long unixTime) { - this.vendor = Objects.requireNonNull(vendor, "vendor"); - this.addressStrictness = Objects.requireNonNull(addressStrictness, "addressStrictness"); - this.serverType = Objects.requireNonNull(serverType, "serverType"); - this.unixTime = unixTime; - } - - public Vendor vendor() { - return vendor; - } - - public AddressStrictness addressStrictness() { - return addressStrictness; - } +public record ConfigSpec(Vendor vendor, AddressStrictness addressStrictness, ServerType serverType, + InstanceType instanceType, boolean pluginMessaging, long unixTime) { - public ServerType serverType() { - return serverType; + public ConfigSpec { + Objects.requireNonNull(vendor, "vendor"); + Objects.requireNonNull(addressStrictness, "addressStrictness"); + Objects.requireNonNull(serverType, "serverType"); + Objects.requireNonNull(instanceType, "instanceType"); } - public Instant unixTime() { + public Instant unixTimestamp() { return Instant.ofEpochSecond(unixTime); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ConfigSpec that = (ConfigSpec) o; - return unixTime == that.unixTime && vendor == that.vendor - && addressStrictness == that.addressStrictness && serverType == that.serverType; - } - - @Override - public int hashCode() { - int result = vendor.hashCode(); - result = 31 * result + addressStrictness.hashCode(); - result = 31 * result + serverType.hashCode(); - result = 31 * result + (int) (unixTime ^ (unixTime >>> 32)); - return result; - } - - @Override - public String toString() { - return "ConfigSpec{" + - "vendor=" + vendor + - ", addressStrictness=" + addressStrictness + - ", serverType=" + serverType + - ", unixTime=" + unixTime + - '}'; - } - } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpecPossiblities.java b/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpecPossiblities.java index a87632e76..140d03f2c 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpecPossiblities.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/ConfigSpecPossiblities.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -20,10 +20,12 @@ import space.arim.libertybans.core.database.Vendor; import space.arim.libertybans.api.select.AddressStrictness; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.core.uuid.ServerType; import java.lang.reflect.AnnotatedElement; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @@ -32,22 +34,24 @@ class ConfigSpecPossiblities { private final AnnotatedElement element; ConfigSpecPossiblities(AnnotatedElement element) { - this.element = element; + this.element = Objects.requireNonNull(element); } - private Stream getAllPossible(long time) { + private Stream getAllPossible(InstanceType instanceType, boolean pluginMessaging, long time) { Set possibilities = new HashSet<>(); for (Vendor vendor : Vendor.values()) { for (AddressStrictness addressStrictness : AddressStrictness.values()) { for (ServerType serverType : ServerType.values()) { - possibilities.add(new ConfigSpec(vendor, addressStrictness, serverType, time)); + possibilities.add(new ConfigSpec( + vendor, addressStrictness, serverType, instanceType, pluginMessaging, time + )); } } } return possibilities.stream(); } - private ConfigConstraints getConstraints() { + private ConfigConstraints getConstraints(PlatformSpecs platformSpecs) { if (element.getAnnotation(NoDbAccess.class) != null) { return new ConfigConstraints( Set.of(Vendor.HSQLDB), Set.of(AddressStrictness.NORMAL), Set.of(ServerType.ONLINE)); @@ -64,15 +68,13 @@ private ConfigConstraints getConstraints() { } } Set serverTypes; - { - SetServerType serverTypeConstraint = element.getAnnotation(SetServerType.class); - if (serverTypeConstraint == null) { - serverTypes = Set.of(ServerType.ONLINE); - } else if (serverTypeConstraint.all()) { - serverTypes = Set.of(ServerType.values()); - } else { - serverTypes = Set.of(serverTypeConstraint.value()); - } + PlatformSpecs.ServerTypes serverTypeConstraint; + if (platformSpecs == null) { + serverTypes = Set.of(ServerType.ONLINE); + } else if ((serverTypeConstraint = platformSpecs.serverTypes()).all()) { + serverTypes = Set.of(ServerType.values()); + } else { + serverTypes = Set.of(serverTypeConstraint.value()); } Set vendors; { @@ -86,39 +88,38 @@ private ConfigConstraints getConstraints() { return new ConfigConstraints(vendors, addressStrictnesses, serverTypes); } - private static class ConfigConstraints { - - private final Set strictnesses; - private final Set vendors; - private final Set serverTypes; - - ConfigConstraints(Set vendors, Set strictnesses, Set serverTypes) { - - this.vendors = vendors; - this.strictnesses = strictnesses; - this.serverTypes = serverTypes; - } + private record ConfigConstraints(Set vendors, Set strictnesses, + Set serverTypes) { boolean allows(ConfigSpec configSpec) { - return vendors.contains(configSpec.vendor()) - && strictnesses.contains(configSpec.addressStrictness()) - && serverTypes.contains(configSpec.serverType()); + return vendors.contains(configSpec.vendor()) + && strictnesses.contains(configSpec.addressStrictness()) + && serverTypes.contains(configSpec.serverType()); } } Stream getAll() { - long defaultTime = SetTime.DEFAULT_TIME; - if (element == null) { - return getAllPossible(defaultTime); - } - long time = defaultTime; + long time; SetTime setTime = element.getAnnotation(SetTime.class); - if (setTime != null) { + if (setTime == null) { + time = SetTime.DEFAULT_TIME; + } else { time = setTime.unixTime(); } - Stream configurations = getAllPossible(time); - ConfigConstraints constraints = getConstraints(); + InstanceType instanceType; + boolean pluginMessaging; + PlatformSpecs platformSpecs = element.getAnnotation(PlatformSpecs.class); + if (platformSpecs == null) { + // Make sure these defaults match those in PlatformSpecs + instanceType = InstanceType.PROXY; + pluginMessaging = false; + } else { + instanceType = platformSpecs.instanceType(); + pluginMessaging = platformSpecs.pluginMessaging(); + } + Stream configurations = getAllPossible(instanceType, pluginMessaging, time); + ConfigConstraints constraints = getConstraints(platformSpecs); return configurations.filter(constraints::allows); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/DatabaseInstance.java b/bans-core/src/test/java/space/arim/libertybans/it/DatabaseInstance.java index 16599edca..caf9c3dd3 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/DatabaseInstance.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/DatabaseInstance.java @@ -92,14 +92,13 @@ Optional createInfo() { } private void createDatabase(String database) { - switch (this) { - case MARIADB_RETRO, MARIADB_LEGACY, MARIADB_MODERN, MYSQL -> { - createDatabaseUsing("jdbc:mariadb", database, " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); - } - case POSTGRES_LEGACY, POSTGRES_MODERN, COCKROACHDB -> { - createDatabaseUsing("jdbc:postgresql", database, ""); - } - default -> throw new IllegalStateException("No database creation exists"); + switch (vendor) { + case MARIADB, MYSQL -> + createDatabaseUsing("jdbc:mariadb", database, " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); + case POSTGRES, COCKROACH -> + createDatabaseUsing("jdbc:postgresql", database, ""); + default -> + throw new IllegalStateException("No database creation exists for " + this); } } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/InjectionInvocationContextProvider.java b/bans-core/src/test/java/space/arim/libertybans/it/InjectionInvocationContextProvider.java index c6094e15f..28db6eb57 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/InjectionInvocationContextProvider.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/InjectionInvocationContextProvider.java @@ -47,37 +47,28 @@ public Stream provideTestTemplateInvocationContex boolean irrelevantData = context.getRequiredTestMethod().isAnnotationPresent(IrrelevantData.class); ResourceCreator creator = new ResourceCreator(context.getRoot().getStore(NAMESPACE)); - return new ConfigSpecPossiblities(context.getElement().orElse(null)) + return new ConfigSpecPossiblities(context.getElement().orElseThrow()) .getAll() .flatMap((throwaway) ? creator::createIsolated : creator::create) .map((injector) -> new InjectorInvocationContext(injector, throwaway, irrelevantData)); } - private static class InjectorInvocationContext implements TestTemplateInvocationContext { - - private final Injector injector; - private final boolean throwaway; - private final boolean irrelevantData; - - InjectorInvocationContext(Injector injector, boolean throwaway, boolean irrelevantData) { - this.injector = injector; - this.throwaway = throwaway; - this.irrelevantData = irrelevantData; - } + private record InjectorInvocationContext(Injector injector, boolean throwaway, + boolean irrelevantData) implements TestTemplateInvocationContext { @Override - public List getAdditionalExtensions() { - List extensions = new ArrayList<>(3); - extensions.add(new InjectorParameterResolver(injector)); - if (!throwaway) { - extensions.add(new InjectorCleanupCallback(injector)); - } - if (irrelevantData) { - extensions.add(new IrrelevantDataCallback(injector)); + public List getAdditionalExtensions() { + List extensions = new ArrayList<>(3); + extensions.add(new InjectorParameterResolver(injector)); + if (!throwaway) { + extensions.add(new InjectorCleanupCallback(injector)); + } + if (irrelevantData) { + extensions.add(new IrrelevantDataCallback(injector)); + } + return extensions; } - return extensions; - } - } + } } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/InjectorCleanupCallback.java b/bans-core/src/test/java/space/arim/libertybans/it/InjectorCleanupCallback.java index 39699850c..e223826db 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/InjectorCleanupCallback.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/InjectorCleanupCallback.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -21,14 +21,11 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import space.arim.injector.Identifier; import space.arim.injector.Injector; import space.arim.libertybans.core.database.InternalDatabase; import space.arim.libertybans.core.punish.sync.SQLSynchronizationMessenger; import space.arim.libertybans.core.service.SettableTime; -import java.time.Instant; - final class InjectorCleanupCallback implements AfterEachCallback { private final Injector injector; @@ -42,8 +39,7 @@ public void afterEach(ExtensionContext context) throws Exception { // Reset database injector.request(InternalDatabase.class).truncateAllTables(); // Reset global clock - Instant startTime = injector.request(Identifier.ofTypeAndNamed(Instant.class, "testStartTime")); - injector.request(SettableTime.class).setTimestamp(startTime); + injector.request(SettableTime.class).reset(); // Reset synchronization injector.request(SQLSynchronizationMessenger.class).resetLastTimestamp(); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/PlatformSpecs.java b/bans-core/src/test/java/space/arim/libertybans/it/PlatformSpecs.java new file mode 100644 index 000000000..2dd3c0868 --- /dev/null +++ b/bans-core/src/test/java/space/arim/libertybans/it/PlatformSpecs.java @@ -0,0 +1,65 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.it; + +import space.arim.libertybans.core.env.InstanceType; +import space.arim.libertybans.core.uuid.ServerType; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Details related to the platform implementation + * + */ +// Thankfully, annotations implement sane equals and hashCode +@Retention(RUNTIME) +@Target(METHOD) +public @interface PlatformSpecs { + + ServerTypes serverTypes() default @ServerTypes(value = {}, all = true); + + // Make sure these defaults match those in ConfigSpecPossibilities + + InstanceType instanceType() default InstanceType.PROXY; + + boolean pluginMessaging() default false; + + @interface ServerTypes { + + /** + * Sets the server types + * + * @return the server types to test + */ + ServerType[] value(); + + /** + * Whether to use all server types. Overrides the value + * + * @return true to use all types + */ + boolean all() default false; + + } +} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/ResourceCreator.java b/bans-core/src/test/java/space/arim/libertybans/it/ResourceCreator.java index eeb138ab9..2315db010 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/ResourceCreator.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/ResourceCreator.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -29,12 +29,12 @@ import space.arim.libertybans.core.CommandsModule; import space.arim.libertybans.core.PillarOneReplacementModule; import space.arim.libertybans.core.PillarTwoBindModule; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.it.env.QuackBindModule; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.time.Instant; import java.util.Optional; import java.util.function.BiFunction; import java.util.stream.Stream; @@ -79,11 +79,9 @@ private Optional createSingle(ConfigSpec configSpec, DatabaseInstance Injector injector = new InjectorBuilder() .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), tempDirectory) + .bindInstance(InstanceType.class, InstanceType.PROXY) .bindInstance(ConfigSpec.class, configSpec) .bindInstance(DatabaseInfo.class, databaseInfo) - // The next two bindings are for ITs only - .bindInstance(DatabaseInstance.class, database) - .bindInstance(Identifier.ofTypeAndNamed(Instant.class, "testStartTime"), configSpec.unixTime()) .addBindModules( new ApiBindModule(), new PillarOneReplacementModule(), diff --git a/bans-core/src/test/java/space/arim/libertybans/it/SetServerType.java b/bans-core/src/test/java/space/arim/libertybans/it/SetServerType.java index 00914f735..76063031f 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/SetServerType.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/SetServerType.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -31,18 +31,6 @@ @Target(METHOD) public @interface SetServerType { - /** - * Sets the server types - * - * @return the server types to test - */ - ServerType[] value() default {}; - - /** - * Whether to use all server types. Overrides {@code value} - * - * @return true to use all server types - */ - boolean all() default false; + ServerType value(); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/QuackBindModule.java b/bans-core/src/test/java/space/arim/libertybans/it/env/QuackBindModule.java index 0bf548309..ee0a4532e 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/env/QuackBindModule.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/QuackBindModule.java @@ -22,6 +22,7 @@ import jakarta.inject.Singleton; import space.arim.api.env.PlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; @@ -60,6 +61,10 @@ public EnvUserResolver resolver(QuackUserResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(EnvMessageChannel.NoOp messageChannel) { + return messageChannel; + } + public EnvServerNameDetection serverNameDetection() { return (scopeManager) -> {}; } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/QuackEnforcer.java b/bans-core/src/test/java/space/arim/libertybans/it/env/QuackEnforcer.java index f5628024c..cd73c52c4 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/env/QuackEnforcer.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/QuackEnforcer.java @@ -25,12 +25,15 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.libertybans.it.env.platform.QuackPlatform; import space.arim.libertybans.it.env.platform.QuackPlayer; +import space.arim.libertybans.it.env.platform.ReceivedPluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.function.Consumer; @@ -46,8 +49,8 @@ public QuackEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter format } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { - platform.getAllPlayers().forEach(callback); + public CentralisedFuture doForAllPlayers(Consumer> callback) { + callback.accept(platform.getAllPlayers()); return completedVoid(); } @@ -62,6 +65,12 @@ public void kickPlayer(QuackPlayer player, Component message) { player.kickPlayer(message); } + @Override + public boolean sendPluginMessageIfListening(QuackPlayer player, PluginMessage pluginMessage, D data) { + player.receivedPluginMessages().add(new ReceivedPluginMessage<>(pluginMessage, data)); + return true; + } + @Override public UUID getUniqueIdFor(QuackPlayer player) { return player.getUniqueId(); @@ -72,6 +81,11 @@ public InetAddress getAddressFor(QuackPlayer player) { return player.getAddress(); } + @Override + public String getNameFor(QuackPlayer player) { + return player.getName(); + } + @Override public boolean hasPermission(QuackPlayer player, String permission) { return player.hasPermission(permission); diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/platform/PlatformSpecsEqualityTest.java b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/PlatformSpecsEqualityTest.java new file mode 100644 index 000000000..4df180017 --- /dev/null +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/PlatformSpecsEqualityTest.java @@ -0,0 +1,60 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.it.env.platform; + +import org.junit.jupiter.api.Test; +import space.arim.libertybans.core.env.InstanceType; +import space.arim.libertybans.it.PlatformSpecs; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class PlatformSpecsEqualityTest { + + private boolean equalityFor(String otherMethod) throws NoSuchMethodException { + var thisClass = getClass(); + Method firstMethod = thisClass.getDeclaredMethod("value1"); + Method secondMethod = thisClass.getDeclaredMethod(otherMethod); + PlatformSpecs firstAnnotation = firstMethod.getAnnotation(PlatformSpecs.class); + PlatformSpecs secondAnnotation = secondMethod.getAnnotation(PlatformSpecs.class); + return firstAnnotation.equals(secondAnnotation); + } + + @Test + public void isEqual() throws NoSuchMethodException { + assertFalse(equalityFor("value2")); + assertTrue(equalityFor("value3")); + assertFalse(equalityFor("value4")); + } + + @PlatformSpecs(instanceType = InstanceType.PROXY, pluginMessaging = false) + void value1() {} + @PlatformSpecs(instanceType = InstanceType.GAME_SERVER) + void value2() {} + @PlatformSpecs(instanceType = InstanceType.PROXY, pluginMessaging = false) + void value3() {} + @PlatformSpecs(instanceType = InstanceType.PROXY, pluginMessaging = true) + void value4() {} + +} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java index e75d85323..35081b255 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2021 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -28,6 +28,7 @@ import space.arim.omnibus.util.ThisClass; import java.net.InetAddress; +import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -39,6 +40,7 @@ public class QuackPlayer implements MessageOnlyAudience { private final InetAddress address; private final Set permissions; + private final Set> receivedPluginMessages = new HashSet<>(); private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); @@ -79,6 +81,10 @@ public void readdToPlatform() { platform.addPlayer(this); } + public Set> receivedPluginMessages() { + return receivedPluginMessages; + } + @Override public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { String displayMessage = platform.toDisplay(message); diff --git a/bans-core/src/test/java/space/arim/libertybans/it/env/platform/ReceivedPluginMessage.java b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/ReceivedPluginMessage.java new file mode 100644 index 000000000..09b49a392 --- /dev/null +++ b/bans-core/src/test/java/space/arim/libertybans/it/env/platform/ReceivedPluginMessage.java @@ -0,0 +1,25 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.it.env.platform; + +import space.arim.libertybans.core.env.message.PluginMessage; + +public record ReceivedPluginMessage(PluginMessage pluginMessage, D data) { +} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/database/DatabaseRequirementsIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/database/DatabaseRequirementsIT.java index 941cc12d9..2e2d9e7fb 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/database/DatabaseRequirementsIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/database/DatabaseRequirementsIT.java @@ -23,8 +23,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import space.arim.libertybans.core.database.DatabaseManager; import space.arim.libertybans.core.database.DatabaseRequirements; +import space.arim.libertybans.core.database.InternalDatabase; import space.arim.libertybans.core.database.Vendor; -import space.arim.libertybans.it.DatabaseInstance; import space.arim.libertybans.it.InjectionInvocationContextProvider; import java.sql.Connection; @@ -36,10 +36,10 @@ public class DatabaseRequirementsIT { @TestTemplate - public void databaseVersion(DatabaseManager databaseManager, - DatabaseInstance databaseInstance) throws SQLException { - Vendor vendor = databaseInstance.getVendor(); - try (Connection connection = databaseManager.getInternal().getConnection()) { + public void databaseVersion(DatabaseManager databaseManager) throws SQLException { + InternalDatabase database = databaseManager.getInternal(); + Vendor vendor = database.getVendor(); + try (Connection connection = database.getConnection()) { assertDoesNotThrow( new DatabaseRequirements(vendor, connection)::checkRequirementsAndYieldRetroSupport); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/AdvancedBanImportIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/AdvancedBanImportIT.java index e480d619f..35caaf55f 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/AdvancedBanImportIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/AdvancedBanImportIT.java @@ -35,6 +35,7 @@ import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; +import space.arim.libertybans.it.PlatformSpecs; import space.arim.libertybans.it.SetServerType; import space.arim.libertybans.it.SetTime; import space.arim.omnibus.util.UUIDUtil; @@ -82,14 +83,14 @@ private void importFrom(String dataFile, ImportStatistics expectedStatistics) { @TestTemplate @SetTime(unixTime = SetTime.DEFAULT_TIME) - @SetServerType(ServerType.OFFLINE) + @PlatformSpecs(serverTypes = @PlatformSpecs.ServerTypes(ServerType.OFFLINE)) public void sampleOneOffline() { importFrom("sample-one-offline", new ImportStatistics(103, 332, 768)); } @TestTemplate @SetTime(unixTime = SetTime.DEFAULT_TIME) - @SetServerType(ServerType.ONLINE) + @PlatformSpecs(serverTypes = @PlatformSpecs.ServerTypes(ServerType.ONLINE)) public void sampleTwoOnline() { addToCache("ed5f12cd600745d9a4b9940524ddaecf", "A248", "Aerodactyl_"); addToCache("840d1667a0e24934a3bd1a7ebbbc0732", "Cxleos"); diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeBindModule.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeBindModule.java index 0d26785ab..0dc2b0a33 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeBindModule.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeBindModule.java @@ -28,6 +28,7 @@ import space.arim.api.env.bungee.BungeePlatformHandle; import space.arim.api.env.PlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; @@ -62,6 +63,10 @@ public EnvUserResolver resolver(BungeeUserResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(BungeeMessageChannel messageChannel) { + return messageChannel; + } + public AddressReporter reporter(StandardAddressReporter reporter) { return reporter; } diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java index d5767945e..24630a587 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java @@ -31,10 +31,12 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.function.Consumer; @@ -43,19 +45,21 @@ public class BungeeEnforcer extends AbstractEnvEnforcer { private final ProxyServer server; private final AddressReporter addressReporter; + private final BungeeMessageChannel messageChannel; @Inject public BungeeEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, Interlocutor interlocutor, AudienceRepresenter audienceRepresenter, - ProxyServer server, AddressReporter addressReporter) { + ProxyServer server, AddressReporter addressReporter, BungeeMessageChannel messageChannel) { super(futuresFactory, formatter, interlocutor, audienceRepresenter); this.server = server; this.addressReporter = addressReporter; + this.messageChannel = messageChannel; } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { - server.getPlayers().forEach(callback); + public CentralisedFuture doForAllPlayers(Consumer> callback) { + callback.accept(server.getPlayers()); return completedVoid(); } @@ -74,6 +78,12 @@ public void kickPlayer(ProxiedPlayer player, Component message) { LegacyComponentSerializer.legacySection().serialize(message))); } + @Override + public boolean sendPluginMessageIfListening(ProxiedPlayer player, PluginMessage pluginMessage, D data) { + messageChannel.sendPluginMessage(player, pluginMessage, data); + return true; + } + @Override public UUID getUniqueIdFor(ProxiedPlayer player) { return player.getUniqueId(); @@ -84,6 +94,11 @@ public InetAddress getAddressFor(ProxiedPlayer player) { return addressReporter.getAddress(player); } + @Override + public String getNameFor(ProxiedPlayer player) { + return player.getName(); + } + @Override public boolean hasPermission(ProxiedPlayer player, String permission) { return player.hasPermission(permission); diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeLauncher.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeLauncher.java index b5876be44..001caac59 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeLauncher.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeLauncher.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2022 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -35,6 +35,7 @@ import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; import space.arim.libertybans.core.addon.AddonLoader; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.Omnibus; import space.arim.omnibus.OmnibusProvider; @@ -60,6 +61,7 @@ public BaseFoundation launch() { .bindInstance(Plugin.class, plugin) .bindInstance(ProxyServer.class, plugin.getProxy()) .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.PROXY) .bindInstance(Omnibus.class, omnibus) .addBindModules( new ApiBindModule(), diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeMessageChannel.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeMessageChannel.java new file mode 100644 index 000000000..16d99f6f6 --- /dev/null +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeMessageChannel.java @@ -0,0 +1,81 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.bungee; + +import jakarta.inject.Inject; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.PluginMessageAsBytes; +import space.arim.libertybans.core.env.message.PluginMessage; + +import java.util.function.Consumer; + +public final class BungeeMessageChannel implements EnvMessageChannel { + + private final Plugin plugin; + + private static final String BUNGEE_CHANNEL = "BungeeCord"; + + @Inject + public BungeeMessageChannel(Plugin plugin) { + this.plugin = plugin; + } + + void sendPluginMessage(ProxiedPlayer player, PluginMessage pluginMessage, D data) { + Server server = player.getServer(); + if (server != null) { + server.sendData(BUNGEE_CHANNEL, new PluginMessageAsBytes<>(pluginMessage).generateBytes(data)); + } + } + + @Override + public void installHandler(Listener handler) { + plugin.getProxy().getPluginManager().registerListener(plugin, handler); + } + + @Override + public void uninstallHandler(Listener handler) { + plugin.getProxy().getPluginManager().unregisterListener(handler); + } + + @Override + public Listener createHandler(Consumer acceptor, PluginMessage pluginMessage) { + return new Handler<>(acceptor, pluginMessage); + } + + // Public for reflection purposes + public record Handler(Consumer acceptor, PluginMessage pluginMessage) implements Listener { + + @EventHandler + public void onReceive(PluginMessageEvent event) { + if (event.getSender() instanceof Server && event.getTag().equals(BUNGEE_CHANNEL)) { + new PluginMessageAsBytes<>(pluginMessage) + .readBytes(event.getData()) + .ifPresent(acceptor); + } + } + } + +} diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessagingListener.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessagingListener.java deleted file mode 100644 index 477ec2742..000000000 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessagingListener.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * LibertyBans - * Copyright © 2023 Anand Beh - * - * LibertyBans is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ - -package space.arim.libertybans.env.spigot; - -import jakarta.inject.Inject; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.messaging.Messenger; -import org.bukkit.plugin.messaging.PluginMessageListener; -import space.arim.libertybans.core.env.PlatformListener; -import space.arim.libertybans.core.env.message.GetServer; -import space.arim.libertybans.core.scope.InternalScopeManager; - -public final class PluginMessagingListener implements PlatformListener, Listener, PluginMessageListener { - - private final Plugin plugin; - private final InternalScopeManager scopeManager; - - private final PluginMessageData getServer = new PluginMessageData<>(new GetServer()); - - static final String BUNGEE_CHANNEL = "BungeeCord"; - - @Inject - public PluginMessagingListener(Plugin plugin, InternalScopeManager scopeManager) { - this.plugin = plugin; - this.scopeManager = scopeManager; - } - - @Override - public void register() { - Server server = plugin.getServer(); - server.getPluginManager().registerEvents(this, plugin); - Messenger messenger = server.getMessenger(); - messenger.registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); - messenger.registerIncomingPluginChannel(plugin, BUNGEE_CHANNEL, this); - - // In case of '/libertybans restart', re-detect server name - if (scopeManager.shouldDetectServerName()) { - // Pick at most 4 online players just in case some of them quit - plugin.getServer().getOnlinePlayers().stream().limit(4).forEach((player) -> { - player.sendPluginMessage( - plugin, BUNGEE_CHANNEL, getServer.generateBytes(null) - ); - }); - } - } - - @Override - public void unregister() { - HandlerList.unregisterAll(this); - Messenger messenger = plugin.getServer().getMessenger(); - messenger.unregisterOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); - messenger.unregisterIncomingPluginChannel(plugin, BUNGEE_CHANNEL, this); - } - - @EventHandler(priority = EventPriority.LOW) - public void onJoin(PlayerJoinEvent event) { - if (scopeManager.shouldDetectServerName()) { - event.getPlayer().sendPluginMessage( - plugin, BUNGEE_CHANNEL, getServer.generateBytes(null) - ); - } - } - - @Override - public void onPluginMessageReceived(String channel, Player player, byte[] message) { - if (channel.equals(BUNGEE_CHANNEL)) { - getServer.readBytes(message) - .map(GetServer.Response::server) - .ifPresent(scopeManager::detectServerName); - } - } -} diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ServerNameListener.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ServerNameListener.java new file mode 100644 index 000000000..57c73e0e6 --- /dev/null +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ServerNameListener.java @@ -0,0 +1,63 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.spigot; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.Plugin; +import space.arim.libertybans.core.env.PlatformListener; +import space.arim.libertybans.core.scope.ServerNameListenerBase; + +@Singleton +public final class ServerNameListener implements PlatformListener, Listener { + + private final Plugin plugin; + private final ServerNameListenerBase baseImpl; + + @Inject + public ServerNameListener(Plugin plugin, ServerNameListenerBase baseImpl) { + this.plugin = plugin; + this.baseImpl = baseImpl; + } + + @Override + public void register() { + baseImpl.register(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void unregister() { + HandlerList.unregisterAll(this); + baseImpl.unregister(); + } + + @EventHandler(priority = EventPriority.LOW) + public void onJoin(PlayerJoinEvent event) { + baseImpl.onJoin(event.getPlayer()); + } + +} diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java index 1f58e8075..11c147eb5 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java @@ -27,6 +27,7 @@ import space.arim.api.env.bukkit.BukkitAudienceRepresenter; import space.arim.api.env.bukkit.BukkitPlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; @@ -63,6 +64,10 @@ public EnvUserResolver resolver(SpigotUserResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(SpigotMessageChannel messageChannel) { + return messageChannel; + } + @Singleton public CommandMapHelper commandMapHelper(SimpleCommandMapHelper scmh) { return new CachingCommandMapHelper(scmh); diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java index 6b09b535e..934fef5e8 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java @@ -25,36 +25,36 @@ import org.bukkit.Server; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.slf4j.LoggerFactory; import space.arim.api.env.AudienceRepresenter; -import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; -import space.arim.libertybans.core.env.message.KickPlayer; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.morepaperlib.adventure.MorePaperLibAdventure; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.function.Consumer; @Singleton public class SpigotEnforcer extends AbstractEnvEnforcer { - private final Configs configs; private final Server server; private final MorePaperLibAdventure morePaperLibAdventure; + private final SpigotMessageChannel messageChannel; @Inject public SpigotEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, Interlocutor interlocutor, AudienceRepresenter audienceRepresenter, - Configs configs, Server server, MorePaperLibAdventure morePaperLibAdventure) { + Server server, MorePaperLibAdventure morePaperLibAdventure, + SpigotMessageChannel messageChannel) { super(futuresFactory, formatter, interlocutor, audienceRepresenter); - this.configs = configs; this.server = server; this.morePaperLibAdventure = morePaperLibAdventure; + this.messageChannel = messageChannel; } @SuppressWarnings("unchecked") @@ -64,37 +64,24 @@ private CentralisedFuture runSync(Runnable command) { } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { + public CentralisedFuture doForAllPlayers(Consumer> callback) { if (morePaperLibAdventure.getMorePaperLib().scheduling().isUsingFolia()) { - server.getOnlinePlayers().forEach(callback); + callback.accept(server.getOnlinePlayers()); return completedVoid(); } - return runSync(() -> server.getOnlinePlayers().forEach(callback)); + return runSync(() -> callback.accept(server.getOnlinePlayers())); } @Override public void kickPlayer(Player player, Component message) { - if (configs.getMainConfig().platforms().gameServers().kickViaPluginMessaging()) { - - if (player.getListeningPluginChannels().contains(PluginMessagingListener.BUNGEE_CHANNEL)) { - byte[] kickPlayerData = new PluginMessageData<>(new KickPlayer()) - .generateBytes(new KickPlayer.Data( - player.getName(), message - )); - player.sendPluginMessage( - morePaperLibAdventure.getMorePaperLib().getPlugin(), - PluginMessagingListener.BUNGEE_CHANNEL, - kickPlayerData - ); - return; - } - LoggerFactory.getLogger(getClass()).warn( - "Kicking via plugin messaging is enabled, but the proper channel is not listened on." - ); - } morePaperLibAdventure.kickPlayer(player, message); } + @Override + public boolean sendPluginMessageIfListening(Player player, PluginMessage pluginMessage, D data) { + return messageChannel.sendPluginMessage(player, pluginMessage, data); + } + @Override public CentralisedFuture doForPlayerIfOnline(UUID uuid, Consumer callback) { if (morePaperLibAdventure.getMorePaperLib().scheduling().isUsingFolia()) { @@ -122,6 +109,11 @@ public InetAddress getAddressFor(Player player) { return player.getAddress().getAddress(); } + @Override + public String getNameFor(Player player) { + return player.getName(); + } + @Override public boolean hasPermission(Player player, String permission) { return player.hasPermission(permission); diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java index 577d830c6..d2e711cb1 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java @@ -31,15 +31,18 @@ public final class SpigotEnv implements Environment { private final Provider connectionListener; private final Provider chatListener; - private final Provider messagingListener; + private final Provider serverNameListener; + private final Provider pluginMessageChannel; private final CommandHandler.CommandHelper commandHelper; @Inject public SpigotEnv(Provider connectionListener, Provider chatListener, - Provider messagingListener, CommandHandler.CommandHelper commandHelper) { + Provider serverNameListener, Provider pluginMessageChannel, + CommandHandler.CommandHelper commandHelper) { this.connectionListener = connectionListener; this.chatListener = chatListener; - this.messagingListener = messagingListener; + this.serverNameListener = serverNameListener; + this.pluginMessageChannel = pluginMessageChannel; this.commandHelper = commandHelper; } @@ -48,7 +51,8 @@ public Set createListeners() { return Set.of( connectionListener.get(), chatListener.get(), - messagingListener.get(), + serverNameListener.get(), + pluginMessageChannel.get(), new CommandHandler(commandHelper, Commands.BASE_COMMAND_NAME, false) ); } diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotLauncher.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotLauncher.java index cd35eb5f3..5c20192b5 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotLauncher.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotLauncher.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2022 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -36,6 +36,7 @@ import org.bukkit.Server; import org.bukkit.plugin.java.JavaPlugin; import space.arim.libertybans.core.addon.AddonLoader; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.Omnibus; import space.arim.omnibus.OmnibusProvider; @@ -62,6 +63,7 @@ public BaseFoundation launch() { .bindInstance(Plugin.class, plugin) .bindInstance(Server.class, plugin.getServer()) .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.GAME_SERVER) .bindInstance(Omnibus.class, omnibus) .addBindModules( new ApiBindModule(), diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotMessageChannel.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotMessageChannel.java new file mode 100644 index 000000000..883286fa3 --- /dev/null +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotMessageChannel.java @@ -0,0 +1,92 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.spigot; + +import jakarta.inject.Inject; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.PluginMessageListener; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.PlatformListener; +import space.arim.libertybans.core.env.PluginMessageAsBytes; +import space.arim.libertybans.core.env.message.PluginMessage; + +import java.util.function.Consumer; + +public final class SpigotMessageChannel implements PlatformListener, EnvMessageChannel { + + private final Plugin plugin; + + static final String BUNGEE_CHANNEL = "BungeeCord"; + + @Inject + public SpigotMessageChannel(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void register() { + plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); + } + + @Override + public void unregister() { + plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); + } + + boolean sendPluginMessage(Player player, PluginMessage pluginMessage, D data) { + boolean listened = player.getListeningPluginChannels().contains(BUNGEE_CHANNEL); + if (listened) { + player.sendPluginMessage( + plugin, BUNGEE_CHANNEL, + new PluginMessageAsBytes<>(pluginMessage).generateBytes(data) + ); + } + return listened; + } + + @Override + public void installHandler(PluginMessageListener handler) { + plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, BUNGEE_CHANNEL, handler); + } + + @Override + public void uninstallHandler(PluginMessageListener handler) { + plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, BUNGEE_CHANNEL, handler); + } + + @Override + public PluginMessageListener createHandler(Consumer acceptor, PluginMessage pluginMessage) { + return new Handler<>(acceptor, pluginMessage); + } + + record Handler(Consumer acceptor, PluginMessage pluginMessage) implements PluginMessageListener { + + @Override + public void onPluginMessageReceived(String channel, Player player, byte[] message) { + if (channel.equals(BUNGEE_CHANNEL)) { + new PluginMessageAsBytes<>(pluginMessage) + .readBytes(message) + .ifPresent(acceptor); + } + } + } + +} diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/PluginMessageData.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/PluginMessageData.java deleted file mode 100644 index 35750affe..000000000 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/PluginMessageData.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * LibertyBans - * Copyright © 2023 Anand Beh - * - * LibertyBans is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ - -package space.arim.libertybans.env.sponge; - -import org.spongepowered.api.network.channel.ChannelBuf; -import space.arim.libertybans.core.env.message.PluginMessage; -import space.arim.libertybans.core.env.message.PluginMessageInput; -import space.arim.libertybans.core.env.message.PluginMessageOutput; - -import java.io.IOException; -import java.util.Optional; - -record PluginMessageData(PluginMessage pluginMessage) { - - void writeBuffer(D data, ChannelBuf buffer) { - pluginMessage.writeTo(data, new ChannelBufAsOutput(buffer)); - } - - Optional readBuffer(ChannelBuf buffer) { - return pluginMessage.readFrom(new ChannelBufAsInput(buffer)); - } - - record ChannelBufAsOutput(ChannelBuf buffer) implements PluginMessageOutput { - @Override - public void writeUTF(String utf) throws IOException { - buffer.writeUTF(utf); - } - } - - record ChannelBufAsInput(ChannelBuf buffer) implements PluginMessageInput { - @Override - public String readUTF() throws IOException { - return buffer.readUTF(); - } - } -} diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/PluginMessagingListener.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/PluginMessagingListener.java deleted file mode 100644 index 815be1dde..000000000 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/PluginMessagingListener.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * LibertyBans - * Copyright © 2023 Anand Beh - * - * LibertyBans is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ - -package space.arim.libertybans.env.sponge; - -import jakarta.inject.Inject; -import org.slf4j.LoggerFactory; -import org.spongepowered.api.Game; -import org.spongepowered.api.ResourceKey; -import org.spongepowered.api.entity.living.player.server.ServerPlayer; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; -import org.spongepowered.api.event.network.ServerSideConnectionEvent; -import org.spongepowered.api.network.EngineConnectionSide; -import org.spongepowered.api.network.ServerSideConnection; -import org.spongepowered.api.network.channel.ChannelBuf; -import org.spongepowered.api.network.channel.raw.RawDataChannel; -import org.spongepowered.api.network.channel.raw.play.RawPlayDataChannel; -import org.spongepowered.api.network.channel.raw.play.RawPlayDataHandler; -import space.arim.libertybans.core.env.PlatformListener; -import space.arim.libertybans.core.env.message.GetServer; -import space.arim.libertybans.core.env.message.PluginMessage; -import space.arim.libertybans.core.scope.InternalScopeManager; -import space.arim.libertybans.env.sponge.listener.RegisterListeners; - -import java.util.Optional; - -public final class PluginMessagingListener implements PlatformListener, RawPlayDataHandler { - - private final RegisterListeners registerListeners; - private final Game game; - private final InternalScopeManager scopeManager; - - @Inject - public PluginMessagingListener(RegisterListeners registerListeners, Game game, InternalScopeManager scopeManager) { - this.registerListeners = registerListeners; - this.game = game; - this.scopeManager = scopeManager; - } - - static Optional findChannel(Game game) { - return game.channelManager() - .get(ResourceKey.of("bungeecord", "main")) - .flatMap((channel) -> { - if (channel instanceof RawDataChannel rawDataChannel) { - return Optional.of(rawDataChannel.play()); - } else { - return Optional.empty(); - } - }); - } - - static void sendPluginMessage(RawPlayDataChannel channel, ServerPlayer player, - PluginMessage pluginMessage, D data) { - channel.sendTo(player, (buffer) -> { - new PluginMessageData<>(pluginMessage).writeBuffer(data, buffer); - }).exceptionally((ex) -> { - LoggerFactory.getLogger(PluginMessagingListener.class).error("Failed to send plugin message", ex); - return null; - }); - } - - @Override - public void register() { - registerListeners.register(this); - findChannel(game).ifPresent((channel) -> { - channel.addHandler(EngineConnectionSide.SERVER, this); - - // In case of '/libertybans restart', re-detect server name - if (scopeManager.shouldDetectServerName()) { - // Pick at most 4 online players just in case some of them quit - game.server().onlinePlayers().stream().limit(4).forEach((player) -> { - sendPluginMessage(channel, player, new GetServer(), null); - }); - } - }); - } - - @Override - public void unregister() { - registerListeners.unregister(this); - findChannel(game) - .ifPresent((channel) -> channel.removeHandler(this)); - } - - @Listener(order = Order.EARLY) - public void onJoin(ServerSideConnectionEvent.Join event) { - if (scopeManager.shouldDetectServerName()) { - findChannel(game).ifPresent((channel) -> { - sendPluginMessage(channel, event.player(), new GetServer(), null); - }); - } - } - - @Override - public void handlePayload(ChannelBuf data, ServerSideConnection connection) { - new PluginMessageData<>(new GetServer()) - .readBuffer(data) - .map(GetServer.Response::server) - .ifPresent(scopeManager::detectServerName); - } -} diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ServerNameListener.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ServerNameListener.java new file mode 100644 index 000000000..5ad4f4b9c --- /dev/null +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ServerNameListener.java @@ -0,0 +1,61 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.sponge; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.network.ServerSideConnectionEvent; +import space.arim.libertybans.core.env.PlatformListener; +import space.arim.libertybans.core.scope.ServerNameListenerBase; +import space.arim.libertybans.env.sponge.listener.RegisterListeners; + +@Singleton +public final class ServerNameListener implements PlatformListener { + + private final RegisterListeners registerListeners; + private final ServerNameListenerBase baseImpl; + + @Inject + public ServerNameListener(RegisterListeners registerListeners, ServerNameListenerBase baseImpl) { + this.registerListeners = registerListeners; + this.baseImpl = baseImpl; + } + + @Override + public void register() { + baseImpl.register(); + registerListeners.register(this); + } + + @Override + public void unregister() { + registerListeners.unregister(this); + baseImpl.unregister(); + } + + @Listener(order = Order.EARLY) + public void onJoin(ServerSideConnectionEvent.Join event) { + baseImpl.onJoin(event.player()); + } + +} diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java index 872479ec5..150def4f7 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java @@ -28,6 +28,7 @@ import space.arim.api.env.sponge.SpongeAudienceRepresenter; import space.arim.api.env.sponge.SpongePlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; @@ -64,6 +65,10 @@ public EnvUserResolver envUserResolver(SpongeUserResolver resolver) { return resolver; } + public EnvMessageChannel envMessageChannel(SpongeMessageChannel messageChannel) { + return messageChannel; + } + public EnvServerNameDetection serverNameDetection() { return (scopeManager) -> {}; } diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnforcer.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnforcer.java index 9cf63d8fb..7fd1624d9 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnforcer.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnforcer.java @@ -26,33 +26,31 @@ import org.spongepowered.api.Game; import org.spongepowered.api.command.exception.CommandException; import org.spongepowered.api.entity.living.player.server.ServerPlayer; -import org.spongepowered.api.network.channel.raw.play.RawPlayDataChannel; import space.arim.api.env.AudienceRepresenter; -import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; -import space.arim.libertybans.core.env.message.KickPlayer; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; -import java.util.Optional; +import java.util.Collection; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; public final class SpongeEnforcer extends AbstractEnvEnforcer { - private final Configs configs; private final Game game; + private final SpongeMessageChannel messageChannel; @Inject public SpongeEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, - Interlocutor interlocutor, Configs configs, Game game) { + Interlocutor interlocutor, Game game, SpongeMessageChannel messageChannel) { super(futuresFactory, formatter, interlocutor, AudienceRepresenter.identity()); - this.configs = configs; this.game = game; + this.messageChannel = messageChannel; } @SuppressWarnings("unchecked") @@ -62,8 +60,8 @@ private CentralisedFuture runSync(Runnable command) { } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { - return runSync(() -> game.server().onlinePlayers().forEach(callback)); + public CentralisedFuture doForAllPlayers(Consumer> callback) { + return runSync(() -> callback.accept(game.server().onlinePlayers())); } @Override @@ -73,23 +71,14 @@ public CentralisedFuture doForPlayerIfOnline(UUID uuid, Consumer channel = PluginMessagingListener.findChannel(game); - if (channel.isPresent()) { - PluginMessagingListener.sendPluginMessage( - channel.get(), player, - new KickPlayer(), new KickPlayer.Data(player.name(), message) - ); - return; - } - LoggerFactory.getLogger(getClass()).warn( - "Kicking via plugin messaging is enabled, but the proper channel does not exist." - ); - } player.kick(message); } + @Override + public boolean sendPluginMessageIfListening(ServerPlayer player, PluginMessage pluginMessage, D data) { + return messageChannel.sendPluginMessage(player, pluginMessage, data); + } + @Override public UUID getUniqueIdFor(ServerPlayer player) { return player.uniqueId(); @@ -100,6 +89,11 @@ public InetAddress getAddressFor(ServerPlayer player) { return player.connection().address().getAddress(); } + @Override + public String getNameFor(ServerPlayer player) { + return player.name(); + } + @Override public boolean hasPermission(ServerPlayer player, String permission) { return player.hasPermission(permission); diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnv.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnv.java index 3d93ba3ca..75dddc499 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnv.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeEnv.java @@ -31,15 +31,15 @@ public final class SpongeEnv implements Environment { private final Provider connectionListener; private final Provider chatListener; - private final Provider pluginMessagingListener; + private final Provider serverNameListener; private final PlatformAccess platformAccess; @Inject public SpongeEnv(Provider connectionListener, Provider chatListener, - Provider pluginMessagingListener, PlatformAccess platformAccess) { + Provider serverNameListener, PlatformAccess platformAccess) { this.connectionListener = connectionListener; this.chatListener = chatListener; - this.pluginMessagingListener = pluginMessagingListener; + this.serverNameListener = serverNameListener; this.platformAccess = platformAccess; } @@ -48,7 +48,7 @@ public Set createListeners() { return Set.of( connectionListener.get(), chatListener.get(), - pluginMessagingListener.get() + serverNameListener.get() ); } diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeLauncher.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeLauncher.java index 3eba51a67..fc0d0a096 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeLauncher.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeLauncher.java @@ -32,6 +32,7 @@ import space.arim.libertybans.core.CommandsModule; import space.arim.libertybans.core.PillarOneBindModule; import space.arim.libertybans.core.PillarTwoBindModule; +import space.arim.libertybans.core.env.InstanceType; import space.arim.libertybans.env.sponge.listener.RegisterListeners; import space.arim.libertybans.env.sponge.listener.RegisterListenersByMethodScan; import space.arim.libertybans.env.sponge.listener.RegisterListenersStandard; @@ -86,6 +87,7 @@ public BaseFoundation launch() { .bindInstance(PluginContainer.class, plugin) .bindInstance(Game.class, game) .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.GAME_SERVER) .bindInstance(Omnibus.class, omnibus) .addBindModules( new ApiBindModule(), diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeMessageChannel.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeMessageChannel.java new file mode 100644 index 000000000..3149bfe53 --- /dev/null +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeMessageChannel.java @@ -0,0 +1,110 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.sponge; + +import jakarta.inject.Inject; +import org.slf4j.LoggerFactory; +import org.spongepowered.api.Game; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.network.EngineConnectionSide; +import org.spongepowered.api.network.ServerSideConnection; +import org.spongepowered.api.network.channel.ChannelBuf; +import org.spongepowered.api.network.channel.raw.RawDataChannel; +import org.spongepowered.api.network.channel.raw.play.RawPlayDataChannel; +import org.spongepowered.api.network.channel.raw.play.RawPlayDataHandler; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.message.PluginMessage; +import space.arim.libertybans.core.env.message.PluginMessageInput; +import space.arim.libertybans.core.env.message.PluginMessageOutput; + +import java.io.IOException; +import java.util.function.Consumer; + +public final class SpongeMessageChannel implements EnvMessageChannel> { + + private final Game game; + + @Inject + public SpongeMessageChannel(Game game) { + this.game = game; + } + + private RawPlayDataChannel channel() { + return game.channelManager() + .ofType(ResourceKey.of("bungeecord", "main"), RawDataChannel.class) + .play(); + } + + boolean sendPluginMessage(ServerPlayer player, PluginMessage pluginMessage, D data) { + var channel = channel(); + boolean supported = channel.isSupportedBy(player.connection()); + if (supported) { + channel.sendTo(player, (buffer) -> { + pluginMessage.writeTo(data, new ChannelBufAsOutput(buffer)); + }).exceptionally((ex) -> { + LoggerFactory.getLogger(getClass()).error("Failed to send plugin message", ex); + return null; + }); + } + return supported; + } + + @Override + public void installHandler(RawPlayDataHandler handler) { + channel().addHandler(EngineConnectionSide.SERVER, handler); + } + + @Override + public void uninstallHandler(RawPlayDataHandler handler) { + channel().removeHandler(handler); + } + + @Override + public RawPlayDataHandler createHandler(Consumer acceptor, + PluginMessage pluginMessage) { + return new Handler<>(acceptor, pluginMessage); + } + + record Handler(Consumer handler, PluginMessage pluginMessage) + implements RawPlayDataHandler { + + @Override + public void handlePayload(ChannelBuf data, ServerSideConnection connection) { + pluginMessage.readFrom(new ChannelBufAsInput(data)).ifPresent(handler); + } + + } + + private record ChannelBufAsOutput(ChannelBuf buffer) implements PluginMessageOutput { + @Override + public void writeUTF(String utf) throws IOException { + buffer.writeUTF(utf); + } + } + + private record ChannelBufAsInput(ChannelBuf buffer) implements PluginMessageInput { + @Override + public String readUTF() throws IOException { + return buffer.readUTF(); + } + } + +} diff --git a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneBindModule.java b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneBindModule.java index 00c565b14..0fe377a0a 100644 --- a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneBindModule.java +++ b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneBindModule.java @@ -23,6 +23,7 @@ import space.arim.api.env.PlatformHandle; import space.arim.api.env.PlatformPluginInfo; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; @@ -82,6 +83,10 @@ public EnvUserResolver resolver(StandaloneResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(EnvMessageChannel.NoOp messageChannel) { + return messageChannel; + } + public EnvServerNameDetection serverNameDetection() { return (scopeManager) -> scopeManager.detectServerName("standalone"); } diff --git a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneEnforcer.java b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneEnforcer.java index b236be032..0332646a0 100644 --- a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneEnforcer.java +++ b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneEnforcer.java @@ -25,10 +25,12 @@ import net.kyori.adventure.text.ComponentLike; import space.arim.libertybans.core.env.EnvEnforcer; import space.arim.libertybans.core.env.TargetMatcher; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.function.Consumer; @@ -53,11 +55,21 @@ public CentralisedFuture doForPlayerIfOnline(UUID uuid, Consumer cal return completedVoid(); } + @Override + public CentralisedFuture doForAllPlayers(Consumer> action) { + return completedVoid(); + } + @Override public void kickPlayer(Void player, Component message) { throw new UnsupportedOperationException(); } + @Override + public boolean sendPluginMessageIfListening(Void player, PluginMessage pluginMessage, D data) { + throw new UnsupportedOperationException(); + } + @Override public void sendMessageNoPrefix(Void player, ComponentLike message) { throw new UnsupportedOperationException(); diff --git a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneLauncher.java b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneLauncher.java index 36aba259f..0a6a15630 100644 --- a/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneLauncher.java +++ b/bans-env/standalone/src/main/java/space/arim/libertybans/env/standalone/StandaloneLauncher.java @@ -31,6 +31,7 @@ import space.arim.libertybans.core.PillarOneBindModule; import space.arim.libertybans.core.PillarTwoBindModule; import space.arim.libertybans.core.addon.AddonLoader; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.Omnibus; import space.arim.omnibus.OmnibusProvider; @@ -53,6 +54,7 @@ public StandaloneLauncher(Path folder, Omnibus omnibus) { public Injector createInjector(ConsoleAudience consoleAudience) { return new InjectorBuilder() .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.STANDALONE) .bindInstance(Omnibus.class, omnibus) .bindInstance(ConsoleAudience.class, consoleAudience) .addBindModules( diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityBindModule.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityBindModule.java index ad3a9117f..180fee720 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityBindModule.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityBindModule.java @@ -25,6 +25,7 @@ import space.arim.api.env.PlatformHandle; import space.arim.api.env.velocity.VelocityPlatformHandle; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.env.EnvMessageChannel; import space.arim.libertybans.core.env.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; @@ -55,6 +56,10 @@ public EnvUserResolver resolver(VelocityUserResolver resolver) { return resolver; } + public EnvMessageChannel messageChannel(VelocityMessageChannel messageChannel) { + return messageChannel; + } + public EnvServerNameDetection serverNameDetection() { return (scopeManager) -> scopeManager.detectServerName("proxy"); } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java index 2a01f0d64..d3ec72758 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2022 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -30,10 +30,12 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.AbstractEnvEnforcer; import space.arim.libertybans.core.env.Interlocutor; +import space.arim.libertybans.core.env.message.PluginMessage; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Collection; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -42,17 +44,19 @@ public class VelocityEnforcer extends AbstractEnvEnforcer { private final ProxyServer server; + private final VelocityMessageChannel messageChannel; @Inject public VelocityEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, - Interlocutor interlocutor, ProxyServer server) { + Interlocutor interlocutor, ProxyServer server, VelocityMessageChannel messageChannel) { super(futuresFactory, formatter, interlocutor, AudienceRepresenter.identity()); this.server = server; + this.messageChannel = messageChannel; } @Override - protected CentralisedFuture doForAllPlayers(Consumer callback) { - server.getAllPlayers().forEach(callback); + public CentralisedFuture doForAllPlayers(Consumer> callback) { + callback.accept(server.getAllPlayers()); return completedVoid(); } @@ -61,6 +65,12 @@ public void kickPlayer(Player player, Component message) { player.disconnect(message); } + @Override + public boolean sendPluginMessageIfListening(Player player, PluginMessage pluginMessage, D data) { + messageChannel.sendPluginMessage(player, pluginMessage, data); + return true; + } + @Override public CentralisedFuture doForPlayerIfOnline(UUID uuid, Consumer callback) { server.getPlayer(uuid).ifPresent(callback); @@ -77,6 +87,11 @@ public InetAddress getAddressFor(Player player) { return player.getRemoteAddress().getAddress(); } + @Override + public String getNameFor(Player player) { + return player.getUsername(); + } + @Override public boolean hasPermission(Player player, String permission) { return player.hasPermission(permission); diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityLauncher.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityLauncher.java index 664654325..f1cb1e208 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityLauncher.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityLauncher.java @@ -1,6 +1,6 @@ /* * LibertyBans - * Copyright © 2022 Anand Beh + * Copyright © 2023 Anand Beh * * LibertyBans is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -31,6 +31,7 @@ import space.arim.libertybans.core.PillarOneBindModule; import space.arim.libertybans.core.PillarTwoBindModule; import space.arim.libertybans.core.addon.AddonLoader; +import space.arim.libertybans.core.env.InstanceType; import space.arim.omnibus.Omnibus; import space.arim.omnibus.OmnibusProvider; @@ -60,6 +61,7 @@ public BaseFoundation launch() { .bindInstance(PluginContainer.class, plugin) .bindInstance(ProxyServer.class, server) .bindInstance(Identifier.ofTypeAndNamed(Path.class, "folder"), folder) + .bindInstance(InstanceType.class, InstanceType.PROXY) .bindInstance(Omnibus.class, omnibus) .addBindModules( new ApiBindModule(), diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityMessageChannel.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityMessageChannel.java new file mode 100644 index 000000000..38addb156 --- /dev/null +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityMessageChannel.java @@ -0,0 +1,84 @@ +/* + * LibertyBans + * Copyright © 2023 Anand Beh + * + * LibertyBans is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ + +package space.arim.libertybans.env.velocity; + +import com.velocitypowered.api.event.EventHandler; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import jakarta.inject.Inject; +import space.arim.libertybans.core.env.EnvMessageChannel; +import space.arim.libertybans.core.env.PluginMessageAsBytes; +import space.arim.libertybans.core.env.message.PluginMessage; + +import java.util.function.Consumer; + +public final class VelocityMessageChannel implements EnvMessageChannel> { + + private final PluginContainer plugin; + private final ProxyServer server; + + private static final ChannelIdentifier BUNGEE_CHANNEL = MinecraftChannelIdentifier.create("bungeecord", "main"); + + @Inject + public VelocityMessageChannel(PluginContainer plugin, ProxyServer server) { + this.plugin = plugin; + this.server = server; + } + + void sendPluginMessage(Player player, PluginMessage pluginMessage, D data) { + player.getCurrentServer().ifPresent((server) -> { + server.sendPluginMessage( + BUNGEE_CHANNEL, + new PluginMessageAsBytes<>(pluginMessage).generateBytes(data) + ); + }); + } + + @Override + public void installHandler(EventHandler handler) { + server.getEventManager().register(plugin, PluginMessageEvent.class, handler); + } + + @Override + public void uninstallHandler(EventHandler handler) { + server.getEventManager().unregister(plugin, handler); + } + + @Override + public EventHandler createHandler(Consumer acceptor, PluginMessage pluginMessage) { + class AsHandler implements EventHandler { + + @Override + public void execute(PluginMessageEvent event) { + if (event.getSource() instanceof ServerConnection && event.getIdentifier().equals(BUNGEE_CHANNEL)) { + new PluginMessageAsBytes<>(pluginMessage) + .readBytes(event.getData()) + .ifPresent(acceptor); + } + } + } + return new AsHandler(); + } +} diff --git a/docs/Comparison-to-LiteBans.md b/docs/Comparison-to-LiteBans.md index d729ea908..131d055a8 100644 --- a/docs/Comparison-to-LiteBans.md +++ b/docs/Comparison-to-LiteBans.md @@ -96,6 +96,8 @@ LiteBans' support for Velocity came after repeated user requests. It was suggest LibertyBans and LiteBans have Geyser support. +However, documentation suggests LiteBans requires the prefix to be the period character (".").[4](#note4) + ### Core punishment types Both LibertyBans and LiteBans include bans, ip-bans, mutes, warns, and kicks. @@ -128,7 +130,7 @@ LiteBans supports importing from AdvancedBan, BanManager, BungeeAdminTools, MaxB Both plugins enable punishments scoped to certain servers. -However, LiteBans lacks scope categories, or the ability to group servers into a single scope.[4](#note4) +However, LiteBans lacks scope categories, or the ability to group servers into a single scope.[5](#note5) ### Multi-Proxy / Multi-Instance Synchronization @@ -142,7 +144,9 @@ Both LibertyBans and LiteBans provide synchronization across multiple instances, 3: Ruan. "[Feature] Spongepowered?". LiteBans Gitlab Issue comment. https://gitlab.com/ruany/LiteBans/-/issues/41#note_324182783 [↩](#note3ret) -4: lewisakura. "Server scope groups". LiteBans Gitlab Issue. https://gitlab.com/ruany/LiteBans/-/issues/452 +4: Ruan. "LiteBans 2.7.5 (Connection error fix)". SpigotMC Resource Update. https://www.spigotmc.org/resources/litebans.3715/update?update=414133 + +5: lewisakura. "Server scope groups". LiteBans Gitlab Issue. https://gitlab.com/ruany/LiteBans/-/issues/452 ### Disclaimer diff --git a/docs/Quick-Plugin-Comparison.md b/docs/Quick-Plugin-Comparison.md index beca41a12..361307a0e 100644 --- a/docs/Quick-Plugin-Comparison.md +++ b/docs/Quick-Plugin-Comparison.md @@ -1,4 +1,4 @@ -This is a quick table for comparing between LibertyBans, AdvancedBan, and LiteBans. +This is a quick table for comparing between LibertyBans, AdvancedBan, BanManager, and LiteBans. **NB: *Tables and graphs do not tell the full picture!*** To really understand what's going on here, read the detailed and explanatory comparisons. You can find these on the sidebar at the right. For each plugin here, there is a detailed comparison between LibertyBans and the plugin. @@ -16,19 +16,19 @@ comparisons. You can find these on the sidebar at the right. For each plugin her [GPL]:https://www.gnu.org/graphics/gplv3-127x51.png [CC-BY-NC]:http://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-nc.png -| Plugin | Supported Platforms | Java Req. | Free (Gratis) | Open License | Database Support | Thread-Safe Design | Stable API | Geyser Support | Multi-Instance Support | Connection Pool | Exemption | Server Scopes | Uses UUIDs | Schema Integrity | Import From | -|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|---------------|----------------|--------------------------------------------|--------------------|------------|----------------|------------------------|-----------------|-----------|---------------|------------|------------------|---------------------------------------------------------------------------------------| -| LibertyBans | ![Bukkit]Bukkit
Bungee
Sponge Velocity | 17+ | ✔️ | ✔️ ![AGPL] | HSQLDB (local), MariaDB, MySQL, PostgreSQL | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | AdvancedBan
BanManager
LiteBans
vanilla | -| AdvancedBan | ![Bukkit]Bukkit
Bungee | 8+ | ✔️ | ✔️ ![GPL] | HSQLDB (local), MariaDB, MySQL | ❌ | ❌ | ❓ | ❌ | ❌ | ✔️ | ❌ | ➖ | ❌ | | -| BanManager | ![Bukkit]Bukkit
Bungee
Sponge | 8+ | ✔️ | ➖️ ![CC-BY-NC] | H2 (local), MariaDB, MySQL | ❌️ | ❌ | ➖ | ✔️ | ✔️ | ❌ | ➖ | ✔️ | ✔️ | AdvancedBan
vanilla | -| LiteBans | ![Bukkit]Bukkit
Bungee
Velocity | 8+ | ❌ | ❌ Proprietary | H2 (local), MariaDB, MySQL, PostgreSQL | ❓ | ➖ | ✔️ | ✔️ | ❓ | ✔️ | ➖ | ✔️ | ❌ | AdvancedBan
BanManager
BungeeAdminTools
MaxBans
UltraBans
vanilla | -| vanilla | ![Bukkit]Bukkit
Sponge | 8+ | ✔️ | ❌ Proprietary | Flatfile | ✔️ | ✔️ | ✔️ | ❌ | NA | ❌ | ❌ | ✔️ | ❌ | | +| Plugin | Supported Platforms | Java Req. | Free (Gratis) | Open License | Debuggable, Modifiable | Databases Available | Thread-Safe Design | Stable API | Geyser Support | Multi-Instance Support | Connection Pool | Exemption | Server Scopes | Uses UUIDs | Schema Integrity | Switch Storage Backends | Import From | +|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|---------------|-----------------|------------------------|--------------------------------------------|--------------------|------------|----------------|------------------------|-----------------|-----------|---------------|------------|------------------|-------------------------|--------------------------------------------------------------------------------------------------------------------------------| +| LibertyBans | ![Bukkit]Bukkit
Bungee
Sponge Velocity | 17+ | ✔️ | ✔️ ![AGPL] | ✔️ | HSQLDB (local), MariaDB, MySQL, PostgreSQL | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | AdvancedBan
BanManager
LiteBans
vanilla | +| AdvancedBan | ![Bukkit]Bukkit
Bungee | 8+ | ✔️ | ✔️ ![GPL] | ✔️ | HSQLDB (local), MariaDB, MySQL | ❌ | ❌ | ❓ | ❌ | ❌ | ✔️ | ❌ | ➖️ | ❌ | ❌ | | +| BanManager | ![Bukkit]Bukkit
Bungee
Sponge | 8+ | ✔️ | ➖️️ ![CC-BY-NC] | ✔️ | H2 (local), MariaDB, MySQL | ❌️ | ❌ | ➖️️ | ✔️ | ✔️ | ❌ | ➖️ | ✔️ | ✔️ | ➖️ | AdvancedBan
vanilla | +| LiteBans | ![Bukkit]Bukkit
Bungee
Velocity | 8+ | ❌ | ❌ Proprietary | ❌ Obfuscated | H2 (local), MariaDB, MySQL, PostgreSQL | ❓ | ➖️ | ➖️️ | ✔️ | ❓ | ✔️ | ➖️ | ✔️ | ❌ | ✔️ | AdvancedBan
BanHammer**
BanManager
BungeeAdminTools**
LibertyBans ➖️
MaxBans**
UltraBans**
vanilla | +| vanilla | ![Bukkit]Bukkit
Sponge | 8+ | ✔️ | ❌ Proprietary | NA | Flatfile | ✔️ | ✔️ | ✔️ | ❌ | NA | ❌ | ❌ | ✔️ | ❌ | NA | | Legend: ✔️ – Yes -➖ – Partially +➖️ – Partially ❌ – No @@ -36,6 +36,16 @@ Legend: NA - Not applicable +** - This plugin has been abandoned for at least 3 years. + +### Why does LibertyBans check all the boxes? + +It didn't used to. This chart was created when LibertyBans didn't have multi-instance support, exemption, server scopes, or switching storage backends. With this exception of switching storage, all these features were listed in the table, but they were unimplemented and unrequested -- with no plans to add them! Back then, LibertyBans used to receive ❌ or ➖️ in many areas. + +Over time, features were requested and implemented, so LibertyBans fulfills these categories now and receives ✔️. Competitor plugins still have capabilities not found in LibertyBans, but they aren't deemed sufficiently important or central to the purpose of punishment to have a place on the table. + +How do we know the features listed in the table are sufficiently important and central to the purpose of a punishment plugin? Well, in most cases, we *predicted* which features would be most important, so we placed them in the table. The predictions turned out to be correct, since the categories in the table were the most popular feature requests. + ## Categories Some categories require further explanation because they are not self-explanatory. They are described here. @@ -60,6 +70,14 @@ BanManager's license prohibits commercial use. This makes it **illegal** to use Disclaimer: This is not legal advice and the writer is not a lawyer. +### Debuggable, Modifiable + +Whether the plugin can be debugged by users and modified by developers. +* Debugging is a critical tool because it allows administrators to fix problems deep in the software stack. This remains true regardless of whether a plugin is at fault for the bug. +* Modifying a plugin safeguards user and developer freedom. It provides an escape hatch if the original author is unwilling to implement a feature or a workaround. + +LiteBans is obfuscated, meaning its plugin jar is intentionally scrambled to prevent anyone from inspecting its operations or making any sort of modification. + ### Thread-Safe Design If a plugin is not thread-safe, it will have unreliable and buggy behavior. However, some of this unreliable behavior may only occur in exceptional and rare circumstances, making it very hard to debug and diagnose. @@ -74,6 +92,12 @@ See [semver.org](https://semver.org/) and the detailed plugin-specific compariso LiteBans' *partial* ranking: LiteBans follows the semver constraint of restricting breaking changes to major versions, but does not follow semver for additions of new API, which *should* be done in minor releases. +### Geyser Support + +Whether the plugin is compatible with Geyser/Floodgate, a plugin which allows Bedrock Edition players to join the server or proxy by changing usernames. + +LiteBans' *partial* ranking: LiteBans claims Geyser compatibility, but its documentation suggests the period character is the only supported prefix for Bedrock usernames. + ### Multi-Instance Support This feature allows synchronizing punishments across multiple instances of the given plugin. It is relevant for proxies. @@ -106,3 +130,19 @@ Whether the data types defined by the plugin's database schema have appropriate * Otherwise, bugs in the plugin may corrupt user data. Data corruption, if it occurs, is not easy to recover from and may require manually interfacing with the database. + +### Switch Storage Backends + +Whether the user can switch between the supported database backends. This feature is also known as "self-importing." + +BanManager's *partial* ranking: BanManager only supports converting from H2 to MySQL/MariaDB, but it is not possible to switch back to H2. + +### Import From + +The other punishment suites this plugin can import from. + +LiteBans' *partial* ranking with respect to LibertyBans: LiteBans does not import server scopes, although scopes are a common feature between them. + +-------------------------------------------------------------------------- + +As a closing note, this information is kept up-to-date within a reasonable timeframe. We welcome PRs from those affiliated with other plugins to update this information when necessary.