diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java index d6ce4679..d1c0a4e4 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java @@ -301,7 +301,6 @@ public class CommandHandler { addCommand(new GivePrefixCommand()); addCommand(new ListPrefixesCommand()); addCommand(new RemovePrefixCommand()); - addCommand(new PrefixBlacklistCommand()); addCommand(new WiredCommand()); addCommand(new TestCommand()); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PrefixBlacklistCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PrefixBlacklistCommand.java deleted file mode 100644 index dc8bbd69..00000000 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PrefixBlacklistCommand.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.eu.habbo.habbohotel.commands; - -import com.eu.habbo.Emulator; -import com.eu.habbo.habbohotel.gameclients.GameClient; -import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -public class PrefixBlacklistCommand extends Command { - private static final Logger LOGGER = LoggerFactory.getLogger(PrefixBlacklistCommand.class); - - public PrefixBlacklistCommand() { - super("cmd_prefix_blacklist", Emulator.getTexts().getValue("commands.keys.cmd_prefix_blacklist").split(";")); - } - - @Override - public boolean handle(GameClient gameClient, String[] params) throws Exception { - if (params.length < 2) { - gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT); - return true; - } - - String action = params[1].toLowerCase(); - - if (action.equals("list")) { - StringBuilder sb = new StringBuilder(); - sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.header")).append("\r"); - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist ORDER BY word")) { - try (ResultSet set = statement.executeQuery()) { - int count = 0; - while (set.next()) { - sb.append("- ").append(set.getString("word")).append("\r"); - count++; - } - if (count == 0) { - sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.empty")); - } - } - } catch (SQLException e) { - LOGGER.error("Error listing prefix blacklist", e); - } - - gameClient.getHabbo().whisper(sb.toString(), RoomChatMessageBubbles.ALERT); - return true; - } - - if (params.length < 3) { - gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT); - return true; - } - - String word = params[2].toLowerCase().trim(); - - if (word.isEmpty()) { - gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.empty_word"), RoomChatMessageBubbles.ALERT); - return true; - } - - if (action.equals("add")) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("INSERT INTO custom_prefix_blacklist (word) VALUES (?)")) { - statement.setString(1, word); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Error adding prefix blacklist word", e); - } - - gameClient.getHabbo().whisper( - Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.added").replace("%word%", word), - RoomChatMessageBubbles.ALERT - ); - } else if (action.equals("remove")) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("DELETE FROM custom_prefix_blacklist WHERE word = ?")) { - statement.setString(1, word); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Error removing prefix blacklist word", e); - } - - gameClient.getHabbo().whisper( - Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.removed").replace("%word%", word), - RoomChatMessageBubbles.ALERT - ); - } else { - gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT); - } - - return true; - } -} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/DeletePrefixEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/DeletePrefixEvent.java index 7cac1bf1..313477ce 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/DeletePrefixEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/DeletePrefixEvent.java @@ -6,6 +6,11 @@ import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer; public class DeletePrefixEvent extends MessageHandler { + @Override + public int getRatelimit() { + return 500; + } + @Override public void handle() throws Exception { int prefixId = this.packet.readInt(); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/PurchasePrefixEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/PurchasePrefixEvent.java index 54fa81c8..aa8c1556 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/PurchasePrefixEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/PurchasePrefixEvent.java @@ -1,15 +1,20 @@ package com.eu.habbo.messages.incoming.inventory.prefixes; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.modtool.WordFilterWord; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.UserPrefix; import com.eu.habbo.messages.incoming.MessageHandler; -import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys; import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer; +import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys; import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer; +import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer; +import com.eu.habbo.messages.outgoing.inventory.prefixes.CustomPrefixPurchaseFailedComposer; import com.eu.habbo.messages.outgoing.inventory.prefixes.PrefixReceivedComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer; import com.eu.habbo.messages.outgoing.users.UserCreditsComposer; import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,10 +22,18 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; public class PurchasePrefixEvent extends MessageHandler { private static final Logger LOGGER = LoggerFactory.getLogger(PurchasePrefixEvent.class); private static final String[] ALLOWED_FONTS = { "", "pixel", "cherry", "vampiro" }; + private static final String[] ALLOWED_EFFECTS = { + "", "glow", "shadow", "italic", "outline", "underline", "pulse", "bounce", "wave", "shake", + "discord-neon", "cartoon", "toon", "pop", "bold-glow", "rainbow", "frost", "gold", "glitch", + "fire", "matrix", "sparkle" + }; + private static final int MAX_ICON_LENGTH = 16; @Override public int getRatelimit() { @@ -39,81 +52,101 @@ public class PurchasePrefixEvent extends MessageHandler { if (habbo == null) return; - // Load settings - int maxLength = getSettingInt("max_length", 15); - int minRank = getSettingInt("min_rank_to_buy", 1); - int priceCredits = getSettingInt("price_credits", 5); - int pricePoints = getSettingInt("price_points", 0); - int pointsType = getSettingInt("points_type", 0); - int fontPriceCredits = getSettingInt("font_price_credits", 10); - int fontPricePoints = getSettingInt("font_price_points", 0); - int fontPointsType = getSettingInt("font_points_type", pointsType); + Map settings = loadSettings(); + int maxLength = setting(settings, "max_length", 15); + int minRank = setting(settings, "min_rank_to_buy", 1); + int priceCredits = setting(settings, "price_credits", 5); + int pricePoints = setting(settings, "price_points", 0); + int pointsType = setting(settings, "points_type", 0); + int fontPriceCredits = setting(settings, "font_price_credits", 10); + int fontPricePoints = setting(settings, "font_price_points", 0); + int fontPointsType = setting(settings, "font_points_type", pointsType); + int maxPrefixes = setting(settings, "max_prefixes", 60); - // Validate text - text = text.trim(); - - if (text.isEmpty() || text.length() > maxLength) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Prefix text is invalid or too long (max " + maxLength + " characters).")); + if (maxPrefixes > 0 && habbo.getInventory().getPrefixesComponent().getPrefixes().size() >= maxPrefixes) { + this.fail(habbo, "You already own the maximum number of prefixes (" + maxPrefixes + ")."); + return; + } + + text = text.trim(); + + if (text.isEmpty() || text.length() > maxLength) { + this.fail(habbo, "Prefix text is invalid or too long (max " + maxLength + " characters)."); + return; + } + + if (containsControlChars(text)) { + this.fail(habbo, "Prefix text contains invalid characters."); + return; + } + + if (containsFilteredWord(text)) { + this.fail(habbo, "This prefix contains a blocked word."); return; } - // Validate color (single hex or comma-separated multi hex for per-letter colors) String[] colorParts = color.split(","); + + if (colorParts.length > text.length()) { + this.fail(habbo, "Invalid color format."); + return; + } + for (String part : colorParts) { if (!part.matches("^#[0-9A-Fa-f]{6}$")) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Invalid color format.")); + this.fail(habbo, "Invalid color format."); return; } } - // Check rank if (habbo.getHabboInfo().getRank().getId() < minRank) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Your rank is too low to purchase prefixes.")); - return; - } - - // Check blacklist - if (isBlacklisted(text)) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "This prefix contains a blocked word.")); + this.fail(habbo, "Your rank is too low to purchase prefixes."); return; } if (icon == null) icon = ""; icon = icon.trim(); + if (!isValidIcon(icon)) { + this.fail(habbo, "Invalid prefix icon."); + return; + } + if (effect == null) effect = ""; - effect = effect.trim(); + effect = effect.trim().toLowerCase(); + + if (!isAllowedEffect(effect)) { + this.fail(habbo, "Invalid prefix effect."); + return; + } if (font == null) font = ""; font = font.trim().toLowerCase(); if (!isAllowedFont(font)) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Invalid font format.")); + this.fail(habbo, "Invalid font format."); return; } int totalPriceCredits = priceCredits + (!font.isEmpty() ? fontPriceCredits : 0); - // Check credits if (totalPriceCredits > 0 && habbo.getHabboInfo().getCredits() < totalPriceCredits) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough credits.")); + this.fail(habbo, "Not enough credits."); return; } int totalPricePointsSameType = pricePoints + ((fontPricePoints > 0 && fontPointsType == pointsType && !font.isEmpty()) ? fontPricePoints : 0); - // Check points if (totalPricePointsSameType > 0 && habbo.getHabboInfo().getCurrencyAmount(pointsType) < totalPricePointsSameType) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points.")); + this.fail(habbo, "Not enough points."); return; } if (!font.isEmpty() && fontPricePoints > 0 && fontPointsType != pointsType && habbo.getHabboInfo().getCurrencyAmount(fontPointsType) < fontPricePoints) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points.")); + this.fail(habbo, "Not enough points."); return; } - // Deduct currency if (totalPriceCredits > 0) { habbo.getHabboInfo().addCredits(-totalPriceCredits); this.client.sendResponse(new UserCreditsComposer(habbo)); @@ -129,47 +162,57 @@ public class PurchasePrefixEvent extends MessageHandler { this.client.sendResponse(new UserCurrencyComposer(habbo)); } - // Create prefix int storedPoints = totalPricePointsSameType; int storedPointsType = (storedPoints > 0) ? pointsType : ((!font.isEmpty() && fontPricePoints > 0) ? fontPointsType : pointsType); UserPrefix prefix = new UserPrefix(habbo.getHabboInfo().getId(), text, color, icon, effect, font, 0, text, storedPoints, storedPointsType, true); prefix.run(); // Insert into DB synchronously to get the ID habbo.getInventory().getPrefixesComponent().addPrefix(prefix); - + habbo.getInventory().getPrefixesComponent().setActive(prefix.getId()); this.client.sendResponse(new PrefixReceivedComposer(prefix)); + this.client.sendResponse(new ActivePrefixUpdatedComposer(prefix)); this.client.sendResponse(new UserNickIconsComposer(habbo)); - } - private int getSettingInt(String key, int defaultValue) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("SELECT `value` FROM custom_prefix_settings WHERE key_name = ?")) { - statement.setString(1, key); - try (ResultSet set = statement.executeQuery()) { - if (set.next()) { - return Integer.parseInt(set.getString("value")); - } - } - } catch (SQLException | NumberFormatException e) { - LOGGER.error("Error reading prefix setting: " + key, e); + if (habbo.getHabboInfo().getCurrentRoom() != null) { + habbo.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(habbo).compose()); } - return defaultValue; } - private boolean isBlacklisted(String text) { - String lowerText = text.toLowerCase(); + private void fail(Habbo habbo, String message) { + this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, message)); + this.client.sendResponse(new CustomPrefixPurchaseFailedComposer(message)); + } + + private Map loadSettings() { + Map settings = new HashMap<>(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist")) { - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - if (lowerText.contains(set.getString("word").toLowerCase())) { - return true; - } - } + PreparedStatement statement = connection.prepareStatement("SELECT key_name, `value` FROM custom_prefix_settings"); + ResultSet set = statement.executeQuery()) { + while (set.next()) { + try { + settings.put(set.getString("key_name"), Integer.parseInt(set.getString("value"))); + } catch (NumberFormatException ignored) {} } } catch (SQLException e) { - LOGGER.error("Error checking prefix blacklist", e); + LOGGER.error("Error reading prefix settings", e); } + return settings; + } + + private int setting(Map settings, String key, int defaultValue) { + Integer value = settings.get(key); + return value != null ? value : defaultValue; + } + + private boolean containsFilteredWord(String text) { + if (text == null || text.isEmpty()) return false; + + for (WordFilterWord word : Emulator.getGameEnvironment().getWordFilter().getWords()) { + if (word.key != null && !word.key.isEmpty() && StringUtils.containsIgnoreCase(text, word.key)) { + return true; + } + } + return false; } @@ -182,4 +225,35 @@ public class PurchasePrefixEvent extends MessageHandler { return false; } + + private boolean isAllowedEffect(String effect) { + for (String allowedEffect : ALLOWED_EFFECTS) { + if (allowedEffect.equals(effect)) { + return true; + } + } + + return false; + } + + private boolean isValidIcon(String icon) { + if (icon.isEmpty()) return true; + if (icon.length() > MAX_ICON_LENGTH) return false; + + for (int i = 0; i < icon.length(); i++) { + char c = icon.charAt(i); + if (c < 0x20 || c == 0x7F || c == '<' || c == '>') return false; + } + + return true; + } + + private boolean containsControlChars(String text) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c < 0x20 || c == 0x7F) return true; + } + + return false; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/RequestUserPrefixesEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/RequestUserPrefixesEvent.java index b3169f70..2db6f2c3 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/RequestUserPrefixesEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/RequestUserPrefixesEvent.java @@ -4,6 +4,11 @@ import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer; public class RequestUserPrefixesEvent extends MessageHandler { + @Override + public int getRatelimit() { + return 500; + } + @Override public void handle() throws Exception { this.client.sendResponse(new UserPrefixesComposer(this.client.getHabbo())); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/SetActivePrefixEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/SetActivePrefixEvent.java index 16d88890..50a4791f 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/SetActivePrefixEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/SetActivePrefixEvent.java @@ -2,11 +2,16 @@ package com.eu.habbo.messages.incoming.inventory.prefixes; import com.eu.habbo.habbohotel.users.UserPrefix; import com.eu.habbo.messages.incoming.MessageHandler; -import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer; import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer; +import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer; public class SetActivePrefixEvent extends MessageHandler { + @Override + public int getRatelimit() { + return 1000; + } + @Override public void handle() throws Exception { int prefixId = this.packet.readInt(); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/SetDisplayOrderEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/SetDisplayOrderEvent.java index b1406b15..694cb4d7 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/SetDisplayOrderEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/inventory/prefixes/SetDisplayOrderEvent.java @@ -7,6 +7,11 @@ import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer; public class SetDisplayOrderEvent extends MessageHandler { + @Override + public int getRatelimit() { + return 1000; + } + @Override public void handle() throws Exception { Habbo habbo = this.client.getHabbo(); @@ -23,4 +28,4 @@ public class SetDisplayOrderEvent extends MessageHandler { habbo.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(habbo).compose()); } } -} +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java index 882365a4..6c0a331f 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java @@ -579,6 +579,7 @@ public class Outgoing { public static final int PrefixReceivedComposer = 7002; public static final int ActivePrefixUpdatedComposer = 7003; public static final int UserNickIconsComposer = 7004; + public static final int CustomPrefixPurchaseFailedComposer = 7005; public static final int AvailableCommandsComposer = 4050; // YouTube Room Broadcast diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/inventory/prefixes/CustomPrefixPurchaseFailedComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/inventory/prefixes/CustomPrefixPurchaseFailedComposer.java new file mode 100644 index 00000000..91f2f178 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/inventory/prefixes/CustomPrefixPurchaseFailedComposer.java @@ -0,0 +1,20 @@ +package com.eu.habbo.messages.outgoing.inventory.prefixes; + +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.messages.outgoing.Outgoing; + +public class CustomPrefixPurchaseFailedComposer extends MessageComposer { + private final String message; + + public CustomPrefixPurchaseFailedComposer(String message) { + this.message = message != null ? message : ""; + } + + @Override + protected ServerMessage composeInternal() { + this.response.init(Outgoing.CustomPrefixPurchaseFailedComposer); + this.response.appendString(this.message); + return this.response; + } +}