Compare commits

...

11 Commits

Author SHA1 Message Date
github-actions[bot] 155b2202c7 🆙 Bump version to 4.2.30 [skip ci] 2026-06-03 07:48:08 +00:00
DuckieTM 10c291eb9f Merge pull request #144 from duckietm/dev
Dev
2026-06-03 09:47:03 +02:00
duckietm 349a8c727e 🆙 Update SQL 2026-06-03 09:46:43 +02:00
duckietm 68f2b71d14 🆙 Updated Prefixes : Now use wordfilter / table custom_prefix_blacklist can be droped 2026-06-03 09:42:43 +02:00
duckietm 69a6c0d060 🆙 Make group forums private, so only memeber can view it 2026-06-03 07:46:59 +02:00
github-actions[bot] 885bdca0c4 🆙 Bump version to 4.2.29 [skip ci] 2026-06-02 16:03:45 +00:00
DuckieTM db035294a7 Merge pull request #142 from duckietm/dev
🆙 Updated Group buy
2026-06-02 18:02:42 +02:00
duckietm 3216ba1df6 🆙 Updated Group buy 2026-06-02 18:02:25 +02:00
github-actions[bot] 8d6b969d75 🆙 Bump version to 4.2.28 [skip ci] 2026-06-02 14:06:26 +00:00
DuckieTM b9723e0298 Merge pull request #141 from duckietm/dev
🆙 Security Fix
2026-06-02 16:05:11 +02:00
duckietm c4aae676b2 🆙 Security Fix
Thanks to @Bop:

