Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import me.confuser.banmanager.common.commands.CommonSender;
import me.confuser.banmanager.common.data.*;
import me.confuser.banmanager.common.kyori.text.TextComponent;
import me.confuser.banmanager.common.util.ColorUtils;
import me.confuser.banmanager.common.util.Message;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.World;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
Expand Down Expand Up @@ -87,8 +87,7 @@ public void broadcast(String message, String permission, CommonSender sender) {
}

public static String formatMessage(String message) {
return ChatColor
.translateAlternateColorCodes('&', message.replace("\\n", "\n"));
return ColorUtils.toDownsampledLegacy(message);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import me.confuser.banmanager.common.data.*;
import me.confuser.banmanager.common.kyori.text.TextComponent;
import me.confuser.banmanager.common.kyori.text.serializer.gson.GsonComponentSerializer;
import me.confuser.banmanager.common.util.ColorUtils;
import me.confuser.banmanager.common.util.Message;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
Expand Down Expand Up @@ -224,7 +224,8 @@ public CommonEvent callEvent(String name, Object... args) {
}

public static BaseComponent[] formatMessage(String message) {
return net.md_5.bungee.api.chat.TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', message));
String json = ColorUtils.toDownsampledJson(message);
return ComponentSerializer.parse(json);
}

public static BaseComponent[] formatMessage(TextComponent message) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package me.confuser.banmanager.common.util;

import me.confuser.banmanager.common.kyori.text.Component;
import me.confuser.banmanager.common.kyori.text.serializer.legacy.LegacyComponentSerializer;
import me.confuser.banmanager.common.kyori.text.serializer.gson.GsonComponentSerializer;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ColorUtils {
// Pattern for &x&r&r&g&g&b&b format (Spigot-style hex)
private static final Pattern SPIGOT_HEX_PATTERN = Pattern.compile(
"&x(&[0-9a-fA-F])(&[0-9a-fA-F])(&[0-9a-fA-F])(&[0-9a-fA-F])(&[0-9a-fA-F])(&[0-9a-fA-F])"
);

// Parses &codes AND &#rrggbb format
private static final LegacyComponentSerializer HEX_PARSER =
LegacyComponentSerializer.builder()
.character('&')
.hexColors()
.build();

// Outputs legacy string with NO hex (downsampled to nearest vanilla)
private static final LegacyComponentSerializer LEGACY_SERIALIZER =
LegacyComponentSerializer.builder()
.character('&')
.build();

// Outputs JSON with NO hex colors - safe for pre-1.16 clients
private static final GsonComponentSerializer DOWNSAMPLING_JSON =
GsonComponentSerializer.colorDownsamplingGson();

/**
* Convert Spigot-style &x&r&r&g&g&b&b to &#rrggbb format
*/
private static String preprocessSpigotHex(String message) {
Matcher matcher = SPIGOT_HEX_PATTERN.matcher(message);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
// Extract each color char (removing the & prefix)
String hex = matcher.group(1).substring(1) +
matcher.group(2).substring(1) +
matcher.group(3).substring(1) +
matcher.group(4).substring(1) +
matcher.group(5).substring(1) +
matcher.group(6).substring(1);
matcher.appendReplacement(result, "&#" + hex);
}
matcher.appendTail(result);
return result.toString();
}

/**
* Parse message with hex color support (&#rrggbb and &x&r&r&g&g&b&b)
*/
public static Component parse(String message) {
String processed = preprocessSpigotHex(message.replace("\\n", "\n"));
return HEX_PARSER.deserialize(processed);
}

/**
* Parse and convert to legacy string, downsampling hex to nearest vanilla.
* Safe for ALL Minecraft versions (1.7.2+).
*/
public static String toDownsampledLegacy(String message) {
Component component = parse(message);
return LEGACY_SERIALIZER.serialize(component);
}

/**
* Parse and convert to JSON with downsampled colors.
* Uses colorDownsamplingGson() - safe for pre-1.16 clients.
*/
public static String toDownsampledJson(String message) {
Component component = parse(message);
return DOWNSAMPLING_JSON.serialize(component);
}

/**
* Parse and convert to full JSON (with hex colors intact).
* Only use when you KNOW the client supports 1.16+ colors.
*/
public static String toJson(String message) {
Component component = parse(message);
return GsonComponentSerializer.gson().serialize(component);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package me.confuser.banmanager.common.util;

import org.junit.Test;
import static org.junit.Assert.*;

public class ColorUtilsTest {

@Test
public void testHexColorParsing() {
String input = "&#ff0000Red Text";
String legacy = ColorUtils.toDownsampledLegacy(input);
assertFalse("Output should not contain #", legacy.contains("#"));
assertTrue("Output should contain 'Red Text'", legacy.contains("Red Text"));
}

@Test
public void testMixedColors() {
String input = "&cRed &#00ff00Green &9Blue";
String legacy = ColorUtils.toDownsampledLegacy(input);
assertFalse("Output should not contain #", legacy.contains("#"));
assertTrue("Output should contain text", legacy.contains("Red"));
assertTrue("Output should contain text", legacy.contains("Green"));
assertTrue("Output should contain text", legacy.contains("Blue"));
}

@Test
public void testSpigotHexFormat() {
String input = "&x&f&f&0&0&0&0Red";
String legacy = ColorUtils.toDownsampledLegacy(input);
assertFalse("Output should not contain &x", legacy.contains("&x"));
assertFalse("Output should not contain #", legacy.contains("#"));
assertTrue("Output should contain 'Red'", legacy.contains("Red"));
}

@Test
public void testDownsampledJsonNoHex() {
String input = "&#ff5733Orange";
String json = ColorUtils.toDownsampledJson(input);
assertFalse("JSON should not contain hex code", json.contains("#ff5733"));
assertFalse("JSON should not contain hex code", json.contains("ff5733"));
assertTrue("JSON should contain text", json.contains("Orange"));
}

@Test
public void testFullJsonHasHex() {
String input = "&#ff5733Orange";
String json = ColorUtils.toJson(input);
assertTrue("JSON should contain text", json.contains("Orange"));
}

@Test
public void testNewlinePreservation() {
String input = "Line1\\nLine2";
String legacy = ColorUtils.toDownsampledLegacy(input);
assertTrue("Should convert \\n to newline", legacy.contains("\n"));
}

@Test
public void testPlainTextUnchanged() {
String input = "Hello World";
String legacy = ColorUtils.toDownsampledLegacy(input);
assertEquals("Plain text should be unchanged", "Hello World", legacy);
}

@Test
public void testLegacyColorsPreserved() {
String input = "&cRed &aGreen";
String legacy = ColorUtils.toDownsampledLegacy(input);
assertTrue("Should contain & codes", legacy.contains("&"));
assertTrue("Should contain text", legacy.contains("Red"));
assertTrue("Should contain text", legacy.contains("Green"));
}
}
2 changes: 1 addition & 1 deletion e2e/platforms/bukkit/configs/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
messages:
duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]'
duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]'
configReloaded: '&aConfiguration reloaded successfully!'
configReloaded: '&#00ff00Configuration &#ff5733reloaded &asuccessfully!'
deniedNotify:
player: '&cWarning: [player] attempted to join the server but was denied due to &4[reason]'
ip: '&cWarning: [ip] attempted to join the server but was denied due to &4[reason]'
Expand Down
18 changes: 9 additions & 9 deletions e2e/platforms/bungee/configs/banmanager/config.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#
#
# Aliases will be found and blocked automatically, e.g. msg will block tell
debug: false
databases:
Expand Down Expand Up @@ -63,13 +63,13 @@ databases:
ipBans: bm_ip_ban_all
ipUnbans: bm_ip_unban_all
mutedCommandBlacklist:
- msg
- msg
softMutedCommandBlacklist:
- msg
- msg
duplicateIpCheck: false
bypassDuplicateChecks:
- 0.0.0.0
- 127.0.0.0
- 0.0.0.0
- 127.0.0.0
logKicks: false
logIps: true
displayNotifications: true
Expand All @@ -96,16 +96,16 @@ warningActions:
enabled: true
actions:
'3':
- cmd: addnote [player] warning-action-triggered
delay: 0
- cmd: addnote [player] warning-action-triggered
delay: 0
warningMute: false
hooks:
enabled: true
events:
mute:
post:
- cmd: addnote [player] hook-fired-mute
delay: 2
- cmd: addnote [player] hook-fired-mute
delay: 2
checkForUpdates: false
offlineAutoComplete: true
punishAlts: false
Expand Down
13 changes: 7 additions & 6 deletions e2e/platforms/bungee/configs/banmanager/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
messages:
duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]'
duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]'
configReloaded: '&aConfiguration reloaded successfully!'
configReloaded: '&#00ff00Configuration &#ff5733reloaded &asuccessfully!'
deniedNotify:
player: '&cWarning: [player] attempted to join the server but was denied due to
&4[reason]'
Expand Down Expand Up @@ -114,8 +114,8 @@ messages:
permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]'
temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which
expires in [expires]'
temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created] which
expires in [expires] (online time)'
temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created]
which expires in [expires] (online time)'
temporaryOnlinePaused: '&6Currently muted for &4[reason]&6 by [actor] at [created]
with [remaining] remaining (online time, paused)'
dateTimeFormat: dd-MM-yyyy HH:mm:ss
Expand Down Expand Up @@ -192,10 +192,11 @@ messages:
player:
disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which
expires in [expires]'
disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor] which
expires in [expires] (online time)'
disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor]
which expires in [expires] (online time)'
notify: '&6[player] has been temporarily muted for [expires] by [actor] for &4[reason]'
notifyOnline: '&6[player] has been temporarily muted for [expires] (online time) by [actor] for &4[reason]'
notifyOnline: '&6[player] has been temporarily muted for [expires] (online time)
by [actor] for &4[reason]'
error:
exists: '&c[player] is already muted'
tempmuteip:
Expand Down
16 changes: 8 additions & 8 deletions e2e/platforms/bungee/configs/banmanager/schedules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ scheduler:
externalIpBans: 5
saveLastChecked: 60
lastChecked:
playerMutes: 1767014596
expiresCheck: 1767014596
nameBans: 1767014596
playerBans: 1767014596
playerWarnings: 1767014596
playerMutes: 1767276627
expiresCheck: 1767276627
nameBans: 1767276627
playerBans: 1767276627
playerWarnings: 1767276627
externalPlayerNotes: 0
ipRangeBans: 1767014596
ipRangeBans: 1767276627
externalPlayerMutes: 0
rollbacks: 1767014587
rollbacks: 1767276626
externalPlayerBans: 0
externalIpBans: 0
ipBans: 1767014596
ipBans: 1767276627
Loading