vote_mute #2

Merged
stijn merged 19 commits from vote_mute into main 2025-01-22 20:45:21 +00:00
10 changed files with 333 additions and 6 deletions
Showing only changes of commit 44d6e994cc - Show all commits

View File

@ -551,4 +551,12 @@ public final class Config {
DEATH_MESSAGES_MAX_PER_PERIOD = getInt("death-messages.max-per-period", DEATH_MESSAGES_MAX_PER_PERIOD);
DEATH_MESSAGES_LIMIT_PERIOD_MINUTES = getInt("death-messages.limit-period-minutes", DEATH_MESSAGES_LIMIT_PERIOD_MINUTES);
}
public static long CHAT_LOG_DELETE_OLDER_THAN_DAYS = 31;
public static long CHAT_LOG_SAVE_DELAY_MINUTES = 5;
private static void chatLogSettings() {
CHAT_LOG_DELETE_OLDER_THAN_DAYS = getLong("chat-log.delete-older-than-days", CHAT_LOG_DELETE_OLDER_THAN_DAYS);
CHAT_LOG_SAVE_DELAY_MINUTES = getLong("chat-log.save-delay-minutes", CHAT_LOG_SAVE_DELAY_MINUTES);
}
}

View File

@ -0,0 +1,102 @@
package com.alttd.chat.database;
import com.alttd.chat.objects.chat_log.ChatLog;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.util.ALogger;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.jetbrains.annotations.NotNull;
import java.sql.*;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
public class ChatLogQueries {
protected static void createChatLogTable() {
String nicknamesTableQuery = "CREATE TABLE IF NOT EXISTS chat_log("
+ "uuid CHAR(48) NOT NULL,"
+ "time_stamp TIMESTAMP(6) NOT NULL, "
+ "server VARCHAR(50) NOT NULL, "
+ "chat_message VARCHAR(300) NOT NULL, "
+ "blocked BIT(1) NOT NULL DEFAULT 0"
+ ")";
try (PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(nicknamesTableQuery)) {
preparedStatement.executeUpdate();
} catch (Throwable throwable) {
ALogger.error("Failed to create chat log table", throwable);
}
}
public static @NotNull CompletableFuture<Boolean> storeMessages(Object2ObjectOpenHashMap<UUID, List<ChatLog>> chatMessages) {
String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, chat_message, blocked) VALUES (?, ?, ?, ?, ?)";
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.createTransactionConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery);
for (List<ChatLog> chatLogList : chatMessages.values()) {
for (ChatLog chatLog : chatLogList) {
chatLog.prepareStatement(preparedStatement);
preparedStatement.addBatch();
}
}
int[] updatedRowsCount = preparedStatement.executeBatch();
boolean isSuccess = Arrays.stream(updatedRowsCount).allMatch(i -> i >= 0);
if (isSuccess) {
connection.commit();
return true;
} else {
connection.rollback();
ALogger.warn("Failed to store messages");
return false;
}
} catch (SQLException sqlException) {
ALogger.error("Failed to store chat messages", sqlException);
throw new CompletionException("Failed to store chat messages", sqlException);
}
});
}
public static @NotNull CompletableFuture<List<ChatLog>> retrieveMessages(ChatLogHandler chatLogHandler, UUID uuid, Duration duration, String server) {
String query = "SELECT * FROM chat_log WHERE uuid = ? AND time_stamp > ? AND server = ?";
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, uuid.toString());
preparedStatement.setTimestamp(2, Timestamp.from(Instant.now().minus(duration)));
preparedStatement.setString(3, server);
ResultSet resultSet = preparedStatement.executeQuery();
List<ChatLog> chatLogs = new ArrayList<>();
while (resultSet.next()) {
ChatLog chatLog = chatLogHandler.loadFromResultSet(resultSet);
chatLogs.add(chatLog);
}
return chatLogs;
} catch (SQLException sqlException) {
ALogger.error(String.format("Failed to retrieve messages for user %s", uuid), sqlException);
throw new CompletionException(String.format("Failed to retrieve messages for user %s", uuid), sqlException);
}
});
}
public static CompletableFuture<Boolean> deleteOldMessages(Duration duration) {
String query = "DELETE FROM chat_log WHERE time_stamp > ?";
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setTimestamp(1, Timestamp.from(Instant.now().minus(duration)));
return preparedStatement.execute();
} catch (SQLException sqlException) {
ALogger.error(String.format("Failed to delete messages older than %s days", duration.toDays()), sqlException);
throw new CompletionException(String.format("Failed to delete messages older than %s days", duration.toDays()), sqlException);
}
});
}
}

View File

