diff --git a/bans-api/src/main/java/space/arim/libertybans/api/scope/ScopeManager.java b/bans-api/src/main/java/space/arim/libertybans/api/scope/ScopeManager.java index dc6767ba7..47a685e2c 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/scope/ScopeManager.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/scope/ScopeManager.java @@ -1,27 +1,28 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify +/* + * 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-api is distributed in the hope that it will be useful, + * + * 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-api. If not, see + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.api.scope; +import java.util.Optional; +import java.util.Set; + /** * Factory which produces {@link ServerScope}s - * - * @author A248 * */ public interface ScopeManager { @@ -29,9 +30,8 @@ public interface ScopeManager { /** * Gets a scope applying to a specific server.
*
- * The server string must be non{@literal -}empty and less long than an - * implementation{@literal -}dependent length. The current limit is set at 32 - * characters but this may change. + * The server string must not be empty and must be less long than a certain length dependent + * on the implementation. The current limit is set at 32 characters but this may change. * * @param server the server * @return a scope applying to the server @@ -40,17 +40,70 @@ public interface ScopeManager { ServerScope specificScope(String server); /** - * Gets the global scope, applying to all servers + * Gets a scope applying to a user defined category.
+ *
+ * The category string must not be empty and must be less long than a certain length dependent + * on the implementation. The current limit is set at 32 characters but this may change. + * + * @param category the category + * @return a scope applying to the category + * @throws IllegalArgumentException if {@code category} is empty or too big + */ + ServerScope category(String category); + + /** + * Gets the global scope, applying to all servers and categories * * @return the global scope */ ServerScope globalScope(); /** - * Gets a scope applying to the current server + * Gets a scope applying to the current server. This will yield a scope which, when used to punish players, + * will enforce their punishments on the current server only.
+ *
+ * This is usually not the appropriate scope with which to select punishments (because only punishments + * made specifically for the server would be selected). Instead, see {@link #scopesApplicableToCurrentServer()}.
+ *
+ * The "current server" is defined with respect to an instance of the API implementation. Thus, using this method + * will apply to the proxy, backend server, or other application running the current instance.
+ *
+ * Please note that, in some circumstances, the current server scope will not be available, depending on user + * configuration. Use {@link #currentServerScopeOrFallback()} if you want to fallback to the scope specified + * in the user's configuration for this purpose. * - * @return a scope applying to the current server + * @return if available, the scope applying to the current server only. On a proxy instance, using this scope will + * affect the entire proxy. On a backend server it will affect the backend server. + */ + Optional currentServerScope(); + + /** + * Gets the current server scope if it is available (see {@link #currentServerScope()}. Otherwise, + * uses the server scope which the user has configured as a fallback for the current server scope + * when it is unavailable. + * + * @return the scope applying to the current server only, or the fallback if not available + */ + ServerScope currentServerScopeOrFallback(); + + /** + * Gets all scopes applying to the current server as defined by user configuration. This will necessarily + * include both the global scope ({@link #globalScope()}) and the current server scope + * ({@link #currentServerScope()}.
+ *
+ * The "current server" is defined with respect to an instance of the API implementation. Thus, using this method + * will yield scopes applying to the proxy, backend server, or other application running this instance. + * + * @return all scopes applicable to the current server + */ + Set scopesApplicableToCurrentServer(); + + /** + * The scope configured as the default with which to punish. For example, if an operator punishes a victim without + * specifying a scope explicitly, this scope is used. + * + * @return the default punishing scope */ - ServerScope currentServerScope(); + ServerScope defaultPunishingScope(); } diff --git a/bans-api/src/main/java/space/arim/libertybans/api/scope/ServerScope.java b/bans-api/src/main/java/space/arim/libertybans/api/scope/ServerScope.java index 77ccd806d..762193a73 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/scope/ServerScope.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/scope/ServerScope.java @@ -1,19 +1,19 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify +/* + * 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-api is distributed in the hope that it will be useful, + * + * 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-api. If not, see + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.api.scope; @@ -32,7 +32,11 @@ public interface ServerScope { * * @param server the server name * @return true if applicable, false otherwise + * @deprecated For categorical scopes produced from {@link ScopeManager#category(String)}, it may not be known + * whether a scope applies to a certain server, because the configuration for the other server is not accessible + * by the current API instance. */ + @Deprecated boolean appliesTo(String server); /** diff --git a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/LayoutsConfig.java b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/LayoutsConfig.java index ead5f1535..22ae4938c 100644 --- a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/LayoutsConfig.java +++ b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/LayoutsConfig.java @@ -27,11 +27,14 @@ import space.arim.dazzleconf.annote.ConfKey; import space.arim.dazzleconf.annote.SubSection; import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.addon.AddonConfig; import space.arim.libertybans.core.commands.extra.DurationParser; import space.arim.libertybans.core.config.ParsedDuration; import space.arim.libertybans.core.config.PunishmentAdditionSection; import space.arim.libertybans.core.config.VictimPermissionSection; +import space.arim.libertybans.core.scope.ConfiguredScope; +import space.arim.libertybans.core.scope.GlobalScope; import java.util.Map; @@ -130,11 +133,15 @@ static Map defaultTracks() { record SimpleLadder(boolean countActive, Map progressions) implements Track.Ladder {} - record SimpleProgression(PunishmentType type, String reason, ParsedDuration duration, String scope) + record SimpleProgression(PunishmentType type, String reason, ParsedDuration duration, ConfiguredScope scope) implements Track.Ladder.Progression { SimpleProgression(PunishmentType type, String reason, String duration) { - this(type, reason, new ParsedDuration(duration, new DurationParser().parse(duration)), ""); + this( + type, reason, + new ParsedDuration(duration, new DurationParser().parse(duration)), + ConfiguredScope.defaultPunishingScope() + ); } } return Map.of( diff --git a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/Track.java b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/Track.java index d4a42ac54..cf696937d 100644 --- a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/Track.java +++ b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/Track.java @@ -24,6 +24,7 @@ import space.arim.dazzleconf.annote.SubSection; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.core.config.ParsedDuration; +import space.arim.libertybans.core.scope.ConfiguredScope; import java.util.Map; @@ -45,8 +46,7 @@ interface Progression { ParsedDuration duration(); - @ConfValidator(ForcedEmptyScopeValidator.class) - String scope(); + ConfiguredScope scope(); } } diff --git a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/TrackCalculator.java b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/TrackCalculator.java index 08f760236..01c473658 100644 --- a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/TrackCalculator.java +++ b/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/TrackCalculator.java @@ -44,7 +44,7 @@ public CalculationResult compute(EscalationTrack escalationTrack, Victim victim, var progression = findProgression(existingPunishments + 1); return new PunishmentDetailsCalculator.CalculationResult( progression.type(), progression.reason(), - progression.duration().duration(), scopeManager.globalScope() + progression.duration().duration(), progression.scope().actualize(scopeManager) ); } diff --git a/bans-core-addons/shortcut-reasons/src/test/java/space/arim/libertybans/core/shortcutreasons/ShortcutReasonsListenerTest.java b/bans-core-addons/shortcut-reasons/src/test/java/space/arim/libertybans/core/shortcutreasons/ShortcutReasonsListenerTest.java index 9799a1b0c..64abe23c0 100644 --- a/bans-core-addons/shortcut-reasons/src/test/java/space/arim/libertybans/core/shortcutreasons/ShortcutReasonsListenerTest.java +++ b/bans-core-addons/shortcut-reasons/src/test/java/space/arim/libertybans/core/shortcutreasons/ShortcutReasonsListenerTest.java @@ -33,13 +33,13 @@ import space.arim.libertybans.api.punish.DraftPunishment; import space.arim.libertybans.api.punish.DraftPunishmentBuilder; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.addon.AddonCenter; import space.arim.libertybans.core.addon.shortcutreasons.ShortcutReasonsAddon; import space.arim.libertybans.core.addon.shortcutreasons.ShortcutReasonsConfig; import space.arim.libertybans.core.addon.shortcutreasons.ShortcutReasonsListener; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.event.PunishEventImpl; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.omnibus.DefaultOmnibus; import space.arim.omnibus.Omnibus; @@ -93,7 +93,8 @@ public void noMatch(@Mock DraftPunishment draftPunishment) { @Test public void simpleSubstitute(@Mock DraftPunishment draftPunishment, - @Mock DraftPunishment newPunishment, @Mock DraftPunishmentBuilder newBuilder) { + @Mock DraftPunishment newPunishment, @Mock DraftPunishmentBuilder newBuilder, + @Mock ServerScope globalScope) { when(config.shortcutIdentifier()).thenReturn("#"); when(config.shortcuts()).thenReturn(Map.of( "hacking", "hello hackers", @@ -104,7 +105,7 @@ public void simpleSubstitute(@Mock DraftPunishment draftPunishment, when(draftPunishment.getVictim()).thenReturn(AddressVictim.of(new byte[4])); when(draftPunishment.getOperator()).thenReturn(ConsoleOperator.INSTANCE); when(draftPunishment.getReason()).thenReturn("#hacking"); - when(draftPunishment.getScope()).thenReturn(ScopeImpl.GLOBAL); + when(draftPunishment.getScope()).thenReturn(globalScope); when(drafter.draftBuilder()).thenReturn(newBuilder); when(newBuilder.type(PunishmentType.KICK)).thenReturn(newBuilder); @@ -112,7 +113,7 @@ public void simpleSubstitute(@Mock DraftPunishment draftPunishment, when(newBuilder.operator(ConsoleOperator.INSTANCE)).thenReturn(newBuilder); when(newBuilder.reason("hello hackers")).thenReturn(newBuilder); when(newBuilder.duration(any())).thenReturn(newBuilder); - when(newBuilder.scope(ScopeImpl.GLOBAL)).thenReturn(newBuilder); + when(newBuilder.scope(globalScope)).thenReturn(newBuilder); when(newBuilder.build()).thenReturn(newPunishment); var event = fireEvent(draftPunishment); diff --git a/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsConfig.java b/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsConfig.java index 71e21a729..3a11ffd5f 100644 --- a/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsConfig.java +++ b/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsConfig.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 @@ -25,10 +25,9 @@ import space.arim.dazzleconf.annote.ConfKey; import space.arim.dazzleconf.annote.SubSection; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.addon.AddonConfig; import space.arim.libertybans.core.config.ParsedDuration; -import space.arim.libertybans.core.scope.ScopeImpl; +import space.arim.libertybans.core.scope.ConfiguredScope; import java.time.Duration; import java.util.Map; @@ -68,10 +67,7 @@ public interface WarnActionsConfig extends AddonConfig { "Punishments to perform.", "", "Each punishment is performed as if by the console. The setting broadcast-notification controls whether", - "punishment notifications will be broadcast as usual; if false, no notifications are sent.", - "", - "Note that 'scope' is currently useless -- it is reserved for a future plugin feature.", - "In LibertyBans 1.1.0, a feature will be added to enable punishments scoped to specific backend servers." + "punishment notifications will be broadcast as usual; if false, no notifications are sent." }) @ConfDefault.DefaultObject("autoPunishmentsDefaults") Map autoPunishments(); @@ -95,8 +91,8 @@ public ParsedDuration duration() { } @Override - public ServerScope scope() { - return ScopeImpl.GLOBAL; + public ConfiguredScope scope() { + return ConfiguredScope.defaultPunishingScope(); } @Override @@ -121,8 +117,8 @@ public ParsedDuration duration() { } @Override - public ServerScope scope() { - return ScopeImpl.GLOBAL; + public ConfiguredScope scope() { + return ConfiguredScope.defaultPunishingScope(); } @Override @@ -141,7 +137,7 @@ interface WarnActionPunishment { ParsedDuration duration(); - ServerScope scope(); + ConfiguredScope scope(); @ConfKey("broadcast-notification") boolean broadcastNotification(); diff --git a/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListener.java b/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListener.java index 5a32d97da..86adcc7d9 100644 --- a/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListener.java +++ b/bans-core-addons/warn-actions/src/main/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListener.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 @@ -34,6 +34,7 @@ import space.arim.libertybans.api.punish.EnforcementOptions; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.env.EnvEnforcer; @@ -48,17 +49,19 @@ public final class WarnActionsListener implements AsynchronousEventConsumer envEnforcer; private final WarnActionsAddon addon; @Inject - public WarnActionsListener(FactoryOfTheFuture futuresFactory, PunishmentDrafter drafter, + public WarnActionsListener(FactoryOfTheFuture futuresFactory, PunishmentDrafter drafter, ScopeManager scopeManager, PunishmentSelector selector, InternalFormatter formatter, EnvEnforcer envEnforcer, WarnActionsAddon addon) { this.futuresFactory = futuresFactory; this.drafter = drafter; + this.scopeManager = scopeManager; this.selector = selector; this.formatter = formatter; this.envEnforcer = envEnforcer; @@ -131,7 +134,7 @@ private CentralisedFuture handleAutoPunish() { .operator(ConsoleOperator.INSTANCE) .reason(additionalPunishment.reason()) .duration(additionalPunishment.duration().duration()) - .scope(additionalPunishment.scope()) + .scope(additionalPunishment.scope().actualize(scopeManager)) .build(); EnforcementOptions enforcementOptions = draftPunishment .enforcementOptionsBuilder() diff --git a/bans-core-addons/warn-actions/src/test/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListenerTest.java b/bans-core-addons/warn-actions/src/test/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListenerTest.java index 0f1ac0c41..647d36884 100644 --- a/bans-core-addons/warn-actions/src/test/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListenerTest.java +++ b/bans-core-addons/warn-actions/src/test/java/space/arim/libertybans/core/addon/warnactions/WarnActionsListenerTest.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 space.arim.libertybans.api.punish.EnforcementOptions; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.api.select.SelectionOrder; @@ -43,6 +44,7 @@ import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.config.ParsedDuration; import space.arim.libertybans.core.env.EnvEnforcer; +import space.arim.libertybans.core.scope.ConfiguredScope; import space.arim.omnibus.DefaultOmnibus; import space.arim.omnibus.events.EventFireController; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; @@ -92,10 +94,10 @@ public WarnActionsListenerTest(@Mock PunishmentDrafter drafter, @Mock Punishment } @BeforeEach - public void setWarnActionsListener() { + public void setWarnActionsListener(@Mock ScopeManager scopeManager) { WarnActionsAddon addon = new WarnActionsAddon(addonCenter, new DefaultOmnibus(), () -> warnActionsListener); warnActionsListener = new WarnActionsListener( - futuresFactory, drafter, selector, formatter, envEnforcer, addon + futuresFactory, drafter, scopeManager, selector, formatter, envEnforcer, addon ); } @@ -172,7 +174,7 @@ public void theThirdWarnIsReached(@Mock SelectionOrderBuilder selectionBuilder, when(draftBuilder.reason("no reason at all")).thenReturn(draftBuilder); when(autoPunishment.duration()).thenReturn(new ParsedDuration("3h", Duration.ofHours(3L))); when(draftBuilder.duration(Duration.ofHours(3L))).thenReturn(draftBuilder); - when(autoPunishment.scope()).thenReturn(autoPunishmentScope); + when(autoPunishment.scope()).thenReturn(ConfiguredScope.create(autoPunishmentScope)); when(draftBuilder.scope(autoPunishmentScope)).thenReturn(draftBuilder); when(draftBuilder.build()).thenReturn(draftPunishment); when(draftPunishment.enforcementOptionsBuilder()).thenReturn(enforcementOptionsBuilder); diff --git a/bans-core/pom.xml b/bans-core/pom.xml index 25e0a745b..125a33dac 100644 --- a/bans-core/pom.xml +++ b/bans-core/pom.xml @@ -1,3 +1,22 @@ + + @@ -101,6 +120,7 @@ VARBINARY(16) BLOB ALTER VIEW + CAST(0 AS SMALLINT) filesystem:src/main/resources/database-migrations @@ -188,18 +208,18 @@ .*\.operator$ ^UUID$ - - space.arim.libertybans.api.scope.ServerScope - space.arim.libertybans.core.database.jooq.ServerScopeConverter - .*\.scope$ - ^CHARACTER\ VARYING\(32\)$ - space.arim.libertybans.api.punish.EscalationTrack space.arim.libertybans.core.database.jooq.EscalationTrackConverter .*\.track$ ^CHARACTER\ VARYING\(129\)$ + + space.arim.libertybans.core.scope.ScopeType + space.arim.libertybans.core.database.jooq.ScopeTypeConverter + ^(scopes\.type|punishments\.scope_type|simple_.*\.scope_type|applicable_.*\.scope_type)$ + ^SMALLINT$ + @@ -706,11 +726,6 @@ org.mockito mockito-junit-jupiter - - net.kyori - adventure-text-serializer-legacy - test - net.kyori adventure-text-serializer-plain @@ -782,6 +797,10 @@ space.arim.api arimapi-util-testing + + net.kyori + adventure-text-serializer-legacy + org.hsqldb diff --git a/bans-core/src/main/java/module-info.java b/bans-core/src/main/java/module-info.java index ecd6aed24..f10e6ce29 100644 --- a/bans-core/src/main/java/module-info.java +++ b/bans-core/src/main/java/module-info.java @@ -26,6 +26,7 @@ requires static java.compiler; requires net.kyori.adventure; requires net.kyori.examination.api; + requires net.kyori.adventure.text.serializer.legacy; requires org.flywaydb.core; requires static org.checkerframework.checker.qual; requires static org.jetbrains.annotations; @@ -53,6 +54,7 @@ exports space.arim.libertybans.core.database.execute to space.arim.injector; exports space.arim.libertybans.core.database.flyway to org.flywaydb.core; exports space.arim.libertybans.core.env; + exports space.arim.libertybans.core.env.message; exports space.arim.libertybans.core.event to space.arim.injector, space.arim.libertybans.core.addon.shortcutreasons, space.arim.libertybans.core.addon.layouts; exports space.arim.libertybans.core.importing; exports space.arim.libertybans.core.punish; 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 7e9f146cb..cb4ad9264 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 @@ -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 @@ -33,7 +33,7 @@ import space.arim.libertybans.core.punish.StandardGlobalEnforcement; import space.arim.libertybans.core.punish.StandardLocalEnforcer; import space.arim.libertybans.core.scope.InternalScopeManager; -import space.arim.libertybans.core.scope.Scoper; +import space.arim.libertybans.core.scope.StandardScopeManager; import space.arim.libertybans.core.selector.InternalSelector; import space.arim.libertybans.core.selector.SelectorImpl; @@ -67,7 +67,7 @@ public InternalSelector selector(SelectorImpl selector) { return selector; } - public InternalScopeManager scopeManager(Scoper scopeManager) { + public InternalScopeManager scopeManager(StandardScopeManager scopeManager) { return scopeManager; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/ArrayCommandPackage.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/ArrayCommandPackage.java index 272b5f897..b8142893e 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/ArrayCommandPackage.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/ArrayCommandPackage.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 @@ -19,6 +19,7 @@ package space.arim.libertybans.core.commands; +import java.util.Locale; import java.util.NoSuchElementException; import java.util.StringJoiner; @@ -46,7 +47,10 @@ public static ArrayCommandPackage create(String...args) { // Maintains the guarantee that position never refers to a hidden argument private void movePastHiddenArguments() { - while (position < args.length && args[position].startsWith(HIDDEN_ARG_PREFIX)) { + String currentArg; + while (position < args.length + && !(currentArg = args[position]).isEmpty() + && currentArg.charAt(0) == HIDDEN_ARG_PREFIX) { position++; } } @@ -73,7 +77,7 @@ public boolean hasNext() { @Override public boolean findHiddenArgument(String argument) { - String searchFor = "-" + argument; + String searchFor = '-' + argument; for (int n = 0; n < position; n++) { if (args[n].equalsIgnoreCase(searchFor)) { return true; @@ -82,6 +86,17 @@ public boolean findHiddenArgument(String argument) { return false; } + @Override + public String findHiddenArgumentSpecifiedValue(String argPrefix) { + String searchFor = '-' + argPrefix + '='; + for (int n = 0; n < position; n++) { + if (args[n].toLowerCase(Locale.ROOT).startsWith(searchFor)) { + return args[n].substring(searchFor.length()); + } + } + return null; + } + @Override public String allRemaining() { StringJoiner joiner = new StringJoiner(" "); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandPackage.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandPackage.java index 0eae9a606..38e2b7445 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandPackage.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandPackage.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 @@ -19,6 +19,8 @@ package space.arim.libertybans.core.commands; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Iterator; /** @@ -43,7 +45,7 @@ public interface CommandPackage extends Iterator { /** * The prefix denoting a hidden argument */ - String HIDDEN_ARG_PREFIX = "-"; + char HIDDEN_ARG_PREFIX = '-'; /** * Gets the current argument and advances to the next argument @@ -79,6 +81,16 @@ public interface CommandPackage extends Iterator { */ boolean findHiddenArgument(String argument); + /** + * Finds a certain hidden argument such as "arg=value" and yields the associated value. + * This is akin to using {@link #findHiddenArgument(String)} except the argument is a wildcard + * wih respect to the specified value. + * + * @param argPrefix the first part of the hidden argument, i.e. "arg" in "arg=value" + * @return the value if it exists + */ + @Nullable String findHiddenArgumentSpecifiedValue(String argPrefix); + /** * Concatenates the current argument and all remaining arguments. This would * be equivalent to joining all calls to {@link #next()}, separating with spaces, diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/ListCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/ListCommands.java index 26f5ae749..98cf20b61 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/ListCommands.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/ListCommands.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,14 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.api.select.SelectionBase; import space.arim.libertybans.api.select.SelectionBuilderBase; import space.arim.libertybans.api.select.SelectionOrderBuilder; import space.arim.libertybans.api.select.SelectionPredicate; import space.arim.libertybans.core.commands.extra.AsCompositeWildcard; +import space.arim.libertybans.core.commands.extra.ParseScope; import space.arim.libertybans.core.commands.extra.ParseVictim; import space.arim.libertybans.core.commands.extra.TabCompletion; import space.arim.libertybans.core.config.InternalFormatter; @@ -187,8 +189,15 @@ private ReactionStage parsePageThenExecute(SelectionBuilderBase sele sender().sendMessage(section.usage()); return completedFuture(null); } + SelectionPredicate scopeSelection = argumentParser().parseScope( + sender(), command(), ParseScope.selectionPredicate() + ); + if (scopeSelection == null) { + return completedFuture(null); + } int perPage = section.perPage(); SelectionBase selection = selectionBuilder + .scopes(scopeSelection) .skipFirstRetrieved(perPage * (selectedPage - 1)) .limitToRetrieve(perPage) .build(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java index 28a646e21..69de3e27f 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java @@ -25,7 +25,9 @@ import space.arim.libertybans.api.event.BasePunishEvent; import space.arim.libertybans.api.punish.DraftPunishment; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.commands.extra.DurationParser; +import space.arim.libertybans.core.commands.extra.ParseScope; import space.arim.libertybans.core.commands.extra.ReasonsConfig; import space.arim.libertybans.core.commands.extra.TabCompletion; import space.arim.libertybans.core.config.AdditionAssistant; @@ -111,6 +113,13 @@ public String exemptionCategory() { @Override public @Nullable DraftPunishment buildDraftSanction(Victim victim, Duration duration, String targetArg) { + + ServerScope serverScope = argumentParser().parseScope( + sender, command, ParseScope.fallbackToDefaultPunishingScope() + ); + if (serverScope == null) { + return null; + } String reason; if (command.hasNext()) { reason = command.allRemaining(); @@ -132,6 +141,7 @@ public String exemptionCategory() { .operator(sender.getOperator()) .reason(reason) .duration(duration) + .scope(serverScope) .build(); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/StringCommandPackage.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/StringCommandPackage.java index a72922e5b..3774bb25a 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/StringCommandPackage.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/StringCommandPackage.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 @@ -19,17 +19,19 @@ package space.arim.libertybans.core.commands; -import java.util.HashSet; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; public final class StringCommandPackage implements CommandPackage { private final String args; /** Position never refers to a hidden command argument */ private transient int position; - private final Set hiddenArguments = new HashSet<>(); + private final Map hiddenArguments = new HashMap<>(); private StringCommandPackage(String args) { this.args = args; @@ -73,14 +75,19 @@ private void consumeHiddenArguments() { if (position == args.length()) { return; } - if (args.charAt(position) != '-') { + if (args.charAt(position) != HIDDEN_ARG_PREFIX) { return; } // Position on the first character of the hidden argument position++; // Collect the hidden argument as if it were a plain argument String hiddenArgument = consumeCurrentArgument(); - hiddenArguments.add(hiddenArgument.toLowerCase(Locale.ROOT)); + // Then parse it and add it to our known collection + String[] hiddenArgPieces = hiddenArgument.split("=", 2); + hiddenArguments.put( + hiddenArgPieces[0].toLowerCase(Locale.ROOT), + hiddenArgPieces.length == 2 ? hiddenArgPieces[1] : null + ); // At this point we will be positioned on the next argument } } @@ -110,7 +117,12 @@ public boolean hasNext() { @Override public boolean findHiddenArgument(String argument) { - return hiddenArguments.contains(argument.toLowerCase(Locale.ROOT)); + return hiddenArguments.containsKey(argument); + } + + @Override + public @Nullable String findHiddenArgumentSpecifiedValue(String argPrefix) { + return hiddenArguments.get(argPrefix); } @Override @@ -124,7 +136,7 @@ public String allRemaining() { public CommandPackage copy() { StringCommandPackage copy = new StringCommandPackage(args); copy.position = position; - copy.hiddenArguments.addAll(hiddenArguments); + copy.hiddenArguments.putAll(hiddenArguments); return copy; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ArgumentParser.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ArgumentParser.java index 40253fe32..480e639b6 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ArgumentParser.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ArgumentParser.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 @@ -19,8 +19,10 @@ package space.arim.libertybans.core.commands.extra; +import org.checkerframework.checker.nullness.qual.Nullable; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.core.commands.CommandPackage; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.env.UUIDAndAddress; import space.arim.omnibus.util.concurrent.CentralisedFuture; @@ -29,12 +31,14 @@ public interface ArgumentParser { - CentralisedFuture parseOrLookupUUID(CmdSender sender, String targetArg); + CentralisedFuture<@Nullable UUID> parseOrLookupUUID(CmdSender sender, String targetArg); - CentralisedFuture parseVictim(CmdSender sender, String targetArg, ParseVictim how); + CentralisedFuture<@Nullable Victim> parseVictim(CmdSender sender, String targetArg, ParseVictim how); - CentralisedFuture parsePlayer(CmdSender sender, String targetArg); + CentralisedFuture<@Nullable UUIDAndAddress> parsePlayer(CmdSender sender, String targetArg); + + CentralisedFuture<@Nullable Operator> parseOperator(CmdSender sender, String operatorArg); + + @Nullable R parseScope(CmdSender sender, CommandPackage command, ParseScope how); - CentralisedFuture parseOperator(CmdSender sender, String operatorArg); - } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ParseScope.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ParseScope.java new file mode 100644 index 000000000..f758f9075 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/ParseScope.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.core.commands.extra; + +import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.api.select.SelectionPredicate; + +public interface ParseScope { + + R explicitScope(ServerScope scope); + + R defaultValue(ScopeManager scopeManager); + + static ParseScope fallbackToDefaultPunishingScope() { + return new ParseScope<>() { + @Override + public ServerScope explicitScope(ServerScope scope) { + return scope; + } + + @Override + public ServerScope defaultValue(ScopeManager scopeManager) { + return scopeManager.defaultPunishingScope(); + } + }; + } + + static ParseScope> selectionPredicate() { + return new ParseScope<>() { + @Override + public SelectionPredicate explicitScope(ServerScope scope) { + return SelectionPredicate.matchingOnly(scope); + } + + @Override + public SelectionPredicate defaultValue(ScopeManager scopeManager) { + return SelectionPredicate.matchingAll(); + } + }; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/StandardArgumentParser.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/StandardArgumentParser.java index 48e86d493..2eeb5ac95 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/StandardArgumentParser.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/extra/StandardArgumentParser.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 @@ -20,6 +20,7 @@ package space.arim.libertybans.core.commands.extra; import jakarta.inject.Inject; +import org.checkerframework.checker.nullness.qual.Nullable; import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.CompositeVictim; import space.arim.libertybans.api.ConsoleOperator; @@ -28,26 +29,36 @@ import space.arim.libertybans.api.PlayerOperator; import space.arim.libertybans.api.PlayerVictim; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.formatter.PunishmentFormatter; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.commands.CommandPackage; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.MessagesConfig; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.env.UUIDAndAddress; +import space.arim.libertybans.core.scope.InternalScopeManager; import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; +import java.util.Optional; import java.util.UUID; public class StandardArgumentParser implements ArgumentParser { private final FactoryOfTheFuture futuresFactory; private final Configs configs; + private final InternalScopeManager scopeManager; + private final PunishmentFormatter formatter; private final UUIDManager uuidManager; - + @Inject - public StandardArgumentParser(FactoryOfTheFuture futuresFactory, Configs configs, UUIDManager uuidManager) { + public StandardArgumentParser(FactoryOfTheFuture futuresFactory, Configs configs, InternalScopeManager scopeManager, + PunishmentFormatter formatter, UUIDManager uuidManager) { this.futuresFactory = futuresFactory; this.configs = configs; + this.scopeManager = scopeManager; + this.formatter = formatter; this.uuidManager = uuidManager; } @@ -55,12 +66,16 @@ private CentralisedFuture completedFuture(T value) { return futuresFactory.completedFuture(value); } + private MessagesConfig.All all() { + return configs.getMessagesConfig().all(); + } + private MessagesConfig.All.NotFound notFound() { - return configs.getMessagesConfig().all().notFound(); + return all().notFound(); } @Override - public CentralisedFuture parseOrLookupUUID(CmdSender sender, String targetArg) { + public CentralisedFuture<@Nullable UUID> parseOrLookupUUID(CmdSender sender, String targetArg) { return switch (targetArg.length()) { case 36 -> { UUID uuid; @@ -95,7 +110,7 @@ public CentralisedFuture parseOrLookupUUID(CmdSender sender, String target } @Override - public CentralisedFuture parseOperator(CmdSender sender, String operatorArg) { + public CentralisedFuture<@Nullable Operator> parseOperator(CmdSender sender, String operatorArg) { if (ContainsCI.containsIgnoreCase(configs.getMessagesConfig().formatting().consoleArguments(), operatorArg)) { return completedFuture(ConsoleOperator.INSTANCE); } @@ -105,7 +120,7 @@ public CentralisedFuture parseOperator(CmdSender sender, String operat } @Override - public CentralisedFuture parseVictim(CmdSender sender, String targetArg, ParseVictim how) { + public CentralisedFuture<@Nullable Victim> parseVictim(CmdSender sender, String targetArg, ParseVictim how) { NetworkAddress parsedAddress = AddressParser.parseIpv4(targetArg); if (parsedAddress != null) { return completedFuture(AddressVictim.of(parsedAddress)); @@ -132,7 +147,7 @@ public CentralisedFuture parseVictim(CmdSender sender, String targetArg, } @Override - public CentralisedFuture parsePlayer(CmdSender sender, String targetArg) { + public CentralisedFuture<@Nullable UUIDAndAddress> parsePlayer(CmdSender sender, String targetArg) { return uuidManager.lookupPlayer(targetArg).thenApply((optUuidAndAddress) -> { if (optUuidAndAddress.isEmpty()) { sender.sendMessage(notFound().player().replaceText("%TARGET%", targetArg)); @@ -142,4 +157,43 @@ public CentralisedFuture parsePlayer(CmdSender sender, String ta }); } + @Override + public @Nullable R parseScope(CmdSender sender, CommandPackage command, ParseScope how) { + ServerScope explicitScope; + String specificServer, category, rawScopeInput; + + if ((specificServer = command.findHiddenArgumentSpecifiedValue("server")) != null) { + explicitScope = scopeManager.specificScope(specificServer); + + } else if ((category = command.findHiddenArgumentSpecifiedValue("category")) != null) { + explicitScope = scopeManager.category(category); + + } else if ((rawScopeInput = command.findHiddenArgumentSpecifiedValue("scope")) != null) { + Optional parsed = scopeManager.parseFrom(rawScopeInput); + if (parsed.isEmpty()) { + sender.sendMessage(all().scopes().invalid().replaceText("%SCOPE_ARG%", rawScopeInput)); + return null; + } + explicitScope = parsed.get(); + } else { + if (!sender.hasPermission("libertybans.scope.default")) { + sender.sendMessage(all().scopes().noPermissionForDefault()); + return null; + } + return how.defaultValue(scopeManager); + } + String permissionSuffix = scopeManager.deconstruct(explicitScope, (type, value) -> { + return switch (type) { + case GLOBAL -> "global"; + case SERVER -> "server." + value; + case CATEGORY -> "category." + value; + }; + }); + if (!sender.hasPermission("libertybans.scope." + permissionSuffix)) { + sender.sendMessage(all().scopes().noPermission().replaceText("%SCOPE%", formatter.formatScope(explicitScope))); + return null; + } + return how.explicitScope(explicitScope); + } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java index 6539cf408..cc64dd727 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.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 @@ -38,7 +38,7 @@ import space.arim.dazzleconf.serialiser.Decomposer; import space.arim.dazzleconf.serialiser.FlexibleType; import space.arim.dazzleconf.serialiser.ValueSerialiser; -import space.arim.libertybans.core.scope.ScopeSerializer; +import space.arim.libertybans.core.scope.ConfiguredScope; final class ConfigSerialisers { @@ -51,7 +51,7 @@ static void addTo(ConfigurationOptions.Builder builder) { new ParsedDuration.Serializer(), new DateTimeFormatterSerialiser(), new ZoneIdSerialiser(), - new ScopeSerializer() + new ConfiguredScope.Serializer() ); } @@ -71,7 +71,7 @@ public Class getTargetClass() { @Override public Component deserialise(FlexibleType flexibleType) throws BadValueException { List lines = flexibleType.getList(FlexibleType::getString); - List components = new ArrayList<>(lines.size()); + List components = new ArrayList<>(2 * lines.size()); for (String line : lines) { if (!components.isEmpty()) { components.add(Component.newline()); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java b/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java index a51a78540..a8414b392 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java @@ -1,19 +1,19 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core is free software: you can redistribute it and/or modify +/* + * 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-core is distributed in the hope that it will be useful, + * + * 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-core. If not, see + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.core.config; @@ -32,7 +32,9 @@ public interface Configs extends Part { SqlConfig getSqlConfig(); ImportConfig getImportConfig(); - + + ScopeConfig getScopeConfig(); + CompletableFuture reloadConfigs(); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java b/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java index c62d23653..52f99bde6 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java @@ -465,7 +465,7 @@ public String formatDuration(Duration duration) { public String formatScope(ServerScope scope) { Objects.requireNonNull(scope, "scope"); String globalScopeDisplay = messages().formatting().globalScopeDisplay(); - return scopeManager.getServer(scope, globalScopeDisplay); + return scopeManager.display(scope, globalScopeDisplay); } } 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 79271957d..576632415 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 @@ -153,27 +153,10 @@ interface Commands { interface Platforms { - @SubSection - Bukkit bukkit(); - - interface Bukkit { - - @ConfKey("kick-via-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.", - "", - "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." - }) - @DefaultBoolean(false) - boolean kickViaPluginMessaging(); - - } - @SubSection Sponge sponge(); + @ConfHeader("Related to the Sponge platform") interface Sponge { @ConfKey("register-ban-service") @@ -202,6 +185,45 @@ interface Sponge { boolean registerBanService(); } + + @ConfKey("game-servers") + @SubSection + GameServers gameServers(); + + @ConfHeader("Related to game servers such as Spigot, Paper, and Sponge") + interface GameServers { + + @ConfKey("kick-via-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.", + "", + "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." + }) + @DefaultBoolean(false) + boolean kickViaPluginMessaging(); + + } + + @SubSection + Proxies proxies(); + + @ConfHeader("Related to proxies such as BungeeCord and Velocity") + interface Proxies { + + @ConfKey("enforce-server-switch") + @ConfComments({ + "Server-scoped punishments will be enforced by preventing server switches for players connecting ", + "to servers on which they are banned. The server name is obtained from the proxy API", + "", + "This option is enabled by default for full functionality. However, it increases performance usage,", + "so you may want to disable it if you do not use the scopes feature." + }) + @DefaultBoolean(true) + boolean enforceServerSwitch(); + + } } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java index 14446ea4f..b6ffe3baf 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java @@ -127,7 +127,26 @@ interface NotFound { @DefaultString("&cUnknown sub command. Displaying usage:") Component usage(); - + + @SubSection + Scopes scopes(); + + @ConfHeader("This section is only relevant if using the server scopes feature") + interface Scopes { + + @DefaultString("&cInvalid scope specified: &e%SCOPE_ARG%&c.") + ComponentText invalid(); + + @ConfKey("no-permission") + @DefaultString("&cYou may not use scope &e%SCOPE%&c.") + ComponentText noPermission(); + + @ConfKey("no-permission-for-default") + @DefaultString("&cYou may not use this command without specifying a scope.") + Component noPermissionForDefault(); + + } + } @SubSection 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 new file mode 100644 index 000000000..a140ac971 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ScopeConfig.java @@ -0,0 +1,124 @@ +/* + * 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.config; + +import space.arim.dazzleconf.annote.ConfComments; +import space.arim.dazzleconf.annote.ConfDefault; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.SubSection; +import space.arim.libertybans.core.scope.ConfiguredScope; + +import java.util.List; + +@ConfHeader({ + "This file is for proxies like BungeeCord and Velocity. It is irrelevant for single servers.", + "It controls scope-related settings for this particular server on the network.", + "", + "Unlike other configuration files, one should NOT copy the scope.yml across multiple server instances" +}) +public interface ScopeConfig { + + @ConfKey("default-punishing-scope") + @ConfComments({ + "The default scope used to punish players", + "", + "GLOBAL - uses the global scope", + "THIS_SERVER - applies to this server only via the server name", + "PRIMARY_CATEGORY - uses the first category listed in 'categories-applicable-to-this-server'.", + "", + "If you use PRIMARY_CATEGORY but no categories are configured, a warning is printed and THIS_SERVER is used." + }) + @ConfDefault.DefaultString("GLOBAL") + DefaultPunishingScope defaultPunishingScope(); + + enum DefaultPunishingScope { + GLOBAL, + THIS_SERVER, + PRIMARY_CATEGORY + } + + @ConfKey("categories-applicable-to-this-server") + @ConfComments({ + "The scope categories applicable to this server", + "", + "For example, multiple servers might fall under the 'kitpvp' category,", + "then staff members may use '-category=kitpvp' to create punishment applying to these servers" + }) + @ConfDefault.DefaultStrings({}) + List categoriesApplicableToThisServer(); + + @ConfKey("server-name") + @SubSection + ServerName serverName(); + + @ConfHeader({ + "Controls how the name of this server is detected for use with the server scope.", + "", + "The server name should correspond to the name of the backend server as configured on the proxy.", + "The name of the proxy itself is 'proxy' by default, unless overridden." + }) + 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'.", + "", + "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." + }) + @ConfDefault.DefaultBoolean(true) + boolean autoDetect(); + + @ConfKey("override-value") + @ConfComments({ + "If auto detection is disabled, this option should be set to the name of the server.", + "", + "Server names should be unique, but this is not a strict requirement.", + "If you want a scope applying to multiple servers, you should use categories instead." + }) + @ConfDefault.DefaultString("myserver") + String overrideValue(); + + @ConfKey("fallback-if-auto-detect-fails") + @ConfComments({ + "Auto detection requires a player to have logged in. But you might punish players, e.g. via console, before that.", + "By default, if auto detection has not yet occurred, the global scope will be used as a fallback.", + "The fallback scope may be configured here to something else." + }) + @ConfDefault.DefaultString("*") + ConfiguredScope fallbackIfAutoDetectFails(); + + } + + @ConfKey("require-permissions") + @ConfComments({ + "Whether to require permissions for using scopes:", + "- libertybans.scope.global, libertybans.scope.server., and libertybans.scope.category.", + " become requirements to use the relevant scopes explicitly.", + "- libertybans.scope.default must be granted to use commands without a scope argument" + }) + @ConfDefault.DefaultBoolean(false) + boolean requirePermissions(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/StandardConfigs.java b/bans-core/src/main/java/space/arim/libertybans/core/config/StandardConfigs.java index 6745053fb..8279c186d 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/StandardConfigs.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/StandardConfigs.java @@ -1,19 +1,19 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core is free software: you can redistribute it and/or modify +/* + * 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-core is distributed in the hope that it will be useful, + * + * 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-core. If not, see + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.core.config; @@ -47,6 +47,7 @@ public class StandardConfigs implements Configs { private final ConfigHolder messagesHolder = new ConfigHolder<>(MessagesConfig.class); private final ConfigHolder sqlHolder = new ConfigHolder<>(SqlConfig.class); private final ConfigHolder importHolder = new ConfigHolder<>(ImportConfig.class); + private final ConfigHolder scopeHolder = new ConfigHolder<>(ScopeConfig.class); @Inject public StandardConfigs(@Named("folder") Path folder) { @@ -72,7 +73,12 @@ public SqlConfig getSqlConfig() { public ImportConfig getImportConfig() { return importHolder.getConfigData(); } - + + @Override + public ScopeConfig getScopeConfig() { + return scopeHolder.getConfigData(); + } + @Override public CompletableFuture reloadConfigs() { Path langFolder = folder.resolve("lang"); @@ -90,6 +96,8 @@ public CompletableFuture reloadConfigs() { CompletableFuture reloadSql = sqlHolder.reload(folder.resolve("sql.yml")); // Reload import config CompletableFuture reloadImport = importHolder.reload(folder.resolve("import.yml")); + // Reload scope config + CompletableFuture reloadScope = scopeHolder.reload(folder.resolve("scope.yml")); // Reload messages config from specified language file CompletableFuture reloadMessages = CompletableFuture.allOf(futureLangFiles, reloadMain) @@ -100,9 +108,9 @@ public CompletableFuture reloadConfigs() { String langFileOption = mainHolder.getConfigData().langFile(); return messagesHolder.reload(langFolder.resolve("messages_" + langFileOption + ".yml")); }); - return CompletableFuture.allOf(reloadMessages, reloadSql, reloadImport).thenApply((ignore) -> { + return CompletableFuture.allOf(reloadMessages, reloadSql, reloadImport, reloadScope).thenApply((ignore) -> { ConfigResult combinedResult = ConfigResult.combinePessimistically( - reloadMain.join(), reloadMessages.join(), reloadSql.join(), reloadImport.join() + reloadMain.join(), reloadMessages.join(), reloadSql.join(), reloadImport.join(), reloadScope.join() ); return combinedResult != ConfigResult.IO_ERROR; }); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java index 15278ab99..5a0789097 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.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 @@ -185,14 +185,13 @@ private void setUsernameAndPassword() { private void setConfiguredDriver() { String jdbcUrl = getBaseUrl() + getUrlProperties(); - JdbcDriver jdbcDriver = vendor.driver(); if (config.useTraditionalJdbcUrl()) { - setDriverClassName(jdbcDriver.driverClassName()); + setDriverClassName(vendor.driver.driverClassName()); hikariConf.setJdbcUrl(jdbcUrl); } else { - hikariConf.setDataSourceClassName(jdbcDriver.dataSourceClassName()); + hikariConf.setDataSourceClassName(vendor.driver.dataSourceClassName()); hikariConf.addDataSourceProperty("url", jdbcUrl); } } @@ -288,7 +287,7 @@ private String getUrlProperties() { } }; logger.trace("Using connection properties {}", properties); - return vendor.driver().formatConnectionProperties(properties); + return vendor.driver.formatConnectionProperties(properties); } /** diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java b/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java index 47179351a..50c316663 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java @@ -35,7 +35,7 @@ public enum Vendor { ; private final String displayName; - private final JdbcDriver driver; + final JdbcDriver driver; Vendor(String displayName, JdbcDriver driver) { this.displayName = displayName; @@ -47,14 +47,6 @@ public String toString() { return displayName; } - JdbcDriver driver() { - return driver; - } - - public boolean hasDeleteFromJoin() { - return isMySQLLike(); - } - public boolean isRemote() { return isMySQLLike() || isPostgresLike(); } @@ -151,11 +143,18 @@ public String arbitraryBinaryType() { public String alterViewStatement() { return switch (this) { - case HSQLDB, MYSQL, MARIADB -> "ALTER VIEW"; + case HSQLDB, MARIADB, MYSQL -> "ALTER VIEW"; case POSTGRES, COCKROACH -> "CREATE OR REPLACE VIEW"; }; } + public String zeroSmallintLiteral() { + return switch (this) { + case HSQLDB, POSTGRES, COCKROACH -> "CAST(0 AS SMALLINT)"; + case MARIADB, MYSQL -> "0"; + }; + } + String getConnectionInitSql() { return switch (this) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/MigrateWithFlyway.java b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/MigrateWithFlyway.java index 2eef9eeb2..07e96b682 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/MigrateWithFlyway.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/MigrateWithFlyway.java @@ -43,6 +43,9 @@ public MigrateWithFlyway(DataSource dataSource, Vendor vendor) { public void migrate(JooqContext jooqContext) throws MigrationFailedException { Flyway flyway = createFlyway(new MigrationState(jooqContext)); try { + if (Boolean.getBoolean("libertybans.database.flywayrepair")) { + flyway.repair(); + } flyway.migrate(); } catch (FlywayException ex) { throw new MigrationFailedException(ex); @@ -52,7 +55,8 @@ public void migrate(JooqContext jooqContext) throws MigrationFailedException { private Flyway createFlyway(MigrationState migrationState) { var classProvider = migrationState.asClassProvider(List.of( V1__Principle.class, V16__Complete_migration_from_08x.class, - V31__Track_identifier_sequence.class, R__Set_Revision.class + V31__Track_identifier_sequence.class, V34__Scope_identifier_sequence.class, V38__Scope_migration.class, + R__Set_Revision.class )); return Flyway .configure(getClass().getClassLoader()) @@ -66,7 +70,8 @@ private Flyway createFlyway(MigrationState migrationState) { "uuidtype", vendor.uuidType(), "inettype", vendor.inetType(), "arbitrarybinarytype", vendor.arbitraryBinaryType(), - "alterviewstatement", vendor.alterViewStatement() + "alterviewstatement", vendor.alterViewStatement(), + "zerosmallintliteral", vendor.zeroSmallintLiteral() )) .locations("classpath:database-migrations") // Override classpath scanning diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V34__Scope_identifier_sequence.java b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V34__Scope_identifier_sequence.java new file mode 100644 index 000000000..1bf3dd757 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V34__Scope_identifier_sequence.java @@ -0,0 +1,44 @@ +/* + * 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.database.flyway; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.jooq.DSLContext; +import space.arim.libertybans.core.database.sql.SequenceDefinition; + +import java.sql.Connection; +import java.sql.Statement; + +public final class V34__Scope_identifier_sequence extends BaseJavaMigration { + + @Override + public void migrate(Context flywayContext) throws Exception { + MigrationState migrationState = MigrationState.retrieveState(flywayContext); + Connection connection = flywayContext.getConnection(); + DSLContext context = migrationState.createJooqContext(connection); + + try (Statement statement = connection.createStatement()) { + SequenceDefinition.integer("scope_ids", 1) + .defineUsing(statement, context.family()); + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V38__Scope_migration.java b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V38__Scope_migration.java new file mode 100644 index 000000000..c5f430085 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/flyway/V38__Scope_migration.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.core.database.flyway; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.impl.SQLDataType; +import space.arim.libertybans.core.database.jooq.BatchExecute; +import space.arim.libertybans.core.database.sql.ScopeIdSequenceValue; +import space.arim.libertybans.core.scope.SpecificServerScope; + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.List; + +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.name; +import static space.arim.libertybans.core.schema.tables.Punishments.PUNISHMENTS; + +public final class V38__Scope_migration extends BaseJavaMigration { + + private static final int BATCH_SIZE = 400; + + @Override + public void migrate(Context flywayContext) throws Exception { + DSLContext context; + { + MigrationState migrationState = MigrationState.retrieveState(flywayContext); + Connection connection = flywayContext.getConnection(); + context = migrationState.createJooqContext(connection); + } + + record RewriteScope(long punishmentId, int scopeId) {} + List scopesToRewrite = new ArrayList<>(); + + Field legacyScopeField = field(name("scope"), SQLDataType.VARCHAR(32)); + try (var cursor = context + .select(PUNISHMENTS.ID, legacyScopeField) + .from(PUNISHMENTS) + .where(legacyScopeField.notEqual("")) + .fetchSize(BATCH_SIZE) + .fetchLazy()) { + + for (var record : cursor) { + Integer scopeId = new ScopeIdSequenceValue(context) + .retrieveScopeIdFieldReified(new SpecificServerScope(record.value2())); + scopesToRewrite.add(new RewriteScope(record.value1(), scopeId)); + } + } + new BatchExecute( + () -> context.batch(context + .update(PUNISHMENTS) + .set(PUNISHMENTS.ID, (Long) null) + .set(PUNISHMENTS.SCOPE_ID, (Integer) null) + ), + (batch, data) -> { + return batch.bind(data.punishmentId, data.scopeId); + } + ).execute(scopesToRewrite, BATCH_SIZE); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchExecute.java b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchExecute.java new file mode 100644 index 000000000..b6c0ef43d --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchExecute.java @@ -0,0 +1,87 @@ +/* + * 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.database.jooq; + +import org.jooq.BatchBindStep; + +import java.util.Objects; + +public class BatchExecute { + + private final BatchProvider batchProvider; + private final BatchAttachment batchAttachment; + + public BatchExecute(BatchProvider batchProvider, BatchAttachment batchAttachment) { + this.batchProvider = Objects.requireNonNull(batchProvider); + this.batchAttachment = Objects.requireNonNull(batchAttachment); + } + + /** + * Provides the batch object for the table into which the data will be inserted + */ + public interface BatchProvider { + + /** + * Creates the batch object + * + * @return a batch object for the target table + */ + BatchBindStep createBatch(); + } + + /** + * Function for attaching the source data to the batch object + * + * @param the source data type + */ + public interface BatchAttachment { + + /** + * Binds data + * + * @param batch the existing batch object + * @param data the data to bind + * @return the new batch object + */ + BatchBindStep attachData(BatchBindStep batch, S data); + + } + + /** + * Performs the batch execution + * + * @param source the source of the data + * @param maxBatchSize how many records to write in a single batch + */ + public void execute(Iterable source, int maxBatchSize) { + BatchBindStep batch = batchProvider.createBatch(); + for (S data : source) { + batch = batchAttachment.attachData(batch, data); + if (batch.size() == maxBatchSize) { + batch.execute(); + batch = batchProvider.createBatch(); + } + } + if (batch.size() > 0) { + batch.execute(); + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchTransfer.java b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchTransfer.java index 9723d0ef5..7a2919b68 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchTransfer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/BatchTransfer.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 @@ -19,52 +19,19 @@ package space.arim.libertybans.core.database.jooq; -import org.jooq.BatchBindStep; import org.jooq.Cursor; -public final class BatchTransfer { +import java.util.Objects; + +public final class BatchTransfer extends BatchExecute { private final Cursor cursor; - private final BatchProvider batchProvider; - private final BatchAttachment batchAttachment; public BatchTransfer(Cursor cursor, BatchProvider batchProvider, BatchAttachment batchAttachment) { - this.cursor = cursor; - this.batchProvider = batchProvider; - this.batchAttachment = batchAttachment; - } - - /** - * Provides the batch object for the table into which the data will be inserted - */ - public interface BatchProvider { - - /** - * Creates the batch object - * - * @return a batch object for the target table - */ - BatchBindStep createBatch(); - } - - /** - * Function for attaching the cursor data to the batch object - * - * @param the record type - */ - public interface BatchAttachment { - - /** - * Binds data - * - * @param batch the existing batch object - * @param record the data to bind - * @return the new batch object - */ - BatchBindStep attachData(BatchBindStep batch, R record); - + super(batchProvider, batchAttachment); + this.cursor = Objects.requireNonNull(cursor); } /** @@ -72,19 +39,9 @@ public interface BatchAttachment { * * @param maxBatchSize how many records to transfer in a single batch */ - public void transferData(int maxBatchSize) { - BatchBindStep batch = batchProvider.createBatch(); + public void execute(int maxBatchSize) { try (cursor) { - for (R record : cursor) { - batch = batchAttachment.attachData(batch, record); - if (batch.size() == maxBatchSize) { - batch.execute(); - batch = batchProvider.createBatch(); - } - } - if (batch.size() > 0) { - batch.execute(); - } + super.execute(cursor, maxBatchSize); } } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/JooqClassloading.java b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/JooqClassloading.java index 5dffdab2a..a85ae2061 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/JooqClassloading.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/JooqClassloading.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 @@ -26,7 +26,6 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; -import space.arim.libertybans.core.scope.ScopeImpl; import java.time.Instant; import java.util.UUID; @@ -58,12 +57,11 @@ public void preinitializeClasses() { } catch (RuntimeException ex) { // Purposefully hide the exception stacktrace to avoid unnecessary noise logger.warn("Failed to pre-initialize classes: {}", ex.getMessage()); + return; } long elapsedMillis = (System.nanoTime() - startNanos) / 1_000_000L; - if (elapsedMillis >= 250) { - String elapsedSeconds = String.format("%.2f", ((double) elapsedMillis) / 1000D); - logger.info("Finished pre-initializing classes in {} seconds", elapsedSeconds); - } + String elapsedSeconds = String.format("%.2f", ((double) elapsedMillis) / 1000D); + logger.info("Finished pre-initializing classes in {} seconds", elapsedSeconds); } private void renderDummyQueries() { @@ -78,11 +76,12 @@ private void renderDummyQueries() { .columns( PUNISHMENTS.ID, PUNISHMENTS.TYPE, PUNISHMENTS.OPERATOR, PUNISHMENTS.REASON, - PUNISHMENTS.SCOPE, PUNISHMENTS.START, PUNISHMENTS.END) + PUNISHMENTS.START, PUNISHMENTS.END + ) .values( 0L, PunishmentType.BAN, ConsoleOperator.INSTANCE, "", - ScopeImpl.GLOBAL, Instant.EPOCH, Instant.MAX + Instant.EPOCH, Instant.MAX ) .getSQL(); context diff --git a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/ForcedEmptyScopeValidator.java b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ScopeTypeConverter.java similarity index 62% rename from bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/ForcedEmptyScopeValidator.java rename to bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ScopeTypeConverter.java index edb9d06ea..f68f45405 100644 --- a/bans-core-addons/layouts/src/main/java/space/arim/libertybans/core/addon/layouts/ForcedEmptyScopeValidator.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/jooq/ScopeTypeConverter.java @@ -17,19 +17,15 @@ * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core.addon.layouts; +package space.arim.libertybans.core.database.jooq; -import space.arim.dazzleconf.error.BadValueException; -import space.arim.dazzleconf.validator.ValueValidator; +import org.jetbrains.annotations.NotNull; +import space.arim.libertybans.core.scope.ScopeType; + +public final class ScopeTypeConverter extends OrdinalEnumConverter { -public class ForcedEmptyScopeValidator implements ValueValidator { @Override - public void validate(String key, Object value) throws BadValueException { - if (!((String) value).isEmpty()) { - throw new BadValueException.Builder() - .key(key) - .message("At the moment, scopes are not supported in punishment layouts") - .build(); - } + public @NotNull Class toType() { + return ScopeType.class; } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ApplicableViewFields.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ApplicableViewFields.java index 3902c6542..2933361c4 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ApplicableViewFields.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ApplicableViewFields.java @@ -21,31 +21,28 @@ import org.jooq.Field; import org.jooq.Record; -import org.jooq.Record13; +import org.jooq.Record14; import org.jooq.Table; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.EscalationTrack; -import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.Objects; import java.util.UUID; -public final class ApplicableViewFields> implements PunishmentFields { - - private final Table applicableView; - private final R fieldSupplier; + Operator, String, String, Instant, Instant, + UUID, NetworkAddress, EscalationTrack, ScopeType + >>(Table applicableView, R fieldSupplier) implements PunishmentFields { public ApplicableViewFields(Table applicableView) { - this.applicableView = applicableView; - this.fieldSupplier = applicableView.newRecord(); + this(applicableView, applicableView.newRecord()); } @Override @@ -89,7 +86,7 @@ public Field reason() { } @Override - public Field scope() { + public Field scope() { return fieldSupplier.field8(); } @@ -108,14 +105,13 @@ public Field track() { return fieldSupplier.field13(); } + @Override + public Field scopeType() { + return fieldSupplier.field14(); + } + public Field uuid() { return Objects.requireNonNull(fieldSupplier.field11(), "uuid field does not exist"); } - @Override - public String toString() { - return "ApplicableViewFields{" + - "applicableView=" + applicableView + - '}'; - } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/MultiFieldCriterion.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/MultiFieldCriterion.java new file mode 100644 index 000000000..979836e93 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/MultiFieldCriterion.java @@ -0,0 +1,46 @@ +/* + * 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.database.sql; + +import org.jooq.Condition; +import space.arim.libertybans.api.select.SelectionPredicate; + +import static org.jooq.impl.DSL.noCondition; +import static org.jooq.impl.DSL.not; + +public interface MultiFieldCriterion { + + Condition matchesValue(F value); + + default Condition buildCondition(SelectionPredicate selection) { + // Check field is accepted + Condition fieldAcceptedCondition = noCondition(); + for (F acceptedValue : selection.acceptedValues()) { + fieldAcceptedCondition = fieldAcceptedCondition.or(matchesValue(acceptedValue)); + } + // Check field is not rejected + Condition fieldNotRejectedCondition = noCondition(); + for (F rejectedValue : selection.rejectedValues()) { + fieldNotRejectedCondition = fieldNotRejectedCondition.and(not(matchesValue(rejectedValue))); + } + return fieldAcceptedCondition.and(fieldNotRejectedCondition); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/PunishmentFields.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/PunishmentFields.java index fd2f1e86c..4df993d58 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/PunishmentFields.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/PunishmentFields.java @@ -26,12 +26,12 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.EscalationTrack; -import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.UUID; -public interface PunishmentFields extends VictimFields { +public interface PunishmentFields extends VictimFields, ScopeFields { Field id(); @@ -41,8 +41,6 @@ public interface PunishmentFields extends VictimFields { Field reason(); - Field scope(); - Field start(); Field end(); @@ -52,14 +50,14 @@ public interface PunishmentFields extends VictimFields { default PunishmentFields withNewTable(Table newTable) { record ModifiedTable(Field id, Field type, Field victimType, Field victimUuid, Field victimAddress, - Field operator, Field reason, Field scope, - Field start, Field end, Field track, + Field operator, Field reason, Field scope, + Field start, Field end, Field track, Field scopeType, Table table) implements PunishmentFields { } return new ModifiedTable( id(), type(), victimType(), victimUuid(), victimAddress(), - operator(), reason(), scope(), start(), end(), track(), newTable + operator(), reason(), scope(), start(), end(), track(), scopeType(), newTable ); - }; + } } 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 new file mode 100644 index 000000000..fe4f31b4b --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeCondition.java @@ -0,0 +1,45 @@ +/* + * 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.database.sql; + +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; + +public record ScopeCondition(ScopeFields scopeFields, InternalScopeManager scopeManager) + implements MultiFieldCriterion { + + @Override + public Condition matchesValue(ServerScope scope) { + return new ScopeParsing().deconstruct(scope, (type, value) -> { + Condition typeMatches = scopeFields.scopeType().eq(type); + Condition valueMatches = switch (type) { + case GLOBAL -> noCondition(); + case SERVER, CATEGORY -> scopeFields.scope().eq(value); + }; + return typeMatches.and(valueMatches); + }); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeFields.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeFields.java new file mode 100644 index 000000000..ee5a672f1 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeFields.java @@ -0,0 +1,31 @@ +/* + * 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.database.sql; + +import org.jooq.Field; +import space.arim.libertybans.core.scope.ScopeType; + +public interface ScopeFields extends TableFieldAccessor { + + Field scope(); + + Field scopeType(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeIdSequenceValue.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeIdSequenceValue.java new file mode 100644 index 000000000..2eb72f4f7 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/ScopeIdSequenceValue.java @@ -0,0 +1,71 @@ +/* + * 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.database.sql; + +import org.jooq.DSLContext; +import org.jooq.Field; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeParsing; +import space.arim.libertybans.core.scope.ScopeType; + +import static org.jooq.impl.DSL.castNull; +import static org.jooq.impl.DSL.val; +import static space.arim.libertybans.core.schema.Sequences.LIBERTYBANS_SCOPE_IDS; +import static space.arim.libertybans.core.schema.tables.Scopes.SCOPES; + +public final class ScopeIdSequenceValue extends SequenceValue { + + public ScopeIdSequenceValue(DSLContext context) { + super(context, LIBERTYBANS_SCOPE_IDS); + } + + private RetrieveOrGenerate retrieveOrGenerate(ScopeType type, String value) { + return new RetrieveOrGenerate( + SCOPES, SCOPES.ID, + SCOPES.TYPE.eq(type).and(SCOPES.VALUE.eq(value)), + (newId) -> { + context + .insertInto(SCOPES) + .columns(SCOPES.ID, SCOPES.TYPE, SCOPES.VALUE) + .values(newId, val(type), val(value)) + .execute(); + } + ); + } + + public Field retrieveScopeId(ServerScope scope) { + return new ScopeParsing().deconstruct(scope, (type, value) -> { + if (type == ScopeType.GLOBAL) { + return castNull(Integer.class); + } + return retrieveOrGenerate(type, value).execute(); + }); + } + + public Integer retrieveScopeIdFieldReified(ServerScope scope) { + return new ScopeParsing().deconstruct(scope, (type, value) -> { + if (type == ScopeType.GLOBAL) { + return null; + } + return retrieveOrGenerate(type, value).executeReified(); + }); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SequenceValue.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SequenceValue.java index 31a1647b3..c53cab875 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SequenceValue.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SequenceValue.java @@ -34,10 +34,12 @@ public class SequenceValue { + final DSLContext context; private final Sequence sequence; private Field lastValueForMySQL; - public SequenceValue(Sequence sequence) { + public SequenceValue(DSLContext context, Sequence sequence) { + this.context = context; this.sequence = Objects.requireNonNull(sequence, "sequence"); } @@ -49,7 +51,7 @@ private Field emulationTableValueField() { return DSL.field("value", sequence.getDataType()); } - public Field nextValue(DSLContext context) { + public Field nextValue() { if (context.family() == SQLDialect.MYSQL) { Table emulationTable = emulationTable(); Field valueField = emulationTableValueField(); @@ -68,7 +70,7 @@ public Field nextValue(DSLContext context) { } } - public Field lastValueInSession(DSLContext context) { + public Field lastValueInSession() { if (context.family() == SQLDialect.MYSQL) { if (lastValueForMySQL == null) { throw new IllegalStateException("For MySQL, a value must be previously generated " + @@ -80,7 +82,7 @@ public Field lastValueInSession(DSLContext context) { } } - public void setValue(DSLContext context, R value) { + public void setValue(R value) { switch (context.family()) { case MYSQL -> context .update(emulationTable()) @@ -100,19 +102,46 @@ public void setValue(DSLContext context, R value) { } } - Field retrieveOrGenerateMatchingRow(DSLContext context, - Table table, Field sequenceValueField, - Condition matchExisting, Consumer> insertNew) { - R existingId = context - .select(sequenceValueField) - .from(table) - .where(matchExisting) - .fetchOne(sequenceValueField); - if (existingId != null) { - return val(existingId); + class RetrieveOrGenerate { + + private final Table table; + private final Field sequenceValueField; + private final Condition matchExisting; + private final Consumer> insertNew; + + RetrieveOrGenerate(Table table, Field sequenceValueField, + Condition matchExisting, Consumer> insertNew) { + this.table = table; + this.sequenceValueField = sequenceValueField; + this.matchExisting = matchExisting; + this.insertNew = insertNew; + } + + Field execute() { + R existingId = context + .select(sequenceValueField) + .from(table) + .where(matchExisting) + .fetchOne(sequenceValueField); + if (existingId != null) { + return val(existingId); + } + insertNew.accept(nextValue()); + return lastValueInSession(); + } + + R executeReified() { + R existingId = context + .select(sequenceValueField) + .from(table) + .where(matchExisting) + .fetchOne(sequenceValueField); + if (existingId != null) { + return existingId; + } + insertNew.accept(nextValue()); + return context.select(lastValueInSession()).fetchSingle(sequenceValueField); } - insertNew.accept(nextValue(context)); - return lastValueInSession(context); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SimpleViewFields.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SimpleViewFields.java index 9a57ac46b..49a1d8e23 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SimpleViewFields.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/SimpleViewFields.java @@ -21,30 +21,26 @@ import org.jooq.Field; import org.jooq.Record; -import org.jooq.Record11; +import org.jooq.Record12; import org.jooq.Table; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.EscalationTrack; -import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.UUID; -public final class SimpleViewFields> implements PunishmentFields { - - private final Table simpleView; - private final R fieldSupplier; + Operator, String, String, Instant, Instant, EscalationTrack, ScopeType + >>(Table simpleView, R fieldSupplier) implements PunishmentFields { public SimpleViewFields(Table simpleView) { - this.simpleView = simpleView; - this.fieldSupplier = simpleView.newRecord(); + this(simpleView, simpleView.newRecord()); } @Override @@ -88,7 +84,7 @@ public Field reason() { } @Override - public Field scope() { + public Field scope() { return fieldSupplier.field8(); } @@ -108,9 +104,8 @@ public Field track() { } @Override - public String toString() { - return "SimpleViewFields{" + - "simpleView=" + simpleView + - '}'; + public Field scopeType() { + return fieldSupplier.field12(); } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TableForType.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TableForType.java index b05a7c94c..7102afd21 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TableForType.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TableForType.java @@ -19,8 +19,8 @@ package space.arim.libertybans.core.database.sql; -import org.jooq.Record11; -import org.jooq.Record13; +import org.jooq.Record12; +import org.jooq.Record14; import org.jooq.Record2; import org.jooq.Table; import space.arim.libertybans.api.NetworkAddress; @@ -28,7 +28,6 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.EscalationTrack; -import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.schema.tables.ApplicableBans; import space.arim.libertybans.core.schema.tables.ApplicableMutes; import space.arim.libertybans.core.schema.tables.ApplicableWarns; @@ -38,6 +37,7 @@ import space.arim.libertybans.core.schema.tables.SimpleMutes; import space.arim.libertybans.core.schema.tables.SimpleWarns; import space.arim.libertybans.core.schema.tables.Warns; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.Objects; @@ -64,9 +64,9 @@ private Table> dataTable0() { }; } - public SimpleViewFields> simpleView() { + public SimpleViewFields> simpleView() { var view = switch (type) { case BAN -> SimpleBans.SIMPLE_BANS; case MUTE -> SimpleMutes.SIMPLE_MUTES; @@ -76,11 +76,11 @@ Operator, String, ServerScope, Instant, Instant, EscalationTrack>> simpleView() return new SimpleViewFields<>(view); } - public ApplicableViewFields> applicableView() { + public ApplicableViewFields> applicableView() { var view = switch (type) { case BAN -> ApplicableBans.APPLICABLE_BANS; case MUTE -> ApplicableMutes.APPLICABLE_MUTES; diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TrackIdSequenceValue.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TrackIdSequenceValue.java index c5711845c..bdcbdf415 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TrackIdSequenceValue.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/TrackIdSequenceValue.java @@ -30,30 +30,26 @@ public final class TrackIdSequenceValue extends SequenceValue { - public TrackIdSequenceValue() { - super(LIBERTYBANS_TRACK_IDS); + public TrackIdSequenceValue(DSLContext context) { + super(context, LIBERTYBANS_TRACK_IDS); } - public Field retrieveTrackIdField(DSLContext context, EscalationTrack escalationTrack) { + public Field retrieveTrackId(EscalationTrack escalationTrack) { if (escalationTrack == null) { return castNull(Integer.class); } - return retrieveOrGenerateMatchingRow( - context, TRACKS, TRACKS.ID, + return new RetrieveOrGenerate( + TRACKS, TRACKS.ID, TRACKS.NAMESPACE.eq(escalationTrack.getNamespace()) .and(TRACKS.VALUE.eq(escalationTrack.getValue())), (newId) -> { context .insertInto(TRACKS) .columns(TRACKS.ID, TRACKS.NAMESPACE, TRACKS.VALUE) - .values( - newId, - val(escalationTrack.getNamespace()), - val(escalationTrack.getValue()) - ) + .values(newId, val(escalationTrack.getNamespace()), val(escalationTrack.getValue())) .execute(); } - ); + ).execute(); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimCondition.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimCondition.java index 6245b7516..6e12141d3 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimCondition.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimCondition.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 @@ -32,7 +32,7 @@ import static space.arim.libertybans.api.CompositeVictim.WILDCARD_ADDRESS; import static space.arim.libertybans.api.CompositeVictim.WILDCARD_UUID; -public final class VictimCondition { +public final class VictimCondition implements MultiFieldCriterion { private final VictimFields fields; @@ -79,7 +79,8 @@ public Condition matchesVictim(VictimData victim) { return fields.victimType().eq(inline(victim.type())).and(matchesData); } - public Condition matchesVictim(Victim victim) { + @Override + public Condition matchesValue(Victim victim) { return matchesVictim(new SerializedVictim(victim)); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimIdSequenceValue.java b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimIdSequenceValue.java index 8b029beff..9aeb8e54c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimIdSequenceValue.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/sql/VictimIdSequenceValue.java @@ -29,14 +29,14 @@ public final class VictimIdSequenceValue extends SequenceValue { - public VictimIdSequenceValue() { - super(LIBERTYBANS_VICTIM_IDS); + public VictimIdSequenceValue(DSLContext context) { + super(context, LIBERTYBANS_VICTIM_IDS); } - public Field retrieveVictimIdField(DSLContext context, Victim victim) { + public Field retrieveVictimId(Victim victim) { VictimData victimData = FixedVictimData.from(new SerializedVictim(victim)); - return retrieveOrGenerateMatchingRow( - context, VICTIMS, VICTIMS.ID, + return new RetrieveOrGenerate( + VICTIMS, VICTIMS.ID, new VictimCondition(new VictimTableFields()).matchesVictim(victimData), (newId) -> { context @@ -50,7 +50,7 @@ public Field retrieveVictimIdField(DSLContext context, Victim victim) { ) .execute(); } - ); + ).execute(); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvServerNameDetection.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvServerNameDetection.java new file mode 100644 index 000000000..13efe9ada --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvServerNameDetection.java @@ -0,0 +1,31 @@ +/* + * 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.scope.InternalScopeManager; + +public interface EnvServerNameDetection { + + /** + * Detects the server name, if immediately known, for use with the scopes feature + */ + void detectName(InternalScopeManager scopeManager); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java index ce0530186..7a5f85d0c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.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 @@ -19,29 +19,34 @@ package space.arim.libertybans.core.env; -import java.util.List; -import java.util.Set; - import jakarta.inject.Inject; import jakarta.inject.Singleton; - import space.arim.libertybans.bootstrap.StartupException; import space.arim.libertybans.core.Part; import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.scope.InternalScopeManager; + +import java.util.List; +import java.util.Set; @Singleton public final class EnvironmentManager implements Part { private final Environment environment; private final Configs configs; + private final EnvServerNameDetection serverNameDetection; + private final InternalScopeManager scopeManager; private Set listeners; private PlatformListener[] commandAliases; @Inject - public EnvironmentManager(Environment environment, Configs configs) { + public EnvironmentManager(Environment environment, Configs configs, + EnvServerNameDetection serverNameDetection, InternalScopeManager scopeManager) { this.environment = environment; this.configs = configs; + this.serverNameDetection = serverNameDetection; + this.scopeManager = scopeManager; } public Object platformAccess() { @@ -68,6 +73,7 @@ private void registerAliases() { public void startup() { registerListeners(); registerAliases(); + serverNameDetection.detectName(scopeManager); } @Override @@ -82,6 +88,7 @@ public void shutdown() { for (PlatformListener commandAlias : commandAliases) { commandAlias.unregister(); } + scopeManager.clearDetectedServerName(); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/ParallelisedListener.java b/bans-core/src/main/java/space/arim/libertybans/core/env/ParallelisedListener.java index a7586fa01..55748dd5b 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/ParallelisedListener.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/ParallelisedListener.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 @@ -63,7 +63,7 @@ protected final void begin(E event, CentralisedFuture computation) { } protected final void debugPrematurelyDenied(E event) { - logger.debug("Event {} is already blocked", event); + logger.trace("Event {} is already blocked", event); } protected final void absentFutureHandler(E event) { diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChannelRegistration.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/GetServer.java similarity index 56% rename from bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChannelRegistration.java rename to bans-core/src/main/java/space/arim/libertybans/core/env/message/GetServer.java index 4182daf6e..aa287e26d 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChannelRegistration.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/GetServer.java @@ -17,31 +17,26 @@ * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.env.spigot; +package space.arim.libertybans.core.env.message; -import jakarta.inject.Inject; -import org.bukkit.plugin.Plugin; -import space.arim.libertybans.core.env.PlatformListener; +import java.io.IOException; -public final class ChannelRegistration implements PlatformListener { +public final class GetServer implements PluginMessage { - private final Plugin plugin; - - static final String BUNGEE_CHANNEL = "BungeeCord"; - - @Inject - public ChannelRegistration(Plugin plugin) { - this.plugin = plugin; + @Override + public String subchannelName() { + return "GetServer"; } @Override - public void register() { - plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); + public void writeData(Void data, PluginMessageOutput output) throws IOException { } @Override - public void unregister() { - plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); + public Response readResponse(PluginMessageInput input) throws IOException { + return new Response(input.readUTF()); } + public record Response(String server) { } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/message/KickPlayer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/KickPlayer.java new file mode 100644 index 000000000..18e2b04a8 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/KickPlayer.java @@ -0,0 +1,47 @@ +/* + * 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.message; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.io.IOException; + +public final class KickPlayer implements PluginMessage { + + @Override + public String subchannelName() { + return "KickPlayer"; + } + + @Override + public void writeData(Data data, PluginMessageOutput output) throws IOException { + output.writeUTF(data.playerName); + output.writeUTF(LegacyComponentSerializer.legacySection().serialize(data.message)); + } + + @Override + public Void readResponse(PluginMessageInput input) throws IOException { + return null; + } + + public record Data(String playerName, Component message) {} + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessage.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessage.java new file mode 100644 index 000000000..c1a2dd949 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessage.java @@ -0,0 +1,56 @@ +/* + * 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.message; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Optional; + +public interface PluginMessage { + + String subchannelName(); + + void writeData(D data, PluginMessageOutput output) throws IOException; + + R readResponse(PluginMessageInput input) throws IOException; + + default void writeTo(D data, PluginMessageOutput output) { + try { + output.writeUTF(subchannelName()); + writeData(data, output); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + default Optional readFrom(PluginMessageInput input) { + try { + String subchannel = input.readUTF(); + if (!subchannel.equals(subchannelName())) { + return Optional.empty(); + } + return Optional.of(readResponse(input)); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageInput.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageInput.java new file mode 100644 index 000000000..79872b547 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageInput.java @@ -0,0 +1,28 @@ +/* + * 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.message; + +import java.io.IOException; + +public interface PluginMessageInput { + + String readUTF() throws IOException; + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageOutput.java b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageOutput.java new file mode 100644 index 000000000..269b98877 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/message/PluginMessageOutput.java @@ -0,0 +1,28 @@ +/* + * 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.message; + +import java.io.IOException; + +public interface PluginMessageOutput { + + void writeUTF(String utf) throws IOException; + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/importing/SelfImportProcess.java b/bans-core/src/main/java/space/arim/libertybans/core/importing/SelfImportProcess.java index 3abb024c8..bd0daa01b 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/importing/SelfImportProcess.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/importing/SelfImportProcess.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 @@ -156,7 +156,7 @@ private void transferTable(Table table) { } return batch.bind(bindValues); } - ).transferData(maxBatchSize); + ).execute(maxBatchSize); } private void updateSequences() { @@ -165,13 +165,13 @@ private void updateSequences() { .select(DSL.max(PUNISHMENTS.ID).plus(1)) .from(PUNISHMENTS) .fetchSingle().value1(); - new SequenceValue<>(LIBERTYBANS_PUNISHMENT_IDS).setValue(target, nextPunishmentId); + new SequenceValue<>(target, LIBERTYBANS_PUNISHMENT_IDS).setValue(nextPunishmentId); int nextVictimId = target .select(DSL.max(VICTIMS.ID).plus(1)) .from(VICTIMS) .fetchSingle().value1(); - new SequenceValue<>(LIBERTYBANS_VICTIM_IDS).setValue(target, nextVictimId); + new SequenceValue<>(target, LIBERTYBANS_VICTIM_IDS).setValue(nextVictimId); } } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/CalculablePunishmentImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/CalculablePunishmentImpl.java index 3a60942ab..7bcaa1d34 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/CalculablePunishmentImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/CalculablePunishmentImpl.java @@ -19,8 +19,6 @@ package space.arim.libertybans.core.punish; -import space.arim.libertybans.api.Operator; -import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.punish.CalculablePunishment; import space.arim.libertybans.api.punish.EnforcementOptions; import space.arim.libertybans.api.punish.EscalationTrack; diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java index 8d869d259..d7105e7c4 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java @@ -63,8 +63,7 @@ public DraftPunishmentBuilder reason(String reason) { @Override public DraftPunishmentBuilder scope(ServerScope scope) { - enactor.scopeManager().checkScope(scope); - this.scope = scope; + this.scope = enactor.scopeManager().checkScope(scope); return this; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enaction.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enaction.java index 5307a3d79..80c5b5141 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enaction.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enaction.java @@ -28,6 +28,7 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.execute.Transaction; +import space.arim.libertybans.core.database.sql.ScopeIdSequenceValue; import space.arim.libertybans.core.database.sql.SequenceValue; import space.arim.libertybans.core.database.sql.TableForType; import space.arim.libertybans.core.database.sql.TrackIdSequenceValue; @@ -84,25 +85,27 @@ private Punishment enact(PunishmentCreator creator, DSLContext context, Transaction transaction, boolean active) { MiscUtil.checkNoCompositeVictimWildcards(victim); - Field escalationTrackId = new TrackIdSequenceValue().retrieveTrackIdField(context, escalationTrack); + Field escalationTrackId = new TrackIdSequenceValue(context).retrieveTrackId(escalationTrack); + Field scopeId = new ScopeIdSequenceValue(context).retrieveScopeId(scope); - SequenceValue punishmentIdSequence = new SequenceValue<>(LIBERTYBANS_PUNISHMENT_IDS); + SequenceValue punishmentIdSequence = new SequenceValue<>(context, LIBERTYBANS_PUNISHMENT_IDS); context .insertInto(PUNISHMENTS) .columns( PUNISHMENTS.ID, PUNISHMENTS.TYPE, PUNISHMENTS.OPERATOR, PUNISHMENTS.REASON, - PUNISHMENTS.SCOPE, PUNISHMENTS.START, PUNISHMENTS.END, PUNISHMENTS.TRACK + PUNISHMENTS.SCOPE, PUNISHMENTS.START, PUNISHMENTS.END, + PUNISHMENTS.TRACK, PUNISHMENTS.SCOPE_ID ) .values( - punishmentIdSequence.nextValue(context), val(type, PUNISHMENTS.TYPE), + punishmentIdSequence.nextValue(), val(type, PUNISHMENTS.TYPE), val(operator, PUNISHMENTS.OPERATOR), val(reason, PUNISHMENTS.REASON), - val(scope, PUNISHMENTS.SCOPE), val(start, PUNISHMENTS.START), val(end, PUNISHMENTS.END), - escalationTrackId + val("", PUNISHMENTS.SCOPE), val(start, PUNISHMENTS.START), val(end, PUNISHMENTS.END), + escalationTrackId, scopeId ) .execute(); - Field punishmentIdField = punishmentIdSequence.lastValueInSession(context); - Field victimIdField = new VictimIdSequenceValue().retrieveVictimIdField(context, victim); + Field punishmentIdField = punishmentIdSequence.lastValueInSession(); + Field victimIdField = new VictimIdSequenceValue(context).retrieveVictimId(victim); if (active && type != PunishmentType.KICK) { var dataTable = new TableForType(type).dataTable(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java index 9daf81904..650be8c5a 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java @@ -33,6 +33,7 @@ import space.arim.libertybans.api.punish.EscalationTrack; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.InternalDatabase; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.database.execute.SQLFunction; @@ -133,13 +134,14 @@ CentralisedFuture calculatePunishment(CalculablePunishment calculabl database.clearExpiredPunishments(context, type, start); } Duration duration = calculationResult.duration(); + ServerScope scope = scopeManager.checkScope(calculationResult.scope()); Instant end = duration.isZero() ? Punishment.PERMANENT_END_DATE : start.plusSeconds(duration.toSeconds()); Enaction enaction = new Enaction( new Enaction.OrderDetails( type, victim, operator, - calculationResult.reason(), calculationResult.scope(), + calculationResult.reason(), scope, start, end, escalationTrack ), creator); @@ -200,6 +202,7 @@ public CentralisedFuture queryWithRetry(int retryCount, SQLTransactionalF return new SelectionResources( futuresFactory, () -> contextualExecutor, + scopeManager, creator, time ); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Modifier.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Modifier.java index d958c2702..61f1ab0aa 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/Modifier.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Modifier.java @@ -21,12 +21,14 @@ import jakarta.inject.Inject; import jakarta.inject.Provider; +import org.checkerframework.checker.nullness.qual.Nullable; import org.jooq.Field; import space.arim.libertybans.api.punish.EscalationTrack; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.PunishmentEditor; import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.InternalDatabase; +import space.arim.libertybans.core.database.sql.ScopeIdSequenceValue; import space.arim.libertybans.core.database.sql.TrackIdSequenceValue; import space.arim.libertybans.core.scope.InternalScopeManager; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; @@ -43,6 +45,7 @@ import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.when; import static space.arim.libertybans.core.schema.tables.Punishments.PUNISHMENTS; +import static space.arim.libertybans.core.schema.tables.Scopes.SCOPES; import static space.arim.libertybans.core.schema.tables.Tracks.TRACKS; public final class Modifier { @@ -64,16 +67,17 @@ public Modifier(FactoryOfTheFuture futuresFactory, Provider db } /** Holder for nullable escalation track */ - record EscalationTrackBox(EscalationTrack track) {} + record EscalationTrackBox(@Nullable EscalationTrack track) {} class Editor implements PunishmentEditor { private final Punishment oldInstance; - private String reason; - private ServerScope scope; - private Instant endDate; - private Duration endDateDelta; - private EscalationTrackBox escalationTrackBox; + // Null indicates unchanged; nonnull requires an update + private @Nullable String reason; + private @Nullable ServerScope scope; + private @Nullable Instant endDate; + private @Nullable Duration endDateDelta; + private @Nullable EscalationTrackBox escalationTrackBox; Editor(Punishment oldInstance) { this.oldInstance = oldInstance; @@ -86,8 +90,7 @@ public void setReason(String reason) { @Override public void setScope(ServerScope scope) { - scopeManager.checkScope(scope); - this.scope = scope; + this.scope = scopeManager.checkScope(scope); } @Override @@ -121,18 +124,18 @@ var record = context.newRecord(PUNISHMENTS); if (reason != null) { record.setReason(reason); } - if (scope != null) { - record.setScope(scope); - } if (endDate != null) { record.setEnd(endDate); } - Map, Field> furtherModifications = new HashMap<>(3, 0.99f); + Map, Field> furtherModifications = new HashMap<>(4, 0.99f); if (escalationTrackBox != null) { - Field newTrack = new TrackIdSequenceValue() - .retrieveTrackIdField(context, escalationTrackBox.track); + Field newTrack = new TrackIdSequenceValue(context).retrieveTrackId(escalationTrackBox.track); furtherModifications.put(PUNISHMENTS.TRACK, newTrack); } + if (scope != null) { + Field newScope = new ScopeIdSequenceValue(context).retrieveScopeId(scope); + furtherModifications.put(PUNISHMENTS.SCOPE, newScope); + } if (endDateDelta != null) { Field newEndDate = when( // Pass-through permanent punishments @@ -152,12 +155,15 @@ var record = context.newRecord(PUNISHMENTS); .execute(); return context .select( - PUNISHMENTS.REASON, PUNISHMENTS.SCOPE, PUNISHMENTS.END, - TRACKS.NAMESPACE, TRACKS.VALUE + PUNISHMENTS.REASON, PUNISHMENTS.END, + TRACKS.NAMESPACE, TRACKS.VALUE, + SCOPES.TYPE, SCOPES.VALUE ) .from(PUNISHMENTS) .leftJoin(TRACKS) .on(PUNISHMENTS.TRACK.eq(TRACKS.ID)) + .leftJoin(SCOPES) + .on(PUNISHMENTS.SCOPE_ID.eq(SCOPES.ID)) .where(PUNISHMENTS.ID.eq(id)) // Can return null if punishment was expunged .fetchOne(creator.punishmentMapperForModifications(oldInstance)); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/PunishmentCreator.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/PunishmentCreator.java index cd4a44d88..d43550d94 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/PunishmentCreator.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/PunishmentCreator.java @@ -21,8 +21,8 @@ import org.jooq.Record10; import org.jooq.Record11; -import org.jooq.Record5; -import org.jooq.Record9; +import org.jooq.Record12; +import org.jooq.Record6; import org.jooq.RecordMapper; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; @@ -31,6 +31,7 @@ import space.arim.libertybans.api.punish.EscalationTrack; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; import java.util.UUID; @@ -41,20 +42,20 @@ Punishment createPunishment(long id, PunishmentType type, Victim victim, Operator operator, String reason, ServerScope scope, Instant start, Instant end, EscalationTrack escalationTrack); - RecordMapper, + RecordMapper, Punishment> punishmentMapper(); - RecordMapper, + RecordMapper, Punishment> punishmentMapper(long id); - RecordMapper, + RecordMapper, Punishment> punishmentMapper(long id, PunishmentType type); - RecordMapper, + RecordMapper, Punishment> punishmentMapperForModifications(Punishment oldPunishment); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java index 3c3c6d4ba..ca91dd1dc 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java @@ -24,7 +24,6 @@ import jakarta.inject.Singleton; import org.jooq.Condition; import org.jooq.DSLContext; -import org.jooq.impl.DSL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import space.arim.libertybans.api.PunishmentType; @@ -32,6 +31,7 @@ import space.arim.libertybans.api.punish.ExpunctionOrder; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.RevocationOrder; +import space.arim.libertybans.api.select.SelectionPredicate; import space.arim.libertybans.core.database.InternalDatabase; import space.arim.libertybans.core.database.sql.EndTimeCondition; import space.arim.libertybans.core.database.sql.TableForType; @@ -43,7 +43,9 @@ import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.time.Instant; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import static space.arim.libertybans.core.schema.tables.Punishments.PUNISHMENTS; import static space.arim.libertybans.core.schema.tables.SimpleActive.SIMPLE_ACTIVE; @@ -151,8 +153,8 @@ private Punishment deleteAndGetActivePunishmentByIdAndType(DSLContext context, Punishment result = context .select( SIMPLE_HISTORY.VICTIM_TYPE, SIMPLE_HISTORY.VICTIM_UUID, SIMPLE_HISTORY.VICTIM_ADDRESS, - SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, - SIMPLE_HISTORY.SCOPE, SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK + SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, SIMPLE_HISTORY.SCOPE, + SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK, SIMPLE_HISTORY.SCOPE_TYPE ) .from(SIMPLE_HISTORY) .where(SIMPLE_HISTORY.ID.eq(id)) @@ -214,14 +216,19 @@ CentralisedFuture undoAndGetPunishmentById(final long id) { } private static Condition matchesAnyVictim(VictimFields victimFields, List victims) { - Condition matchesAnyVictim = DSL.noCondition(); - VictimCondition victimCondition = new VictimCondition(victimFields); - for (Victim victim : victims) { - matchesAnyVictim = matchesAnyVictim.or( - victimCondition.matchesVictim(victim) - ); - } - return matchesAnyVictim; + LinkedHashSet victimSet = new LinkedHashSet<>(victims); + // Implement it ourself because preserving order is important + return new VictimCondition(victimFields).buildCondition(new SelectionPredicate<>() { + @Override + public Set acceptedValues() { + return victimSet; + } + + @Override + public Set rejectedValues() { + return Set.of(); + } + }); } CentralisedFuture undoPunishmentByTypeAndPossibleVictims(final PunishmentType type, diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishmentCreator.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishmentCreator.java index 83c6e65e8..74af6a728 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishmentCreator.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishmentCreator.java @@ -24,8 +24,8 @@ import jakarta.inject.Singleton; import org.jooq.Record10; import org.jooq.Record11; -import org.jooq.Record5; -import org.jooq.Record9; +import org.jooq.Record12; +import org.jooq.Record6; import org.jooq.RecordMapper; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; @@ -35,20 +35,25 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.sql.DeserializedVictim; +import space.arim.libertybans.core.scope.InternalScopeManager; +import space.arim.libertybans.core.scope.ScopeType; import java.time.Instant; +import java.util.Objects; import java.util.UUID; @Singleton public class SecurePunishmentCreator implements PunishmentCreator { + private final InternalScopeManager scopeManager; private final Provider revoker; private final Provider enforcement; private final Provider modifier; @Inject - public SecurePunishmentCreator(Provider revoker, Provider enforcement, - Provider modifier) { + public SecurePunishmentCreator(InternalScopeManager scopeManager, Provider revoker, + Provider enforcement, Provider modifier) { + this.scopeManager = scopeManager; this.revoker = revoker; this.enforcement = enforcement; this.modifier = modifier; @@ -73,76 +78,89 @@ public Punishment createPunishment(long id, PunishmentType type, Victim victim, } @Override - public RecordMapper, + public RecordMapper, Punishment> punishmentMapper() { return (record) -> { Victim victim = new DeserializedVictim( record.value4(), record.value5() ).victim(record.value3()); + ServerScope scope = scopeManager.deserialize( + record.value12(), record.value8() + ); return new SecurePunishment( SecurePunishmentCreator.this, record.value1(), record.value2(), // id, type victim, record.value6(), record.value7(), // victim, operator, reason - record.value8(), record.value9(), record.value10(), record.value11() // scope, start, end, track + scope, record.value9(), record.value10(), record.value11() // scope, start, end, track ); }; } @Override - public RecordMapper, + public RecordMapper, Punishment> punishmentMapper(long id) { return (record) -> { Victim victim = new DeserializedVictim( record.value3(), record.value4() ).victim(record.value2()); + ServerScope scope = scopeManager.deserialize( + record.value11(), record.value7() + ); return new SecurePunishment( SecurePunishmentCreator.this, id, /* type */ record.value1(), victim, record.value5(), record.value6(), // operator, reason - record.value7(), record.value8(), record.value9(), record.value10() // scope, start, end, track + scope, record.value8(), record.value9(), record.value10() // scope, start, end, track ); }; } @Override - public RecordMapper, + public RecordMapper, Punishment> punishmentMapper(long id, PunishmentType type) { return (record) -> { Victim victim = new DeserializedVictim( record.value2(), record.value3() ).victim(record.value1()); + ServerScope scope = scopeManager.deserialize( + record.value10(), record.value6() + ); return new SecurePunishment( SecurePunishmentCreator.this, id, type, victim, record.value4(), record.value5(), // operator, reason - record.value6(), record.value7(), record.value8(), record.value9() // scope, start, end, track + scope, record.value7(), record.value8(), record.value9() // scope, start, end, track ); }; } @Override - public RecordMapper, + public RecordMapper, Punishment> punishmentMapperForModifications(Punishment oldPunishment) { return (record) -> { EscalationTrack escalationTrack; { - String trackNamespace = record.value4(); - String trackValue = record.value5(); + String trackNamespace = record.value3(); + String trackValue = record.value4(); if (trackNamespace == null && trackValue == null) { escalationTrack = null; } else { escalationTrack = EscalationTrack.create(trackNamespace, trackValue); } } + ServerScope scope = scopeManager.deserialize( + Objects.requireNonNullElse(record.value5(), ScopeType.GLOBAL), + record.value6() + ); return new SecurePunishment( SecurePunishmentCreator.this, oldPunishment.getIdentifier(), oldPunishment.getType(), oldPunishment.getVictim(), oldPunishment.getOperator(), record.value1(), - record.value2(), oldPunishment.getStartDate(), record.value3(), + scope, oldPunishment.getStartDate(), record.value2(), escalationTrack ); }; diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeSerializer.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/CategoryScope.java similarity index 58% rename from bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeSerializer.java rename to bans-core/src/main/java/space/arim/libertybans/core/scope/CategoryScope.java index 31a30300a..77700ec36 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeSerializer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/CategoryScope.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 @@ -19,25 +19,20 @@ package space.arim.libertybans.core.scope; -import space.arim.dazzleconf.error.BadValueException; -import space.arim.dazzleconf.serialiser.Decomposer; -import space.arim.dazzleconf.serialiser.FlexibleType; -import space.arim.dazzleconf.serialiser.ValueSerialiser; import space.arim.libertybans.api.scope.ServerScope; -public final class ScopeSerializer implements ValueSerialiser { - @Override - public Class getTargetClass() { - return ServerScope.class; - } +import java.util.Objects; - @Override - public ServerScope deserialise(FlexibleType flexibleType) throws BadValueException { - return ScopeImpl.GLOBAL; +record CategoryScope(String category) implements ServerScope { + + CategoryScope { + ScopeParsing.checkScopeValue(category); } @Override - public Object serialise(ServerScope value, Decomposer decomposer) { - return "*"; + public boolean appliesTo(String server) { + Objects.requireNonNull(server); + return false; } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ConfiguredScope.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ConfiguredScope.java new file mode 100644 index 000000000..5ce17ae78 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/ConfiguredScope.java @@ -0,0 +1,94 @@ +/* + * 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 org.checkerframework.checker.nullness.qual.Nullable; +import space.arim.dazzleconf.error.BadValueException; +import space.arim.dazzleconf.serialiser.Decomposer; +import space.arim.dazzleconf.serialiser.FlexibleType; +import space.arim.dazzleconf.serialiser.ValueSerialiser; +import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; + +import java.util.Objects; +import java.util.Optional; + +public final class ConfiguredScope { + + private final @Nullable ServerScope scope; + + private ConfiguredScope(@Nullable ServerScope scope) { + this.scope = scope; + } + + Optional rawOptional() { + return Optional.ofNullable(scope); + } + + public static ConfiguredScope create(ServerScope scope) { + Objects.requireNonNull(scope); + return new ConfiguredScope(scope); + } + + public static ConfiguredScope defaultPunishingScope() { + return new ConfiguredScope(null); + } + + public ServerScope actualize(ScopeManager scopeManager) { + return scope == null ? scopeManager.defaultPunishingScope() : scope; + } + + public static final class Serializer implements ValueSerialiser { + + private final ScopeParsing scopeParsing = new ScopeParsing(); + + @Override + public Class getTargetClass() { + return ConfiguredScope.class; + } + + @Override + public ConfiguredScope deserialise(FlexibleType flexibleType) throws BadValueException { + String input = flexibleType.getString(); + if (input.isEmpty()) { + return ConfiguredScope.defaultPunishingScope(); + } + ServerScope scope; + try { + scope = scopeParsing.parseFrom(input); + } catch (IllegalArgumentException ex) { + throw flexibleType.badValueExceptionBuilder() + .message("Invalid scope details") + .cause(ex) + .build(); + } + return new ConfiguredScope(scope); + } + + @Override + public Object serialise(ConfiguredScope value, Decomposer decomposer) { + ServerScope scope = value.scope; + if (scope == null) { + return ""; + } + return scopeParsing.display(scope, ScopeParsing.GLOBAL_SCOPE_USER_INPUT); + } + } +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/GlobalScope.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/GlobalScope.java new file mode 100644 index 000000000..9e1be8f24 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/GlobalScope.java @@ -0,0 +1,38 @@ +/* + * 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 space.arim.libertybans.api.scope.ServerScope; + +import java.util.Objects; + +public final class GlobalScope implements ServerScope { + + public static final ServerScope INSTANCE = new GlobalScope(); + + private GlobalScope() {} + + @Override + public boolean appliesTo(String server) { + Objects.requireNonNull(server); + return true; + } + +} 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 0e873b7ba..f7342cb85 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 @@ -22,17 +22,47 @@ import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; +import java.util.Optional; +import java.util.function.BiFunction; + public interface InternalScopeManager extends ScopeManager { - String getServer(ServerScope scope, String defaultIfGlobal); - + ServerScope deserialize(ScopeType scopeType, String value); + + R deconstruct(ServerScope scope, BiFunction computeResult); + + String display(ServerScope scope, String defaultIfGlobal); + + Optional parseFrom(String userInput); + /** * Checks that a server scope is nonnull and of the right implementation class * * @param scope the server scope * @throws NullPointerException if {@code scope} is null * @throws IllegalArgumentException if {@code scope} is a foreign implementation + * @return the same scope + */ + ServerScope checkScope(ServerScope scope); + + /** + * Whether to automatically detect the name of this server instance + * + * @return whether to detect the server name */ - void checkScope(ServerScope scope); - + boolean shouldDetectServerName(); + + /** + * Sets the automatically detected name of this server instance + * + * @param serverName the server name + */ + void detectServerName(String serverName); + + /** + * Clears the automatically detected server name + * + */ + void clearDetectedServerName(); + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeParsing.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeParsing.java new file mode 100644 index 000000000..eda318e7a --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeParsing.java @@ -0,0 +1,128 @@ +/* + * 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 space.arim.libertybans.api.scope.ServerScope; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiFunction; + +public class ScopeParsing { + + public static final String GLOBAL_SCOPE_USER_INPUT = "*"; + + private static final int SCOPE_VALUE_LENGTH_LIMIT = 32; + + static void checkScopeValue(String value) { + if (value.isEmpty()) { + throw new IllegalArgumentException("Scope is empty"); + } + if (value.length() > SCOPE_VALUE_LENGTH_LIMIT) { + throw new IllegalArgumentException("Scope length must be less than " + SCOPE_VALUE_LENGTH_LIMIT); + } + } + + public ServerScope specificScope(String server) { + return new SpecificServerScope(server); + } + + public ServerScope category(String category) { + return new CategoryScope(category); + } + + public ServerScope globalScope() { + return GlobalScope.INSTANCE; + } + + /** + * Same as {@link #parseFrom(String)} except yields optional. + * + * @param userInput the user input + * @return the scope if the input matched an appropriate pattern and was valid + */ + public Optional parseInputOptionally(String userInput) { + ServerScope scope; + try { + scope = parseFrom(userInput); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + return Optional.of(scope); + } + + /** + * Inverse of display(scope, GLOBAL_SCOPE_USER_INPUT) + * + * @param userInput the user input + * @return the scope if the input matched an appropriate pattern and was valid + * @throws IllegalArgumentException if not a valid scope + */ + public ServerScope parseFrom(String userInput) { + + if (userInput.equals(GLOBAL_SCOPE_USER_INPUT)) { + return globalScope(); + + } else if (userInput.startsWith("server:")) { + String server = userInput.substring("server:".length()); + return specificScope(server); + + } else if (userInput.startsWith("category:")) { + String category = userInput.substring("category:".length()); + return category(category); + } + throw new IllegalArgumentException( + "Scopes must be of the form '" + GLOBAL_SCOPE_USER_INPUT + "', 'server:' plus a server, " + + "or 'category:' plus a category" + ); + } + + String display(ServerScope scope, String defaultIfGlobal) { + return deconstruct(scope, (type, value) -> { + String prefix = switch (type) { + case GLOBAL -> defaultIfGlobal; + case SERVER -> "server:"; + case CATEGORY -> "category:"; + }; + return prefix + value; + }); + } + + public R deconstruct(ServerScope scope, BiFunction computeResult) { + ScopeType type; + String value; + if (scope instanceof GlobalScope) { + type = ScopeType.GLOBAL; + value = ""; + } else if (scope instanceof SpecificServerScope specificServer) { + type = ScopeType.SERVER; + value = specificServer.server(); + } else if (scope instanceof CategoryScope category) { + type = ScopeType.CATEGORY; + value = category.category(); + } else { + Objects.requireNonNull(scope, "scope"); + throw new IllegalArgumentException( + "Unknown server scope implementation: " + scope + " (" + scope.getClass() + ')' + ); + } + return computeResult.apply(type, value); + } +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeType.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeType.java new file mode 100644 index 000000000..f6f2d57f6 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/ScopeType.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.scope; + +public enum ScopeType { + GLOBAL, + SERVER, + CATEGORY +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/Scoper.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/Scoper.java deleted file mode 100644 index 3aac66070..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/scope/Scoper.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core 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-core 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-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core.scope; - -import java.util.Objects; - -import jakarta.inject.Singleton; - -import space.arim.libertybans.api.scope.ServerScope; - -@Singleton -public class Scoper implements InternalScopeManager { - - public Scoper() { - - } - - @Override - public ServerScope specificScope(String server) { - Objects.requireNonNull(server, "server"); - return ScopeImpl.specificServer(server); - } - - @Override - public ServerScope globalScope() { - return ScopeImpl.GLOBAL; - } - - @Override - public ServerScope currentServerScope() { - return ScopeImpl.GLOBAL; // TODO implement scopes - } - - @Override - public String getServer(ServerScope scope, String defaultIfGlobal) { - return ScopeImpl.getServer(scope, defaultIfGlobal); - } - - @Override - public void checkScope(ServerScope scope) { - if (scope == null) { - throw new NullPointerException("scope"); - } - if (!(scope instanceof ScopeImpl)) { - throw new IllegalArgumentException("Foreign implementation of scope " + scope.getClass()); - } - } - -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/scope/SpecificServerScope.java b/bans-core/src/main/java/space/arim/libertybans/core/scope/SpecificServerScope.java new file mode 100644 index 000000000..e163710ba --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/SpecificServerScope.java @@ -0,0 +1,38 @@ +/* + * 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 space.arim.libertybans.api.scope.ServerScope; + +import java.util.Objects; + +public record SpecificServerScope(String server) implements ServerScope { + + public SpecificServerScope { + ScopeParsing.checkScopeValue(server); + } + + @Override + public boolean appliesTo(String server) { + Objects.requireNonNull(server); + return this.server.equals(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 new file mode 100644 index 000000000..fbdd8192c --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/scope/StandardScopeManager.java @@ -0,0 +1,167 @@ +/* + * 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 org.slf4j.LoggerFactory; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.config.ScopeConfig; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; + +@Singleton +public class StandardScopeManager implements InternalScopeManager { + + private final Configs configs; + private final ScopeParsing scopeParsing; + + private volatile String serverName; + + @Inject + public StandardScopeManager(Configs configs, ScopeParsing scopeParsing) { + this.configs = configs; + this.scopeParsing = scopeParsing; + } + + private ScopeConfig config() { + return configs.getScopeConfig(); + } + + @Override + public ServerScope specificScope(String server) { + return new SpecificServerScope(server); + } + + @Override + public ServerScope category(String category) { + return new CategoryScope(category); + } + + @Override + public ServerScope globalScope() { + return GlobalScope.INSTANCE; + } + + @Override + public Optional currentServerScope() { + var nameConfig = config().serverName(); + if (nameConfig.autoDetect()) { + return Optional.ofNullable(this.serverName).map(this::specificScope); + } else { + return Optional.of(specificScope(nameConfig.overrideValue())); + } + } + + @Override + public ServerScope currentServerScopeOrFallback() { + return currentServerScope().orElseGet(() -> { + ConfiguredScope fallback = config().serverName().fallbackIfAutoDetectFails(); + // Don't call fallback.actualize(this) to prevent infinite recursion + return fallback.rawOptional().orElse(globalScope()); + }); + } + + @Override + public Set scopesApplicableToCurrentServer() { + Set applicable = new HashSet<>(); + applicable.add(globalScope()); + applicable.add(currentServerScopeOrFallback()); + for (String category : config().categoriesApplicableToThisServer()) { + applicable.add(category(category)); + } + return applicable; + } + + @Override + public ServerScope defaultPunishingScope() { + return switch (config().defaultPunishingScope()) { + case GLOBAL -> globalScope(); + case THIS_SERVER -> currentServerScopeOrFallback(); + case PRIMARY_CATEGORY -> { + var categories = config().categoriesApplicableToThisServer(); + if (categories.isEmpty()) { + LoggerFactory.getLogger(getClass()).warn( + "In the scope.yml configuration, the option default-punishing-scope is set to " + + "'PRIMARY_CATEGORY', but no categories were specified. Either configure a category " + + "or use a different default-punishing-scope value. The value 'THIS_SERVER' will be " + + "temporarily used while you fix the configuration." + ); + yield currentServerScopeOrFallback(); + } else { + yield category(categories.get(0)); + } + } + }; + } + + @Override + public ServerScope deserialize(ScopeType scopeType, String value) { + return switch (scopeType) { + case GLOBAL -> GlobalScope.INSTANCE; + case SERVER -> new SpecificServerScope(value); + case CATEGORY -> new CategoryScope(value); + }; + } + + @Override + public R deconstruct(ServerScope scope, BiFunction computeResult) { + return scopeParsing.deconstruct(scope, computeResult); + } + + @Override + public String display(ServerScope scope, String defaultIfGlobal) { + return scopeParsing.display(scope, defaultIfGlobal); + } + + @Override + public Optional parseFrom(String userInput) { + return scopeParsing.parseInputOptionally(userInput); + } + + @Override + public ServerScope checkScope(ServerScope scope) { + // If deconstruct succeeds, scope is okay + deconstruct(scope, (_type, _value) -> null); + return scope; + } + + @Override + public boolean shouldDetectServerName() { + return serverName == null && config().serverName().autoDetect(); + } + + @Override + public void detectServerName(String serverName) { + Objects.requireNonNull(serverName); + this.serverName = serverName; + } + + @Override + public void clearDetectedServerName() { + serverName = null; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/Gatekeeper.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/Gatekeeper.java index 8ed3ea08c..a47fadb97 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/Gatekeeper.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/Gatekeeper.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 @@ -25,6 +25,8 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.api.select.SelectionPredicate; import space.arim.libertybans.api.select.SortPunishments; import space.arim.libertybans.core.alts.AltDetection; import space.arim.libertybans.core.alts.AltNotification; @@ -40,6 +42,7 @@ import java.time.Instant; import java.util.List; +import java.util.Set; import java.util.UUID; public final class Gatekeeper { @@ -68,7 +71,7 @@ public Gatekeeper(Configs configs, FactoryOfTheFuture futuresFactory, Provider executeAndCheckConnection(UUID uuid, String name, NetworkAddress address, - SelectorImpl selector) { + Set scopes, SelectorImpl selector) { return queryExecutor.get().queryWithRetry((context, transaction) -> { Instant currentTime = time.currentTimestamp(); @@ -78,6 +81,7 @@ CentralisedFuture executeAndCheckConnection(UUID uuid, String name, N Punishment ban = selector.selectionByApplicabilityBuilder(uuid, address) .type(PunishmentType.BAN) + .scopes(SelectionPredicate.matchingAnyOf(scopes)) .build() .findFirstSpecificPunishment(context, () -> currentTime, SortPunishments.LATEST_END_DATE_FIRST); if (ban != null) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/Guardian.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/Guardian.java index c465416a7..488bcd127 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/Guardian.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/Guardian.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 @@ -20,6 +20,7 @@ package space.arim.libertybans.core.selector; import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; import space.arim.libertybans.api.NetworkAddress; import space.arim.omnibus.util.concurrent.CentralisedFuture; @@ -39,7 +40,7 @@ public interface Guardian { * @param address the player's network address * @return a future which yields the punishment message if denied, else null if allowed */ - CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address); + CentralisedFuture<@Nullable Component> executeAndCheckConnection(UUID uuid, String name, NetworkAddress address); /** * Enforces an incoming connection, returning a punishment message if denied, null if allowed.
@@ -52,10 +53,22 @@ public interface Guardian { * @param address the player's network address * @return a future which yields the punishment message if denied, else null if allowed */ - default CentralisedFuture executeAndCheckConnection(UUID uuid, String name, InetAddress address) { + default CentralisedFuture<@Nullable Component> executeAndCheckConnection(UUID uuid, String name, InetAddress address) { return executeAndCheckConnection(uuid, name, NetworkAddress.of(address)); } + /** + * Enforces a server switch, returning a punishment message if denied, null if allowed.
+ *
+ * Queries for an applicable ban, and formats the ban reason as the punishment message. + * + * @param uuid the player's uuid + * @param address the player's network address + * @param destinationServer the player's destination server + * @return a future which yields the punishment message if denied, else null if allowed + */ + CentralisedFuture<@Nullable Component> checkServerSwitch(UUID uuid, InetAddress address, String destinationServer); + /** * Enforces a chat message or executed command, returning a punishment message if denied, null if allowed.
*
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java index ed5814bc9..a258f4557 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java @@ -60,8 +60,8 @@ CentralisedFuture getActivePunishmentById(long id) { .select( SIMPLE_ACTIVE.TYPE, SIMPLE_ACTIVE.VICTIM_TYPE, SIMPLE_ACTIVE.VICTIM_UUID, SIMPLE_ACTIVE.VICTIM_ADDRESS, - SIMPLE_ACTIVE.OPERATOR, SIMPLE_ACTIVE.REASON, - SIMPLE_ACTIVE.SCOPE, SIMPLE_ACTIVE.START, SIMPLE_ACTIVE.END, SIMPLE_ACTIVE.TRACK + SIMPLE_ACTIVE.OPERATOR, SIMPLE_ACTIVE.REASON, SIMPLE_ACTIVE.SCOPE, + SIMPLE_ACTIVE.START, SIMPLE_ACTIVE.END, SIMPLE_ACTIVE.TRACK, SIMPLE_ACTIVE.SCOPE_TYPE ) .from(SIMPLE_ACTIVE) .where(SIMPLE_ACTIVE.ID.eq(id)) @@ -81,8 +81,8 @@ CentralisedFuture getActivePunishmentByIdAndType(long id, Punishment return context .select( simpleView.victimType(), simpleView.victimUuid(), simpleView.victimAddress(), - simpleView.operator(), simpleView.reason(), - simpleView.scope(), simpleView.start(), simpleView.end(), simpleView.track() + simpleView.operator(), simpleView.reason(), simpleView.scope(), + simpleView.start(), simpleView.end(), simpleView.track(), simpleView.scopeType() ) .from(simpleView.table()) .where(simpleView.id().eq(id)) @@ -98,8 +98,8 @@ CentralisedFuture getHistoricalPunishmentById(long id) { .select( SIMPLE_HISTORY.TYPE, SIMPLE_HISTORY.VICTIM_TYPE, SIMPLE_HISTORY.VICTIM_UUID, SIMPLE_HISTORY.VICTIM_ADDRESS, - SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, - SIMPLE_HISTORY.SCOPE, SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK + SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, SIMPLE_HISTORY.SCOPE, + SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK, SIMPLE_HISTORY.SCOPE_TYPE ) .from(SIMPLE_HISTORY) .where(SIMPLE_HISTORY.ID.eq(id)) @@ -113,8 +113,8 @@ CentralisedFuture getHistoricalPunishmentByIdAndType(long id, Punish return context .select( SIMPLE_HISTORY.VICTIM_TYPE, SIMPLE_HISTORY.VICTIM_UUID, SIMPLE_HISTORY.VICTIM_ADDRESS, - SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, - SIMPLE_HISTORY.SCOPE, SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK + SIMPLE_HISTORY.OPERATOR, SIMPLE_HISTORY.REASON, SIMPLE_HISTORY.SCOPE, + SIMPLE_HISTORY.START, SIMPLE_HISTORY.END, SIMPLE_HISTORY.TRACK, SIMPLE_HISTORY.SCOPE_TYPE ) .from(SIMPLE_HISTORY) .where(SIMPLE_HISTORY.ID.eq(id)) diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/IntelligentGuardian.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/IntelligentGuardian.java index 7f663d171..632dfc7c7 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/IntelligentGuardian.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/IntelligentGuardian.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 @@ -22,41 +22,70 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.select.SortPunishments; import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.selector.cache.MuteCache; import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; +import java.net.InetAddress; import java.util.UUID; import java.util.concurrent.CompletionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Function; @Singleton public final class IntelligentGuardian implements Guardian { private final Configs configs; private final FactoryOfTheFuture futuresFactory; + private final ScopeManager scopeManager; + private final InternalFormatter formatter; private final InternalSelector selector; private final UUIDManager uuidManager; private final MuteCache muteCache; @Inject - public IntelligentGuardian(Configs configs, FactoryOfTheFuture futuresFactory, - InternalSelector selector, UUIDManager uuidManager, MuteCache muteCache) { + public IntelligentGuardian(Configs configs, FactoryOfTheFuture futuresFactory, ScopeManager scopeManager, + InternalFormatter formatter, InternalSelector selector, UUIDManager uuidManager, MuteCache muteCache) { this.configs = configs; this.futuresFactory = futuresFactory; + this.scopeManager = scopeManager; + this.formatter = formatter; this.selector = selector; this.uuidManager = uuidManager; this.muteCache = muteCache; } + private static Function timeoutHandler(String where) { + return (ex) -> { + if (ex instanceof TimeoutException) { + throw new IllegalStateException( + "Database timeout while attempting to execute " + where + ". " + + "Your database likely took too long to respond.", + ex); + } else if (ex instanceof CompletionException) { + throw (CompletionException) ex; + } + throw new CompletionException(ex); + }; + } + @Override - public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address) { + public CentralisedFuture<@Nullable Component> executeAndCheckConnection(UUID uuid, String name, + NetworkAddress address) { uuidManager.addCache(uuid, name); - return selector.executeAndCheckConnection(uuid, name, address) + return selector + .executeAndCheckConnection( + uuid, name, address, scopeManager.scopesApplicableToCurrentServer() + ) .thenCompose((component) -> { // Contact the mute cache, but only if needed if (component != null) { @@ -65,17 +94,30 @@ public CentralisedFuture executeAndCheckConnection(UUID uuid, String return muteCache.cacheOnLogin(uuid, address).thenApply((ignore) -> null); }) .orTimeout(12, TimeUnit.SECONDS) - .exceptionally((ex) -> { - if (ex instanceof TimeoutException) { - throw new IllegalStateException( - "Database timeout while attempting to execute incoming login. " + - "Your database likely took too long to respond.", - ex); - } else if (ex instanceof CompletionException) { - throw (CompletionException) ex; + .exceptionally(timeoutHandler("incoming login")); + } + + @Override + public CentralisedFuture<@Nullable Component> checkServerSwitch(UUID uuid, InetAddress address, + String destinationServer) { + if (!configs.getMainConfig().platforms().proxies().enforceServerSwitch()) { + return futuresFactory.completedFuture(null); + } + return selector + .selectionByApplicabilityBuilder(uuid, address) + .type(PunishmentType.BAN) + .scope(scopeManager.specificScope(destinationServer)) + .build() + .getFirstSpecificPunishment(SortPunishments.LATEST_END_DATE_FIRST) + .thenCompose((punishment) -> { + if (punishment.isEmpty()) { + return futuresFactory.completedFuture(null); } - throw new CompletionException(ex); - }); + return formatter.getPunishmentMessage(punishment.get()); + }) + .toCompletableFuture() + .orTimeout(12, TimeUnit.SECONDS) + .exceptionally(timeoutHandler("server switch")); } @Override diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/InternalSelector.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/InternalSelector.java index 066138e08..bb3a43cad 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/InternalSelector.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/InternalSelector.java @@ -21,10 +21,12 @@ import net.kyori.adventure.text.Component; import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.api.select.SelectionOrderBuilder; import space.arim.omnibus.util.concurrent.CentralisedFuture; +import java.util.Set; import java.util.UUID; public interface InternalSelector extends PunishmentSelector { @@ -44,8 +46,10 @@ public interface InternalSelector extends PunishmentSelector { * @param uuid the player uuid * @param name the player name * @param address the player address + * @param scopes the server scopes to include in the selection query * @return a future which yields the denial message, or null if there is none */ - CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address); + CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address, + Set scopes); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionBaseSQL.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionBaseSQL.java index 460bce718..4d872dcd0 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionBaseSQL.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionBaseSQL.java @@ -41,8 +41,10 @@ import space.arim.libertybans.core.database.sql.ApplicableViewFields; import space.arim.libertybans.core.database.sql.EndTimeCondition; import space.arim.libertybans.core.database.sql.PunishmentFields; +import space.arim.libertybans.core.database.sql.ScopeCondition; import space.arim.libertybans.core.database.sql.SimpleViewFields; import space.arim.libertybans.core.database.sql.TableForType; +import space.arim.libertybans.core.scope.ScopeType; import space.arim.omnibus.util.concurrent.ReactionStage; import java.time.Instant; @@ -142,6 +144,7 @@ private List> getColumnsToRetrieve(List> additionalColumns) { } columns.add(fields.reason()); if (getScopes().isNotSimpleEquality()) { + columns.add(fields.scopeType()); columns.add(fields.scope()); } columns.add(fields.start()); @@ -164,14 +167,12 @@ private Condition getPredication(Supplier timeSupplier) { // Type is ensured by selected table } else { condition = condition - .and(new Criteria<>(getTypes()).matchesField(fields.type())); + .and(new SingleFieldCriterion<>(fields.type()).matches(getTypes())); } condition = condition - .and(new Criteria<>(getOperators()).matchesField(fields.operator())) - .and(new Criteria<>(getScopes()).matchesField(fields.scope())) - .and(Criteria.createViaTransform(getEscalationTracks(), (optTrack) -> optTrack.orElse(null)) - .matchesField(fields.track()) - ); + .and(new SingleFieldCriterion<>(fields.operator()).matches(getOperators())) + .and(new SingleFieldCriterion<>(fields.track()).matches(getEscalationTracks(), (optTrack) -> optTrack.orElse(null))) + .and(new ScopeCondition(fields, resources.scopeManager()).buildCondition(getScopes())); if (active) { condition = condition .and(new EndTimeCondition(fields).isNotExpired(timeSupplier.get())); @@ -236,9 +237,14 @@ private Punishment mapRecord(Record record) { Operator operator = retrieveValueFromRecordOrSelection( getOperators(), record, fields.operator() ); - ServerScope scope = retrieveValueFromRecordOrSelection( - getScopes(), record, fields.scope() - ); + ServerScope scope; + if (getScopes().isSimpleEquality()) { + scope = getScopes().acceptedValues().iterator().next(); + } else { + ScopeType scopeType = record.get(aggregateIfNeeded(fields.scopeType())); + String scopeValue = record.get(aggregateIfNeeded(fields.scope())); + scope = resources.scopeManager().deserialize(scopeType, scopeValue); + } EscalationTrack escalationTrack = retrieveValueFromRecordOrSelection( getEscalationTracks(), record, fields.track(), (optTrack) -> optTrack.orElse(null) diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionByApplicabilityBuilderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionByApplicabilityBuilderImpl.java index 3adb8819c..cbb0f6747 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionByApplicabilityBuilderImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionByApplicabilityBuilderImpl.java @@ -21,9 +21,11 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.AddressStrictness; import space.arim.libertybans.api.select.SelectionByApplicability; import space.arim.libertybans.api.select.SelectionByApplicabilityBuilder; +import space.arim.libertybans.api.select.SelectionPredicate; import java.util.Objects; import java.util.UUID; @@ -77,6 +79,11 @@ public SelectionByApplicabilityBuilderImpl type(PunishmentType type) { return (SelectionByApplicabilityBuilderImpl) super.type(type); } + @Override + public SelectionByApplicabilityBuilderImpl scopes(SelectionPredicate scopes) { + return (SelectionByApplicabilityBuilderImpl) super.scopes(scopes); + } + @Override public SelectionByApplicabilityImpl build() { return (SelectionByApplicabilityImpl) super.build(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java index 5f53f43ba..84b3788ef 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java @@ -34,7 +34,6 @@ import java.util.Objects; import static org.jooq.impl.DSL.noCondition; -import static org.jooq.impl.DSL.not; final class SelectionOrderImpl extends SelectionBaseSQL implements SelectionOrder { @@ -70,29 +69,10 @@ Query requestQuery(QueryParameters parameters) { additionalColumns.add(fields.victimUuid()); additionalColumns.add(fields.victimAddress()); } - Condition additionalPredication; - { - VictimCondition victimCondition = new VictimCondition(fields); - // Check victim is accepted - Condition victimAcceptedCondition = noCondition(); - for (Victim acceptedVictim : getVictims().acceptedValues()) { - victimAcceptedCondition = victimAcceptedCondition.or( - victimCondition.matchesVictim(acceptedVictim) - ); - } - // Check victim is not rejected - Condition victimNotRejectedCondition = noCondition(); - for (Victim rejectedVictim : getVictims().rejectedValues()) { - Condition notEqual = not( - victimCondition.matchesVictim(rejectedVictim) - ); - victimNotRejectedCondition = victimNotRejectedCondition.and(notEqual); - } - // Check victim type is accepted - additionalPredication = new Criteria<>(getVictimTypes()).matchesField(fields.victimType()) - .and(victimAcceptedCondition) - .and(victimNotRejectedCondition); - } + Condition additionalPredication = noCondition() + .and(new SingleFieldCriterion<>(fields.victimType()).matches(getVictimTypes())) + .and(new VictimCondition(fields).buildCondition(getVictims())); + return new QueryBuilder(parameters, fields, fields.table()) { @Override Victim victimFromRecord(Record record) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionResources.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionResources.java index 8b1026e4f..b39cc3f6d 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionResources.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionResources.java @@ -23,11 +23,13 @@ import jakarta.inject.Provider; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.punish.PunishmentCreator; +import space.arim.libertybans.core.scope.InternalScopeManager; import space.arim.libertybans.core.service.Time; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; public record SelectionResources(FactoryOfTheFuture futuresFactory, Provider dbProvider, + InternalScopeManager scopeManager, PunishmentCreator creator, Time time) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImpl.java index a6f434824..630e97fb1 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImpl.java @@ -26,6 +26,7 @@ import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.api.select.AddressStrictness; import space.arim.libertybans.api.select.SelectionOrderBuilder; import space.arim.libertybans.core.config.Configs; @@ -35,6 +36,7 @@ import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; @Singleton @@ -105,8 +107,9 @@ public ReactionStage> getHistoricalPunishmentByIdAndType(lo */ @Override - public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address) { - return gatekeeper.executeAndCheckConnection(uuid, name, address, this); + public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address, + Set scopes) { + return gatekeeper.executeAndCheckConnection(uuid, name, address, scopes, this); } @Override diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/Criteria.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SingleFieldCriterion.java similarity index 76% rename from bans-core/src/main/java/space/arim/libertybans/core/selector/Criteria.java rename to bans-core/src/main/java/space/arim/libertybans/core/selector/SingleFieldCriterion.java index 91a0c4d6a..7cecf87be 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/Criteria.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SingleFieldCriterion.java @@ -21,8 +21,6 @@ import org.jooq.Condition; import org.jooq.Field; -import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.select.SelectionPredicate; import java.util.AbstractSet; @@ -35,56 +33,12 @@ import static org.jooq.impl.DSL.noCondition; import static org.jooq.impl.DSL.val; -record Criteria(Set acceptedValues, Set rejectedValues) { +record SingleFieldCriterion(Field field) { - Criteria(SelectionPredicate selection) { - this(selection.acceptedValues(), selection.rejectedValues()); - } - - static Criteria createViaTransform(SelectionPredicate selection, Function converter) { - class SetView extends AbstractSet { - - private final Set original; - - SetView(Set original) { - this.original = original; - } - - @Override - public Iterator iterator() { - Iterator iter = original.iterator(); - class IteratorView implements Iterator { - - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public F next() { - return converter.apply(iter.next()); - } - - @Override - public void forEachRemaining(Consumer action) { - iter.forEachRemaining((g) -> action.accept(converter.apply(g))); - } - } - return new IteratorView(); - } - - @Override - public int size() { - return original.size(); - } - } - return new Criteria<>(new SetView(selection.acceptedValues()), new SetView(selection.rejectedValues())); - } - - private static Field inlineIfNeeded(Field field, U value) { - // Automatically inline PunishmentType and VictimType comparisons - Class fieldType = field.getType(); - boolean shouldInline = fieldType.equals(PunishmentType.class) || fieldType.equals(Victim.VictimType.class); + private Field inlineIfNeeded(F value) { + // Automatically inline enum comparisons (e.g. PunishmentType, VictimType, and ScopeType) + Class fieldType = field.getType(); + boolean shouldInline = fieldType.isEnum(); return shouldInline ? inline(value) : val(value); } @@ -98,7 +52,7 @@ private static boolean containsNull(Set set) { return false; } - Condition matchesField(Field field) { + private Condition matches(Set acceptedValues, Set rejectedValues) { Condition acceptedCondition = switch (acceptedValues.size()) { case 0 -> noCondition(); case 1 -> { @@ -106,7 +60,7 @@ Condition matchesField(Field field) { if (singleAcceptedValue == null) { yield field.isNull(); } - yield field.eq(inlineIfNeeded(field, singleAcceptedValue)); + yield field.eq(inlineIfNeeded(singleAcceptedValue)); } default -> { if (containsNull(acceptedValues)) { @@ -123,7 +77,7 @@ Condition matchesField(Field field) { if (singleRejectedValue == null) { yield field.isNotNull(); } - yield field.notEqual(inlineIfNeeded(field, singleRejectedValue)); + yield field.notEqual(inlineIfNeeded(singleRejectedValue)); } default -> { if (containsNull(rejectedValues)) { @@ -136,4 +90,50 @@ Condition matchesField(Field field) { return acceptedCondition.and(notRejectedCondition); } + Condition matches(SelectionPredicate selection) { + return matches(selection.acceptedValues(), selection.rejectedValues()); + } + + Condition matches(SelectionPredicate selection, Function converter) { + class SetView extends AbstractSet { + + private final Set original; + + SetView(Set original) { + this.original = original; + } + + @Override + public Iterator iterator() { + Iterator iter = original.iterator(); + class IteratorView implements Iterator { + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public F next() { + return converter.apply(iter.next()); + } + + @Override + public void forEachRemaining(Consumer action) { + iter.forEachRemaining((g) -> action.accept(converter.apply(g))); + } + } + return new IteratorView(); + } + + @Override + public int size() { + return original.size(); + } + } + return matches( + new SetView(selection.acceptedValues()), new SetView(selection.rejectedValues()) + ); + } + } diff --git a/bans-core/src/main/resources/database-migrations/V36__Server_scopes.sql b/bans-core/src/main/resources/database-migrations/V36__Server_scopes.sql new file mode 100644 index 000000000..9b7a35bec --- /dev/null +++ b/bans-core/src/main/resources/database-migrations/V36__Server_scopes.sql @@ -0,0 +1,191 @@ + +CREATE TABLE "${tableprefix}scopes" ( + "id" INT NOT NULL, + "type" SMALLINT NOT NULL, + "value" CHARACTER VARYING(32) NOT NULL, + CONSTRAINT "${tableprefix}scope_id_uniqueness" UNIQUE ("id"), + CONSTRAINT "${tableprefix}scope_type_validity" CHECK ("type" IN (1, 2)), + CONSTRAINT "${tableprefix}scope_data_uniqueness" UNIQUE ("type", "value") +)${extratableoptions}; + +-- Include the scope ID column in the punishments table +-- For backwards compatibility, the old textual scope column is kept +ALTER TABLE "${tableprefix}punishments" ADD COLUMN "scope_id" INT NULL; + +-- Recreate every punishment view to include the scope-related columns + +-- Bans + +${alterviewstatement} "${tableprefix}simple_bans" AS + SELECT "puns"."id", "puns"."type", + "victims"."type" AS "victim_type", "victims"."uuid" AS "victim_uuid", "victims"."address" AS "victim_address", + "puns"."operator", "puns"."reason", + (CASE + WHEN "puns"."scope_id" IS NULL THEN '' + ELSE "scopes"."value" + END) AS "scope", + "puns"."start", "puns"."end", + (CASE + WHEN "tracks"."namespace" IS NULL THEN NULL + ELSE (("tracks"."namespace" || ':') || "tracks"."value") + END) AS "track", + (CASE + WHEN "puns"."scope_id" IS NULL THEN ${zerosmallintliteral} + ELSE "scopes"."type" + END) AS "scope_type" + FROM "${tableprefix}bans" AS "thetype" + INNER JOIN "${tableprefix}punishments" AS "puns" + ON "thetype"."id" = "puns"."id" + INNER JOIN "${tableprefix}victims" AS "victims" + ON "thetype"."victim" = "victims"."id" + LEFT JOIN "${tableprefix}tracks" AS "tracks" + ON "puns"."track" = "tracks"."id" + LEFT JOIN "${tableprefix}scopes" AS "scopes" + ON "puns"."scope_id" = "scopes"."id"; + +${alterviewstatement} "${tableprefix}applicable_bans" AS + SELECT "puns"."id", "puns"."type", "puns"."victim_type", "puns"."victim_uuid", "puns"."victim_address", + "puns"."operator", "puns"."reason", "puns"."scope", "puns"."start", "puns"."end", "addrs"."uuid", "addrs"."address", + "puns"."track", "puns"."scope_type" + FROM "${tableprefix}simple_bans" AS "puns" + INNER JOIN "${tableprefix}addresses" AS "addrs" + ON ("puns"."victim_type" = 0 AND "puns"."victim_uuid" = "addrs"."uuid" + OR "puns"."victim_type" = 1 AND "puns"."victim_address" = "addrs"."address" + OR "puns"."victim_type" = 2 AND ("puns"."victim_uuid" = "addrs"."uuid" OR "puns"."victim_address" = "addrs"."address")); + +-- Mutes + +${alterviewstatement} "${tableprefix}simple_mutes" AS + SELECT "puns"."id", "puns"."type", + "victims"."type" AS "victim_type", "victims"."uuid" AS "victim_uuid", "victims"."address" AS "victim_address", + "puns"."operator", "puns"."reason", + (CASE + WHEN "puns"."scope_id" IS NULL THEN '' + ELSE "scopes"."value" + END) AS "scope", + "puns"."start", "puns"."end", + (CASE + WHEN "tracks"."namespace" IS NULL THEN NULL + ELSE (("tracks"."namespace" || ':') || "tracks"."value") + END) AS "track", + (CASE + WHEN "puns"."scope_id" IS NULL THEN ${zerosmallintliteral} + ELSE "scopes"."type" + END) AS "scope_type" + FROM "${tableprefix}mutes" AS "thetype" + INNER JOIN "${tableprefix}punishments" AS "puns" + ON "thetype"."id" = "puns"."id" + INNER JOIN "${tableprefix}victims" AS "victims" + ON "thetype"."victim" = "victims"."id" + LEFT JOIN "${tableprefix}tracks" AS "tracks" + ON "puns"."track" = "tracks"."id" + LEFT JOIN "${tableprefix}scopes" AS "scopes" + ON "puns"."scope_id" = "scopes"."id"; + +${alterviewstatement} "${tableprefix}applicable_mutes" AS + SELECT "puns"."id", "puns"."type", "puns"."victim_type", "puns"."victim_uuid", "puns"."victim_address", + "puns"."operator", "puns"."reason", "puns"."scope", "puns"."start", "puns"."end", "addrs"."uuid", "addrs"."address", + "puns"."track", "puns"."scope_type" + FROM "${tableprefix}simple_mutes" AS "puns" + INNER JOIN "${tableprefix}addresses" AS "addrs" + ON ("puns"."victim_type" = 0 AND "puns"."victim_uuid" = "addrs"."uuid" + OR "puns"."victim_type" = 1 AND "puns"."victim_address" = "addrs"."address" + OR "puns"."victim_type" = 2 AND ("puns"."victim_uuid" = "addrs"."uuid" OR "puns"."victim_address" = "addrs"."address")); + +-- Warns + +${alterviewstatement} "${tableprefix}simple_warns" AS + SELECT "puns"."id", "puns"."type", + "victims"."type" AS "victim_type", "victims"."uuid" AS "victim_uuid", "victims"."address" AS "victim_address", + "puns"."operator", "puns"."reason", + (CASE + WHEN "puns"."scope_id" IS NULL THEN '' + ELSE "scopes"."value" + END) AS "scope", + "puns"."start", "puns"."end", + (CASE + WHEN "tracks"."namespace" IS NULL THEN NULL + ELSE (("tracks"."namespace" || ':') || "tracks"."value") + END) AS "track", + (CASE + WHEN "puns"."scope_id" IS NULL THEN ${zerosmallintliteral} + ELSE "scopes"."type" + END) AS "scope_type" + FROM "${tableprefix}warns" AS "thetype" + INNER JOIN "${tableprefix}punishments" AS "puns" + ON "thetype"."id" = "puns"."id" + INNER JOIN "${tableprefix}victims" AS "victims" + ON "thetype"."victim" = "victims"."id" + LEFT JOIN "${tableprefix}tracks" AS "tracks" + ON "puns"."track" = "tracks"."id" + LEFT JOIN "${tableprefix}scopes" AS "scopes" + ON "puns"."scope_id" = "scopes"."id"; + +${alterviewstatement} "${tableprefix}applicable_warns" AS + SELECT "puns"."id", "puns"."type", "puns"."victim_type", "puns"."victim_uuid", "puns"."victim_address", + "puns"."operator", "puns"."reason", "puns"."scope", "puns"."start", "puns"."end", "addrs"."uuid", "addrs"."address", + "puns"."track", "puns"."scope_type" + FROM "${tableprefix}simple_warns" AS "puns" + INNER JOIN "${tableprefix}addresses" AS "addrs" + ON ("puns"."victim_type" = 0 AND "puns"."victim_uuid" = "addrs"."uuid" + OR "puns"."victim_type" = 1 AND "puns"."victim_address" = "addrs"."address" + OR "puns"."victim_type" = 2 AND ("puns"."victim_uuid" = "addrs"."uuid" OR "puns"."victim_address" = "addrs"."address")); + +-- Other helpers + +${alterviewstatement} "${tableprefix}simple_history" AS + SELECT "puns"."id", "puns"."type", + "victims"."type" AS "victim_type", "victims"."uuid" AS "victim_uuid", "victims"."address" AS "victim_address", + "puns"."operator", "puns"."reason", + (CASE + WHEN "puns"."scope_id" IS NULL THEN '' + ELSE "scopes"."value" + END) AS "scope", + "puns"."start", "puns"."end", + (CASE + WHEN "tracks"."namespace" IS NULL THEN NULL + ELSE (("tracks"."namespace" || ':') || "tracks"."value") + END) AS "track", + (CASE + WHEN "puns"."scope_id" IS NULL THEN ${zerosmallintliteral} + ELSE "scopes"."type" + END) AS "scope_type" + FROM "${tableprefix}history" AS "thetype" + INNER JOIN "${tableprefix}punishments" AS "puns" + ON "thetype"."id" = "puns"."id" + INNER JOIN "${tableprefix}victims" AS "victims" + ON "thetype"."victim" = "victims"."id" + LEFT JOIN "${tableprefix}tracks" AS "tracks" + ON "puns"."track" = "tracks"."id" + LEFT JOIN "${tableprefix}scopes" AS "scopes" + ON "puns"."scope_id" = "scopes"."id"; + +${alterviewstatement} "${tableprefix}simple_active" AS + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "track", "scope_type" + FROM "${tableprefix}simple_bans" + UNION ALL + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "track", "scope_type" + FROM "${tableprefix}simple_mutes" + UNION ALL + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "track", "scope_type" + FROM "${tableprefix}simple_warns"; + +${alterviewstatement} "${tableprefix}applicable_active" AS + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "uuid", "address", "track", "scope_type" + FROM "${tableprefix}applicable_bans" + UNION ALL + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "uuid", "address", "track", "scope_type" + FROM "${tableprefix}applicable_mutes" + UNION ALL + SELECT "id", "type", "victim_type", "victim_uuid", "victim_address", "operator", "reason", "scope", "start", "end", "uuid", "address", "track", "scope_type" + FROM "${tableprefix}applicable_warns"; + +${alterviewstatement} "${tableprefix}applicable_history" AS + SELECT "puns"."id", "puns"."type", "puns"."victim_type", "puns"."victim_uuid", "puns"."victim_address", + "puns"."operator", "puns"."reason", "puns"."scope", "puns"."start", "puns"."end", "addrs"."uuid", "addrs"."address", + "puns"."track", "puns"."scope_type" + FROM "${tableprefix}simple_history" AS "puns" + INNER JOIN "${tableprefix}addresses" AS "addrs" + ON ("puns"."victim_type" = 0 AND "puns"."victim_uuid" = "addrs"."uuid" + OR "puns"."victim_type" = 1 AND "puns"."victim_address" = "addrs"."address" + OR "puns"."victim_type" = 2 AND ("puns"."victim_uuid" = "addrs"."uuid" OR "puns"."victim_address" = "addrs"."address")); diff --git a/bans-core/src/test/java/space/arim/libertybans/core/commands/CommandPackageTest.java b/bans-core/src/test/java/space/arim/libertybans/core/commands/CommandPackageTest.java index 9604197b4..db145c7d2 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/commands/CommandPackageTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/commands/CommandPackageTest.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 @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -176,4 +177,28 @@ public void multipleNormalAndHiddenArgsWithPeek(CommandPackageImpl impl) { assertFalse(cmd.hasNext()); } + @ParameterizedTest + @ArgumentsSource(CommandPackageImpl.Provider.class) + public void hiddenArgSpecifiedValue(CommandPackageImpl impl) { + CommandPackage cmd = impl.create("user1 -s -scope=hello 30d teaming -green in kitpvp"); + + assertFalse(cmd.findHiddenArgument("s"), "-s not yet visible"); + assertNull(cmd.findHiddenArgumentSpecifiedValue("scope"), "scope not yet visible"); + assertNull(cmd.findHiddenArgumentSpecifiedValue("track")); + + assertTrue(cmd.hasNext()); + assertEquals("user1", cmd.next()); + + assertTrue(cmd.hasNext()); + assertEquals("30d", cmd.next()); + + assertTrue(cmd.findHiddenArgument("s")); + assertEquals("hello", cmd.findHiddenArgumentSpecifiedValue("scope")); + assertFalse(cmd.findHiddenArgument("green")); + assertNull(cmd.findHiddenArgumentSpecifiedValue("track")); + + assertEquals("teaming -green in kitpvp", cmd.allRemaining()); + assertFalse(cmd.hasNext()); + } + } diff --git a/bans-core/src/test/java/space/arim/libertybans/core/commands/UnspecifiedReasonsTest.java b/bans-core/src/test/java/space/arim/libertybans/core/commands/UnspecifiedReasonsTest.java index 2c9a2b157..2533a95ff 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/commands/UnspecifiedReasonsTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/commands/UnspecifiedReasonsTest.java @@ -30,21 +30,24 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.DraftPunishmentBuilder; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.addon.exempt.Exemption; import space.arim.libertybans.core.commands.extra.ArgumentParser; -import space.arim.libertybans.core.config.AdditionAssistant; -import space.arim.libertybans.core.config.ParsedDuration; -import space.arim.libertybans.core.event.FireEventWithTimeout; -import space.arim.libertybans.core.punish.permission.DurationPermissionsConfig; +import space.arim.libertybans.core.commands.extra.ParseScope; import space.arim.libertybans.core.commands.extra.ReasonsConfig; import space.arim.libertybans.core.commands.extra.TabCompletion; +import space.arim.libertybans.core.config.AdditionAssistant; import space.arim.libertybans.core.config.AdditionsSection; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.config.MainConfig; import space.arim.libertybans.core.config.MessagesConfig; +import space.arim.libertybans.core.config.ParsedDuration; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.env.EnvUserResolver; +import space.arim.libertybans.core.event.FireEventWithTimeout; +import space.arim.libertybans.core.punish.permission.DurationPermissionsConfig; import space.arim.omnibus.DefaultOmnibus; import space.arim.omnibus.util.concurrent.impl.IndifferentFactoryOfTheFuture; @@ -84,7 +87,7 @@ public UnspecifiedReasonsTest(@Mock PunishmentDrafter drafter, @Mock InternalFor @BeforeEach public void setConfigSection(AbstractSubCommandGroup.Dependencies dependencies, /* Mock */ Configs configs, /* Mock */ ArgumentParser argParser, - @Mock EnvUserResolver envUserResolver) { + @Mock ScopeManager scopeManager, @Mock EnvUserResolver envUserResolver) { { MainConfig mainConfig = mock(MainConfig.class); when(configs.getMainConfig()).thenReturn(mainConfig); @@ -106,6 +109,10 @@ public void setConfigSection(AbstractSubCommandGroup.Dependencies dependencies, } when(argParser.parseVictim(any(), eq("A248"), any())).thenAnswer((i) -> new IndifferentFactoryOfTheFuture().completedFuture(PlayerVictim.of(UUID.randomUUID()))); + when(argParser.parseScope(any(), any(), any())).thenAnswer((invocation) -> { + return invocation.getArgument(2, ParseScope.class).defaultValue(scopeManager); + }); + when(scopeManager.defaultPunishingScope()).thenReturn(mock(ServerScope.class)); punishCommands = new PlayerPunishCommands( dependencies, drafter, formatter, diff --git a/bans-core/src/test/java/space/arim/libertybans/core/commands/extra/StandardArgumentParserTest.java b/bans-core/src/test/java/space/arim/libertybans/core/commands/extra/StandardArgumentParserTest.java index 37c15607e..110a99ae5 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/commands/extra/StandardArgumentParserTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/commands/extra/StandardArgumentParserTest.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 @@ -36,11 +36,17 @@ import space.arim.libertybans.api.PlayerOperator; import space.arim.libertybans.api.PlayerVictim; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.formatter.PunishmentFormatter; +import space.arim.libertybans.api.scope.ServerScope; +import space.arim.libertybans.core.commands.CommandPackage; import space.arim.libertybans.core.commands.ComponentMatcher; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.MessagesConfig; import space.arim.libertybans.core.env.CmdSender; import space.arim.libertybans.core.env.UUIDAndAddress; +import space.arim.libertybans.core.scope.InternalScopeManager; +import space.arim.libertybans.core.scope.ScopeParsing; +import space.arim.libertybans.core.scope.ScopeType; import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.libertybans.it.util.RandomUtil; import space.arim.omnibus.util.concurrent.CentralisedFuture; @@ -50,11 +56,14 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.BiFunction; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -66,33 +75,38 @@ public class StandardArgumentParserTest { private final FactoryOfTheFuture futuresFactory = new IndifferentFactoryOfTheFuture(); private final Configs configs; + private final InternalScopeManager scopeManager; private final UUIDManager uuidManager; private final CmdSender sender; private final ArgumentParser argumentParser; private final MessagesConfig messagesConfig; - private final MessagesConfig.All.NotFound notFound; + private final MessagesConfig.All allConfig; + private final MessagesConfig.All.NotFound notFoundConfig; private final Component notFoundMessage = Component.text("Not found", NamedTextColor.RED); - public StandardArgumentParserTest(@Mock Configs configs, @Mock UUIDManager uuidManager, @Mock CmdSender sender, - @Mock MessagesConfig messagesConfig, @Mock MessagesConfig.All.NotFound notFound) { + public StandardArgumentParserTest( + @Mock Configs configs, @Mock InternalScopeManager scopeManager, @Mock PunishmentFormatter formatter, + @Mock UUIDManager uuidManager, @Mock CmdSender sender, @Mock MessagesConfig messagesConfig, + @Mock MessagesConfig.All allConfig, @Mock MessagesConfig.All.NotFound notFoundConfig) { this.configs = configs; + this.scopeManager = scopeManager; this.uuidManager = uuidManager; this.sender = sender; this.messagesConfig = messagesConfig; - this.notFound = notFound; + this.allConfig = allConfig; + this.notFoundConfig = notFoundConfig; - argumentParser = new StandardArgumentParser(futuresFactory, configs, uuidManager); + argumentParser = new StandardArgumentParser(futuresFactory, configs, scopeManager, formatter, uuidManager); } @BeforeEach public void setupMocks() { lenient().when(configs.getMessagesConfig()).thenReturn(messagesConfig).getMock(); - MessagesConfig.All all = mock(MessagesConfig.All.class); - lenient().when(messagesConfig.all()).thenReturn(all); - lenient().when(all.notFound()).thenReturn(notFound); + lenient().when(messagesConfig.all()).thenReturn(allConfig); + lenient().when(allConfig.notFound()).thenReturn(notFoundConfig); } private CentralisedFuture completedFuture(T value) { @@ -143,7 +157,7 @@ public void lookupPlayerVictim() { public void unknownPlayerVictimForName() { String name = "A248"; when(uuidManager.lookupUUID(name)).thenReturn(completedFuture(Optional.empty())); - when(notFound.player()).thenReturn(ComponentText.create(notFoundMessage)); + when(notFoundConfig.player()).thenReturn(ComponentText.create(notFoundMessage)); assertNull(parseVictim(name)); @@ -179,7 +193,7 @@ public void lookupPlayerVictim() { public void unknownPlayerVictimForName() { String name = "A248"; when(uuidManager.lookupPlayer(name)).thenReturn(completedFuture(Optional.empty())); - when(notFound.player()).thenReturn(ComponentText.create(notFoundMessage)); + when(notFoundConfig.player()).thenReturn(ComponentText.create(notFoundMessage)); assertNull(parseVictim(name)); @@ -231,7 +245,7 @@ public void unknownPlayerOperatorForName() { String name = "A248"; mockConsoleArguments(Set.of()); when(uuidManager.lookupUUID(name)).thenReturn(completedFuture(Optional.empty())); - when(notFound.player()).thenReturn(ComponentText.create(notFoundMessage)); + when(notFoundConfig.player()).thenReturn(ComponentText.create(notFoundMessage)); assertNull(parseOperator(name)); @@ -282,7 +296,7 @@ public void lookupAddressVictim() { public void unknownAddressVictimForName() { String name = "A248"; when(uuidManager.lookupAddress(name)).thenReturn(completedFuture(null)); - when(notFound.playerOrAddress()).thenReturn(ComponentText.create(notFoundMessage)); + when(notFoundConfig.playerOrAddress()).thenReturn(ComponentText.create(notFoundMessage)); assertNull(parseVictim(name)); @@ -291,5 +305,101 @@ public void unknownAddressVictimForName() { } } - + + @Nested + public class ParseScopeFromCommand { + + @BeforeEach + public void setup() { + lenient().when(scopeManager.parseFrom(any())).thenAnswer((invocation) -> { + return new ScopeParsing() { + @Override + public ServerScope specificScope(String server) { + return scopeManager.specificScope(server); + } + + @Override + public ServerScope category(String category) { + return scopeManager.category(category); + } + + @Override + public ServerScope globalScope() { + return scopeManager.globalScope(); + } + }.parseInputOptionally(invocation.getArgument(0)); + }); + lenient().when(scopeManager.deconstruct(any(), any())).thenAnswer((invocation) -> { + BiFunction computeResult = invocation.getArgument(1); + return computeResult.apply(ScopeType.GLOBAL, ""); + }); + when(sender.hasPermission(any())).thenReturn(true); + } + + @Test + public void noneSpecified(@Mock CommandPackage command, + @Mock ServerScope defaultScope) { + when(scopeManager.defaultPunishingScope()).thenReturn(defaultScope); + when(command.findHiddenArgumentSpecifiedValue(any())).thenReturn(null); + assertEquals(defaultScope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void serverScope(@Mock CommandPackage command, @Mock ServerScope scope) { + when(scopeManager.specificScope("lobby")).thenReturn(scope); + when(command.findHiddenArgumentSpecifiedValue("server")).thenReturn("lobby"); + assertEquals(scope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void serverScopeLiteral(@Mock CommandPackage command, @Mock ServerScope scope) { + when(scopeManager.specificScope("lobby")).thenReturn(scope); + when(command.findHiddenArgumentSpecifiedValue("scope")).thenReturn("server:lobby"); + when(command.findHiddenArgumentSpecifiedValue(not(eq("scope")))).thenReturn(null); + assertEquals(scope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void categoryScope(@Mock CommandPackage command, @Mock ServerScope scope) { + when(scopeManager.category("pvp")).thenReturn(scope); + when(command.findHiddenArgumentSpecifiedValue("category")).thenReturn("pvp"); + when(command.findHiddenArgumentSpecifiedValue(not(eq("category")))).thenReturn(null); + assertEquals(scope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void categoryScopeLiteral(@Mock CommandPackage command, @Mock ServerScope scope) { + when(scopeManager.category("pvp")).thenReturn(scope); + when(command.findHiddenArgumentSpecifiedValue("scope")).thenReturn("category:pvp"); + when(command.findHiddenArgumentSpecifiedValue(not(eq("scope")))).thenReturn(null); + assertEquals(scope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void globalLiteral(@Mock CommandPackage command, @Mock ServerScope globalScope) { + when(scopeManager.globalScope()).thenReturn(globalScope); + when(command.findHiddenArgumentSpecifiedValue("scope")).thenReturn(ScopeParsing.GLOBAL_SCOPE_USER_INPUT); + when(command.findHiddenArgumentSpecifiedValue(not(eq("scope")))).thenReturn(null); + assertEquals(globalScope, argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender, never()).sendMessage(any()); + } + + @Test + public void invalidScope(@Mock CommandPackage command, @Mock MessagesConfig.All.Scopes section) { + ComponentText errorMsg = ComponentText.create(Component.text("Not a valid scope")); + when(allConfig.scopes()).thenReturn(section); + when(section.invalid()).thenReturn(errorMsg); + + when(command.findHiddenArgumentSpecifiedValue("scope")).thenReturn("gibberish"); + when(command.findHiddenArgumentSpecifiedValue(not(eq("scope")))).thenReturn(null); + assertNull(argumentParser.parseScope(sender, command, ParseScope.fallbackToDefaultPunishingScope())); + verify(sender).sendMessage(errorMsg); + } + } + } diff --git a/bans-core/src/test/java/space/arim/libertybans/core/config/FormatterTest.java b/bans-core/src/test/java/space/arim/libertybans/core/config/FormatterTest.java index a2dddcfbf..2997e36c2 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/config/FormatterTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/config/FormatterTest.java @@ -1,7 +1,7 @@ /* * LibertyBans - * Copyright © 2020 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 @@ -99,7 +99,7 @@ public void setupMocks() { when(configs.getMessagesConfig()).thenReturn(messagesConfig); lenient().when(scopeManager.globalScope()).thenReturn(globalScope); - lenient().when(scopeManager.getServer(same(globalScope), any())).thenAnswer( + lenient().when(scopeManager.display(same(globalScope), any())).thenAnswer( invocationOnMock -> invocationOnMock.getArgument(1, String.class)); } @@ -333,7 +333,7 @@ private Punishment punishmentFor(FormatterTestInfo testInfo, Instant start, Inst private ServerScope specificScope(String server) { ServerScope scope = mock(ServerScope.class); - when(scopeManager.getServer(same(scope), any())).thenReturn(server); + when(scopeManager.display(same(scope), any())).thenReturn(server); return scope; } diff --git a/bans-core/src/test/java/space/arim/libertybans/core/config/SpecifiedConfigs.java b/bans-core/src/test/java/space/arim/libertybans/core/config/SpecifiedConfigs.java index a965968bb..3d894981a 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/config/SpecifiedConfigs.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/config/SpecifiedConfigs.java @@ -152,6 +152,11 @@ public ImportConfig getImportConfig() { return delegate.getImportConfig(); } + @Override + public ScopeConfig getScopeConfig() { + return delegate.getScopeConfig(); + } + @Override public CompletableFuture reloadConfigs() { return delegate.reloadConfigs(); diff --git a/bans-core/src/test/java/space/arim/libertybans/core/importing/AdvancedBanImportSourceTest.java b/bans-core/src/test/java/space/arim/libertybans/core/importing/AdvancedBanImportSourceTest.java index 8b4a3aa35..1d18b1aa9 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/importing/AdvancedBanImportSourceTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/importing/AdvancedBanImportSourceTest.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,7 +31,6 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.omnibus.util.UUIDUtil; import java.net.InetAddress; @@ -57,7 +56,7 @@ public class AdvancedBanImportSourceTest { private DSLContext context; private ImportSource importSource; - private ServerScope globalScope = ScopeImpl.GLOBAL; + private ServerScope globalScope; @BeforeEach public void setup(DSLContext context, ConnectionSource connectionSource) throws SQLException { diff --git a/bans-core/src/test/java/space/arim/libertybans/core/importing/BanManagerImportSourceTest.java b/bans-core/src/test/java/space/arim/libertybans/core/importing/BanManagerImportSourceTest.java index b00f14e99..e4e143ccb 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/importing/BanManagerImportSourceTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/importing/BanManagerImportSourceTest.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 @@ -34,7 +34,6 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.it.util.RandomUtil; import space.arim.omnibus.util.UUIDUtil; @@ -63,7 +62,7 @@ public class BanManagerImportSourceTest { private ImportSource importSource; private final UUID consoleUuid = UUID.randomUUID(); - private ServerScope globalScope = ScopeImpl.GLOBAL; + private ServerScope globalScope; @BeforeEach public void setup(DSLContext context, ConnectionSource connectionSource) throws SQLException { diff --git a/bans-core/src/test/java/space/arim/libertybans/core/importing/LiteBansImportSourceTest.java b/bans-core/src/test/java/space/arim/libertybans/core/importing/LiteBansImportSourceTest.java index 77aa193db..c0e421b44 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/importing/LiteBansImportSourceTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/importing/LiteBansImportSourceTest.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,7 +31,6 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.api.scope.ServerScope; -import space.arim.libertybans.core.scope.ScopeImpl; import java.io.IOException; import java.net.InetAddress; @@ -57,14 +56,17 @@ public class LiteBansImportSourceTest { private ImportSource importSource; private DSLContext context; - private final ServerScope globalScope = ScopeImpl.GLOBAL; - private final ServerScope kitpvpScope = ScopeImpl.specificServer("kitpvp"); - private final ServerScope lobbyScope = ScopeImpl.specificServer("lobby"); + private ServerScope globalScope; + private ServerScope kitpvpScope; + private ServerScope lobbyScope; @BeforeEach public void setup(DSLContext context, ConnectionSource connectionSource) throws SQLException, IOException { this.context = context; + globalScope = mock(ServerScope.class); + kitpvpScope = mock(ServerScope.class); + lobbyScope = mock(ServerScope.class); ScopeManager scopeManager = mock(ScopeManager.class); lenient().when(scopeManager.globalScope()).thenReturn(globalScope); lenient().when(scopeManager.specificScope("kitpvp")).thenReturn(kitpvpScope); diff --git a/bans-core/src/test/java/space/arim/libertybans/core/punish/IntelligentGuardianTest.java b/bans-core/src/test/java/space/arim/libertybans/core/punish/IntelligentGuardianTest.java index 74aec5e1b..c9608fa5e 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/punish/IntelligentGuardianTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/punish/IntelligentGuardianTest.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 @@ -27,7 +27,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.core.config.Configs; +import space.arim.libertybans.core.config.InternalFormatter; import space.arim.libertybans.core.config.MainConfig; import space.arim.libertybans.core.selector.EnforcementConfig; import space.arim.libertybans.core.selector.Guardian; @@ -65,11 +67,12 @@ public IntelligentGuardianTest(@Mock MuteCache muteCache) { } @BeforeEach - public void setup(@Mock Configs configs, @Mock InternalSelector selector, @Mock UUIDManager uuidManager) { + public void setup(@Mock Configs configs, @Mock ScopeManager scopeManager, @Mock InternalFormatter formatter, + @Mock InternalSelector selector, @Mock UUIDManager uuidManager) { uuid = UUID.randomUUID(); address = RandomUtil.randomAddress(); - guardian = new IntelligentGuardian(configs, futuresFactory, selector, uuidManager, muteCache); + guardian = new IntelligentGuardian(configs, futuresFactory, scopeManager, formatter, selector, uuidManager, muteCache); MainConfig mainConfig = mock(MainConfig.class); EnforcementConfig enforcementConfig = mock(EnforcementConfig.class); diff --git a/bans-core/src/test/java/space/arim/libertybans/core/selector/SelectionBaseSQLTest.java b/bans-core/src/test/java/space/arim/libertybans/core/selector/SelectionBaseSQLTest.java index cd5b90cc7..f3dcaa603 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/selector/SelectionBaseSQLTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/selector/SelectionBaseSQLTest.java @@ -31,6 +31,7 @@ import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.database.jooq.JooqContext; import space.arim.libertybans.core.punish.PunishmentCreator; +import space.arim.libertybans.core.scope.InternalScopeManager; import space.arim.libertybans.core.service.Time; import space.arim.omnibus.util.concurrent.impl.IndifferentFactoryOfTheFuture; @@ -48,7 +49,7 @@ public class SelectionBaseSQLTest { public void optimizedApplicabilityQuery(AddressStrictness strictness) { SelectionResources selectionResources = new SelectionResources( new IndifferentFactoryOfTheFuture(), () -> mock(QueryExecutor.class), - mock(PunishmentCreator.class), mock(Time.class) + mock(InternalScopeManager.class), mock(PunishmentCreator.class), mock(Time.class) ); UUID uuid = UUID.randomUUID(); NetworkAddress address = NetworkAddress.of(InetAddress.getLoopbackAddress()); 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 c580dfb77..0bf548309 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 @@ -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 @@ -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.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -59,6 +60,10 @@ public EnvUserResolver resolver(QuackUserResolver resolver) { return resolver; } + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> {}; + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("PlatformImportSource not available"); } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/MigrationResult.java b/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/MigrationResult.java deleted file mode 100644 index f9d163758..000000000 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/MigrationResult.java +++ /dev/null @@ -1,125 +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.it.test.database.migrate08; - -import org.jooq.DSLContext; -import space.arim.libertybans.api.NetworkAddress; -import space.arim.libertybans.api.Operator; -import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Victim; -import space.arim.libertybans.api.punish.Punishment; -import space.arim.libertybans.core.importing.NameAddressRecord; -import space.arim.libertybans.core.punish.PunishmentCreator; -import space.arim.libertybans.core.scope.ScopeImpl; -import space.arim.libertybans.core.service.Time; - -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -import static space.arim.libertybans.core.schema.tables.SimpleActive.SIMPLE_ACTIVE; -import static space.arim.libertybans.core.schema.tables.SimpleHistory.SIMPLE_HISTORY; - -record MigrationResult(List activePunishments, - List historicalPunishments) { - - MigrationResult { - activePunishments = List.copyOf(activePunishments); - historicalPunishments = List.copyOf(historicalPunishments); - } - - static MigrationResult retrieveFrom(DSLContext context, PunishmentCreator creator) { - return new MigrationResult( - context - .selectFrom(SIMPLE_ACTIVE) - .orderBy(SIMPLE_ACTIVE.START) - .fetch(creator.punishmentMapper()), - context - .selectFrom(SIMPLE_HISTORY) - .orderBy(SIMPLE_HISTORY.START) - .fetch(creator.punishmentMapper()) - ); - } - - static final class Builder { - - private final Time time; - private final ZeroeightInterlocutor zeroeightInterlocutor; - private final PunishmentCreator creator; - - private final List activePunishments = new ArrayList<>(); - private final List historicalPunishments = new ArrayList<>(); - - private final AtomicInteger idGenerator = new AtomicInteger(); - - Builder(Time time, ZeroeightInterlocutor zeroeightInterlocutor, PunishmentCreator creator) { - this.time = Objects.requireNonNull(time, "time"); - this.zeroeightInterlocutor = Objects.requireNonNull(zeroeightInterlocutor, "zeroeightInterlocutor"); - this.creator = Objects.requireNonNull(creator, "creator"); - } - - Builder addUser(UUID uuid, String name, NetworkAddress address) { - Instant currentTime = time.currentTimestamp(); - NameAddressRecord nameAddressRecord = new NameAddressRecord(uuid, name, address, currentTime); - zeroeightInterlocutor.insertUser(nameAddressRecord); - return this; - } - - void addActivePunishment(PunishmentType type, Victim victim, - Operator operator, String reason, Duration duration) { - addPunishment(type, victim, operator, reason, duration, true); - } - - void addHistoricalPunishment(PunishmentType type, Victim victim, - Operator operator, String reason, Duration duration) { - addPunishment(type, victim, operator, reason, duration, false); - } - - private void addPunishment(PunishmentType type, Victim victim, - Operator operator, String reason, Duration duration, - boolean active) { - Instant start = time.currentTimestamp(); - Instant end; - if (duration.equals(Duration.ZERO)) { // Permanent - end = Instant.MAX; - } else { - end = start.plus(duration); - } - long id = idGenerator.getAndIncrement(); - Punishment punishment = creator.createPunishment( - id, type, victim, operator, reason, ScopeImpl.GLOBAL, start, end, null - ); - zeroeightInterlocutor.insertPunishment(punishment, active); - if (active) { - activePunishments.add(punishment); - } - historicalPunishments.add(punishment); - - } - - MigrationResult build() { - return new MigrationResult(activePunishments, historicalPunishments); - } - } -} diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/ZeroeightInterlocutor.java b/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/ZeroeightInterlocutor.java deleted file mode 100644 index 54e04e773..000000000 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/database/migrate08/ZeroeightInterlocutor.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * LibertyBans - * Copyright © 2021 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.test.database.migrate08; - -import org.flywaydb.core.Flyway; -import org.jooq.DSLContext; -import org.jooq.Record3; -import org.jooq.SQLDialect; -import org.jooq.Table; -import org.jooq.impl.TableImpl; -import space.arim.libertybans.api.AddressVictim; -import space.arim.libertybans.api.NetworkAddress; -import space.arim.libertybans.api.PlayerVictim; -import space.arim.libertybans.api.Victim; -import space.arim.libertybans.api.punish.Punishment; -import space.arim.libertybans.core.database.DatabaseConstants; -import space.arim.libertybans.core.database.jooq.OperatorBinding; -import space.arim.libertybans.core.importing.NameAddressRecord; -import space.arim.libertybans.core.schema.Sequences; -import space.arim.omnibus.util.UUIDUtil; - -import javax.sql.DataSource; -import java.sql.SQLException; -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -import static org.jooq.impl.DSL.table; -import static space.arim.libertybans.core.database.DatabaseConstants.LIBERTYBANS_08X_FLYWAY_TABLE; -import static space.arim.libertybans.core.schema.tables.SchemaHistory.SCHEMA_HISTORY; -import static space.arim.libertybans.core.schema.tables.ZeroeightAddresses.ZEROEIGHT_ADDRESSES; -import static space.arim.libertybans.core.schema.tables.ZeroeightBans.ZEROEIGHT_BANS; -import static space.arim.libertybans.core.schema.tables.ZeroeightHistory.ZEROEIGHT_HISTORY; -import static space.arim.libertybans.core.schema.tables.ZeroeightMutes.ZEROEIGHT_MUTES; -import static space.arim.libertybans.core.schema.tables.ZeroeightNames.ZEROEIGHT_NAMES; -import static space.arim.libertybans.core.schema.tables.ZeroeightPunishments.ZEROEIGHT_PUNISHMENTS; -import static space.arim.libertybans.core.schema.tables.ZeroeightWarns.ZEROEIGHT_WARNS; - -final class ZeroeightInterlocutor { - - private final DSLContext context; - - ZeroeightInterlocutor(DSLContext context) { - this.context = context; - } - - private void dropAllTables() { - SQLDialect dialect = context.family(); - // Drop views - for (Table table : DatabaseConstants.allViews()) { - context.dropView(table).execute(); - } - // Drop tables - for (Table table : DatabaseConstants.allTables(DatabaseConstants.TableOrder.REFERENTS_LAST)) { - context.dropTable(table).execute(); - } - context.dropTable(SCHEMA_HISTORY).execute(); - // Drop sequences - if (dialect == SQLDialect.MYSQL) { - context.dropTable(table(Sequences.LIBERTYBANS_PUNISHMENT_IDS.getName())).execute(); - context.dropTable(table(Sequences.LIBERTYBANS_VICTIM_IDS.getName())).execute(); - } else { - context.dropSequence(Sequences.LIBERTYBANS_PUNISHMENT_IDS).execute(); - context.dropSequence(Sequences.LIBERTYBANS_VICTIM_IDS).execute(); - } - } - - void prepareTables(DataSource dataSource) throws SQLException { - // Delete existing tables - dropAllTables(); - // Create 0.8.x tables using Flyway - //noinspection deprecation - Flyway - .configure(getClass().getClassLoader()) - .table(LIBERTYBANS_08X_FLYWAY_TABLE) - .placeholders(Map.of( - "zeroeighttableprefix", "libertybans_" - )) - .locations("classpath:extra-database-migrations/zeroeight") - // This will need to be modified when Flyway 9 removes this deprecated method - .ignoreFutureMigrations(false) - .validateMigrationNaming(true) - .dataSource(dataSource) - .baselineOnMigrate(true).baselineVersion("0") - .load() - .migrate(); - } - - private byte[] serializeVictim(Victim victim) { - return switch (victim.getType()) { - case PLAYER -> UUIDUtil.toByteArray(((PlayerVictim) victim).getUUID()); - case ADDRESS -> ((AddressVictim) victim).getAddress().getRawAddress(); - default -> throw new UnsupportedOperationException("Victim type " + victim.getType()); - }; - } - - void insertUser(NameAddressRecord nameAddressRecord) { - Instant time = nameAddressRecord.timeRecorded(); - UUID uuid = nameAddressRecord.uuid(); - String name = nameAddressRecord.name().orElseThrow(AssertionError::new); - NetworkAddress address = nameAddressRecord.address().orElseThrow(AssertionError::new); - context - .insertInto(ZEROEIGHT_NAMES) - .columns( - ZEROEIGHT_NAMES.UUID, ZEROEIGHT_NAMES.NAME, ZEROEIGHT_NAMES.UPDATED - ) - .values(UUIDUtil.toByteArray(uuid), name, time) - .onConflict(ZEROEIGHT_NAMES.UUID, ZEROEIGHT_NAMES.NAME) - .doUpdate() - .set(ZEROEIGHT_NAMES.UPDATED, time) - .execute(); - context - .insertInto(ZEROEIGHT_ADDRESSES) - .columns( - ZEROEIGHT_ADDRESSES.UUID, ZEROEIGHT_ADDRESSES.ADDRESS, ZEROEIGHT_ADDRESSES.UPDATED - ).values(UUIDUtil.toByteArray(uuid), address, time) - .onConflict(ZEROEIGHT_ADDRESSES.UUID, ZEROEIGHT_ADDRESSES.ADDRESS) - .doUpdate() - .set(ZEROEIGHT_ADDRESSES.UPDATED, time) - .execute(); - } - - void insertPunishment(Punishment punishment, boolean active) { - int id = (int) punishment.getIdentifier(); - byte[] operator = UUIDUtil.toByteArray(new OperatorBinding().operatorToUuid(punishment.getOperator())); - context - .insertInto(ZEROEIGHT_PUNISHMENTS) - .columns( - ZEROEIGHT_PUNISHMENTS.ID, ZEROEIGHT_PUNISHMENTS.TYPE, - ZEROEIGHT_PUNISHMENTS.OPERATOR, ZEROEIGHT_PUNISHMENTS.REASON, - ZEROEIGHT_PUNISHMENTS.SCOPE, ZEROEIGHT_PUNISHMENTS.START, ZEROEIGHT_PUNISHMENTS.END - ) - .values( - id, punishment.getType().name(), - operator, punishment.getReason(), - punishment.getScope(), punishment.getStartDate(), punishment.getEndDate() - ) - .execute(); - Victim victim = punishment.getVictim(); - byte[] victimData = serializeVictim(victim); - String victimType = victim.getType().name(); - context - .insertInto(ZEROEIGHT_HISTORY) - .columns(ZEROEIGHT_HISTORY.ID, ZEROEIGHT_HISTORY.VICTIM, ZEROEIGHT_HISTORY.VICTIM_TYPE) - .values(id, victimData, victimType) - .execute(); - TableImpl> dataTable = switch (punishment.getType()) { - case BAN -> ZEROEIGHT_BANS; - case MUTE -> ZEROEIGHT_MUTES; - case WARN -> ZEROEIGHT_WARNS; - case KICK -> null; - }; - if (!active || dataTable == null) { - return; - } - var fields = dataTable.newRecord(); - context - .insertInto(dataTable) - .columns(fields.field1(), fields.field2(), fields.field3()) - .values(id, victimData, victimType) - .execute(); - - } -} 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 de821ecca..2dedd3336 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 @@ -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,13 +25,13 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.importing.ConnectionSource; import space.arim.libertybans.core.importing.ImportExecutor; import space.arim.libertybans.core.importing.ImportSource; import space.arim.libertybans.core.importing.ImportStatistics; import space.arim.libertybans.core.importing.LocalDatabaseSetup; import space.arim.libertybans.core.importing.PluginDatabaseSetup; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.core.uuid.ServerType; import space.arim.libertybans.core.uuid.UUIDManager; import space.arim.libertybans.it.DontInject; @@ -73,7 +73,7 @@ public void setup(@DontInject ConnectionSource connectionSource) { private ScopeManager createScopeManager() { ScopeManager scopeManager = mock(ScopeManager.class); - when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); + when(scopeManager.globalScope()).thenReturn(mock(ServerScope.class)); return scopeManager; } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/BanManagerImportIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/BanManagerImportIT.java index e5ba5ced6..66208472d 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/BanManagerImportIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/BanManagerImportIT.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 @@ -26,6 +26,7 @@ import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.importing.ConnectionSource; import space.arim.libertybans.core.importing.ImportExecutor; @@ -33,7 +34,6 @@ import space.arim.libertybans.core.importing.ImportStatistics; import space.arim.libertybans.core.importing.LocalDatabaseSetup; import space.arim.libertybans.core.importing.PluginDatabaseSetup; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; @@ -67,7 +67,7 @@ public void setup(@DontInject ConnectionSource connectionSource) { private ScopeManager createScopeManager() { ScopeManager scopeManager = mock(ScopeManager.class); - when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); + when(scopeManager.globalScope()).thenReturn(mock(ServerScope.class)); return scopeManager; } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/LiteBansImportIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/LiteBansImportIT.java index ed10efbba..f9eb22773 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/LiteBansImportIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/LiteBansImportIT.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,13 +25,13 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.importing.ConnectionSource; import space.arim.libertybans.core.importing.ImportExecutor; import space.arim.libertybans.core.importing.ImportSource; import space.arim.libertybans.core.importing.ImportStatistics; import space.arim.libertybans.core.importing.LocalDatabaseSetup; import space.arim.libertybans.core.importing.PluginDatabaseSetup; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; import space.arim.libertybans.it.SetTime; @@ -65,7 +65,7 @@ public void setup(@DontInject ConnectionSource connectionSource) { private ScopeManager createScopeManager() { ScopeManager scopeManager = mock(ScopeManager.class); // Specific server scopes not covered by this IT - when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); + when(scopeManager.globalScope()).thenReturn(mock(ServerScope.class)); return scopeManager; } diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/SelfImportIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/SelfImportIT.java index e28543237..a739cad3f 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/importing/SelfImportIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/importing/SelfImportIT.java @@ -35,10 +35,10 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.EnforcementOptions; import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.core.database.execute.QueryExecutor; import space.arim.libertybans.core.importing.SelfImportProcess; import space.arim.libertybans.core.punish.PunishmentCreator; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; @@ -66,13 +66,16 @@ public class SelfImportIT { private final SelfImportProcess selfImportProcess; private final Provider queryExecutor; + private final ScopeManager scopeManager; private SelfImportData selfImportData; @Inject - public SelfImportIT(SelfImportProcess selfImportProcess, Provider queryExecutor) { + public SelfImportIT(SelfImportProcess selfImportProcess, Provider queryExecutor, + ScopeManager scopeManager) { this.selfImportProcess = selfImportProcess; this.queryExecutor = queryExecutor; + this.scopeManager = scopeManager; } @BeforeEach @@ -132,7 +135,7 @@ public void blueTree242(PunishmentCreator creator) throws IOException { creator.createPunishment( 17L, PunishmentType.BAN, AddressVictim.of(addressUnchecked("80.100.23.146")), PlayerOperator.of(UUID.fromString("f360da52-6304-3af4-8b30-b7d9c5e6e162")), "swearing", - ScopeImpl.GLOBAL, Instant.ofEpochSecond(1636139831L), Instant.MAX, null + scopeManager.globalScope(), Instant.ofEpochSecond(1636139831L), Instant.MAX, null ), context .selectFrom(SIMPLE_ACTIVE) @@ -143,7 +146,7 @@ public void blueTree242(PunishmentCreator creator) throws IOException { creator.createPunishment( 44L, PunishmentType.WARN, PlayerVictim.of(UUID.fromString("ef1275f7-5c3a-36ed-92f6-6b3716c72896")), PlayerOperator.of(UUID.fromString("f360da52-6304-3af4-8b30-b7d9c5e6e162")), "abusing and getting items from creative", - ScopeImpl.GLOBAL, Instant.ofEpochSecond(1636916014L), Instant.MAX, null + scopeManager.globalScope(), Instant.ofEpochSecond(1636916014L), Instant.MAX, null ), context .selectFrom(SIMPLE_ACTIVE) @@ -154,7 +157,7 @@ public void blueTree242(PunishmentCreator creator) throws IOException { creator.createPunishment( 135L, PunishmentType.BAN, PlayerVictim.of(UUID.fromString("47df0fc2-3213-3401-af68-58cafb0e99f5")), ConsoleOperator.INSTANCE, "Everyone wants you banned, nerd", - ScopeImpl.GLOBAL, Instant.ofEpochSecond(1638635845L), Instant.ofEpochSecond(1639240645L), null + scopeManager.globalScope(), Instant.ofEpochSecond(1638635845L), Instant.ofEpochSecond(1639240645L), null ), context .selectFrom(SIMPLE_HISTORY) diff --git a/bans-core/src/test/java/space/arim/libertybans/it/test/punish/EscalationIT.java b/bans-core/src/test/java/space/arim/libertybans/it/test/punish/EscalationIT.java index 33817161f..c445ab4a2 100644 --- a/bans-core/src/test/java/space/arim/libertybans/it/test/punish/EscalationIT.java +++ b/bans-core/src/test/java/space/arim/libertybans/it/test/punish/EscalationIT.java @@ -28,7 +28,7 @@ import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.punish.PunishmentDetailsCalculator; import space.arim.libertybans.api.punish.PunishmentDrafter; -import space.arim.libertybans.core.scope.ScopeImpl; +import space.arim.libertybans.api.scope.ScopeManager; import space.arim.libertybans.it.DontInject; import space.arim.libertybans.it.InjectionInvocationContextProvider; import space.arim.libertybans.it.IrrelevantData; @@ -46,12 +46,14 @@ public class EscalationIT { private final PunishmentDrafter drafter; + private final ScopeManager scopeManager; private final Victim victim; private final EscalationTrack escalationTrack; - public EscalationIT(PunishmentDrafter drafter, + public EscalationIT(PunishmentDrafter drafter, ScopeManager scopeManager, @DontInject Victim victim, @DontInject @NonNullTrack EscalationTrack escalationTrack) { this.drafter = drafter; + this.scopeManager = scopeManager; this.victim = victim; this.escalationTrack = escalationTrack; } @@ -101,7 +103,7 @@ public void escalateSimply(@DontInject Operator operator1, @DontInject Operator } PunishmentType type = PunishmentType.WARN; return new PunishmentDetailsCalculator.CalculationResult( - type, "Now at " + (existingPunishments + 1), Duration.ZERO, ScopeImpl.GLOBAL + type, "Now at " + (existingPunishments + 1), Duration.ZERO, scopeManager.globalScope() ); }; addPunishment(PunishmentType.WARN, operator1, "first warn"); @@ -129,7 +131,7 @@ public void escalateWithExtraData(@DontInject Operator operator1, @DontInject Op } PunishmentType type = PunishmentType.WARN; return new PunishmentDetailsCalculator.CalculationResult( - type, "Now at " + (existingPunishments + 1), Duration.ZERO, ScopeImpl.GLOBAL + type, "Now at " + (existingPunishments + 1), Duration.ZERO, scopeManager.globalScope() ); }; addPunishment(PunishmentType.WARN, operator1, "first warn"); diff --git a/bans-core/src/test/resources/extra-database-migrations/codegen/V0__Sequences.sql b/bans-core/src/test/resources/extra-database-migrations/codegen/V0__Sequences.sql index d6daa870b..cc87216ff 100644 --- a/bans-core/src/test/resources/extra-database-migrations/codegen/V0__Sequences.sql +++ b/bans-core/src/test/resources/extra-database-migrations/codegen/V0__Sequences.sql @@ -6,3 +6,5 @@ CREATE SEQUENCE "libertybans_punishment_ids" AS BIGINT; CREATE SEQUENCE "libertybans_victim_ids" AS INT; CREATE SEQUENCE "libertybans_track_ids" AS INT; + +CREATE SEQUENCE "libertybans_scope_ids" AS INT; diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/AddressReporter.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/AddressReporter.java index 346c9cf69..63e6314eb 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/AddressReporter.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/AddressReporter.java @@ -1,19 +1,19 @@ -/* - * LibertyBans-env-bungee - * Copyright © 2020 Anand Beh - * - * LibertyBans-env-bungee is free software: you can redistribute it and/or modify +/* + * 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-env-bungee is distributed in the hope that it will be useful, + * + * 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-env-bungee. If not, see + * along with LibertyBans. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ package space.arim.libertybans.env.bungee; @@ -22,7 +22,7 @@ import net.md_5.bungee.api.connection.Connection; -interface AddressReporter { +public interface AddressReporter { InetAddress getAddress(Connection bungeePlayer); 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 83c44a5f9..0d26785ab 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 @@ -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 @@ -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.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -65,6 +66,10 @@ public AddressReporter reporter(StandardAddressReporter reporter) { return reporter; } + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> scopeManager.detectServerName("proxy"); + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("It is impossible to import from vanilla on BungeeCord"); } diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java index e259a31ee..ef24d4848 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.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 @@ -20,36 +20,43 @@ package space.arim.libertybans.env.bungee; import jakarta.inject.Inject; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.LoginEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import space.arim.api.env.AudienceRepresenter; import space.arim.libertybans.core.env.PlatformListener; import space.arim.libertybans.core.selector.Guardian; import space.arim.omnibus.util.ThisClass; import java.net.InetAddress; -import java.util.UUID; public final class ConnectionListener implements Listener, PlatformListener { private final Plugin plugin; private final Guardian guardian; private final AddressReporter addressReporter; + private final AudienceRepresenter audienceRepresenter; private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); @Inject - public ConnectionListener(Plugin plugin, Guardian guardian, AddressReporter addressReporter) { + public ConnectionListener(Plugin plugin, Guardian guardian, AddressReporter addressReporter, + AudienceRepresenter audienceRepresenter) { this.plugin = plugin; this.guardian = guardian; this.addressReporter = addressReporter; + this.audienceRepresenter = audienceRepresenter; } @Override @@ -65,17 +72,17 @@ public void unregister() { @EventHandler(priority = EventPriority.LOW) public void onConnect(LoginEvent event) { if (event.isCancelled()) { - logger.debug("Event {} is already blocked", event); + logger.trace("Event {} is already blocked", event); return; } PendingConnection connection = event.getConnection(); - UUID uuid = connection.getUniqueId(); - String name = connection.getName(); InetAddress address = addressReporter.getAddress(connection); event.registerIntent(plugin); - guardian.executeAndCheckConnection(uuid, name, address).thenAccept((message) -> { + guardian.executeAndCheckConnection( + connection.getUniqueId(), connection.getName(), address + ).thenAccept((message) -> { if (message == null) { logger.trace("Event {} will be permitted", event); } else { @@ -91,4 +98,22 @@ public void onConnect(LoginEvent event) { }); } + @EventHandler(priority = EventPriority.HIGH) + public void onServerSwitch(ServerConnectEvent event) { + if (event.getReason() == ServerConnectEvent.Reason.LOBBY_FALLBACK) { + // Don't kick players if there is no alternative server for them + return; + } + ProxiedPlayer player = event.getPlayer(); + InetAddress address = addressReporter.getAddress(player); + + Component message = guardian.checkServerSwitch( + player.getUniqueId(), address, event.getTarget().getName() + ).join(); + if (message != null) { + event.setCancelled(true); + audienceRepresenter.toAudience(player).sendMessage(message); + } + } + } diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessageData.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessageData.java new file mode 100644 index 000000000..ed61d78fa --- /dev/null +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessageData.java @@ -0,0 +1,73 @@ +/* + * 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 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Optional; + +record PluginMessageData(PluginMessage pluginMessage) { + + byte[] generateBytes(D data) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutput = new DataOutputStream(outputStream)) { + + pluginMessage.writeTo(data, new DataOutputAsOutput(dataOutput)); + return outputStream.toByteArray(); + + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + Optional readBytes(byte[] data) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data); + DataInputStream dataInput = new DataInputStream(inputStream)) { + + return pluginMessage.readFrom(new DataInputAsInput(dataInput)); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + record DataOutputAsOutput(DataOutput dataOutput) implements PluginMessageOutput { + @Override + public void writeUTF(String utf) throws IOException { + dataOutput.writeUTF(utf); + } + } + + record DataInputAsInput(DataInput dataInput) implements PluginMessageInput { + @Override + public String readUTF() throws IOException { + return dataInput.readUTF(); + } + } +} 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 new file mode 100644 index 000000000..477ec2742 --- /dev/null +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/PluginMessagingListener.java @@ -0,0 +1,96 @@ +/* + * 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/SpigotBindModule.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotBindModule.java index ea7dbe592..1f58e8075 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 @@ -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 @@ -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.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -77,6 +78,10 @@ public MorePaperLibAdventure morePaperLibAdventure(MorePaperLib morePaperLib) { return new MorePaperLibAdventure(morePaperLib); } + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> {}; + } + public PlatformImportSource platformImportSource(BukkitImportSource importSource) { return importSource; } 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 8704fa719..6b09b535e 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 @@ -22,23 +22,20 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 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.morepaperlib.adventure.MorePaperLibAdventure; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.UncheckedIOException; import java.net.InetAddress; import java.util.UUID; import java.util.function.Consumer; @@ -77,26 +74,25 @@ protected CentralisedFuture doForAllPlayers(Consumer callback) { @Override public void kickPlayer(Player player, Component message) { - if (configs.getMainConfig().platforms().bukkit().kickViaPluginMessaging()) { - byte[] kickData; - try (ByteArrayOutputStream kickDataStream = new ByteArrayOutputStream(); - DataOutputStream write = new DataOutputStream(kickDataStream)) { - - write.writeUTF("KickPlayer"); - write.writeUTF(player.getName()); - write.writeUTF(LegacyComponentSerializer.legacySection().serialize(message)); - kickData = kickDataStream.toByteArray(); - } catch (IOException ex) { - throw new UncheckedIOException(ex); + 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; } - player.sendPluginMessage( - morePaperLibAdventure.getMorePaperLib().getPlugin(), - ChannelRegistration.BUNGEE_CHANNEL, - kickData + LoggerFactory.getLogger(getClass()).warn( + "Kicking via plugin messaging is enabled, but the proper channel is not listened on." ); - } else { - morePaperLibAdventure.kickPlayer(player, message); } + morePaperLibAdventure.kickPlayer(player, message); } @Override 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 179e878a9..577d830c6 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,16 +31,16 @@ public final class SpigotEnv implements Environment { private final Provider connectionListener; private final Provider chatListener; + private final Provider messagingListener; private final CommandHandler.CommandHelper commandHelper; - private final ChannelRegistration channelRegistration; @Inject public SpigotEnv(Provider connectionListener, Provider chatListener, - CommandHandler.CommandHelper commandHelper, ChannelRegistration channelRegistration) { + Provider messagingListener, CommandHandler.CommandHelper commandHelper) { this.connectionListener = connectionListener; this.chatListener = chatListener; + this.messagingListener = messagingListener; this.commandHelper = commandHelper; - this.channelRegistration = channelRegistration; } @Override @@ -48,8 +48,8 @@ public Set createListeners() { return Set.of( connectionListener.get(), chatListener.get(), - new CommandHandler(commandHelper, Commands.BASE_COMMAND_NAME, false), - channelRegistration + messagingListener.get(), + new CommandHandler(commandHelper, Commands.BASE_COMMAND_NAME, false) ); } @@ -57,5 +57,5 @@ public Set createListeners() { public PlatformListener createAliasCommand(String command) { return new CommandHandler(commandHelper, command, true); } - + } diff --git a/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/BukkitImportSourceTest.java b/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/BukkitImportSourceTest.java index ee0783b11..db5b20553 100644 --- a/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/BukkitImportSourceTest.java +++ b/bans-env/spigot/src/test/java/space/arim/libertybans/env/spigot/BukkitImportSourceTest.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,15 +20,17 @@ package space.arim.libertybans.env.spigot; import org.bukkit.Server; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.api.scope.ScopeManager; +import space.arim.libertybans.api.scope.ServerScope; import space.arim.libertybans.core.importing.PortablePunishment; -import space.arim.libertybans.core.scope.ScopeImpl; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import space.arim.omnibus.util.concurrent.impl.IndifferentFactoryOfTheFuture; @@ -47,10 +49,15 @@ @ExtendWith(MockitoExtension.class) public class BukkitImportSourceTest { - private Set sourcePunishments(Server server) { - ScopeManager scopeManager = mock(ScopeManager.class); - lenient().when(scopeManager.globalScope()).thenReturn(ScopeImpl.GLOBAL); + private ScopeManager scopeManager; + + @BeforeEach + public void setScopeManager(@Mock ScopeManager scopeManager) { + this.scopeManager = scopeManager; + lenient().when(scopeManager.globalScope()).thenReturn(mock(ServerScope.class)); + } + private Set sourcePunishments(Server server) { FactoryOfTheFuture futuresFactory = new IndifferentFactoryOfTheFuture(); return new BukkitImportSource(futuresFactory, scopeManager, server) .sourcePunishments().collect(Collectors.toUnmodifiableSet()); @@ -81,7 +88,7 @@ public void userBan() { Set.of(new PortablePunishment( null, new PortablePunishment.KnownDetails( - PunishmentType.BAN, reason, ScopeImpl.GLOBAL, + PunishmentType.BAN, reason, scopeManager.globalScope(), start, Punishment.PERMANENT_END_DATE), new PortablePunishment.VictimInfo(null, username, null), PortablePunishment.OperatorInfo.createUser(null, operator), @@ -104,7 +111,7 @@ public void ipBan() throws UnknownHostException { Set.of(new PortablePunishment( null, new PortablePunishment.KnownDetails( - PunishmentType.BAN, reason, ScopeImpl.GLOBAL, + PunishmentType.BAN, reason, scopeManager.globalScope(), start, Punishment.PERMANENT_END_DATE), new PortablePunishment.VictimInfo(null, null, NetworkAddress.of(address)), PortablePunishment.OperatorInfo.createUser(null, operator), @@ -127,7 +134,7 @@ public void temporaryBan() { Set.of(new PortablePunishment( null, new PortablePunishment.KnownDetails( - PunishmentType.BAN, reason, ScopeImpl.GLOBAL, + PunishmentType.BAN, reason, scopeManager.globalScope(), start, end), new PortablePunishment.VictimInfo(null, username, null), PortablePunishment.OperatorInfo.createUser(null, operator), diff --git a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ChatListener.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ChatListener.java index 5488e07a3..cf6899510 100644 --- a/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ChatListener.java +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/ChatListener.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 @@ -61,7 +61,7 @@ public void onChat(PlayerChatEvent event) { combinedChatEvent(event, null); } - @Listener + @Listener(order = Order.LATE) public void onCommand(ExecuteCommandEvent.Pre event) { combinedChatEvent(event, event.command()); } 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 new file mode 100644 index 000000000..35750affe --- /dev/null +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/PluginMessageData.java @@ -0,0 +1,53 @@ +/* + * 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 new file mode 100644 index 000000000..815be1dde --- /dev/null +++ b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/PluginMessagingListener.java @@ -0,0 +1,118 @@ +/* + * 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/SpongeBindModule.java b/bans-env/sponge/src/main/java/space/arim/libertybans/env/sponge/SpongeBindModule.java index 1e189860f..872479ec5 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 @@ -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 @@ -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.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -63,6 +64,10 @@ public EnvUserResolver envUserResolver(SpongeUserResolver resolver) { return resolver; } + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> {}; + } + public PlatformImportSource platformImportSource(SpongeImportSource importSource) { return importSource; } 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 a17ad8abb..9cf63d8fb 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 @@ -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 @@ -26,26 +26,32 @@ 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.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import java.net.InetAddress; +import java.util.Optional; 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; @Inject public SpongeEnforcer(FactoryOfTheFuture futuresFactory, InternalFormatter formatter, - Interlocutor interlocutor, Game game) { + Interlocutor interlocutor, Configs configs, Game game) { super(futuresFactory, formatter, interlocutor, AudienceRepresenter.identity()); + this.configs = configs; this.game = game; } @@ -67,6 +73,20 @@ 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); } 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 b41864bba..3d93ba3ca 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 @@ -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,13 +31,15 @@ public final class SpongeEnv implements Environment { private final Provider connectionListener; private final Provider chatListener; + private final Provider pluginMessagingListener; private final PlatformAccess platformAccess; @Inject public SpongeEnv(Provider connectionListener, Provider chatListener, - PlatformAccess platformAccess) { + Provider pluginMessagingListener, PlatformAccess platformAccess) { this.connectionListener = connectionListener; this.chatListener = chatListener; + this.pluginMessagingListener = pluginMessagingListener; this.platformAccess = platformAccess; } @@ -45,7 +47,8 @@ public SpongeEnv(Provider connectionListener, Provider createListeners() { return Set.of( connectionListener.get(), - chatListener.get() + chatListener.get(), + pluginMessagingListener.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 6dedb940a..3eba51a67 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 @@ -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 diff --git a/bans-env/spongeplugin/pom.xml b/bans-env/spongeplugin/pom.xml index 1a7a3a760..28bce5913 100644 --- a/bans-env/spongeplugin/pom.xml +++ b/bans-env/spongeplugin/pom.xml @@ -1,3 +1,22 @@ + + 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 54110285e..00c565b14 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.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -81,6 +82,10 @@ public EnvUserResolver resolver(StandaloneResolver resolver) { return resolver; } + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> scopeManager.detectServerName("standalone"); + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("It is impossible to import from vanilla on the standalone application"); } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java index 612911e9a..edca8cda5 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.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 @@ -19,41 +19,69 @@ package space.arim.libertybans.env.velocity; +import com.velocitypowered.api.event.EventTask; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.player.PlayerChatEvent; import com.velocitypowered.api.event.player.PlayerChatEvent.ChatResult; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import jakarta.inject.Inject; -import net.kyori.adventure.text.Component; +import space.arim.libertybans.core.env.PlatformListener; import space.arim.libertybans.core.selector.Guardian; -import space.arim.omnibus.util.concurrent.CentralisedFuture; -public final class ChatListener extends VelocityAsyncListener { +public final class ChatListener implements PlatformListener { + private final PluginContainer plugin; + private final ProxyServer server; private final Guardian guardian; @Inject public ChatListener(PluginContainer plugin, ProxyServer server, Guardian guardian) { - super(plugin, server); + this.plugin = plugin; + this.server = server; this.guardian = guardian; } @Override - public Class getEventClass() { - return PlayerChatEvent.class; + public void register() { + server.getEventManager().register(plugin, this); } @Override - protected CentralisedFuture beginComputation(PlayerChatEvent event) { - Player player = event.getPlayer(); - return guardian.checkChat(player.getUniqueId(), player.getRemoteAddress().getAddress(), null); + public void unregister() { + server.getEventManager().unregisterListener(plugin, this); } - @Override - protected void executeNonNullResult(PlayerChatEvent event, Component message) { - event.setResult(ChatResult.denied()); - event.getPlayer().sendMessage(message); + @Subscribe(order = PostOrder.EARLY) + public EventTask onChat(PlayerChatEvent event) { + return combinedChatEvent(event, event.getPlayer(), null, ChatResult.denied()); + } + + @Subscribe(order = PostOrder.EARLY) + public EventTask onCommand(CommandExecuteEvent event) { + if (!(event.getCommandSource() instanceof Player player)) { + return null; + } + return combinedChatEvent(event, player, event.getCommand(), CommandExecuteEvent.CommandResult.denied()); + } + + private , R extends ResultedEvent.Result> EventTask combinedChatEvent( + E event, Player player, String command, R deniedResult) { + if (!event.getResult().isAllowed()) { + return null; + } + return EventTask.resumeWhenComplete(guardian.checkChat( + player.getUniqueId(), player.getRemoteAddress().getAddress(), command + ).thenAccept((message) -> { + if (message != null) { + event.setResult(deniedResult); + player.sendMessage(message); + } + })); } } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java deleted file mode 100644 index 5ac6e9bb3..000000000 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * LibertyBans - * Copyright © 2022 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.command.CommandExecuteEvent; -import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult; -import com.velocitypowered.api.plugin.PluginContainer; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ProxyServer; -import jakarta.inject.Inject; -import net.kyori.adventure.text.Component; -import space.arim.libertybans.core.selector.Guardian; -import space.arim.omnibus.util.concurrent.CentralisedFuture; - -public final class CommandListener extends VelocityAsyncListener { - - private final Guardian guardian; - - @Inject - public CommandListener(PluginContainer plugin, ProxyServer server, Guardian guardian) { - super(plugin, server); - this.guardian = guardian; - } - - @Override - public Class getEventClass() { - return CommandExecuteEvent.class; - } - - @Override - protected boolean skipEvent(CommandExecuteEvent event) { - return !(event.getCommandSource() instanceof Player); - } - - @Override - protected CentralisedFuture beginComputation(CommandExecuteEvent event) { - Player player = (Player) event.getCommandSource(); - return guardian.checkChat(player.getUniqueId(), player.getRemoteAddress().getAddress(), event.getCommand()); - } - - @Override - protected void executeNonNullResult(CommandExecuteEvent event, Component message) { - event.setResult(CommandResult.denied()); - event.getCommandSource().sendMessage(message); - } - -} diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java index af845a4ca..ce019f2c6 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.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 @@ -19,46 +19,85 @@ package space.arim.libertybans.env.velocity; +import com.velocitypowered.api.event.EventTask; +import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.ResultedEvent.ComponentResult; +import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.RegisteredServer; import jakarta.inject.Inject; -import net.kyori.adventure.text.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import space.arim.libertybans.core.env.PlatformListener; import space.arim.libertybans.core.selector.Guardian; -import space.arim.omnibus.util.concurrent.CentralisedFuture; +import space.arim.omnibus.util.ThisClass; -import java.net.InetAddress; -import java.util.UUID; - -public final class ConnectionListener extends VelocityAsyncListener { +public final class ConnectionListener implements PlatformListener { + private final PluginContainer plugin; + private final ProxyServer server; private final Guardian guardian; + private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); + @Inject public ConnectionListener(PluginContainer plugin, ProxyServer server, Guardian guardian) { - super(plugin, server); + this.plugin = plugin; + this.server = server; this.guardian = guardian; } @Override - public Class getEventClass() { - return LoginEvent.class; + public void register() { + server.getEventManager().register(plugin, this); } @Override - protected CentralisedFuture beginComputation(LoginEvent event) { + public void unregister() { + server.getEventManager().unregisterListener(plugin, this); + } + + @Subscribe(order = PostOrder.EARLY) + public EventTask onConnect(LoginEvent event) { + if (!event.getResult().isAllowed()) { + logger.trace("Event {} is already blocked", event); + return null; + } Player player = event.getPlayer(); - UUID uuid = player.getUniqueId(); - String name = player.getUsername(); - InetAddress address = player.getRemoteAddress().getAddress(); - return guardian.executeAndCheckConnection(uuid, name, address); + return EventTask.resumeWhenComplete(guardian.executeAndCheckConnection( + player.getUniqueId(), player.getUsername(), player.getRemoteAddress().getAddress() + ).thenAccept((message) -> { + if (message == null) { + logger.trace("Event {} will be permitted", event); + } else { + event.setResult(ComponentResult.denied(message)); + } + })); } - @Override - protected void executeNonNullResult(LoginEvent event, Component message) { - event.setResult(ComponentResult.denied(message)); + @Subscribe(order = PostOrder.EARLY) + public EventTask onServerSwitch(ServerPreConnectEvent event) { + if (!event.getResult().isAllowed()) { + return null; + } + RegisteredServer destination = event.getResult().getServer().orElse(null); + if (destination == null) { + // Properly speaking, the API does not exclude this possibility + return null; + } + Player player = event.getPlayer(); + return EventTask.resumeWhenComplete(guardian.checkServerSwitch( + player.getUniqueId(), player.getRemoteAddress().getAddress(), destination.getServerInfo().getName() + ).thenAccept((message) -> { + if (message != null) { + event.setResult(ServerPreConnectEvent.ServerResult.denied()); + player.sendMessage(message); + } + })); } } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityAsyncListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityAsyncListener.java deleted file mode 100644 index 97c05cd01..000000000 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityAsyncListener.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * LibertyBans - * Copyright © 2022 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.AwaitingEventExecutor; -import com.velocitypowered.api.event.Continuation; -import com.velocitypowered.api.event.EventManager; -import com.velocitypowered.api.event.EventTask; -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.ResultedEvent; -import com.velocitypowered.api.plugin.PluginContainer; -import com.velocitypowered.api.proxy.ProxyServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import space.arim.libertybans.core.env.PlatformListener; -import space.arim.omnibus.util.ThisClass; -import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.omnibus.util.concurrent.impl.IndifferentCentralisedFuture; - -abstract class VelocityAsyncListener, R> implements PlatformListener { - - private final PluginContainer plugin; - private final ProxyServer server; - - // Visible for testing - final AsyncHandler handler = new AsyncHandler(); - - private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - - VelocityAsyncListener(PluginContainer plugin, ProxyServer server) { - this.plugin = plugin; - this.server = server; - } - - @Override - public final void register() { - Class eventClass = getEventClass(); - EventManager eventManager = server.getEventManager(); - eventManager.register(plugin, eventClass, PostOrder.EARLY, handler); - } - - abstract Class getEventClass(); - - @Override - public void unregister() { - EventManager eventManager = server.getEventManager(); - eventManager.unregister(plugin, handler); - } - - /** Can be overridden to skip events */ - protected boolean skipEvent(E event) { - return false; - } - - protected abstract CentralisedFuture beginComputation(E event); - - protected abstract void executeNonNullResult(E event, R result); - - // Visible for testing - class AsyncHandler implements AwaitingEventExecutor { - - @Override - public EventTask executeAsync(E event) { - if (skipEvent(event)) { - return null; - } - if (!event.getResult().isAllowed()) { - logger.debug("Event {} is already blocked", event); - return null; - } - CentralisedFuture future = beginComputation(event); - return EventTask.resumeWhenComplete(future.thenAccept((result) -> { - if (result == null) { - logger.trace("Event {} will be permitted", event); - return; - } - executeNonNullResult(event, result); - })); - } - - void executeAndWait(E event) { - EventTask eventTask = executeAsync(event); - if (eventTask == null) { - return; - } - CentralisedFuture future = new IndifferentCentralisedFuture<>(); - eventTask.execute(new Continuation() { - @Override - public void resume() { - future.complete(null); - } - - @Override - public void resumeWithException(Throwable exception) { - future.completeExceptionally(exception); - } - }); - future.join(); - } - } - -} 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 b8fc2f3f4..ad3a9117f 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 @@ -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 @@ -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.EnvServerNameDetection; import space.arim.libertybans.core.env.EnvUserResolver; import space.arim.libertybans.core.env.Environment; import space.arim.libertybans.core.importing.PlatformImportSource; @@ -54,6 +55,10 @@ public EnvUserResolver resolver(VelocityUserResolver resolver) { return resolver; } + public EnvServerNameDetection serverNameDetection() { + return (scopeManager) -> scopeManager.detectServerName("proxy"); + } + public PlatformImportSource platformImportSource() { throw new UnsupportedOperationException("It is impossible to import from vanilla on Velocity"); } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java index a0197701e..c80e1b8f4 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.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,15 +31,13 @@ public final class VelocityEnv implements Environment { private final Provider connectionListener; private final Provider chatListener; - private final Provider commandListener; private final CommandHandler.CommandHelper commandHelper; @Inject public VelocityEnv(Provider connectionListener, Provider chatListener, - Provider commandListener, CommandHandler.CommandHelper commandHelper) { + CommandHandler.CommandHelper commandHelper) { this.connectionListener = connectionListener; this.chatListener = chatListener; - this.commandListener = commandListener; this.commandHelper = commandHelper; } @@ -48,7 +46,6 @@ public Set createListeners() { return Set.of( connectionListener.get(), chatListener.get(), - commandListener.get(), new CommandHandler(commandHelper, Commands.BASE_COMMAND_NAME, false) ); } diff --git a/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/ConnectionListenerTest.java b/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/ConnectionListenerTest.java index 2c0034847..e20047ef4 100644 --- a/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/ConnectionListenerTest.java +++ b/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/ConnectionListenerTest.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 @@ -19,6 +19,8 @@ package space.arim.libertybans.env.velocity; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.EventTask; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.plugin.PluginContainer; @@ -40,6 +42,7 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -97,13 +100,32 @@ private void deniedResult(String text) { fixedResult(Component.text(text)); } + private void fireAndAwait(LoginEvent event) { + EventTask task = listener.onConnect(event); + if (task != null) { + var future = new CompletableFuture<>(); + task.execute(new Continuation() { + @Override + public void resume() { + future.complete(null); + } + + @Override + public void resumeWithException(Throwable exception) { + future.completeExceptionally(exception); + } + }); + future.join(); + } + } + @Test public void allowed() { allowedResult(); Player player = mockPlayer(); LoginEvent event = new LoginEvent(player); var originalResult = event.getResult(); - listener.handler.executeAndWait(event); + fireAndAwait(event); assertEquals(originalResult.getReasonComponent(), event.getResult().getReasonComponent()); } @@ -112,11 +134,12 @@ public void denied() { deniedResult("denied"); Player player = mockPlayer(); LoginEvent event = new LoginEvent(player); - listener.handler.executeAndWait(event); + fireAndAwait(event); assertEquals( "denied", event.getResult().getReasonComponent() - .map(PlainComponentSerializer.plain()::serialize).orElse(null)); + .map(PlainComponentSerializer.plain()::serialize).orElse(null) + ); } @Test @@ -126,9 +149,11 @@ public void deniedByOtherPlugin() { LoginEvent event = new LoginEvent(player); var denial = ResultedEvent.ComponentResult.denied(Component.text("denial")); event.setResult(denial); - listener.handler.executeAndWait(event); + fireAndAwait(event); assertEquals( denial.getReasonComponent(), - event.getResult().getReasonComponent()); + event.getResult().getReasonComponent() + ); } + } diff --git a/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/VelocityEnvTest.java b/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/VelocityEnvTest.java index e51147ae3..6513aa549 100644 --- a/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/VelocityEnvTest.java +++ b/bans-env/velocity/src/test/java/space/arim/libertybans/env/velocity/VelocityEnvTest.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 @@ -33,9 +33,9 @@ public void allListenersDeclared() { new InjectableConstructor(VelocityEnv.class) .verifyParametersContainSubclassesOf(PlatformListener.class, (clazz) -> { // Exclude CommandHandler since it is constructed directly - // Exclude VelocityAsyncListener and ParallelisedListener, which would never be injected + // Exclude ParallelisedListener, which would never be injected boolean excluded = Set.of( - CommandHandler.class, VelocityAsyncListener.class, ParallelisedListener.class + CommandHandler.class, ParallelisedListener.class ).contains(clazz); return !excluded; }); diff --git a/docs/Comparison-to-LiteBans.md b/docs/Comparison-to-LiteBans.md index ab8c1f031..d729ea908 100644 --- a/docs/Comparison-to-LiteBans.md +++ b/docs/Comparison-to-LiteBans.md @@ -77,9 +77,9 @@ LibertyBans requires Java 17 whereas LiteBans permits Java 8. Neither LibertyBans nor LiteBans requires an external database. LibertyBans uses HSQLDB by default, and LiteBans uses H2 by default. -Both LibertyBans and LiteBans supports MariaDB, MySQL, and PostgreSQL. LiteBans also has support for SQLite, but SQLite usage is discouraged by LiteBans. +Both LibertyBans and LiteBans support MariaDB, MySQL, and PostgreSQL. LiteBans also has support for SQLite, but SQLite usage is discouraged by LiteBans. -LibertyBans requires certain minimum versions for database servers. At least MySQL 8.0, MariaDB 10.6, or PostgreSQL 12 is required. Older versions are not supported. +LibertyBans requires certain minimum versions for database servers. At least MySQL 8.0, MariaDB 10.3, or PostgreSQL 12 is required. Older versions are not supported. ## Platform Support @@ -94,7 +94,7 @@ LiteBans' support for Velocity came after repeated user requests. It was suggest ### Geyser Support -LibertyBans has Geyser support (since 30 May 2021), whereas LiteBans does not. +LibertyBans and LiteBans have Geyser support. ### Core punishment types @@ -116,7 +116,7 @@ Although both plugins support the exemption feature which prevents staff from ba For offline players, LiteBans's permission checking depends on Vault on single servers. Without a Vault-compatible permissions plugin, the feature breaks silently for offline players. -LibertyBans' exemption feature will never break silently. However, this imposes greater requirements. LibertyBans requires the installation of a supported exemption provider - currently LuckPerms or Vault. Without an exemption provider, the feature is entirely unavailable. +LibertyBans' exemption feature will never break silently. However, it requires the installation of a supported exemption provider - currently LuckPerms or Vault. Without an exemption provider, the feature is entirely unavailable. ### Importing From Other Plugins @@ -126,9 +126,9 @@ LiteBans supports importing from AdvancedBan, BanManager, BungeeAdminTools, MaxB ### Server Scopes -LiteBans enables punishments scoped to certain servers. +Both plugins enable punishments scoped to certain servers. -LibertyBans does not implement this feature. +However, LiteBans lacks scope categories, or the ability to group servers into a single scope.[4](#note4) ### Multi-Proxy / Multi-Instance Synchronization @@ -142,6 +142,8 @@ 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 + ### Disclaimer Please note that no harm is meant to subjects of criticism. If the writing sounds harsh, we apologize; please let us know and we will make the language less harsh. diff --git a/docs/Permissions.md b/docs/Permissions.md index db7879eb3..f01be3b0c 100644 --- a/docs/Permissions.md +++ b/docs/Permissions.md @@ -8,6 +8,8 @@ As a pre-requisite for any command, you must have the permission: `libertybans.commands` +## Punishment ## + ### Bans ### * `libertybans.ban.do.target.uuid` - ban players @@ -66,6 +68,10 @@ For obvious reasons, there are no duration permissions for kicks; moreover there * `libertybans.admin.import` - /libertybans import * `libertybans.admin.viewips` - Allows staff to view IP addresses if *censor-ip-addresses* is turned on in the configuration. +### Scopes + +If scope permissions are enabled, additional permissions are required to punish and list punishments. See the [Scoped Punishments](Scoped-Punishments.md) page. + ## Addons Addon-specific permissions are described on the [Addons](Addons) page. diff --git a/docs/Plugin-Comparison-Conclusions.md b/docs/Plugin-Comparison-Conclusions.md index c2be7a9c0..2e9b331c5 100644 --- a/docs/Plugin-Comparison-Conclusions.md +++ b/docs/Plugin-Comparison-Conclusions.md @@ -50,37 +50,29 @@ A good reason to use BanManager would be if you absolutely needed to use Java 8, ## Why not LiteBans -Little information is known about LiteBans. In fact, it may be illegal for us to disclose the results of our investigation into the workings of LiteBans. +Most importantly, LiteBans is proprietary and closed-source. The developer team consists of a single individual, and the plugin jar is obfuscated. If LiteBans were ever to halt development, or the author to vanish, the whole plugin would have to be rewritten from scratch, because the source code could not be recovered. -Because it is impossible for us to audit LiteBans, we cannot make any hard conclusions regarding the LiteBans codebase. It may be wholly *incorrect* or *unreliable*. We can't say. +Due to LiteBans' proprietary nature, even simple updates and bug fixes must wait for the lone author. Contrast this to LibertyBans, which has two official maintainers, A248 and Simon. If one of them is absent, LibertyBans can still receive critical bug fixes such as updates to newer Minecraft versions, which has happened before. -We will focus on the major *known* reason not to use LiteBans. There may be other reasons than this. +As a further consequence, it is impossible for us to fully audit LiteBans. We cannot make any hard conclusions regarding the LiteBans codebase. It may be wholly *incorrect* or *unreliable*. We can't say. LiteBans might break down in specific situations, or fail silently in some cases. Or it might run fine all the time. LiteBans might have security vulnerabilities from using H2, a database which has [a history of vulnerabilities](https://www.cvedetails.com/vulnerability-list.php?vendor_id=17893&product_id=45580). We'll never know. -### The Future is Uncertain +There are other reasons not to use LiteBans. Evidence suggests LiteBans does not employ automated testing much, increasing the prevalence of bugs in releases. Moreover, its database schema does not utilize integrity constraints, which can lead to data corruption. Yet because accessing the database is recommended over API use, LiteBans has to keep backwards compatibility with its backwards database, hindering development. -The most compelling reason not to use LiteBans regards its future. LiteBans' future is arguably in peril. By relying on LiteBans, you place your server at risk. +Features that languish on the LiteBans issue tracker will stay incomplete until the author decides to add them. No one else can contribute them or prioritize them. Even if a feature is heavily demanded, only the LiteBans author decides. As of 18 August 2023, the following LiteBans feature requests are fully implemented by LibertyBans: +* [Define scopes in punishment templates](https://gitlab.com/ruany/LiteBans/-/issues/502) +* [Extend punishment duration](https://gitlab.com/ruany/LiteBans/-/issues/494) +* [Purge a punishment completely](https://gitlab.com/ruany/LiteBans/-/issues/494) (same link) +* [Server scope groups](https://gitlab.com/ruany/LiteBans/-/issues/452) +* [Default reason for kicking](https://gitlab.com/ruany/LiteBans/-/issues/406) +* [Tab complete offline player names](https://gitlab.com/ruany/LiteBans/-/issues/349) +* [Add /ipkick command](https://gitlab.com/ruany/LiteBans/-/issues/301) +* [Confirmation for /staffrollback](https://gitlab.com/ruany/LiteBans/-/issues/185) +* [Notification permissions per punishment type](https://gitlab.com/ruany/LiteBans/-/issues/130) +* [Support Sponge platform](https://gitlab.com/ruany/LiteBans/-/issues/41) +* [Import vanilla IP bans](https://gitlab.com/ruany/LiteBans/-/issues/22) -The development of LiteBans depends on a single person. This has several implications, which bear on both the present and future: +For developers, the LiteBans API is poorly defined and commonly cited as a pain to work with. This makes it harder to integrate other plugins with LiteBans, unless the developer resorts to brittle command execution, forfeiting API guarantees. Not to mention that the inability to look at method implementations shackles debugging attempts. The API does not fully follow semantic versioning, either. -* If, for any reason, the author loses interest, has a better job, or is otherwise busy with other matters, development of LiteBans will stop. - * Unlike other proprietary plugins, LiteBans is a 1-person team. Other popular proprietary plugins are often composed of a team, so that if one developer leaves, development as a whole may continue. - * If the development of LiteBans stops, that is the end of LiteBans. No on else has a copy of the source code. No amount of money would bring LiteBans back if the author decided to abandon it permanently. +Ultimately, little information is known about LiteBans. In fact, it may even be illegal for us to disclose the results of our investigation into the workings of LiteBans. -* Only the author can debug the plugin. - * Even if you never plan to debug any of your plugins, other users will. As a user, you benefit from *other users* who *do* debug a plugin you both use. - * On a large network, it is imperative that you retain the ability to debug your own plugins not only *in isolation*, but also *in relation to one another*. Complex bugs can arise through interactions between plugins. - * See also [The Bug Nobody is Allowed to Understand](https://www.gnu.org/philosophy/bug-nobody-allowed-to-understand.en.html) - * Complex bugs arising from multiple plugins need not be LiteBans' fault, but they can still exist because of the presence of LiteBans. Sometimes, a bug is dependent on the most extraneous circumstances. Retaining the ability to debug your own proxy, with all its various intricacies, is essential. - * The LiteBans author will not personally debug your entire proxy. - -* Only the author can make a new release. LiteBans follows a fixed release model. - * Suppose a bug is fixed. The LiteBans author tells you to wait for the next release. You *must* wait until the next release; there is no alternative. - * If a critical security vulnerability is discovered, you will need to shut down your server until a new LiteBans release is made. - * It is naïve to think that the LiteBans author will *always* respond in a timely manner to the latest security vulnerabilities. Sometimes, you need to patch your own software. Even if you aren't a developer, you can apply someone else's patch. With LiteBans, this is impossible. - * Think security vulnerabilities don't happen to plugins? Think again. The H2 database, bundled by LiteBans, has [multiple security notices (CVEs)](https://www.cvedetails.com/vulnerability-list.php?vendor_id=17893&product_id=45580). It is possible to be vulnerable to problems in H2 even if you do not use H2 with LiteBans. - * More importantly, the modern software chain depends on code from dozens of libraries. Through no fault of a plugin, a plugin can rely on a library which has a security exploit. This can, in extreme situations, lead to a system takeover. - -* Have a feature request, but the author of LiteBans doesn't want to implement it? You will *never* see the request implemented. - * No one can fork LiteBans and add the feature. - * Even if you try to hire another developer, the other developer will not be able to modify LiteBans. - * Because only LiteBans can implement feature requests, and no one can fork it, competition is reduced. Intentionally limiting competition is an indicator of a bad product. +Imagine one day that you find a very complex bug and track it down to LiteBans. Complex bugs arising from multiple plugins need not even be LiteBans' fault, but they can still exist because of the presence of LiteBans. Sometimes, a bug is dependent on the most extraneous circumstances. However, with LiteBans, we'd have a case of [The Bug Nobody is Allowed to Understand](https://www.gnu.org/philosophy/bug-nobody-allowed-to-understand.en.html). diff --git a/docs/Quick-Plugin-Comparison.md b/docs/Quick-Plugin-Comparison.md index 096bac549..beca41a12 100644 --- a/docs/Quick-Plugin-Comparison.md +++ b/docs/Quick-Plugin-Comparison.md @@ -18,10 +18,10 @@ comparisons. You can find these on the sidebar at the right. For each plugin her | 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 | +| 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 | +| 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 | ❌ | ❌ | ✔️ | ❌ | | Legend: @@ -90,7 +90,7 @@ AdvancedBan has a connection pool, but in practice, can only use 1 connection at This feature is relevant for proxies. Whether the plugin has the ability to define "scopes" and create punishments applying to certain scopes. This allows server administrators to create punishments applying to a specific backend server or a group of backend servers. -BanManager's *partial* ranking: BanManager has the ability to create a "local" punishment, meaning it applies to one backend server. However, it does not have the ability to define punishments applying to a group of backend servers. +BanManager's and LiteBan's *partial* ranking: BanManager has the ability to create a "local" punishment, meaning it applies to one backend server. However, it does not have the ability to define punishments applying to a group of backend servers; further, local punishments cannot be created from separate servers. LiteBans has server scopes, but similarly, it cannot create scopes applying to a group of servers. ### Uses UUIDs diff --git a/docs/Scoped-Punishments.md b/docs/Scoped-Punishments.md new file mode 100644 index 000000000..933c8c569 --- /dev/null +++ b/docs/Scoped-Punishments.md @@ -0,0 +1,56 @@ + +# Scoped Punishments + +The server scopes feature allows you to ban players from specific backend servers on a network such as BungeeCord or Velocity. + +For example, perhaps you want to ban a player from the PvP server for combat hacking; however, they beg and plead until eventually convincing you to allow them to play the Creative game mode on a different backend server. + +Scope configuration is controlled by the `scope.yml`. + +## Introduction + +There are three kinds of scopes: +1. The global scope, applying everywhere. +2. Per-server scopes. +3. Category scopes, applying to a group of servers. + +### Server Name + +The server name controls how the server itself is identified in per-server scopes. This is the same server name you configured proxy-wide: on BungeeCord, in the `config.yml`, and for Velocity, the `velocity.toml`. + +Usually, backend servers can automatically detect their server name (using a technique called plugin messaging). However, if automatic detection is insufficient, you can override the detected server name. + +On the proxy, LibertyBans will always know each backend server's name. The proxy *itself* can also be named by setting a value manually, although per-proxy punishments have limited practical use. + +### Categories + +Categories may be configured per-server. Simply write out the categories it will be included in. + +## Commands + +Scopes may be used in commands by specifying `-server=` with a server name, `-category=` with a category, or `-scope=` with *any* scope. + +With `-scope=`, the scope itself must be one of `server:`, `category:` or `*` for the global scope. For example, `-scope=category:pvp` is valid. + +### Punish commands + +If no scope is specified, punishing commands will use the configured default punishing scope. You may change this to the current server to ban players on the same server by default. + +### Listing commands + +Listing commands such as /banlist, /history, etc. will include punishments from all scopes. Specifying a scope will narrow the list of punishments to ones using that scope -- critically, this is NOT the same as punishments *applying* to that scope. For example, `/banlist -scope=*` will yield only punishments with the global scope, but will not show punishments with per-server or category scopes. + +### Examples + +* /ban A248 -server=lobby You're just too annoying in the lobby +* /mute A248 -category=clans A conversationalist is far too dangerous of a clans player +* /warn A248 -scope=* 7d It's possible to place the -server, -category, or -scope argument anywhere before the punishment reason +* /kick A248 Did you know that scoped kicks work too? The player will only be kicked if they're currently connected to the given scope + +### Permissions + +By default, permissions for scopes are disabled for compatibility. To require specific permissions, turn on `require-scope-permissions`. + +If enabled: +* Staff must have the `libertybans.scope.global`, `libertybans.scope.server.`, or `libertybans.scope.category.` permissions. +* The permission `libertybans.scope.default` must be granted to use commands without a scope argument. Not granting this permission requires staff members to specify an explicit scope. diff --git a/docs/_sidebar.md b/docs/_sidebar.md index e7ae93bf1..2f0943f4b 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -7,6 +7,7 @@ * [Lenient, Normal, and Strict punishment enforcement](Punishment-Enforcement_-Lenient,-Normal,-and-Strict-settings) * [Silent Punishments](Silent-Punishments) * [Composite Punishments](Guide-to-Composite-Punishments) + * [Scoped Punishments](Scoped-Punishments) * [Running Multiple Instances](Running-Multiple-Instances) * [Switching Storage Backends (Self-Importing)](Self-Importing) * [Addons](Addons) diff --git a/pom.xml b/pom.xml index 9e0b1a734..ab8204d94 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ 3 - 4 + 5 true