From 16e8758351e6a9fe91087624d419d6058e69c18b Mon Sep 17 00:00:00 2001 From: IxPrumxI Date: Mon, 15 Dec 2025 09:32:29 +0300 Subject: [PATCH 1/4] Discord tie-breaker on first join/link for ban sync --- .../api/player/DiscordSRVPlayer.java | 6 ++++++ .../discordsrv/bukkit/player/BukkitPlayer.java | 5 +++++ .../common/config/main/sync/BanSyncConfig.java | 10 ++++++++-- .../events/player/PlayerConnectedEvent.java | 4 ++++ .../common/feature/bansync/BanSyncModule.java | 18 ++++++++++++++++++ .../fabric/module/chat/FabricJoinModule.java | 3 +-- .../discordsrv/fabric/player/FabricPlayer.java | 6 ++++++ 7 files changed, 48 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/com/discordsrv/api/player/DiscordSRVPlayer.java b/api/src/main/java/com/discordsrv/api/player/DiscordSRVPlayer.java index c5fc9cf81..f3ef471e2 100644 --- a/api/src/main/java/com/discordsrv/api/player/DiscordSRVPlayer.java +++ b/api/src/main/java/com/discordsrv/api/player/DiscordSRVPlayer.java @@ -71,6 +71,12 @@ public interface DiscordSRVPlayer { */ boolean isChatVisible(); + /** + * If this is the player's first time joining the server. + * @return {@code true} if this is the player's first join + */ + boolean isFirstJoin(); + /** * Sends the provided message to the player. * @param component the message diff --git a/bukkit/shared/src/main/java/com/discordsrv/bukkit/player/BukkitPlayer.java b/bukkit/shared/src/main/java/com/discordsrv/bukkit/player/BukkitPlayer.java index 35b05f4cd..84e0c91ea 100644 --- a/bukkit/shared/src/main/java/com/discordsrv/bukkit/player/BukkitPlayer.java +++ b/bukkit/shared/src/main/java/com/discordsrv/bukkit/player/BukkitPlayer.java @@ -97,6 +97,11 @@ public boolean isVanished() { return BukkitComponentSerializer.legacy().deserialize(player.getDisplayName()); } + @Override + public boolean isFirstJoin() { + return !player.hasPlayedBefore(); + } + @Override public @NotNull Identity identity() { return identity; diff --git a/common/src/main/java/com/discordsrv/common/config/main/sync/BanSyncConfig.java b/common/src/main/java/com/discordsrv/common/config/main/sync/BanSyncConfig.java index 99da91d51..ece411086 100644 --- a/common/src/main/java/com/discordsrv/common/config/main/sync/BanSyncConfig.java +++ b/common/src/main/java/com/discordsrv/common/config/main/sync/BanSyncConfig.java @@ -39,6 +39,12 @@ public BanSyncConfig() { @Order(-10) public long serverId = 0L; + @Comment("On a player's first join to the server, prefer the Discord side for tie-breaking") + public boolean preferDiscordTieBreakerOnFirstJoin = true; + + @Comment("When a player links their account, prefer the Discord side for tie-breaking") + public boolean preferDiscordTieBreakerOnAccountLink = true; + @Comment("Role id that will be used for role related actions, if they are configured below") public Long bannedRoleId = 0L; @@ -80,8 +86,8 @@ public static class DiscordToMinecraftConfig { + "ban: A ban/unban on the Discord Server\n" + "role: Addition/removal of the banned role (specified above) to the user on Discord\n" + "either: Either of the above\n" - + "BEWARE: Settings of 'role' or 'either' can be exploited to remove bans from players if %1 is set to 'discord'") - @Constants.Comment("tie-breaker") + + "BEWARE: Settings of 'role' or 'either' can be exploited to remove bans from players if %1 is set to 'discord', and %2 or %3 aren't set to `true`") + @Constants.Comment({"tie-breaker", "prefer-discord-on-first-join", "prefer-discord-on-account-link"}) public BanSyncDiscordTrigger trigger = BanSyncDiscordTrigger.BAN; @Comment("The reason used when creating new bans in Minecraft") diff --git a/common/src/main/java/com/discordsrv/common/events/player/PlayerConnectedEvent.java b/common/src/main/java/com/discordsrv/common/events/player/PlayerConnectedEvent.java index 16cd8fd07..cecc0aa97 100644 --- a/common/src/main/java/com/discordsrv/common/events/player/PlayerConnectedEvent.java +++ b/common/src/main/java/com/discordsrv/common/events/player/PlayerConnectedEvent.java @@ -42,4 +42,8 @@ public IPlayer player() { public boolean joinedBeforeInitialization() { return joinedBeforeInitialization; } + + public boolean firstJoin() { + return player.isFirstJoin(); + } } diff --git a/common/src/main/java/com/discordsrv/common/feature/bansync/BanSyncModule.java b/common/src/main/java/com/discordsrv/common/feature/bansync/BanSyncModule.java index 08855f25b..d3ea8b14c 100644 --- a/common/src/main/java/com/discordsrv/common/feature/bansync/BanSyncModule.java +++ b/common/src/main/java/com/discordsrv/common/feature/bansync/BanSyncModule.java @@ -65,6 +65,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import com.discordsrv.common.abstraction.sync.enums.SyncSide; +import com.discordsrv.common.abstraction.sync.cause.GenericSyncCauses; +import com.discordsrv.api.events.linking.AccountLinkedEvent; +import com.discordsrv.common.events.player.PlayerConnectedEvent; public class BanSyncModule extends AbstractSyncModule { @@ -158,6 +162,20 @@ public void applyPunishment(@Nullable Punishment punishment, ISyncCause cause) { } } + // Make sure players can't bypass bans by joining the server while banned on Discord + @Subscribe + @Override + public void onPlayerConnected(PlayerConnectedEvent event) { + resyncAll(GenericSyncCauses.GAME_JOIN, Someone.of(discordSRV, event.player()), config -> config.preferDiscordTieBreakerOnFirstJoin && event.firstJoin() ? SyncSide.DISCORD : config.tieBreakers.join); + + } + + @Subscribe + @Override + public void onAccountLinked(AccountLinkedEvent event) { + resyncAll(GenericSyncCauses.LINK, Someone.of(discordSRV, event.getPlayerUUID()), config -> config.preferDiscordTieBreakerOnAccountLink ? SyncSide.DISCORD : config.tieBreakers.link); + } + @Subscribe public void onGuildBan(GuildBanEvent event) { handleDiscordBanChange(event.getGuild(), event.getUser(), true); diff --git a/fabric/src/main/java/com/discordsrv/fabric/module/chat/FabricJoinModule.java b/fabric/src/main/java/com/discordsrv/fabric/module/chat/FabricJoinModule.java index 3367b38b8..2dd850ba0 100644 --- a/fabric/src/main/java/com/discordsrv/fabric/module/chat/FabricJoinModule.java +++ b/fabric/src/main/java/com/discordsrv/fabric/module/chat/FabricJoinModule.java @@ -48,7 +48,6 @@ private void onJoin(ServerGamePacketListenerImpl serverPlayNetworkHandler, Packe if (!enabled) return; ServerPlayer playerEntity = serverPlayNetworkHandler.player; - boolean firstJoin = playerEntity.getStats().getValue(Stats.CUSTOM.get(Stats.LEAVE_GAME)) == 0; MinecraftComponent component; if (discordSRV.getNameFromGameProfile(playerEntity.getGameProfile()).equalsIgnoreCase(playerEntity.getName().getString())) { @@ -68,7 +67,7 @@ private void onJoin(ServerGamePacketListenerImpl serverPlayNetworkHandler, Packe player, component, null, - firstJoin, + player.isFirstJoin(), false, component == null, false diff --git a/fabric/src/main/java/com/discordsrv/fabric/player/FabricPlayer.java b/fabric/src/main/java/com/discordsrv/fabric/player/FabricPlayer.java index 29cb4c76c..1b949911e 100644 --- a/fabric/src/main/java/com/discordsrv/fabric/player/FabricPlayer.java +++ b/fabric/src/main/java/com/discordsrv/fabric/player/FabricPlayer.java @@ -28,6 +28,7 @@ import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.Stats; import net.minecraft.world.scores.PlayerTeam; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -162,6 +163,11 @@ public void removeChatSuggestions(Collection suggestions) { return discordSRV.componentFactory().fromNative(team.getFormattedName(player.getName())); } + @Override + public boolean isFirstJoin() { + return player.getStats().getValue(Stats.CUSTOM.get(Stats.LEAVE_GAME)) == 0; + } + @Override public boolean isChatVisible() { //? if minecraft: >1.20.1 { From 9e2c04df29f477ace20e1d3371bd200a422a3597 Mon Sep 17 00:00:00 2001 From: IxPrumxI Date: Mon, 15 Dec 2025 12:59:59 +0300 Subject: [PATCH 2/4] Add isFirstJoin method for bungee and velocity --- .../main/java/com/discordsrv/bungee/player/BungeePlayer.java | 5 +++++ .../java/com/discordsrv/velocity/player/VelocityPlayer.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/bungee/src/main/java/com/discordsrv/bungee/player/BungeePlayer.java b/bungee/src/main/java/com/discordsrv/bungee/player/BungeePlayer.java index 16b022d52..e11d7519f 100644 --- a/bungee/src/main/java/com/discordsrv/bungee/player/BungeePlayer.java +++ b/bungee/src/main/java/com/discordsrv/bungee/player/BungeePlayer.java @@ -92,6 +92,11 @@ public void removeChatSuggestions(Collection suggestions) { return BungeeComponentUtil.fromLegacy(player.getDisplayName()); } + @Override + public boolean isFirstJoin() { + return false; + } + @Override public String toString() { return "BungeePlayer{" + username() + "}"; diff --git a/velocity/src/main/java/com/discordsrv/velocity/player/VelocityPlayer.java b/velocity/src/main/java/com/discordsrv/velocity/player/VelocityPlayer.java index fdef52e4c..e9f174812 100644 --- a/velocity/src/main/java/com/discordsrv/velocity/player/VelocityPlayer.java +++ b/velocity/src/main/java/com/discordsrv/velocity/player/VelocityPlayer.java @@ -123,6 +123,11 @@ public boolean isChatVisible() { && chatMode != PlayerSettings.ChatMode.HIDDEN; } + @Override + public boolean isFirstJoin() { + return false; + } + @Override public String toString() { return "VelocityPlayer{" + username() + "}"; From 7c7f312e7e113cfd590a66c7225858490ce9cdd7 Mon Sep 17 00:00:00 2001 From: IxPrumxI Date: Mon, 15 Dec 2025 15:23:10 +0300 Subject: [PATCH 3/4] Fix test --- .../game/MinecraftToDiscordChatMessageTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/src/test/java/com/discordsrv/common/messageforwarding/game/MinecraftToDiscordChatMessageTest.java b/common/src/test/java/com/discordsrv/common/messageforwarding/game/MinecraftToDiscordChatMessageTest.java index 92a2eb8ff..8b3bbf79f 100644 --- a/common/src/test/java/com/discordsrv/common/messageforwarding/game/MinecraftToDiscordChatMessageTest.java +++ b/common/src/test/java/com/discordsrv/common/messageforwarding/game/MinecraftToDiscordChatMessageTest.java @@ -119,6 +119,11 @@ public void removeChatSuggestions(Collection suggestions) {} return Component.text("Vankka"); } + @Override + public boolean isFirstJoin() { + return false; + } + @Override public boolean hasPermission(Permission permission) { return true; From 9886cdc3cc0d7d188cde304a18c1e472b7131c09 Mon Sep 17 00:00:00 2001 From: IxPrumxI Date: Tue, 16 Dec 2025 03:27:03 +0300 Subject: [PATCH 4/4] Remove link discord tie-breaker --- .../discordsrv/common/config/main/sync/BanSyncConfig.java | 7 ++----- .../discordsrv/common/feature/bansync/BanSyncModule.java | 6 ------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/com/discordsrv/common/config/main/sync/BanSyncConfig.java b/common/src/main/java/com/discordsrv/common/config/main/sync/BanSyncConfig.java index ece411086..5f486e269 100644 --- a/common/src/main/java/com/discordsrv/common/config/main/sync/BanSyncConfig.java +++ b/common/src/main/java/com/discordsrv/common/config/main/sync/BanSyncConfig.java @@ -42,9 +42,6 @@ public BanSyncConfig() { @Comment("On a player's first join to the server, prefer the Discord side for tie-breaking") public boolean preferDiscordTieBreakerOnFirstJoin = true; - @Comment("When a player links their account, prefer the Discord side for tie-breaking") - public boolean preferDiscordTieBreakerOnAccountLink = true; - @Comment("Role id that will be used for role related actions, if they are configured below") public Long bannedRoleId = 0L; @@ -86,8 +83,8 @@ public static class DiscordToMinecraftConfig { + "ban: A ban/unban on the Discord Server\n" + "role: Addition/removal of the banned role (specified above) to the user on Discord\n" + "either: Either of the above\n" - + "BEWARE: Settings of 'role' or 'either' can be exploited to remove bans from players if %1 is set to 'discord', and %2 or %3 aren't set to `true`") - @Constants.Comment({"tie-breaker", "prefer-discord-on-first-join", "prefer-discord-on-account-link"}) + + "BEWARE: Settings of 'role' or 'either' can be exploited to remove bans from players if %1 is set to 'discord', and %2 isn't set to `true`") + @Constants.Comment({"tie-breaker", "prefer-discord-on-first-join"}) public BanSyncDiscordTrigger trigger = BanSyncDiscordTrigger.BAN; @Comment("The reason used when creating new bans in Minecraft") diff --git a/common/src/main/java/com/discordsrv/common/feature/bansync/BanSyncModule.java b/common/src/main/java/com/discordsrv/common/feature/bansync/BanSyncModule.java index d3ea8b14c..799fd8be5 100644 --- a/common/src/main/java/com/discordsrv/common/feature/bansync/BanSyncModule.java +++ b/common/src/main/java/com/discordsrv/common/feature/bansync/BanSyncModule.java @@ -170,12 +170,6 @@ public void onPlayerConnected(PlayerConnectedEvent event) { } - @Subscribe - @Override - public void onAccountLinked(AccountLinkedEvent event) { - resyncAll(GenericSyncCauses.LINK, Someone.of(discordSRV, event.getPlayerUUID()), config -> config.preferDiscordTieBreakerOnAccountLink ? SyncSide.DISCORD : config.tieBreakers.link); - } - @Subscribe public void onGuildBan(GuildBanEvent event) { handleDiscordBanChange(event.getGuild(), event.getUser(), true);