vote_mute #2
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
102
api/src/main/java/com/alttd/chat/database/ChatLogQueries.java
Normal file
102
api/src/main/java/com/alttd/chat/database/ChatLogQueries.java
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user