You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
@@ -8,6 +8,7 @@ public class Permission {
|
||||
public static String ACC_SEE_WHISPERS = "acc_see_whispers";
|
||||
public static String ACC_SEE_TENTCHAT = "acc_see_tentchat";
|
||||
public static String ACC_SUPERWIRED = "acc_superwired";
|
||||
public static String ACC_HOUSEKEEPING = "acc_housekeeping";
|
||||
public static String ACC_SUPPORTTOOL = "acc_supporttool";
|
||||
public static String ACC_UNKICKABLE = "acc_unkickable";
|
||||
public static String ACC_GUILDGATE = "acc_guildgate";
|
||||
|
||||
@@ -719,5 +719,31 @@ public class PacketManager {
|
||||
this.registerHandler(Incoming.YouTubeRoomPlayEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomPlayEvent.class);
|
||||
this.registerHandler(Incoming.YouTubeRoomWatchingEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomWatchingEvent.class);
|
||||
this.registerHandler(Incoming.YouTubeRoomSettingsEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomSettingsEvent.class);
|
||||
|
||||
// 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);
|
||||
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);
|
||||
this.registerHandler(Incoming.HousekeepingForceDisconnectUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingForceDisconnectUserEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingSetUserRankEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSetUserRankEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingTradeLockUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingTradeLockUserEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingResetUserPasswordEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingResetUserPasswordEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingFindRoomByIdEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingFindRoomByIdEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingSearchRoomsEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSearchRoomsEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingRoomStateEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingRoomStateEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingMuteRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingMuteRoomEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingKickAllFromRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingKickAllFromRoomEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingTransferRoomOwnershipEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingTransferRoomOwnershipEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingDeleteRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingDeleteRoomEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingGiveCreditsEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGiveCreditsEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingGiveCurrencyEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGiveCurrencyEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingGrantItemEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGrantItemEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingSetHcSubscriptionEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSetHcSubscriptionEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingSendHotelAlertEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSendHotelAlertEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingGetDashboardEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGetDashboardEvent.class);
|
||||
this.registerHandler(Incoming.HousekeepingListActionLogEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingListActionLogEvent.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,4 +460,30 @@ public class Incoming {
|
||||
public static final int YouTubeRoomPlayEvent = 8001;
|
||||
public static final int YouTubeRoomWatchingEvent = 8002;
|
||||
public static final int YouTubeRoomSettingsEvent = 8003;
|
||||
|
||||
// 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;
|
||||
public static final int HousekeepingUnbanUserEvent = 9103;
|
||||
public static final int HousekeepingMuteUserEvent = 9104;
|
||||
public static final int HousekeepingKickUserEvent = 9105;
|
||||
public static final int HousekeepingForceDisconnectUserEvent = 9106;
|
||||
public static final int HousekeepingSetUserRankEvent = 9107;
|
||||
public static final int HousekeepingTradeLockUserEvent = 9108;
|
||||
public static final int HousekeepingResetUserPasswordEvent = 9109;
|
||||
public static final int HousekeepingFindRoomByIdEvent = 9110;
|
||||
public static final int HousekeepingSearchRoomsEvent = 9111;
|
||||
public static final int HousekeepingRoomStateEvent = 9112;
|
||||
public static final int HousekeepingMuteRoomEvent = 9113;
|
||||
public static final int HousekeepingKickAllFromRoomEvent = 9114;
|
||||
public static final int HousekeepingTransferRoomOwnershipEvent = 9115;
|
||||
public static final int HousekeepingDeleteRoomEvent = 9116;
|
||||
public static final int HousekeepingGiveCreditsEvent = 9117;
|
||||
public static final int HousekeepingGiveCurrencyEvent = 9118;
|
||||
public static final int HousekeepingGrantItemEvent = 9119;
|
||||
public static final int HousekeepingSetHcSubscriptionEvent = 9120;
|
||||
public static final int HousekeepingSendHotelAlertEvent = 9121;
|
||||
public static final int HousekeepingGetDashboardEvent = 9122;
|
||||
public static final int HousekeepingListActionLogEvent = 9123;
|
||||
}
|
||||
|
||||
+61
@@ -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, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
long durationLong = (long) hours * SECONDS_IN_HOUR;
|
||||
int duration = durationLong > MAX_DURATION_SECONDS ? MAX_DURATION_SECONDS : (int) durationLong;
|
||||
|
||||
List<ModToolBan> 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, "housekeeping.error.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, ""));
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Permanently delete a room. Mirrors the minimum-viable subset of
|
||||
* RequestDeleteRoomEvent: eject all users from the live room, dispose
|
||||
* + uncache, then DELETE FROM rooms. Pets/guild/custom-layout cleanup
|
||||
* is intentionally skipped on this slice — leftover rows in those
|
||||
* tables become orphans but don't crash the emulator; a follow-up
|
||||
* pass can cascade once we have a HK audit-log row to attach the
|
||||
* orphan-cleanup to.
|
||||
*/
|
||||
public class HousekeepingDeleteRoomEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "room.delete";
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int roomId = this.packet.readInt();
|
||||
|
||||
if (roomId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false);
|
||||
|
||||
if (room != null) {
|
||||
room.ejectAll();
|
||||
room.preventUnloading = false;
|
||||
room.dispose();
|
||||
Emulator.getGameEnvironment().getRoomManager().uncacheRoom(room);
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM rooms WHERE id = ? LIMIT 1")) {
|
||||
statement.setInt(1, roomId);
|
||||
int rows = statement.executeUpdate();
|
||||
|
||||
if (rows == 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found"));
|
||||
return;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, ""));
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingRoomDetailComposer;
|
||||
|
||||
public class HousekeepingFindRoomByIdEvent extends MessageHandler {
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int roomId = this.packet.readInt();
|
||||
|
||||
if (roomId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingRoomDetailComposer(null));
|
||||
return;
|
||||
}
|
||||
|
||||
// loadRoom covers both the in-memory cache (already-loaded rooms)
|
||||
// and the offline path (SELECT * FROM rooms WHERE id=?). Pass
|
||||
// false for loadData so we don't pull furni/bots/pets just to
|
||||
// render an HK panel summary — getOwnerName / getUserCount work
|
||||
// on the pre-loaded skeleton.
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false);
|
||||
|
||||
this.client.sendResponse(new HousekeepingRoomDetailComposer(room));
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
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.HabboInfo;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingUserDetailComposer;
|
||||
|
||||
public class HousekeepingFindUserByIdEvent extends MessageHandler {
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int userId = this.packet.readInt();
|
||||
|
||||
if (userId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingUserDetailComposer(null));
|
||||
return;
|
||||
}
|
||||
|
||||
// HabboManager.getHabboInfo(int) returns the in-memory record for
|
||||
// online users and falls through to the offline SQL lookup
|
||||
// otherwise, so a single call covers both branches.
|
||||
HabboInfo info = Emulator.getGameEnvironment().getHabboManager().getHabboInfo(userId);
|
||||
|
||||
this.client.sendResponse(new HousekeepingUserDetailComposer(info));
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
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.habbohotel.users.HabboInfo;
|
||||
import com.eu.habbo.habbohotel.users.HabboManager;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingUserDetailComposer;
|
||||
|
||||
public class HousekeepingFindUserByNameEvent extends MessageHandler {
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String username = this.packet.readString();
|
||||
|
||||
if (username == null || username.isEmpty()) {
|
||||
this.client.sendResponse(new HousekeepingUserDetailComposer(null));
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(username);
|
||||
HabboInfo info = online != null ? online.getHabboInfo() : HabboManager.getOfflineHabboInfo(username);
|
||||
|
||||
this.client.sendResponse(new HousekeepingUserDetailComposer(info));
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Force-close the session of an online user. Unlike kick (which only
|
||||
* removes them from the current room), this drops their socket. Equivalent
|
||||
* to /disconnect in command form but issued through the HK panel.
|
||||
*/
|
||||
public class HousekeepingForceDisconnectUserEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "user.disconnect";
|
||||
|
||||
@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, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (target == null) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_offline"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason != null && !reason.isEmpty()) {
|
||||
target.alert(reason);
|
||||
}
|
||||
|
||||
// ACK first so the action result lands before the target's socket
|
||||
// closes (otherwise an alerted user on the same emulator thread may
|
||||
// already be torn down when we try to write).
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||
|
||||
target.disconnect();
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingDashboardComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class HousekeepingGetDashboardEvent extends MessageHandler {
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int onlineUsers = Emulator.getGameEnvironment().getHabboManager().getOnlineHabbos().size();
|
||||
int activeRooms = 0;
|
||||
int totalUsers = 0;
|
||||
int totalRooms = 0;
|
||||
int pendingTickets = 0;
|
||||
int sanctionsLast24h = 0;
|
||||
int now = Emulator.getIntUnixTimestamp();
|
||||
|
||||
// activeRooms = loaded rooms with at least one user
|
||||
try {
|
||||
for (var room : Emulator.getGameEnvironment().getRoomManager().getActiveRooms()) {
|
||||
if (room != null && room.getUserCount() > 0) activeRooms++;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// fall through with 0
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM users");
|
||||
ResultSet rs = statement.executeQuery()) {
|
||||
if (rs.next()) totalUsers = rs.getInt(1);
|
||||
}
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM rooms");
|
||||
ResultSet rs = statement.executeQuery()) {
|
||||
if (rs.next()) totalRooms = rs.getInt(1);
|
||||
}
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM support_tickets WHERE state = 0");
|
||||
ResultSet rs = statement.executeQuery()) {
|
||||
if (rs.next()) pendingTickets = rs.getInt(1);
|
||||
}
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM bans WHERE timestamp > ?")) {
|
||||
statement.setInt(1, now - (24 * 3600));
|
||||
try (ResultSet rs = statement.executeQuery()) {
|
||||
if (rs.next()) sanctionsLast24h = rs.getInt(1);
|
||||
}
|
||||
}
|
||||
} catch (SQLException ignored) {
|
||||
// Surface 0s rather than failing the whole dashboard on a missing
|
||||
// optional table — the HK panel can render partial data.
|
||||
}
|
||||
|
||||
int uptime = (int) ((System.currentTimeMillis() - HOUSEKEEPING_BOOT_MILLIS) / 1000);
|
||||
String version = "Arcturus-Morningstar-Extended";
|
||||
|
||||
this.client.sendResponse(new HousekeepingDashboardComposer(
|
||||
onlineUsers,
|
||||
totalUsers,
|
||||
activeRooms,
|
||||
totalRooms,
|
||||
onlineUsers, // peakOnlineToday — not tracked, use current as best-effort
|
||||
onlineUsers, // peakOnlineAllTime — same
|
||||
pendingTickets,
|
||||
sanctionsLast24h,
|
||||
Math.max(uptime, 0),
|
||||
version
|
||||
));
|
||||
}
|
||||
|
||||
// Approximate uptime — captured at class-load time rather than emu startup
|
||||
// (Emulator.java doesn't expose a public startup timestamp). For HK panel
|
||||
// headline metrics this is close enough; if tighter accuracy is needed
|
||||
// later, plumb Emulator.startup through and read it here.
|
||||
private static final long HOUSEKEEPING_BOOT_MILLIS = System.currentTimeMillis();
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
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;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class HousekeepingGiveCreditsEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "user.give_credits";
|
||||
|
||||
@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();
|
||||
int amount = this.packet.readInt();
|
||||
|
||||
if (userId <= 0 || amount == 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (online != null) {
|
||||
// giveCredits already pushes UserCreditsComposer and persists via the
|
||||
// standard HabboInfo write path; nothing extra needed for the online branch.
|
||||
online.giveCredits(amount);
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("UPDATE users SET credits = credits + ? WHERE id = ? LIMIT 1")) {
|
||||
statement.setInt(1, amount);
|
||||
statement.setInt(2, userId);
|
||||
int rows = statement.executeUpdate();
|
||||
|
||||
if (rows == 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found"));
|
||||
return;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
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;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Generic non-credits currency grant. Wire field `currencyType`:
|
||||
* 0 => duckets / pixels, 5 => diamonds, 101 => seasonal-primary.
|
||||
* Online users go through Habbo.givePoints / givePixels which dispatches
|
||||
* a UserCurrencyComposer; offline goes straight to `users_currency`.
|
||||
*/
|
||||
public class HousekeepingGiveCurrencyEvent extends MessageHandler {
|
||||
private static final int CURRENCY_DUCKETS = 0;
|
||||
|
||||
@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();
|
||||
int currencyType = this.packet.readInt();
|
||||
int amount = this.packet.readInt();
|
||||
|
||||
String actionKey = "user.give_currency_" + currencyType;
|
||||
|
||||
if (userId <= 0 || amount == 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (online != null) {
|
||||
// givePixels writes users_currency type=0 and ships UserCurrency;
|
||||
// givePoints(type, amount) is the generalised path for everything else.
|
||||
if (currencyType == CURRENCY_DUCKETS) {
|
||||
online.givePixels(amount);
|
||||
} else {
|
||||
online.givePoints(currencyType, amount);
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, true, userId, ""));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"INSERT INTO users_currency (user_id, type, amount) VALUES (?, ?, ?) " +
|
||||
"ON DUPLICATE KEY UPDATE amount = amount + VALUES(amount)")) {
|
||||
statement.setInt(1, userId);
|
||||
statement.setInt(2, currencyType);
|
||||
statement.setInt(3, amount);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.db_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, true, userId, ""));
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Grant a furni item (by items_base id) `quantity` times. Each row in
|
||||
* the `items` table is one furni unit; quantity > 1 just batches the
|
||||
* insert. The online user's HabboInventory isn't proactively refreshed
|
||||
* — they'll see the new items next time they open the hand inventory
|
||||
* (or after a relog).
|
||||
*/
|
||||
public class HousekeepingGrantItemEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "user.grant_item";
|
||||
private static final int MAX_QUANTITY_PER_CALL = 100;
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int userId = this.packet.readInt();
|
||||
int itemId = this.packet.readInt();
|
||||
int quantity = this.packet.readInt();
|
||||
|
||||
if (userId <= 0 || itemId <= 0 || quantity <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (quantity > MAX_QUANTITY_PER_CALL) {
|
||||
quantity = MAX_QUANTITY_PER_CALL;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO items (user_id, item_id, extra_data) VALUES (?, ?, '')")) {
|
||||
for (int i = 0; i < quantity; i++) {
|
||||
statement.setInt(1, userId);
|
||||
statement.setInt(2, itemId);
|
||||
statement.addBatch();
|
||||
}
|
||||
statement.executeBatch();
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.economy_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
|
||||
public class HousekeepingKickAllFromRoomEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "room.kick_all";
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int roomId = this.packet.readInt();
|
||||
|
||||
if (roomId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false);
|
||||
|
||||
if (room == null) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found"));
|
||||
return;
|
||||
}
|
||||
|
||||
room.ejectAll();
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, ""));
|
||||
}
|
||||
}
|
||||
+60
@@ -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, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (target == null) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_offline"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.hasPermission(Permission.ACC_UNKICKABLE)) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.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, ""));
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionLogComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Read the housekeeping_log audit table. The table isn't part of the
|
||||
* base FullDatabase.sql yet — operators who want audit have to create
|
||||
* it once:
|
||||
*
|
||||
* CREATE TABLE IF NOT EXISTS `housekeeping_log` (
|
||||
* `id` INT NOT NULL AUTO_INCREMENT,
|
||||
* `timestamp` INT NOT NULL,
|
||||
* `actor_id` INT NOT NULL,
|
||||
* `actor_name` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
* `target_type` VARCHAR(16) NOT NULL DEFAULT 'user',
|
||||
* `target_id` INT NOT NULL DEFAULT 0,
|
||||
* `target_label` VARCHAR(128) NOT NULL DEFAULT '',
|
||||
* `action` VARCHAR(64) NOT NULL DEFAULT '',
|
||||
* `detail` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
* `success` TINYINT NOT NULL DEFAULT 1,
|
||||
* PRIMARY KEY (`id`), KEY `timestamp` (`timestamp`)
|
||||
* ) ENGINE=InnoDB;
|
||||
*
|
||||
* If the table is missing we swallow the SQL error and return an empty
|
||||
* list — the panel just shows "no audit entries" instead of breaking.
|
||||
* Writing into the table is a follow-up: each HK handler will append
|
||||
* a row once the table exists; for now the listing is read-only.
|
||||
*/
|
||||
public class HousekeepingListActionLogEvent extends MessageHandler {
|
||||
private static final int HARD_LIMIT = 500;
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int limit = Math.min(Math.max(this.packet.readInt(), 1), HARD_LIMIT);
|
||||
|
||||
List<HousekeepingActionLogComposer.Row> rows = new ArrayList<>();
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT id, timestamp, actor_id, actor_name, target_type, target_id, target_label, action, detail, success " +
|
||||
"FROM housekeeping_log ORDER BY id DESC LIMIT ?")) {
|
||||
statement.setInt(1, limit);
|
||||
|
||||
try (ResultSet rs = statement.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
rows.add(new HousekeepingActionLogComposer.Row(
|
||||
rs.getInt("id"),
|
||||
rs.getInt("timestamp"),
|
||||
rs.getInt("actor_id"),
|
||||
rs.getString("actor_name"),
|
||||
rs.getString("target_type"),
|
||||
rs.getInt("target_id"),
|
||||
rs.getString("target_label"),
|
||||
rs.getString("action"),
|
||||
rs.getString("detail"),
|
||||
rs.getInt("success") == 1
|
||||
));
|
||||
}
|
||||
}
|
||||
} catch (SQLException ignored) {
|
||||
// table absent — return empty list, log via emu logger left to the panel UI
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionLogComposer(rows));
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
|
||||
/**
|
||||
* Toggle room-wide mute. Habbo's Room.setMuted is boolean, not duration-
|
||||
* scoped, so the wire `minutes` arg picks the semantic: minutes==0 =>
|
||||
* unmute, minutes>0 => mute. An emulator-side scheduled unmute could
|
||||
* use the value as a timer, but for now the mute stays until the
|
||||
* operator unmutes manually — the minutes is reserved as a forward-
|
||||
* compat field on the wire.
|
||||
*/
|
||||
public class HousekeepingMuteRoomEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "room.mute";
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int roomId = this.packet.readInt();
|
||||
int minutes = this.packet.readInt();
|
||||
|
||||
if (roomId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false);
|
||||
|
||||
if (room == null) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found"));
|
||||
return;
|
||||
}
|
||||
|
||||
room.setMuted(minutes > 0);
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, ""));
|
||||
}
|
||||
}
|
||||
+55
@@ -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, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (target == null) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.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, ""));
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Reset a user's password to a fresh random 12-character alphanumeric
|
||||
* string. Persists a BCrypt `$2a$` hash of the new password into
|
||||
* `users.password` (matches what `AuthHttpUtil.checkPassword` /
|
||||
* `SessionEndpoints` / `AccountChangeEndpoints` already write and read),
|
||||
* clears `auth_ticket` so any active session can't be re-used to bypass
|
||||
* the reset, and ships the PLAINTEXT new password back to the operator
|
||||
* in the action-result `message` so they can communicate it out-of-band.
|
||||
*/
|
||||
public class HousekeepingResetUserPasswordEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "user.reset_password";
|
||||
private static final String PASSWORD_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
|
||||
private static final int PASSWORD_LENGTH = 12;
|
||||
private static final int BCRYPT_COST = 10;
|
||||
|
||||
private static final SecureRandom RNG = new SecureRandom();
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int userId = this.packet.readInt();
|
||||
|
||||
if (userId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
String plain = randomPassword();
|
||||
String hash;
|
||||
|
||||
try {
|
||||
hash = BCrypt.hashpw(plain, BCrypt.gensalt(BCRYPT_COST));
|
||||
} catch (IllegalArgumentException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.hash_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("UPDATE users SET password = ?, auth_ticket = '' WHERE id = ? LIMIT 1")) {
|
||||
statement.setString(1, hash);
|
||||
statement.setInt(2, userId);
|
||||
int rows = statement.executeUpdate();
|
||||
|
||||
if (rows == 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found"));
|
||||
return;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Plaintext flows through `message` — the client surfaces it via the
|
||||
// status banner so the operator can read it once. SSL is on the
|
||||
// operator: the only secure transport for the WS is wss://.
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, plain));
|
||||
}
|
||||
|
||||
private static String randomPassword() {
|
||||
StringBuilder sb = new StringBuilder(PASSWORD_LENGTH);
|
||||
|
||||
for (int i = 0; i < PASSWORD_LENGTH; i++) {
|
||||
sb.append(PASSWORD_ALPHABET.charAt(RNG.nextInt(PASSWORD_ALPHABET.length())));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomState;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
|
||||
/**
|
||||
* Toggle the room state between OPEN (open) and LOCKED (closed). The
|
||||
* client picks which transition it wants via the boolean — true => OPEN,
|
||||
* false => LOCKED. Persists state through `Room.save()` so the change
|
||||
* outlives an unload.
|
||||
*/
|
||||
public class HousekeepingRoomStateEvent extends MessageHandler {
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int roomId = this.packet.readInt();
|
||||
boolean open = this.packet.readBoolean();
|
||||
String actionKey = open ? "room.open" : "room.close";
|
||||
|
||||
if (roomId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false);
|
||||
|
||||
if (room == null) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.room_not_found"));
|
||||
return;
|
||||
}
|
||||
|
||||
room.setState(open ? RoomState.OPEN : RoomState.LOCKED);
|
||||
room.save();
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, true, roomId, ""));
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingRoomListComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Search rooms by name. `exactMatch=true` => `name = ?` (used by the
|
||||
* findByName autocomplete that wants a unique hit). `exactMatch=false`
|
||||
* => `name LIKE concat(?, '%')` (used by the prefix dropdown).
|
||||
*
|
||||
* Both branches go through the same packet because the wire shape is
|
||||
* identical — the client picks which mode it wants by toggling the
|
||||
* boolean.
|
||||
*/
|
||||
public class HousekeepingSearchRoomsEvent extends MessageHandler {
|
||||
private static final int HARD_LIMIT = 50;
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String query = this.packet.readString();
|
||||
boolean exactMatch = this.packet.readBoolean();
|
||||
int limit = Math.min(Math.max(this.packet.readInt(), 1), HARD_LIMIT);
|
||||
|
||||
if (query == null) query = "";
|
||||
query = query.trim();
|
||||
|
||||
if (query.isEmpty()) {
|
||||
this.client.sendResponse(new HousekeepingRoomListComposer(new ArrayList<>()));
|
||||
return;
|
||||
}
|
||||
|
||||
String sql = exactMatch
|
||||
? "SELECT id FROM rooms WHERE name = ? LIMIT ?"
|
||||
: "SELECT id FROM rooms WHERE name LIKE ? LIMIT ?";
|
||||
|
||||
List<Room> rooms = new ArrayList<>();
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, exactMatch ? query : query + "%");
|
||||
statement.setInt(2, limit);
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
while (set.next()) {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(set.getInt("id"), false);
|
||||
|
||||
if (room != null) rooms.add(room);
|
||||
}
|
||||
}
|
||||
} catch (SQLException ignored) {
|
||||
// fall through with whatever we collected before the failure
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingRoomListComposer(rooms));
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
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.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.generic.alerts.StaffAlertWithLinkComposer;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Mirrors :ha — staff alert with sender attribution, broadcast to
|
||||
* every online user whose `blockStaffAlerts` flag isn't set. Composed
|
||||
* once and forwarded by reference (sendResponse compiles to the same
|
||||
* underlying buffer) so the broadcast is O(N habbos) wire writes,
|
||||
* not O(N) compose calls.
|
||||
*/
|
||||
public class HousekeepingSendHotelAlertEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "hotel.alert";
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String message = this.packet.readString();
|
||||
|
||||
if (message == null || message.trim().isEmpty()) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.alert_empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
String body = message + "\r\n-" + this.client.getHabbo().getHabboInfo().getUsername();
|
||||
ServerMessage broadcast = new StaffAlertWithLinkComposer(body, "").compose();
|
||||
|
||||
int reached = 0;
|
||||
|
||||
for (Map.Entry<Integer, Habbo> entry : Emulator.getGameEnvironment().getHabboManager().getOnlineHabbos().entrySet()) {
|
||||
Habbo habbo = entry.getValue();
|
||||
|
||||
if (habbo == null || habbo.getClient() == null) continue;
|
||||
if (habbo.getHabboStats() != null && habbo.getHabboStats().blockStaffAlerts) continue;
|
||||
|
||||
habbo.getClient().sendResponse(broadcast);
|
||||
reached++;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, reached, ""));
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
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;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Extend a user's HC by `days`. Adds to the existing club_expire_timestamp
|
||||
* if it's still in the future, otherwise stretches from `now`. Days==0
|
||||
* means cancel the active subscription (timestamp clamped to `now`).
|
||||
*/
|
||||
public class HousekeepingSetHcSubscriptionEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "user.set_hc";
|
||||
private static final int SECONDS_IN_DAY = 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();
|
||||
int days = this.packet.readInt();
|
||||
|
||||
if (userId <= 0 || days < 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
int now = Emulator.getIntUnixTimestamp();
|
||||
int newExpire;
|
||||
|
||||
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (days == 0) {
|
||||
newExpire = now;
|
||||
} else if (online != null) {
|
||||
int current = online.getHabboStats().getClubExpireTimestamp();
|
||||
newExpire = (current > now ? current : now) + (days * SECONDS_IN_DAY);
|
||||
} else {
|
||||
newExpire = now + (days * SECONDS_IN_DAY); // best-effort offline; can't read previous expiry cheaply
|
||||
}
|
||||
|
||||
if (online != null) {
|
||||
online.getHabboStats().setClubExpireTimestamp(newExpire);
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("UPDATE users_settings SET club_expire_timestamp = ? WHERE user_id = ? LIMIT 1")) {
|
||||
statement.setInt(1, newExpire);
|
||||
statement.setInt(2, userId);
|
||||
int rows = statement.executeUpdate();
|
||||
|
||||
if (rows == 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found"));
|
||||
return;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.eu.habbo.messages.incoming.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.permissions.PermissionsManager;
|
||||
import com.eu.habbo.habbohotel.permissions.Rank;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
import com.eu.habbo.messages.outgoing.users.UserPermissionsComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class HousekeepingSetUserRankEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "user.set_rank";
|
||||
|
||||
@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();
|
||||
int rankId = this.packet.readInt();
|
||||
|
||||
if (userId <= 0 || rankId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
PermissionsManager permissions = Emulator.getGameEnvironment().getPermissionsManager();
|
||||
|
||||
if (!permissions.rankExists(rankId)) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_not_found"));
|
||||
return;
|
||||
}
|
||||
|
||||
Rank rank = permissions.getRank(rankId);
|
||||
|
||||
// Persist for the offline path. Online users get their in-memory
|
||||
// HabboInfo.rank rebound below so server-side hasPermission()
|
||||
// checks land on the new permission set without a relogin.
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("UPDATE users SET rank = ? WHERE id = ? LIMIT 1")) {
|
||||
statement.setInt(1, rankId);
|
||||
statement.setInt(2, userId);
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (online != null) {
|
||||
online.getHabboInfo().setRank(rank);
|
||||
// Ship the refreshed permissions snapshot — same payload the
|
||||
// :update_permissions command emits when a rank is rebound.
|
||||
online.getClient().sendResponse(new UserPermissionsComposer(online));
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
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;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Apply an arbitrary-duration trade lock. Writes
|
||||
* `users_settings.trade_locked_until = now + hours*3600` so the lock
|
||||
* survives logout/login — that column is the canonical timestamp the
|
||||
* mod-tool user-info composer queries on. Online users also get their
|
||||
* in-memory HabboStats.allowTrade flag cleared so the lock takes
|
||||
* effect on the active session without waiting for a relog.
|
||||
*/
|
||||
public class HousekeepingTradeLockUserEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "user.trade_lock";
|
||||
private static final int SECONDS_IN_HOUR = 3600;
|
||||
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();
|
||||
int hours = this.packet.readInt();
|
||||
String reason = this.packet.readString();
|
||||
|
||||
if (userId <= 0 || hours <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
long durationLong = (long) hours * SECONDS_IN_HOUR;
|
||||
int duration = durationLong > MAX_DURATION_SECONDS ? MAX_DURATION_SECONDS : (int) durationLong;
|
||||
int lockedUntil = Emulator.getIntUnixTimestamp() + duration;
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("UPDATE users_settings SET trade_locked_until = ? WHERE user_id = ? LIMIT 1")) {
|
||||
statement.setInt(1, lockedUntil);
|
||||
statement.setInt(2, userId);
|
||||
int rows = statement.executeUpdate();
|
||||
|
||||
if (rows == 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found"));
|
||||
return;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (online != null) {
|
||||
online.getHabboStats().setAllowTrade(false);
|
||||
|
||||
if (reason != null && !reason.isEmpty()) {
|
||||
online.alert(reason);
|
||||
}
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
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.HabboInfo;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Transfer ownership of a room to a different user. Updates both
|
||||
* `rooms.owner_id` and `rooms.owner_name` so the cached owner name on
|
||||
* the navigator stays in sync without forcing a relog. The room is
|
||||
* touched via direct SQL rather than via Room.setOwnerId() because
|
||||
* the room may not be loaded.
|
||||
*/
|
||||
public class HousekeepingTransferRoomOwnershipEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "room.transfer";
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int roomId = this.packet.readInt();
|
||||
int newOwnerId = this.packet.readInt();
|
||||
|
||||
if (roomId <= 0 || newOwnerId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
HabboInfo newOwner = Emulator.getGameEnvironment().getHabboManager().getHabboInfo(newOwnerId);
|
||||
|
||||
if (newOwner == null) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.new_owner_not_found"));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("UPDATE rooms SET owner_id = ?, owner_name = ? WHERE id = ? LIMIT 1")) {
|
||||
statement.setInt(1, newOwnerId);
|
||||
statement.setString(2, newOwner.getUsername());
|
||||
statement.setInt(3, roomId);
|
||||
int rows = statement.executeUpdate();
|
||||
|
||||
if (rows == 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found"));
|
||||
return;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, ""));
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
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.HabboInfo;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||
|
||||
public class HousekeepingUnbanUserEvent extends MessageHandler {
|
||||
private static final String ACTION_KEY = "user.unban";
|
||||
|
||||
@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();
|
||||
|
||||
if (userId <= 0) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||
return;
|
||||
}
|
||||
|
||||
HabboInfo info = Emulator.getGameEnvironment().getHabboManager().getHabboInfo(userId);
|
||||
|
||||
if (info == null) {
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found"));
|
||||
return;
|
||||
}
|
||||
|
||||
// ModToolManager.unban only takes a username; the SQL UPDATE
|
||||
// happens against active bans (ban_expire > now), so calling it
|
||||
// on a never-banned user is a benign no-op that returns false.
|
||||
boolean cleared = Emulator.getGameEnvironment().getModToolManager().unban(info.getUsername());
|
||||
|
||||
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, cleared, cleared ? userId : 0, cleared ? "" : "housekeeping.error.no_active_ban"));
|
||||
}
|
||||
}
|
||||
@@ -586,4 +586,12 @@ public class Outgoing {
|
||||
public static final int YouTubeRoomWatchersComposer = 8002;
|
||||
public static final int YouTubeRoomSettingsComposer = 8003;
|
||||
|
||||
// Housekeeping (in-client admin panel) — IDs 9200..9299 reserved
|
||||
public static final int HousekeepingUserDetailComposer = 9200;
|
||||
public static final int HousekeepingActionResultComposer = 9201;
|
||||
public static final int HousekeepingRoomDetailComposer = 9202;
|
||||
public static final int HousekeepingRoomListComposer = 9203;
|
||||
public static final int HousekeepingDashboardComposer = 9204;
|
||||
public static final int HousekeepingActionLogComposer = 9205;
|
||||
|
||||
}
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HousekeepingActionLogComposer extends MessageComposer {
|
||||
public static class Row {
|
||||
public final int id;
|
||||
public final int timestamp;
|
||||
public final int actorId;
|
||||
public final String actorName;
|
||||
public final String targetType;
|
||||
public final int targetId;
|
||||
public final String targetLabel;
|
||||
public final String action;
|
||||
public final String detail;
|
||||
public final boolean success;
|
||||
|
||||
public Row(int id, int timestamp, int actorId, String actorName, String targetType, int targetId,
|
||||
String targetLabel, String action, String detail, boolean success) {
|
||||
this.id = id;
|
||||
this.timestamp = timestamp;
|
||||
this.actorId = actorId;
|
||||
this.actorName = actorName != null ? actorName : "";
|
||||
this.targetType = targetType != null ? targetType : "user";
|
||||
this.targetId = targetId;
|
||||
this.targetLabel = targetLabel != null ? targetLabel : "";
|
||||
this.action = action != null ? action : "";
|
||||
this.detail = detail != null ? detail : "";
|
||||
this.success = success;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<Row> rows;
|
||||
|
||||
public HousekeepingActionLogComposer(List<Row> rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.HousekeepingActionLogComposer);
|
||||
this.response.appendInt(this.rows != null ? this.rows.size() : 0);
|
||||
|
||||
if (this.rows != null) {
|
||||
for (Row r : this.rows) {
|
||||
this.response.appendInt(r.id);
|
||||
this.response.appendInt(r.timestamp);
|
||||
this.response.appendInt(r.actorId);
|
||||
this.response.appendString(r.actorName);
|
||||
this.response.appendString(r.targetType);
|
||||
this.response.appendInt(r.targetId);
|
||||
this.response.appendString(r.targetLabel);
|
||||
this.response.appendString(r.action);
|
||||
this.response.appendString(r.detail);
|
||||
this.response.appendBoolean(r.success);
|
||||
}
|
||||
}
|
||||
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+36
@@ -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;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
|
||||
public class HousekeepingDashboardComposer extends MessageComposer {
|
||||
private final int onlineUsers;
|
||||
private final int totalUsers;
|
||||
private final int activeRooms;
|
||||
private final int totalRooms;
|
||||
private final int peakOnlineToday;
|
||||
private final int peakOnlineAllTime;
|
||||
private final int pendingTickets;
|
||||
private final int sanctionsLast24h;
|
||||
private final int serverUptimeSeconds;
|
||||
private final String serverVersion;
|
||||
|
||||
public HousekeepingDashboardComposer(int onlineUsers, int totalUsers, int activeRooms, int totalRooms,
|
||||
int peakOnlineToday, int peakOnlineAllTime, int pendingTickets,
|
||||
int sanctionsLast24h, int serverUptimeSeconds, String serverVersion) {
|
||||
this.onlineUsers = onlineUsers;
|
||||
this.totalUsers = totalUsers;
|
||||
this.activeRooms = activeRooms;
|
||||
this.totalRooms = totalRooms;
|
||||
this.peakOnlineToday = peakOnlineToday;
|
||||
this.peakOnlineAllTime = peakOnlineAllTime;
|
||||
this.pendingTickets = pendingTickets;
|
||||
this.sanctionsLast24h = sanctionsLast24h;
|
||||
this.serverUptimeSeconds = serverUptimeSeconds;
|
||||
this.serverVersion = serverVersion != null ? serverVersion : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.HousekeepingDashboardComposer);
|
||||
this.response.appendInt(this.onlineUsers);
|
||||
this.response.appendInt(this.totalUsers);
|
||||
this.response.appendInt(this.activeRooms);
|
||||
this.response.appendInt(this.totalRooms);
|
||||
this.response.appendInt(this.peakOnlineToday);
|
||||
this.response.appendInt(this.peakOnlineAllTime);
|
||||
this.response.appendInt(this.pendingTickets);
|
||||
this.response.appendInt(this.sanctionsLast24h);
|
||||
this.response.appendInt(this.serverUptimeSeconds);
|
||||
this.response.appendString(this.serverVersion);
|
||||
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.eu.habbo.messages.outgoing.housekeeping;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomState;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
public class HousekeepingRoomDetailComposer extends MessageComposer {
|
||||
private final Room room;
|
||||
|
||||
public HousekeepingRoomDetailComposer(Room room) {
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.HousekeepingRoomDetailComposer);
|
||||
|
||||
if (this.room == null) {
|
||||
this.response.appendBoolean(false);
|
||||
return this.response;
|
||||
}
|
||||
|
||||
this.response.appendBoolean(true);
|
||||
appendRoomFields(this.response, this.room);
|
||||
|
||||
return this.response;
|
||||
}
|
||||
|
||||
/** Shared by HousekeepingRoomListComposer too. */
|
||||
public static void appendRoomFields(ServerMessage response, Room room) {
|
||||
response.appendInt(room.getId());
|
||||
response.appendString(safe(room.getName()));
|
||||
response.appendString(safe(room.getDescription()));
|
||||
response.appendInt(room.getOwnerId());
|
||||
response.appendString(safe(room.getOwnerName()));
|
||||
response.appendInt(room.getUserCount());
|
||||
response.appendInt(room.getUsersMax());
|
||||
response.appendBoolean(room.getState() != null && room.getState() != RoomState.OPEN);
|
||||
response.appendBoolean(room.isMuted());
|
||||
response.appendBoolean(room.isPublicRoom());
|
||||
response.appendInt(0); // createdAt — Room doesn't expose; left as 0 until a schema-side timestamp surfaces.
|
||||
}
|
||||
|
||||
private static String safe(String value) {
|
||||
return value != null ? value : "";
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.eu.habbo.messages.outgoing.housekeeping;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HousekeepingRoomListComposer extends MessageComposer {
|
||||
private final List<Room> rooms;
|
||||
|
||||
public HousekeepingRoomListComposer(List<Room> rooms) {
|
||||
this.rooms = rooms;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.HousekeepingRoomListComposer);
|
||||
this.response.appendInt(this.rooms != null ? this.rooms.size() : 0);
|
||||
|
||||
if (this.rooms != null) {
|
||||
for (Room room : this.rooms) {
|
||||
HousekeepingRoomDetailComposer.appendRoomFields(this.response, room);
|
||||
}
|
||||
}
|
||||
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.eu.habbo.messages.outgoing.housekeeping;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.modtool.ModToolBan;
|
||||
import com.eu.habbo.habbohotel.permissions.Rank;
|
||||
import com.eu.habbo.habbohotel.users.HabboInfo;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
public class HousekeepingUserDetailComposer extends MessageComposer {
|
||||
private static final int CURRENCY_DUCKETS = 0;
|
||||
private static final int CURRENCY_DIAMONDS = 5;
|
||||
|
||||
private final HabboInfo info;
|
||||
|
||||
public HousekeepingUserDetailComposer(HabboInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.HousekeepingUserDetailComposer);
|
||||
|
||||
if (this.info == null) {
|
||||
this.response.appendBoolean(false);
|
||||
return this.response;
|
||||
}
|
||||
|
||||
Rank rank = this.info.getRank();
|
||||
ModToolBan ban = Emulator.getGameEnvironment().getModToolManager().checkForBan(this.info.getId());
|
||||
|
||||
this.response.appendBoolean(true);
|
||||
this.response.appendInt(this.info.getId());
|
||||
this.response.appendString(safe(this.info.getUsername()));
|
||||
this.response.appendString(safe(this.info.getMotto()));
|
||||
this.response.appendString(safe(this.info.getLook()));
|
||||
this.response.appendInt(rank != null ? rank.getId() : 0);
|
||||
this.response.appendString(rank != null ? safe(rank.getName()) : "");
|
||||
this.response.appendBoolean(this.info.isOnline());
|
||||
this.response.appendInt(this.info.getLastOnline());
|
||||
this.response.appendInt(this.info.getCredits());
|
||||
this.response.appendInt(this.info.getCurrencyAmount(CURRENCY_DUCKETS));
|
||||
this.response.appendInt(this.info.getCurrencyAmount(CURRENCY_DIAMONDS));
|
||||
this.response.appendString(safe(this.info.getMail()));
|
||||
this.response.appendString(safe(this.info.getIpLogin()));
|
||||
this.response.appendBoolean(ban != null);
|
||||
// Mute / trade-lock surface as future packet extensions; see the
|
||||
// optional-trailing-field parser pattern on the renderer side.
|
||||
this.response.appendBoolean(false);
|
||||
this.response.appendBoolean(false);
|
||||
|
||||
return this.response;
|
||||
}
|
||||
|
||||
private static String safe(String value) {
|
||||
return value != null ? value : "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user