You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-20 07:26:18 +00:00
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `guild_forum_views` ADD UNIQUE KEY `user_guild` (`user_id`, `guild_id`);
|
||||||
@@ -210,6 +210,19 @@ public class ForumThread implements Runnable, ISerialize {
|
|||||||
guildThreads.add(thread);
|
guildThreads.add(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void clearCacheForGuild(int guildId) {
|
||||||
|
synchronized (guildThreadsCache) {
|
||||||
|
THashSet<ForumThread> threads = guildThreadsCache.remove(guildId);
|
||||||
|
if (threads != null) {
|
||||||
|
synchronized (forumThreadsCache) {
|
||||||
|
for (ForumThread thread : threads) {
|
||||||
|
forumThreadsCache.remove(thread.threadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void clearCache() {
|
public static void clearCache() {
|
||||||
for (THashSet<ForumThread> threads : guildThreadsCache.values()) {
|
for (THashSet<ForumThread> threads : guildThreadsCache.values()) {
|
||||||
for (ForumThread thread : threads) {
|
for (ForumThread thread : threads) {
|
||||||
|
|||||||
@@ -572,6 +572,7 @@ public class PacketManager {
|
|||||||
this.registerHandler(Incoming.GuildForumModerateMessageEvent, GuildForumModerateMessageEvent.class);
|
this.registerHandler(Incoming.GuildForumModerateMessageEvent, GuildForumModerateMessageEvent.class);
|
||||||
this.registerHandler(Incoming.GuildForumModerateThreadEvent, GuildForumModerateThreadEvent.class);
|
this.registerHandler(Incoming.GuildForumModerateThreadEvent, GuildForumModerateThreadEvent.class);
|
||||||
this.registerHandler(Incoming.GuildForumThreadUpdateEvent, GuildForumThreadUpdateEvent.class);
|
this.registerHandler(Incoming.GuildForumThreadUpdateEvent, GuildForumThreadUpdateEvent.class);
|
||||||
|
this.registerHandler(Incoming.GuildForumMarkAsReadEvent, GuildForumMarkAsReadEvent.class);
|
||||||
this.registerHandler(Incoming.GetHabboGuildBadgesMessageEvent, GetHabboGuildBadgesMessageEvent.class);
|
this.registerHandler(Incoming.GetHabboGuildBadgesMessageEvent, GetHabboGuildBadgesMessageEvent.class);
|
||||||
|
|
||||||
// this.registerHandler(Incoming.GuildForumDataEvent, GuildForumModerateMessageEvent.class);
|
// this.registerHandler(Incoming.GuildForumDataEvent, GuildForumModerateMessageEvent.class);
|
||||||
|
|||||||
+56
@@ -6,9 +6,20 @@ import com.eu.habbo.habbohotel.guilds.GuildState;
|
|||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.habbohotel.rooms.Room;
|
import com.eu.habbo.habbohotel.rooms.Room;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.habbohotel.guilds.forums.ForumThread;
|
||||||
|
import com.eu.habbo.messages.incoming.guilds.forums.GuildForumListEvent;
|
||||||
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
|
||||||
import com.eu.habbo.plugin.events.guilds.GuildChangedSettingsEvent;
|
import com.eu.habbo.plugin.events.guilds.GuildChangedSettingsEvent;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
public class GuildChangeSettingsEvent extends MessageHandler {
|
public class GuildChangeSettingsEvent extends MessageHandler {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(GuildChangeSettingsEvent.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRatelimit() {
|
public int getRatelimit() {
|
||||||
return 500;
|
return 500;
|
||||||
@@ -31,6 +42,24 @@ public class GuildChangeSettingsEvent extends MessageHandler {
|
|||||||
guild.setState(GuildState.valueOf(settingsEvent.state));
|
guild.setState(GuildState.valueOf(settingsEvent.state));
|
||||||
guild.setRights(settingsEvent.rights);
|
guild.setRights(settingsEvent.rights);
|
||||||
|
|
||||||
|
// Read forum toggle
|
||||||
|
boolean forumEnabled = this.packet.readBoolean();
|
||||||
|
boolean wasForumEnabled = guild.hasForum();
|
||||||
|
|
||||||
|
if (forumEnabled != wasForumEnabled) {
|
||||||
|
guild.setForum(forumEnabled);
|
||||||
|
|
||||||
|
if (!forumEnabled) {
|
||||||
|
// Delete all threads and comments for this guild
|
||||||
|
ForumThread.clearCacheForGuild(guildId);
|
||||||
|
deleteForumData(guildId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate caches
|
||||||
|
GuildForumDataComposer.invalidateUnreadCache(guildId);
|
||||||
|
GuildForumListEvent.invalidateActiveForumsCache();
|
||||||
|
}
|
||||||
|
|
||||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(guild.getRoomId());
|
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(guild.getRoomId());
|
||||||
if(room != null) {
|
if(room != null) {
|
||||||
room.refreshGuild(guild);
|
room.refreshGuild(guild);
|
||||||
@@ -42,4 +71,31 @@ public class GuildChangeSettingsEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteForumData(int guildId) {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||||
|
// Delete comments for all threads in this guild
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"DELETE FROM `guilds_forums_comments` WHERE `thread_id` IN (SELECT `id` FROM `guilds_forums_threads` WHERE `guild_id` = ?)")) {
|
||||||
|
statement.setInt(1, guildId);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all threads for this guild
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"DELETE FROM `guilds_forums_threads` WHERE `guild_id` = ?")) {
|
||||||
|
statement.setInt(1, guildId);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete forum view records for this guild
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"DELETE FROM `guild_forum_views` WHERE `guild_id` = ?")) {
|
||||||
|
statement.setInt(1, guildId);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to delete forum data for guild " + guildId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-1
@@ -14,6 +14,7 @@ import java.sql.PreparedStatement;
|
|||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class GuildForumListEvent extends MessageHandler {
|
public class GuildForumListEvent extends MessageHandler {
|
||||||
@Override
|
@Override
|
||||||
@@ -23,6 +24,26 @@ public class GuildForumListEvent extends MessageHandler {
|
|||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(GuildForumListEvent.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(GuildForumListEvent.class);
|
||||||
|
|
||||||
|
// Cache for active forums list (shared across all users)
|
||||||
|
private static volatile THashSet<Guild> activeForumsCache = null;
|
||||||
|
private static volatile long activeForumsCachedAt = 0;
|
||||||
|
private static final long ACTIVE_FORUMS_TTL = 30 * 60 * 1000; // 30 minutes
|
||||||
|
|
||||||
|
// Cache for user's forum list
|
||||||
|
private static final ConcurrentHashMap<Integer, long[]> myForumsCache = new ConcurrentHashMap<>(); // userId -> {cachedAt}
|
||||||
|
private static final ConcurrentHashMap<Integer, THashSet<Guild>> myForumsData = new ConcurrentHashMap<>();
|
||||||
|
private static final long MY_FORUMS_TTL = 10 * 60 * 1000; // 10 minutes
|
||||||
|
|
||||||
|
public static void invalidateActiveForumsCache() {
|
||||||
|
activeForumsCache = null;
|
||||||
|
activeForumsCachedAt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void invalidateMyForumsCache(int userId) {
|
||||||
|
myForumsCache.remove(userId);
|
||||||
|
myForumsData.remove(userId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle() throws Exception {
|
public void handle() throws Exception {
|
||||||
int mode = this.packet.readInt();
|
int mode = this.packet.readInt();
|
||||||
@@ -50,12 +71,18 @@ public class GuildForumListEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private THashSet<Guild> getActiveForums() {
|
private THashSet<Guild> getActiveForums() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (activeForumsCache != null && (now - activeForumsCachedAt) < ACTIVE_FORUMS_TTL) {
|
||||||
|
return activeForumsCache;
|
||||||
|
}
|
||||||
|
|
||||||
THashSet<Guild> guilds = new THashSet<Guild>();
|
THashSet<Guild> guilds = new THashSet<Guild>();
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT `guilds`.`id`, SUM(`guilds_forums_threads`.`posts_count`) AS `post_count` " +
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT `guilds`.`id`, SUM(`guilds_forums_threads`.`posts_count`) AS `post_count` " +
|
||||||
"FROM `guilds_forums_threads` " +
|
"FROM `guilds_forums_threads` " +
|
||||||
"LEFT JOIN `guilds` ON `guilds`.`id` = `guilds_forums_threads`.`guild_id` " +
|
"LEFT JOIN `guilds` ON `guilds`.`id` = `guilds_forums_threads`.`guild_id` " +
|
||||||
"WHERE `guilds`.`read_forum` = 'EVERYONE' AND `guilds_forums_threads`.`created_at` > ? " +
|
"WHERE `guilds`.`forum` = '1' AND `guilds_forums_threads`.`created_at` > ? " +
|
||||||
"GROUP BY `guilds`.`id` " +
|
"GROUP BY `guilds`.`id` " +
|
||||||
"ORDER BY `post_count` DESC LIMIT 100")) {
|
"ORDER BY `post_count` DESC LIMIT 100")) {
|
||||||
statement.setInt(1, Emulator.getIntUnixTimestamp() - 7 * 24 * 60 * 60);
|
statement.setInt(1, Emulator.getIntUnixTimestamp() - 7 * 24 * 60 * 60);
|
||||||
@@ -73,10 +100,21 @@ public class GuildForumListEvent extends MessageHandler {
|
|||||||
this.client.sendResponse(new ConnectionErrorComposer(500));
|
this.client.sendResponse(new ConnectionErrorComposer(500));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeForumsCache = guilds;
|
||||||
|
activeForumsCachedAt = now;
|
||||||
|
|
||||||
return guilds;
|
return guilds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private THashSet<Guild> getMyForums(int userId) {
|
private THashSet<Guild> getMyForums(int userId) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
long[] cached = myForumsCache.get(userId);
|
||||||
|
if (cached != null && (now - cached[0]) < MY_FORUMS_TTL) {
|
||||||
|
THashSet<Guild> data = myForumsData.get(userId);
|
||||||
|
if (data != null) return data;
|
||||||
|
}
|
||||||
|
|
||||||
THashSet<Guild> guilds = new THashSet<Guild>();
|
THashSet<Guild> guilds = new THashSet<Guild>();
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT `guilds`.`id` FROM `guilds_members` " +
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT `guilds`.`id` FROM `guilds_members` " +
|
||||||
@@ -97,6 +135,9 @@ public class GuildForumListEvent extends MessageHandler {
|
|||||||
this.client.sendResponse(new ConnectionErrorComposer(500));
|
this.client.sendResponse(new ConnectionErrorComposer(500));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
myForumsCache.put(userId, new long[]{now});
|
||||||
|
myForumsData.put(userId, guilds);
|
||||||
|
|
||||||
return guilds;
|
return guilds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.guilds.forums;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class GuildForumMarkAsReadEvent extends MessageHandler {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(GuildForumMarkAsReadEvent.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
int count = this.packet.readInt();
|
||||||
|
int userId = this.client.getHabbo().getHabboInfo().getId();
|
||||||
|
int timestamp = Emulator.getIntUnixTimestamp();
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
int guildId = this.packet.readInt();
|
||||||
|
this.packet.readInt(); // messageId (not used, we track by timestamp)
|
||||||
|
this.packet.readBoolean(); // isRead
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"INSERT INTO `guild_forum_views` (`user_id`, `guild_id`, `timestamp`) VALUES (?, ?, ?) " +
|
||||||
|
"ON DUPLICATE KEY UPDATE `timestamp` = ?"
|
||||||
|
)) {
|
||||||
|
statement.setInt(1, userId);
|
||||||
|
statement.setInt(2, guildId);
|
||||||
|
statement.setInt(3, timestamp);
|
||||||
|
statement.setInt(4, timestamp);
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Caught SQL exception", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate caches so next request gets fresh data
|
||||||
|
GuildForumDataComposer.invalidateLastSeenCache(userId, guildId);
|
||||||
|
GuildForumDataComposer.invalidateUnreadCache(guildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-3
@@ -36,6 +36,11 @@ public class GuildForumModerateMessageEvent extends MessageHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (thread.getGuildId() != guildId) {
|
||||||
|
this.client.sendResponse(new ConnectionErrorComposer(403));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ForumThreadComment comment = thread.getCommentById(messageId);
|
ForumThreadComment comment = thread.getCommentById(messageId);
|
||||||
if (comment == null) {
|
if (comment == null) {
|
||||||
this.client.sendResponse(new ConnectionErrorComposer(404));
|
this.client.sendResponse(new ConnectionErrorComposer(404));
|
||||||
@@ -45,19 +50,20 @@ public class GuildForumModerateMessageEvent extends MessageHandler {
|
|||||||
boolean hasStaffPermissions = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
|
boolean hasStaffPermissions = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
|
||||||
|
|
||||||
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
||||||
if (member == null) {
|
if (member == null && !hasStaffPermissions) {
|
||||||
this.client.sendResponse(new ConnectionErrorComposer(401));
|
this.client.sendResponse(new ConnectionErrorComposer(401));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || member.getRank().equals(GuildRank.ADMIN));
|
boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && member.getRank().equals(GuildRank.ADMIN)));
|
||||||
|
|
||||||
if (!isGuildAdministrator && !hasStaffPermissions) {
|
if (!isGuildAdministrator && !hasStaffPermissions) {
|
||||||
this.client.sendResponse(new ConnectionErrorComposer(403));
|
this.client.sendResponse(new ConnectionErrorComposer(403));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == ForumThreadState.HIDDEN_BY_GUILD_ADMIN.getStateId() && !hasStaffPermissions) {
|
// Restrict state 20 (staff hidden) to staff only
|
||||||
|
if (state == 20 && !hasStaffPermissions) {
|
||||||
this.client.sendResponse(new ConnectionErrorComposer(403));
|
this.client.sendResponse(new ConnectionErrorComposer(403));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-4
@@ -10,12 +10,21 @@ import com.eu.habbo.habbohotel.permissions.Permission;
|
|||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
|
||||||
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
|
||||||
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadMessagesComposer;
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadMessagesComposer;
|
||||||
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadsComposer;
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadsComposer;
|
||||||
import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer;
|
import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
|
||||||
public class GuildForumModerateThreadEvent extends MessageHandler {
|
public class GuildForumModerateThreadEvent extends MessageHandler {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(GuildForumModerateThreadEvent.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRatelimit() {
|
public int getRatelimit() {
|
||||||
return 500;
|
return 500;
|
||||||
@@ -26,8 +35,6 @@ public class GuildForumModerateThreadEvent extends MessageHandler {
|
|||||||
int guildId = packet.readInt();
|
int guildId = packet.readInt();
|
||||||
int threadId = packet.readInt();
|
int threadId = packet.readInt();
|
||||||
int state = packet.readInt();
|
int state = packet.readInt();
|
||||||
// STATE 20 - HIDDEN_BY_GUILD_ADMIN = HIDDEN BY GUILD ADMINS/ HOTEL MODERATORS
|
|
||||||
// STATE 1 = VISIBLE THREAD
|
|
||||||
|
|
||||||
Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId);
|
Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId);
|
||||||
ForumThread thread = ForumThread.getById(threadId);
|
ForumThread thread = ForumThread.getById(threadId);
|
||||||
@@ -37,6 +44,11 @@ public class GuildForumModerateThreadEvent extends MessageHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (thread.getGuildId() != guildId) {
|
||||||
|
this.client.sendResponse(new ConnectionErrorComposer(403));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
||||||
boolean hasStaffPerms = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
|
boolean hasStaffPerms = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
|
||||||
|
|
||||||
@@ -52,12 +64,22 @@ public class GuildForumModerateThreadEvent extends MessageHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.setState(ForumThreadState.fromValue(state)); // sets state as defined in the packet
|
// State 20 = permanent delete (thread + comments removed from DB)
|
||||||
|
if (state == 20) {
|
||||||
|
deleteThread(threadId);
|
||||||
|
ForumThread.clearCacheForGuild(guildId);
|
||||||
|
GuildForumDataComposer.invalidateUnreadCache(guildId);
|
||||||
|
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_THREAD_HIDDEN.key).compose());
|
||||||
|
this.client.sendResponse(new GuildForumThreadsComposer(guild, 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread.setState(ForumThreadState.fromValue(state));
|
||||||
thread.run();
|
thread.run();
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 10:
|
case 10:
|
||||||
case 20:
|
|
||||||
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_THREAD_HIDDEN.key).compose());
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_THREAD_HIDDEN.key).compose());
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
@@ -68,4 +90,22 @@ public class GuildForumModerateThreadEvent extends MessageHandler {
|
|||||||
this.client.sendResponse(new GuildForumThreadMessagesComposer(thread));
|
this.client.sendResponse(new GuildForumThreadMessagesComposer(thread));
|
||||||
this.client.sendResponse(new GuildForumThreadsComposer(guild, 0));
|
this.client.sendResponse(new GuildForumThreadsComposer(guild, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteThread(int threadId) {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"DELETE FROM `guilds_forums_comments` WHERE `thread_id` = ?")) {
|
||||||
|
statement.setInt(1, threadId);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"DELETE FROM `guilds_forums_threads` WHERE `id` = ?")) {
|
||||||
|
statement.setInt(1, threadId);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to delete thread " + threadId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+12
@@ -9,6 +9,7 @@ import com.eu.habbo.habbohotel.guilds.forums.ForumThreadComment;
|
|||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumAddCommentComposer;
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumAddCommentComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
|
||||||
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadMessagesComposer;
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadMessagesComposer;
|
||||||
import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer;
|
import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer;
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ public class GuildForumPostThreadEvent extends MessageHandler {
|
|||||||
|
|
||||||
this.client.getHabbo().getHabboStats().forumPostsCount += 1;
|
this.client.getHabbo().getHabboStats().forumPostsCount += 1;
|
||||||
thread.setPostsCount(thread.getPostsCount() + 1);
|
thread.setPostsCount(thread.getPostsCount() + 1);
|
||||||
|
GuildForumDataComposer.invalidateUnreadCache(guildId);
|
||||||
this.client.sendResponse(new GuildForumThreadMessagesComposer(thread));
|
this.client.sendResponse(new GuildForumThreadMessagesComposer(thread));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -74,6 +76,15 @@ public class GuildForumPostThreadEvent extends MessageHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (thread.getGuildId() != guildId) {
|
||||||
|
this.client.sendResponse(new ConnectionErrorComposer(403));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thread.isLocked() && !isStaff) {
|
||||||
|
this.client.sendResponse(new ConnectionErrorComposer(403));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!((guild.canPostMessages().state == 0)
|
if (!((guild.canPostMessages().state == 0)
|
||||||
|| (guild.canPostMessages().state == 1 && member != null)
|
|| (guild.canPostMessages().state == 1 && member != null)
|
||||||
@@ -91,6 +102,7 @@ public class GuildForumPostThreadEvent extends MessageHandler {
|
|||||||
thread.setUpdatedAt(Emulator.getIntUnixTimestamp());
|
thread.setUpdatedAt(Emulator.getIntUnixTimestamp());
|
||||||
this.client.getHabbo().getHabboStats().forumPostsCount += 1;
|
this.client.getHabbo().getHabboStats().forumPostsCount += 1;
|
||||||
thread.setPostsCount(thread.getPostsCount() + 1);
|
thread.setPostsCount(thread.getPostsCount() + 1);
|
||||||
|
GuildForumDataComposer.invalidateUnreadCache(guildId);
|
||||||
this.client.sendResponse(new GuildForumAddCommentComposer(comment));
|
this.client.sendResponse(new GuildForumAddCommentComposer(comment));
|
||||||
} else {
|
} else {
|
||||||
this.client.sendResponse(new ConnectionErrorComposer(500));
|
this.client.sendResponse(new ConnectionErrorComposer(500));
|
||||||
|
|||||||
+7
@@ -37,6 +37,13 @@ public class GuildForumThreadsMessagesEvent extends MessageHandler {
|
|||||||
this.client.sendResponse(new ConnectionErrorComposer(404));
|
this.client.sendResponse(new ConnectionErrorComposer(404));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify thread belongs to the requested guild
|
||||||
|
if (thread.getGuildId() != guildId) {
|
||||||
|
this.client.sendResponse(new ConnectionErrorComposer(403));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
||||||
boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && member.getRank().equals(GuildRank.ADMIN)));
|
boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && member.getRank().equals(GuildRank.ADMIN)));
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ public class GuildManageComposer extends MessageComposer {
|
|||||||
}
|
}
|
||||||
this.response.appendString(this.guild.getBadge());
|
this.response.appendString(this.guild.getBadge());
|
||||||
this.response.appendInt(this.guild.getMemberCount());
|
this.response.appendInt(this.guild.getMemberCount());
|
||||||
|
this.response.appendBoolean(this.guild.hasForum());
|
||||||
return this.response;
|
return this.response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+76
-27
@@ -20,11 +20,20 @@ import java.sql.Connection;
|
|||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
|
||||||
public class GuildForumDataComposer extends MessageComposer {
|
public class GuildForumDataComposer extends MessageComposer {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(GuildForumDataComposer.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(GuildForumDataComposer.class);
|
||||||
|
|
||||||
|
// Cache for user last-seen timestamps: key = "userId:guildId", value = {timestamp, cachedAt}
|
||||||
|
private static final ConcurrentHashMap<String, long[]> lastSeenCache = new ConcurrentHashMap<>();
|
||||||
|
private static final long LAST_SEEN_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||||
|
|
||||||
|
// Cache for unread counts: key = "guildId:lastSeenAt", value = {count, cachedAt}
|
||||||
|
private static final ConcurrentHashMap<String, long[]> unreadCache = new ConcurrentHashMap<>();
|
||||||
|
private static final long UNREAD_CACHE_TTL = 2 * 60 * 1000; // 2 minutes
|
||||||
|
|
||||||
public final Guild guild;
|
public final Guild guild;
|
||||||
public Habbo habbo;
|
public Habbo habbo;
|
||||||
|
|
||||||
@@ -33,13 +42,77 @@ public class GuildForumDataComposer extends MessageComposer {
|
|||||||
this.habbo = habbo;
|
this.habbo = habbo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void invalidateLastSeenCache(int userId, int guildId) {
|
||||||
|
lastSeenCache.remove(userId + ":" + guildId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void invalidateUnreadCache(int guildId) {
|
||||||
|
unreadCache.entrySet().removeIf(entry -> entry.getKey().startsWith(guildId + ":"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getLastSeenAt(int userId, int guildId) {
|
||||||
|
String key = userId + ":" + guildId;
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
long[] cached = lastSeenCache.get(key);
|
||||||
|
if (cached != null && (now - cached[1]) < LAST_SEEN_CACHE_TTL) {
|
||||||
|
return (int) cached[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastSeenAt = 0;
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"SELECT `timestamp` FROM `guild_forum_views` WHERE `user_id` = ? AND `guild_id` = ? LIMIT 1"
|
||||||
|
)) {
|
||||||
|
statement.setInt(1, userId);
|
||||||
|
statement.setInt(2, guildId);
|
||||||
|
ResultSet set = statement.executeQuery();
|
||||||
|
if (set.next()) {
|
||||||
|
lastSeenAt = set.getInt("timestamp");
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Caught SQL exception", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSeenCache.put(key, new long[]{lastSeenAt, now});
|
||||||
|
return lastSeenAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getUnreadCount(int guildId, int lastSeenAt) {
|
||||||
|
String key = guildId + ":" + lastSeenAt;
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
long[] cached = unreadCache.get(key);
|
||||||
|
if (cached != null && (now - cached[1]) < UNREAD_CACHE_TTL) {
|
||||||
|
return (int) cached[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
int newComments = 0;
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"SELECT COUNT(*) FROM `guilds_forums_comments` " +
|
||||||
|
"JOIN `guilds_forums_threads` ON `guilds_forums_threads`.`id` = `guilds_forums_comments`.`thread_id` " +
|
||||||
|
"WHERE `guilds_forums_threads`.`guild_id` = ? AND `guilds_forums_comments`.`created_at` > ?"
|
||||||
|
)) {
|
||||||
|
statement.setInt(1, guildId);
|
||||||
|
statement.setInt(2, lastSeenAt);
|
||||||
|
|
||||||
|
ResultSet set = statement.executeQuery();
|
||||||
|
if (set.next()) {
|
||||||
|
newComments = set.getInt(1);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Caught SQL exception", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
unreadCache.put(key, new long[]{newComments, now});
|
||||||
|
return newComments;
|
||||||
|
}
|
||||||
|
|
||||||
public static void serializeForumData(ServerMessage response, Guild guild, Habbo habbo) {
|
public static void serializeForumData(ServerMessage response, Guild guild, Habbo habbo) {
|
||||||
|
|
||||||
final THashSet<ForumThread> forumThreads = ForumThread.getByGuildId(guild.getId());
|
final THashSet<ForumThread> forumThreads = ForumThread.getByGuildId(guild.getId());
|
||||||
int lastSeenAt = 0;
|
int lastSeenAt = getLastSeenAt(habbo.getHabboInfo().getId(), guild.getId());
|
||||||
|
|
||||||
int totalComments = 0;
|
int totalComments = 0;
|
||||||
int newComments = 0;
|
|
||||||
int totalThreads = 0;
|
int totalThreads = 0;
|
||||||
ForumThreadComment lastComment = null;
|
ForumThreadComment lastComment = null;
|
||||||
|
|
||||||
@@ -55,31 +128,7 @@ public class GuildForumDataComposer extends MessageComposer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(
|
int newComments = getUnreadCount(guild.getId(), lastSeenAt);
|
||||||
"SELECT COUNT(*) " +
|
|
||||||
"FROM guilds_forums_threads A " +
|
|
||||||
"JOIN ( " +
|
|
||||||
"SELECT * " +
|
|
||||||
"FROM `guilds_forums_comments` " +
|
|
||||||
"WHERE `id` IN ( " +
|
|
||||||
"SELECT id " +
|
|
||||||
"FROM `guilds_forums_comments` B " +
|
|
||||||
"ORDER BY B.`id` ASC " +
|
|
||||||
") " +
|
|
||||||
"ORDER BY `id` DESC " +
|
|
||||||
") B ON A.`id` = B.`thread_id` " +
|
|
||||||
"WHERE A.`guild_id` = ? AND B.`created_at` > ?"
|
|
||||||
)) {
|
|
||||||
statement.setInt(1, guild.getId());
|
|
||||||
statement.setInt(2, lastSeenAt);
|
|
||||||
|
|
||||||
ResultSet set = statement.executeQuery();
|
|
||||||
while (set.next()) {
|
|
||||||
newComments = set.getInt(1);
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
LOGGER.error("Caught SQL exception", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.appendInt(guild.getId());
|
response.appendInt(guild.getId());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user