feat(housekeeping): mute-user + kick-user packets

Incoming 9104 HousekeepingMuteUserEvent — (userId, reason, minutes).
Unlike ModToolSanctionMute which takes a fixed-bucket minutes arg
from a CFH context, this one applies an arbitrary in-session mute via
Habbo.mute(seconds, false). Mute is online-only (the live Habbo object
holds the remaining seconds), so an offline target returns ok=false
with `user_offline`. The reason string, if non-empty, is delivered via
Habbo.alert so the muted user sees why.

Incoming 9105 HousekeepingKickUserEvent — (userId, reason). Replicates
the ModToolManager.kick body (leave room + alert) locally so HK doesn't
piggyback on ACC_SUPPORTTOOL the way ModToolManager.kick does — keeps
the permission model `acc_housekeeping`-only. Respects ACC_UNKICKABLE
the same way the legacy path does.

Both reuse HousekeepingActionResultComposer with their own actionKey
(user.mute / user.kick).

`mvn compile` clean.
This commit is contained in:
simoleo89
2026-05-24 11:00:01 +02:00
committed by simoleo89
parent 8419f11883
commit 418c753e6c
4 changed files with 119 additions and 0 deletions
@@ -722,5 +722,7 @@ public class PacketManager {
this.registerHandler(Incoming.HousekeepingFindUserByIdEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingFindUserByIdEvent.class);
this.registerHandler(Incoming.HousekeepingBanUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingBanUserEvent.class);
this.registerHandler(Incoming.HousekeepingUnbanUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingUnbanUserEvent.class);
this.registerHandler(Incoming.HousekeepingMuteUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingMuteUserEvent.class);
this.registerHandler(Incoming.HousekeepingKickUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingKickUserEvent.class);
}
}
@@ -466,4 +466,6 @@ public class Incoming {
public static final int HousekeepingFindUserByIdEvent = 9101;
public static final int HousekeepingBanUserEvent = 9102;
public static final int HousekeepingUnbanUserEvent = 9103;
public static final int HousekeepingMuteUserEvent = 9104;
public static final int HousekeepingKickUserEvent = 9105;
}
@@ -0,0 +1,60 @@
package com.eu.habbo.messages.incoming.housekeeping;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
/**
* Kick a user out of their current room. Mirrors ModToolManager.kick
* (leave room + alert), but the legacy method gates on ACC_SUPPORTTOOL,
* which would force HK operators to also hold the support-tool permission.
* Replicating the few lines locally keeps the HK module self-gated on
* ACC_HOUSEKEEPING.
*/
public class HousekeepingKickUserEvent extends MessageHandler {
private static final String ACTION_KEY = "user.kick";
@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();
if (userId <= 0) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "invalid_input"));
return;
}
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
if (target == null) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "user_offline"));
return;
}
if (target.hasPermission(Permission.ACC_UNKICKABLE)) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "target_unkickable"));
return;
}
if (target.getHabboInfo().getCurrentRoom() != null) {
Emulator.getGameEnvironment().getRoomManager().leaveRoom(target, target.getHabboInfo().getCurrentRoom());
}
if (reason != null && !reason.isEmpty()) {
target.alert(reason);
}
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
}
}
@@ -0,0 +1,55 @@
package com.eu.habbo.messages.incoming.housekeeping;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
/**
* Apply an arbitrary-duration in-room mute. Habbo.mute is a session-only
* mute (it stores remaining seconds on the live Habbo object), so the
* target must be online for the action to take effect — when the target
* isn't online the handler returns ok=false with `user_offline` so the
* UI can fall back to ModToolSanctionMute or surface a clear error.
*/
public class HousekeepingMuteUserEvent extends MessageHandler {
private static final String ACTION_KEY = "user.mute";
private static final int SECONDS_IN_MINUTE = 60;
@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 minutes = this.packet.readInt();
if (userId <= 0 || minutes <= 0) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "invalid_input"));
return;
}
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
if (target == null) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "user_offline"));
return;
}
target.mute(minutes * SECONDS_IN_MINUTE, false);
if (reason != null && !reason.isEmpty()) {
target.alert(reason);
}
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
}
}