There's a group bug where you can accept anyone into a group within MS. There's no packet validation for accepting members if the group is invite only.
This is crucial because if you allow users to have rights who are group members, your rooms can be trashed. AKA YOUR EVENT ROOMS
2026-06-02 16:04:47 +02:00
19 changed files with 351 additions and 293 deletions
@@ -322,13 +322,6 @@ CREATE TABLE IF NOT EXISTS `custom_prefix_settings` (
PRIMARY KEY (`key_name`) PRIMARY KEY (`key_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `custom_prefix_blacklist` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`word` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_custom_prefix_blacklist_word` (`word`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO `custom_prefix_settings` (`key_name`, `value`) VALUES INSERT IGNORE INTO `custom_prefix_settings` (`key_name`, `value`) VALUES
('max_length', '15'), ('max_length', '15'),
('min_rank_to_buy', '1'), ('min_rank_to_buy', '1'),
@@ -63,15 +63,6 @@ CREATE TABLE IF NOT EXISTS `custom_prefix_settings` (
PRIMARY KEY (`key_name`) PRIMARY KEY (`key_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ------------------------------------------------------------
-- 5. Blacklist table
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `custom_prefix_blacklist` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`word` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_word` (`word`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================ -- ============================================================
-- Schema upgrades for existing installations -- Schema upgrades for existing installations
@@ -296,14 +287,6 @@ INSERT IGNORE INTO `custom_prefixes_catalog`
(2, 'Legend', 'Legend', '#8B5CF6', '', 'discord-neon', '', 15, 0, 1, 2), (2, 'Legend', 'Legend', '#8B5CF6', '', 'discord-neon', '', 15, 0, 1, 2),
(3, 'Staff Pick', 'Staff', '#3B82F6', '*', 'cartoon', '', 20, 0, 1, 3); (3, 'Staff Pick', 'Staff', '#3B82F6', '*', 'cartoon', '', 20, 0, 1, 3);
-- ============================================================
-- Example blacklist entries
-- ============================================================
INSERT IGNORE INTO `custom_prefix_blacklist` (`word`) VALUES
('admin'),
('staff'),
('mod'),
('owner');
-- ============================================================ -- ============================================================
-- Notes -- Notes
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.eu.habbo</groupId> <groupId>com.eu.habbo</groupId>
<artifactId>Habbo</artifactId> <artifactId>Habbo</artifactId>
<version>4.2.27</version> <version>4.2.30</version>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -301,7 +301,6 @@ public class CommandHandler {
addCommand(new GivePrefixCommand()); addCommand(new GivePrefixCommand());
addCommand(new ListPrefixesCommand()); addCommand(new ListPrefixesCommand());
addCommand(new RemovePrefixCommand()); addCommand(new RemovePrefixCommand());
addCommand(new PrefixBlacklistCommand());
addCommand(new WiredCommand()); addCommand(new WiredCommand());
addCommand(new TestCommand()); addCommand(new TestCommand());
} }
@@ -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;
}
}
@@ -252,6 +252,25 @@ public class Guild implements Runnable {
return this.readForum; return this.readForum;
} }
public boolean canHabboReadForum(int habboId, GuildMember member, boolean staff) {
if (staff || this.getOwnerId() == habboId) {
return true;
}
switch (this.readForum) {
case EVERYONE:
return true;
case MEMBERS:
return member != null && member.getRank().type <= GuildRank.MEMBER.type;
case ADMINS:
return member != null && member.getRank().type < GuildRank.MEMBER.type;
case OWNER:
return false;
default:
return true;
}
}
public void setReadForum(SettingsState readForum) { public void setReadForum(SettingsState readForum) {
this.readForum = readForum; this.readForum = readForum;
} }
@@ -25,46 +25,56 @@ public class GuildAcceptMembershipEvent extends MessageHandler {
int userId = this.packet.readInt(); int userId = this.packet.readInt();
Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId); Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId);
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
if (guild != null) { if (guild == null) {
GuildMember groupMember = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
if (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId()
|| this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)
|| (groupMember != null && (groupMember.getRank().equals(GuildRank.ADMIN) || groupMember.getRank().equals(GuildRank.OWNER)))) {
if (habbo != null) {
if (habbo.getHabboStats().hasGuild(guild.getId())) {
this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.ALREADY_ACCEPTED));
return; return;
} else { }
//Check the user has requested
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, habbo); GuildMember actorMember = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
if (member == null || member.getRank().type != GuildRank.REQUESTED.type) { boolean canAccept = guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId()
|| this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)
|| (actorMember != null && (actorMember.getRank().equals(GuildRank.ADMIN) || actorMember.getRank().equals(GuildRank.OWNER)));
if (!canAccept) {
return;
}
GuildMember targetMember = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId);
if (targetMember == null) {
this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.NO_LONGER_MEMBER)); this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.NO_LONGER_MEMBER));
return; return;
} else { }
if (targetMember.getRank().type != GuildRank.REQUESTED.type) {
this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.ALREADY_ACCEPTED));
return;
}
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
GuildAcceptedMembershipEvent event = new GuildAcceptedMembershipEvent(guild, userId, habbo); GuildAcceptedMembershipEvent event = new GuildAcceptedMembershipEvent(guild, userId, habbo);
Emulator.getPluginManager().fireEvent(event); Emulator.getPluginManager().fireEvent(event);
if (!event.isCancelled()) {
if (event.isCancelled()) {
return;
}
if (habbo != null) {
habbo.getHabboStats().addGuild(guild.getId()); habbo.getHabboStats().addGuild(guild.getId());
Emulator.getGameEnvironment().getGuildManager().joinGuild(guild, this.client, habbo.getHabboInfo().getId(), true); }
Emulator.getGameEnvironment().getGuildManager().joinGuild(guild, this.client, userId, true);
guild.decreaseRequestCount(); guild.decreaseRequestCount();
guild.increaseMemberCount(); guild.increaseMemberCount();
this.client.sendResponse(new GuildRefreshMembersListComposer(guild)); this.client.sendResponse(new GuildRefreshMembersListComposer(guild));
if (habbo != null) {
Room room = habbo.getHabboInfo().getCurrentRoom(); Room room = habbo.getHabboInfo().getCurrentRoom();
if (room != null) { if (room != null && room.getGuildId() == guildId) {
if (room.getGuildId() == guildId) {
habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId))); habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId)));
room.refreshRightsForHabbo(habbo); room.refreshRightsForHabbo(habbo);
} }
} }
} }
}
}
} else {
Emulator.getGameEnvironment().getGuildManager().joinGuild(guild, this.client, userId, true);
}
}
}
}
} }
@@ -29,6 +29,11 @@ public class GuildDeclineMembershipEvent extends MessageHandler {
if (guild != null) { if (guild != null) {
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo()); GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
if (userId == this.client.getHabbo().getHabboInfo().getId() || guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && (member.getRank().equals(GuildRank.ADMIN) || member.getRank().equals(GuildRank.OWNER))) || this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)) { if (userId == this.client.getHabbo().getHabboInfo().getId() || guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && (member.getRank().equals(GuildRank.ADMIN) || member.getRank().equals(GuildRank.OWNER))) || this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)) {
GuildMember target = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId);
if (target == null || target.getRank().type != GuildRank.REQUESTED.type) {
return;
}
guild.decreaseRequestCount(); guild.decreaseRequestCount();
Emulator.getGameEnvironment().getGuildManager().removeMember(guild, userId); Emulator.getGameEnvironment().getGuildManager().removeMember(guild, userId);
this.client.sendResponse(new GuildMembersComposer(guild, Emulator.getGameEnvironment().getGuildManager().getGuildMembers(guild, 0, 0, ""), this.client.getHabbo(), 0, 0, "", true, Emulator.getGameEnvironment().getGuildManager().getGuildMembersCount(guild, 0, 0, ""))); this.client.sendResponse(new GuildMembersComposer(guild, Emulator.getGameEnvironment().getGuildManager().getGuildMembers(guild, 0, 0, ""), this.client.getHabbo(), 0, 0, "", true, Emulator.getGameEnvironment().getGuildManager().getGuildMembersCount(guild, 0, 0, "")));
@@ -30,42 +30,40 @@ public class RequestGuildBuyEvent extends MessageHandler {
final String name = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo()); final String name = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
final String description = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo()); final String description = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
if(name.length() > 29){ if (name.length() == 0 || name.length() > 29) {
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.INVALID_GUILD_NAME)); this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.INVALID_GUILD_NAME));
return; return;
} }
if(description.length() > 254){ if (description.length() > 254) {
return; return;
} }
if (Emulator.getConfig().getBoolean("catalog.guild.hc_required", true) && !this.client.getHabbo().getHabboStats().hasActiveClub()) { if (Emulator.getConfig().getBoolean("catalog.guild.hc_required", true) && !this.client.getHabbo().getHabboStats().hasActiveClub()) {
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.HC_REQUIRED)); this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.HC_REQUIRED));
return; return;
} }
if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_CREDITS)) {
int guildPrice = Emulator.getConfig().getInt("catalog.guild.price");
if (this.client.getHabbo().getHabboInfo().getCredits() >= guildPrice) {
this.client.getHabbo().giveCredits(-guildPrice);
} else {
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
return;
}
}
int roomId = this.packet.readInt(); int roomId = this.packet.readInt();
Room r = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId); Room r = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
if (r != null) { if (r == null) {
if (r.hasGuild()) { this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
return;
}
if (r.hasGuild() || r.getGuildId() != 0) {
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.ROOM_ALREADY_IN_USE)); this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.ROOM_ALREADY_IN_USE));
return; return;
} }
if (r.getOwnerId() == this.client.getHabbo().getHabboInfo().getId()) { if (r.getOwnerId() != this.client.getHabbo().getHabboInfo().getId()) {
if (r.getGuildId() == 0) { String message = Emulator.getTexts().getValue("scripter.warning.guild.buy.owner").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%roomname%", r.getName().replace("%owner%", r.getOwnerName()));
ScripterManager.scripterDetected(this.client, message);
LOGGER.info(message);
return;
}
int colorOne = this.packet.readInt(); int colorOne = this.packet.readInt();
int colorTwo = this.packet.readInt(); int colorTwo = this.packet.readInt();
@@ -91,6 +89,20 @@ public class RequestGuildBuyEvent extends MessageHandler {
base += 3; base += 3;
} }
// Only charge the player once every step has been validated. Previously the
// credits were deducted before the room was checked, so a purchase that
// failed afterwards (missing room, room already used by a guild, not the
// owner) still took the credits without ever creating the group.
if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_CREDITS)) {
int guildPrice = Emulator.getConfig().getInt("catalog.guild.price");
if (this.client.getHabbo().getHabboInfo().getCredits() >= guildPrice) {
this.client.getHabbo().giveCredits(-guildPrice);
} else {
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
return;
}
}
Guild guild = Emulator.getGameEnvironment().getGuildManager().createGuild(this.client.getHabbo(), roomId, r.getName(), name, description, badge.toString(), colorOne, colorTwo); Guild guild = Emulator.getGameEnvironment().getGuildManager().createGuild(this.client.getHabbo(), roomId, r.getName(), name, description, badge.toString(), colorOne, colorTwo);
r.setGuild(guild.getId()); r.setGuild(guild.getId());
@@ -121,11 +133,4 @@ public class RequestGuildBuyEvent extends MessageHandler {
Emulator.getPluginManager().fireEvent(new GuildPurchasedEvent(guild, this.client.getHabbo())); Emulator.getPluginManager().fireEvent(new GuildPurchasedEvent(guild, this.client.getHabbo()));
} }
} else {
String message = Emulator.getTexts().getValue("scripter.warning.guild.buy.owner").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%roomname%", r.getName().replace("%owner%", r.getOwnerName()));
ScripterManager.scripterDetected(this.client, message);
LOGGER.info(message);
}
}
}
} }
@@ -2,7 +2,11 @@ package com.eu.habbo.messages.incoming.guilds.forums;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.guilds.Guild; import com.eu.habbo.habbohotel.guilds.Guild;
import com.eu.habbo.habbohotel.guilds.GuildMember;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer; import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
public class GuildForumDataEvent extends MessageHandler { public class GuildForumDataEvent extends MessageHandler {
@@ -20,6 +24,14 @@ public class GuildForumDataEvent extends MessageHandler {
if (guild == null) return; if (guild == null) return;
if (!guild.hasForum()) return; if (!guild.hasForum()) return;
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
boolean staff = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, staff)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
return;
}
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo())); this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
if (!Emulator.getGameEnvironment().getGuildManager().hasViewedForum(this.client.getHabbo().getHabboInfo().getId(), guildId)) { if (!Emulator.getGameEnvironment().getGuildManager().hasViewedForum(this.client.getHabbo().getHabboInfo().getId(), guildId)) {
@@ -2,7 +2,11 @@ package com.eu.habbo.messages.incoming.guilds.forums;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.guilds.Guild; import com.eu.habbo.habbohotel.guilds.Guild;
import com.eu.habbo.habbohotel.guilds.GuildMember;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer; import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadsComposer; import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadsComposer;
import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer; import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer;
@@ -24,6 +28,13 @@ public class GuildForumThreadsEvent extends MessageHandler {
this.client.sendResponse(new ConnectionErrorComposer(404)); this.client.sendResponse(new ConnectionErrorComposer(404));
return; return;
} }
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
boolean staff = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, staff)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
return;
}
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo())); this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
this.client.sendResponse(new GuildForumThreadsComposer(guild, index)); this.client.sendResponse(new GuildForumThreadsComposer(guild, index));
@@ -38,7 +38,6 @@ public class GuildForumThreadsMessagesEvent extends MessageHandler {
return; return;
} }
// Verify thread belongs to the requested guild
if (thread.getGuildId() != guildId) { if (thread.getGuildId() != guildId) {
this.client.sendResponse(new ConnectionErrorComposer(403)); this.client.sendResponse(new ConnectionErrorComposer(403));
return; return;
@@ -47,6 +46,11 @@ public class GuildForumThreadsMessagesEvent extends MessageHandler {
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId()); GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && member.getRank().equals(GuildRank.ADMIN))); boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && member.getRank().equals(GuildRank.ADMIN)));
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, hasStaffPermissions)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
return;
}
if (thread.getState() != ForumThreadState.HIDDEN_BY_GUILD_ADMIN || hasStaffPermissions || isGuildAdministrator) { if (thread.getState() != ForumThreadState.HIDDEN_BY_GUILD_ADMIN || hasStaffPermissions || isGuildAdministrator) {
this.client.sendResponse(new GuildForumCommentsComposer(guildId, threadId, index, thread.getComments(limit, index))); this.client.sendResponse(new GuildForumCommentsComposer(guildId, threadId, index, thread.getComments(limit, index)));
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo())); this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
@@ -6,6 +6,11 @@ import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer; import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
public class DeletePrefixEvent extends MessageHandler { public class DeletePrefixEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 500;
}
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
int prefixId = this.packet.readInt(); int prefixId = this.packet.readInt();
@@ -1,15 +1,20 @@
package com.eu.habbo.messages.incoming.inventory.prefixes; package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.Emulator; 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.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix; import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler; 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.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.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.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.UserCreditsComposer;
import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer; import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -17,10 +22,18 @@ import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
public class PurchasePrefixEvent extends MessageHandler { public class PurchasePrefixEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchasePrefixEvent.class); private static final Logger LOGGER = LoggerFactory.getLogger(PurchasePrefixEvent.class);
private static final String[] ALLOWED_FONTS = { "", "pixel", "cherry", "vampiro" }; 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 @Override
public int getRatelimit() { public int getRatelimit() {
@@ -39,81 +52,101 @@ public class PurchasePrefixEvent extends MessageHandler {
if (habbo == null) return; if (habbo == null) return;
// Load settings Map<String, Integer> settings = loadSettings();
int maxLength = getSettingInt("max_length", 15); int maxLength = setting(settings, "max_length", 15);
int minRank = getSettingInt("min_rank_to_buy", 1); int minRank = setting(settings, "min_rank_to_buy", 1);
int priceCredits = getSettingInt("price_credits", 5); int priceCredits = setting(settings, "price_credits", 5);
int pricePoints = getSettingInt("price_points", 0); int pricePoints = setting(settings, "price_points", 0);
int pointsType = getSettingInt("points_type", 0); int pointsType = setting(settings, "points_type", 0);
int fontPriceCredits = getSettingInt("font_price_credits", 10); int fontPriceCredits = setting(settings, "font_price_credits", 10);
int fontPricePoints = getSettingInt("font_price_points", 0); int fontPricePoints = setting(settings, "font_price_points", 0);
int fontPointsType = getSettingInt("font_points_type", pointsType); int fontPointsType = setting(settings, "font_points_type", pointsType);
int maxPrefixes = setting(settings, "max_prefixes", 60);
if (maxPrefixes > 0 && habbo.getInventory().getPrefixesComponent().getPrefixes().size() >= maxPrefixes) {
this.fail(habbo, "You already own the maximum number of prefixes (" + maxPrefixes + ").");
return;
}
// Validate text
text = text.trim(); text = text.trim();
if (text.isEmpty() || text.length() > maxLength) { 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).")); 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; return;
} }
// Validate color (single hex or comma-separated multi hex for per-letter colors)
String[] colorParts = color.split(","); String[] colorParts = color.split(",");
if (colorParts.length > text.length()) {
this.fail(habbo, "Invalid color format.");
return;
}
for (String part : colorParts) { for (String part : colorParts) {
if (!part.matches("^#[0-9A-Fa-f]{6}$")) { 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; return;
} }
} }
// Check rank
if (habbo.getHabboInfo().getRank().getId() < minRank) { if (habbo.getHabboInfo().getRank().getId() < minRank) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Your rank is too low to purchase prefixes.")); this.fail(habbo, "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."));
return; return;
} }
if (icon == null) icon = ""; if (icon == null) icon = "";
icon = icon.trim(); icon = icon.trim();
if (!isValidIcon(icon)) {
this.fail(habbo, "Invalid prefix icon.");
return;
}
if (effect == null) effect = ""; 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 = ""; if (font == null) font = "";
font = font.trim().toLowerCase(); font = font.trim().toLowerCase();
if (!isAllowedFont(font)) { if (!isAllowedFont(font)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Invalid font format.")); this.fail(habbo, "Invalid font format.");
return; return;
} }
int totalPriceCredits = priceCredits + (!font.isEmpty() ? fontPriceCredits : 0); int totalPriceCredits = priceCredits + (!font.isEmpty() ? fontPriceCredits : 0);
// Check credits
if (totalPriceCredits > 0 && habbo.getHabboInfo().getCredits() < totalPriceCredits) { 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; return;
} }
int totalPricePointsSameType = pricePoints + ((fontPricePoints > 0 && fontPointsType == pointsType && !font.isEmpty()) ? fontPricePoints : 0); int totalPricePointsSameType = pricePoints + ((fontPricePoints > 0 && fontPointsType == pointsType && !font.isEmpty()) ? fontPricePoints : 0);
// Check points
if (totalPricePointsSameType > 0 && habbo.getHabboInfo().getCurrencyAmount(pointsType) < totalPricePointsSameType) { 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; return;
} }
if (!font.isEmpty() && fontPricePoints > 0 && fontPointsType != pointsType && habbo.getHabboInfo().getCurrencyAmount(fontPointsType) < fontPricePoints) { 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; return;
} }
// Deduct currency
if (totalPriceCredits > 0) { if (totalPriceCredits > 0) {
habbo.getHabboInfo().addCredits(-totalPriceCredits); habbo.getHabboInfo().addCredits(-totalPriceCredits);
this.client.sendResponse(new UserCreditsComposer(habbo)); this.client.sendResponse(new UserCreditsComposer(habbo));
@@ -129,47 +162,57 @@ public class PurchasePrefixEvent extends MessageHandler {
this.client.sendResponse(new UserCurrencyComposer(habbo)); this.client.sendResponse(new UserCurrencyComposer(habbo));
} }
// Create prefix
int storedPoints = totalPricePointsSameType; int storedPoints = totalPricePointsSameType;
int storedPointsType = (storedPoints > 0) ? pointsType : ((!font.isEmpty() && fontPricePoints > 0) ? fontPointsType : pointsType); 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); 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 prefix.run(); // Insert into DB synchronously to get the ID
habbo.getInventory().getPrefixesComponent().addPrefix(prefix); habbo.getInventory().getPrefixesComponent().addPrefix(prefix);
habbo.getInventory().getPrefixesComponent().setActive(prefix.getId());
this.client.sendResponse(new PrefixReceivedComposer(prefix)); this.client.sendResponse(new PrefixReceivedComposer(prefix));
this.client.sendResponse(new ActivePrefixUpdatedComposer(prefix));
this.client.sendResponse(new UserNickIconsComposer(habbo)); this.client.sendResponse(new UserNickIconsComposer(habbo));
if (habbo.getHabboInfo().getCurrentRoom() != null) {
habbo.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(habbo).compose());
}
} }
private int getSettingInt(String key, int defaultValue) { private void fail(Habbo habbo, String message) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, message));
PreparedStatement statement = connection.prepareStatement("SELECT `value` FROM custom_prefix_settings WHERE key_name = ?")) { this.client.sendResponse(new CustomPrefixPurchaseFailedComposer(message));
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);
}
return defaultValue;
} }
private boolean isBlacklisted(String text) { private Map<String, Integer> loadSettings() {
String lowerText = text.toLowerCase(); Map<String, Integer> settings = new HashMap<>();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist")) { PreparedStatement statement = connection.prepareStatement("SELECT key_name, `value` FROM custom_prefix_settings");
try (ResultSet set = statement.executeQuery()) { ResultSet set = statement.executeQuery()) {
while (set.next()) { while (set.next()) {
if (lowerText.contains(set.getString("word").toLowerCase())) { try {
settings.put(set.getString("key_name"), Integer.parseInt(set.getString("value")));
} catch (NumberFormatException ignored) {}
}
} catch (SQLException e) {
LOGGER.error("Error reading prefix settings", e);
}
return settings;
}
private int setting(Map<String, Integer> 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 true;
} }
} }
}
} catch (SQLException e) {
LOGGER.error("Error checking prefix blacklist", e);
}
return false; return false;
} }
@@ -182,4 +225,35 @@ public class PurchasePrefixEvent extends MessageHandler {
return false; 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;
}
} }
@@ -4,6 +4,11 @@ import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer; import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
public class RequestUserPrefixesEvent extends MessageHandler { public class RequestUserPrefixesEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 500;
}
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
this.client.sendResponse(new UserPrefixesComposer(this.client.getHabbo())); this.client.sendResponse(new UserPrefixesComposer(this.client.getHabbo()));
@@ -2,11 +2,16 @@ package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.habbohotel.users.UserPrefix; import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler; 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.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
public class SetActivePrefixEvent extends MessageHandler { public class SetActivePrefixEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 1000;
}
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
int prefixId = this.packet.readInt(); int prefixId = this.packet.readInt();
@@ -7,6 +7,11 @@ import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
public class SetDisplayOrderEvent extends MessageHandler { public class SetDisplayOrderEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 1000;
}
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
Habbo habbo = this.client.getHabbo(); Habbo habbo = this.client.getHabbo();
@@ -579,6 +579,7 @@ public class Outgoing {
public static final int PrefixReceivedComposer = 7002; public static final int PrefixReceivedComposer = 7002;
public static final int ActivePrefixUpdatedComposer = 7003; public static final int ActivePrefixUpdatedComposer = 7003;
public static final int UserNickIconsComposer = 7004; public static final int UserNickIconsComposer = 7004;
public static final int CustomPrefixPurchaseFailedComposer = 7005;
public static final int AvailableCommandsComposer = 4050; public static final int AvailableCommandsComposer = 4050;
// YouTube Room Broadcast // YouTube Room Broadcast
@@ -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;
}
}