From 1a0d783ff767faea8270e8739ca718e30bfdbfe6 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 24 May 2026 10:53:38 +0200 Subject: [PATCH] feat(housekeeping): ban-user with arbitrary duration + ack composer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two new packets: * Incoming 9102 HousekeepingBanUserEvent — reads (userId, reason, hours). Unlike ModToolSanctionBanEvent which only accepts the four fixed Habbo-protocol banType buckets (18h / 7d / 30d / 100y), this one converts the hours arg straight to seconds and feeds them into ModToolManager.ban with ModToolBanType.ACCOUNT and cfhTopic=0. Duration is clamped to 100 years to keep it inside `int` range. * Outgoing 9201 HousekeepingActionResultComposer — generic ack for any HK action (ban / mute / kick / give-credits / room-close / …). Wire shape is (actionKey, ok, actionId, message). The actionKey lets the client filter multiple in-flight actions to the right Promise via `accept`, so concurrent admin operations don't cross-resolve. actionId here is the target user id because ModToolBan doesn't expose the `bans` autoinc id on the object — there's a TODO to swap this for a dedicated housekeeping_log row id once that table goes in. Same ACC_HOUSEKEEPING permission gate as the find-user packets, so operators only need to grant the permission once. `mvn compile` clean. --- .../com/eu/habbo/messages/PacketManager.java | 1 + .../eu/habbo/messages/incoming/Incoming.java | 1 + .../HousekeepingBanUserEvent.java | 61 +++++++++++++++++++ .../eu/habbo/messages/outgoing/Outgoing.java | 1 + .../HousekeepingActionResultComposer.java | 36 +++++++++++ 5 files changed, 100 insertions(+) create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingBanUserEvent.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingActionResultComposer.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java index 3edb7266..a81b7e31 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java @@ -720,5 +720,6 @@ public class PacketManager { // Housekeeping (in-client admin panel) this.registerHandler(Incoming.HousekeepingFindUserByNameEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingFindUserByNameEvent.class); this.registerHandler(Incoming.HousekeepingFindUserByIdEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingFindUserByIdEvent.class); + this.registerHandler(Incoming.HousekeepingBanUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingBanUserEvent.class); } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java index 07cb7617..f0e29f46 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java @@ -464,4 +464,5 @@ public class Incoming { // Housekeeping (in-client admin panel) — IDs 9100..9199 reserved public static final int HousekeepingFindUserByNameEvent = 9100; public static final int HousekeepingFindUserByIdEvent = 9101; + public static final int HousekeepingBanUserEvent = 9102; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingBanUserEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingBanUserEvent.java new file mode 100644 index 00000000..d8666f7b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingBanUserEvent.java @@ -0,0 +1,61 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.modtool.ModToolBan; +import com.eu.habbo.habbohotel.modtool.ModToolBanType; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +import java.util.List; + +/** + * Apply an arbitrary-duration account ban. Duration is taken in hours + * from the wire and converted to seconds for ModToolManager.ban — + * unlike ModToolSanctionBanEvent which only accepts the four fixed + * Habbo-protocol banType buckets. + */ +public class HousekeepingBanUserEvent extends MessageHandler { + private static final String ACTION_KEY = "user.ban"; + private static final int SECONDS_IN_HOUR = 3600; + // 100-year ceiling, matches ModToolSanctionBanEvent's permanent ban. + private static final int MAX_DURATION_SECONDS = 100 * 365 * 24 * 3600; + + @Override + public int getRatelimit() { + return 1000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int userId = this.packet.readInt(); + String reason = this.packet.readString(); + int hours = this.packet.readInt(); + + if (userId <= 0 || hours <= 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "invalid_input")); + return; + } + + long durationLong = (long) hours * SECONDS_IN_HOUR; + int duration = durationLong > MAX_DURATION_SECONDS ? MAX_DURATION_SECONDS : (int) durationLong; + + List bans = Emulator.getGameEnvironment().getModToolManager() + .ban(userId, this.client.getHabbo(), reason != null ? reason : "", duration, ModToolBanType.ACCOUNT, 0); + + if (bans == null || bans.isEmpty()) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "ban_failed")); + return; + } + + // ModToolBan doesn't expose the `bans` table autoinc id on the + // object, so we return the target user id as the actionId — it's + // the only stable handle the client can use until a dedicated + // housekeeping_log row id supersedes it. + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, "")); + } +} 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 902421c5..aee805e8 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 @@ -588,5 +588,6 @@ public class Outgoing { // Housekeeping (in-client admin panel) — IDs 9200..9299 reserved public static final int HousekeepingUserDetailComposer = 9200; + public static final int HousekeepingActionResultComposer = 9201; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingActionResultComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingActionResultComposer.java new file mode 100644 index 00000000..30fdfa9c --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingActionResultComposer.java @@ -0,0 +1,36 @@ +package com.eu.habbo.messages.outgoing.housekeeping; + +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.messages.outgoing.Outgoing; + +/** + * Generic ack for any housekeeping action (ban, mute, kick, give-credits, + * room-close, …). The client matches it back to the originating call via + * the `actionKey` field, which lets multiple in-flight actions share the + * same event stream without ordering bugs. + */ +public class HousekeepingActionResultComposer extends MessageComposer { + private final String actionKey; + private final boolean ok; + private final int actionId; + private final String message; + + public HousekeepingActionResultComposer(String actionKey, boolean ok, int actionId, String message) { + this.actionKey = actionKey != null ? actionKey : ""; + this.ok = ok; + this.actionId = actionId; + this.message = message != null ? message : ""; + } + + @Override + protected ServerMessage composeInternal() { + this.response.init(Outgoing.HousekeepingActionResultComposer); + this.response.appendString(this.actionKey); + this.response.appendBoolean(this.ok); + this.response.appendInt(this.actionId); + this.response.appendString(this.message); + + return this.response; + } +}