@ -2,7 +2,6 @@ package com.alttd.chat.database;
import com.alttd.chat.config.Config;
import com.alttd.chat.util.ALogger;
import java.sql.Connection;
import java.sql.DriverManager;
@ -66,6 +65,21 @@ public class DatabaseConnection {
return connection;
}
/**
* Creates a transactional database connection.
*
* @return A {@code Connection} object representing the transactional database connection.
* @throws SQLException If there is an error creating the database connection.
*/
public static Connection createTransactionConnection() throws SQLException {
connection = DriverManager.getConnection(
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true"+
"&useSSL=false",
Config.USERNAME, Config.PASSWORD);
connection.setAutoCommit(false);
return connection;
}
/**
* Sets the connection for this instance
*/

View File

@ -21,6 +21,7 @@ public class Queries {
tables.add("CREATE TABLE IF NOT EXISTS mails (`id` INT NOT NULL AUTO_INCREMENT, `uuid` VARCHAR(36) NOT NULL, `sender` VARCHAR(36) NOT NULL, `message` VARCHAR(256) NOT NULL, `sendtime` BIGINT default 0, `readtime` BIGINT default 0, PRIMARY KEY (`id`))");
createNicknamesTable();
createRequestedNicknamesTable();
ChatLogQueries.createChatLogTable();
try {
Connection connection = DatabaseConnection.getConnection();

View File

@ -0,0 +1,10 @@
package com.alttd.chat.objects;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public interface BatchInsertable {
void prepareStatement(PreparedStatement preparedStatement) throws SQLException;
}

View File

@ -0,0 +1,52 @@
package com.alttd.chat.objects.chat_log;
import com.alttd.chat.objects.BatchInsertable;
import org.jetbrains.annotations.NotNull;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.UUID;
public class ChatLog implements BatchInsertable {
private final UUID uuid;
private final Instant timestamp;
private final String server;
private final String message;
private final boolean blocked;
protected ChatLog(UUID uuid, Instant timestamp, String server, String message, boolean blocked) {
this.uuid = uuid;
this.timestamp = timestamp;
this.server = server;
this.message = message;
this.blocked = blocked;
}
@Override
public void prepareStatement(@NotNull PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setString(1, uuid.toString());
preparedStatement.setTimestamp(2, Timestamp.from(timestamp));
preparedStatement.setString(3, server);
preparedStatement.setString(4, message);
preparedStatement.setInt(5, blocked ? 1 : 0);
}
public UUID getUuid() {
return uuid;
}
public Instant getTimestamp() {
return timestamp;
}
public String getMessage() {
return message;
}
public boolean isBlocked() {
return blocked;
}
}

View File

@ -0,0 +1,112 @@
package com.alttd.chat.objects.chat_log;
import com.alttd.chat.config.Config;
import com.alttd.chat.database.ChatLogQueries;
import com.alttd.chat.util.ALogger;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.jetbrains.annotations.NotNull;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.*;
public class ChatLogHandler {
private static ChatLogHandler instance = null;
private final ScheduledExecutorService executorService;
public static ChatLogHandler getInstance() {
if (instance == null)
instance = new ChatLogHandler();
return instance;
}
private boolean isSaving;
private final Queue<ChatLog> chatLogQueue = new ConcurrentLinkedQueue<>();
private final Object2ObjectOpenHashMap<UUID, List<ChatLog>> chatLogs = new Object2ObjectOpenHashMap<>();
public ChatLogHandler() {
Duration deleteThreshold = Duration.ofDays(Config.CHAT_LOG_DELETE_OLDER_THAN_DAYS);
ChatLogQueries.deleteOldMessages(deleteThreshold).thenAccept(success -> {
if (success) {
ALogger.info(String.format("Deleted all messages older than %s days from chat log database.", deleteThreshold.toDays()));
} else {
ALogger.warn(String.format("Failed to delete all messages older than %s days from chat log database.", deleteThreshold.toDays()));
}
});
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(() -> saveToDatabase(false),
Config.CHAT_LOG_SAVE_DELAY_MINUTES, Config.CHAT_LOG_SAVE_DELAY_MINUTES, TimeUnit.MINUTES);
}
public void shutDown() {
executorService.shutdown();
saveToDatabase(true);
}
private synchronized void savingToDatabase(boolean saving) {
isSaving = saving;
}
private synchronized boolean isBlocked() {
return isSaving;
}
public synchronized void addLog(ChatLog chatLog) {
if (isBlocked()) {
chatLogQueue.add(chatLog);
} else {
chatLogs.computeIfAbsent(chatLog.getUuid(), k -> new ArrayList<>()).add(chatLog);
}
}
private void saveToDatabase(boolean onMainThread) {
savingToDatabase(true);
CompletableFuture<Boolean> booleanCompletableFuture = ChatLogQueries.storeMessages(chatLogs);
if (onMainThread) {
booleanCompletableFuture.join();
return;
}
booleanCompletableFuture.whenComplete((result, throwable) -> {
if (throwable == null && result) {
chatLogs.clear();
}
savingToDatabase(false);
while (!chatLogQueue.isEmpty()) {
addLog(chatLogQueue.remove());
}
});
}
public ChatLog loadFromResultSet(@NotNull ResultSet resultSet) throws SQLException {
UUID chatLogUUID = UUID.fromString(resultSet.getString("uuid"));
Instant chatTimestamp = resultSet.getTimestamp("time_stamp").toInstant();
String server = resultSet.getString("server");
String chatMessage = resultSet.getString("chat_message");
boolean chatMessageBlocked = resultSet.getInt("blocked") == 1;
return new ChatLog(chatLogUUID, chatTimestamp, server, chatMessage, chatMessageBlocked);
}
public void addChatLog(UUID uuid, String server, String message, boolean blocked) {
addLog(new ChatLog(uuid, Instant.now(), server, message, blocked));
}
public CompletableFuture<List<ChatLog>> retrieveChatLogs(UUID uuid, Duration duration, String server) {
List<ChatLog> chatLogList = chatLogs.getOrDefault(uuid, new ArrayList<>());
return ChatLogQueries.retrieveMessages(this, uuid, duration, server)
.thenCompose(chatLogs -> CompletableFuture.supplyAsync(() -> {
chatLogList.addAll(chatLogs);
return chatLogList;
}))
.exceptionally(ex -> {
throw new CompletionException(ex);
});
}
}

View File

@ -5,14 +5,12 @@ import com.alttd.chat.config.Config;
import com.alttd.chat.config.ServerConfig;
import com.alttd.chat.database.DatabaseConnection;
import com.alttd.chat.handler.ChatHandler;
import com.alttd.chat.listeners.BookListener;
import com.alttd.chat.listeners.ChatListener;
import com.alttd.chat.listeners.PlayerListener;
import com.alttd.chat.listeners.PluginMessage;
import com.alttd.chat.listeners.*;
import com.alttd.chat.nicknames.Nicknames;
import com.alttd.chat.nicknames.NicknamesEvents;
import com.alttd.chat.objects.channels.Channel;
import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.Utility;
import org.bukkit.Bukkit;
@ -41,7 +39,8 @@ public class ChatPlugin extends JavaPlugin {
chatHandler = new ChatHandler();
DatabaseConnection.initialize();
serverConfig = new ServerConfig(Bukkit.getServerName());
registerListener(new PlayerListener(serverConfig), new ChatListener(), new BookListener());
ChatLogHandler chatLogHandler = ChatLogHandler.getInstance();
registerListener(new PlayerListener(serverConfig), new ChatListener(chatLogHandler), new BookListener(), new ShutdownListener(chatLogHandler));
if(serverConfig.GLOBALCHAT) {
registerCommand("globalchat", new GlobalChat());
registerCommand("toggleglobalchat", new ToggleGlobalChat());

View File

@ -6,6 +6,7 @@ import com.alttd.chat.handler.ChatHandler;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.RegexManager;
import com.alttd.chat.objects.*;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.GalaxyUtility;
import com.alttd.chat.util.Utility;
@ -37,6 +38,11 @@ import java.util.stream.Collectors;
public class ChatListener implements Listener {
private final PlainTextComponentSerializer plainTextComponentSerializer = PlainTextComponentSerializer.plainText();
private final ChatLogHandler chatLogHandler;
public ChatListener(ChatLogHandler chatLogHandler) {
this.chatLogHandler = chatLogHandler;
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
@ -102,6 +108,7 @@ public class ChatListener implements Listener {
GalaxyUtility.sendBlockedNotification("Language", player,
modifiableString.component(),
"");
chatLogHandler.addChatLog(player.getUniqueId(), player.getServer().getServerName(), PlainTextComponentSerializer.plainText().serialize(input), true);
return; // the message was blocked
}
@ -115,6 +122,7 @@ public class ChatListener implements Listener {
for (Player pingPlayer : playersToPing) {
pingPlayer.playSound(pingPlayer.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1);
}
chatLogHandler.addChatLog(player.getUniqueId(), player.getServer().getServerName(), modifiableString.string(), false);
ALogger.info(PlainTextComponentSerializer.plainText().serialize(input));
}

View File

@ -0,0 +1,21 @@
package com.alttd.chat.listeners;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
public class ShutdownListener implements Listener {
private final ChatLogHandler chatLogHandler;
public ShutdownListener(ChatLogHandler chatLogHandler) {
this.chatLogHandler = chatLogHandler;
}
@EventHandler
public void onShutdown(PluginDisableEvent event) {
chatLogHandler.shutDown();
}
}