Integrate ChatLogHandler across chat modules and add centralized message logging with validation through ChatLogMapper.

This commit is contained in:
akastijn 2026-07-19 23:00:40 +02:00
parent 92979e9789
commit 7492dfa418
17 changed files with 418 additions and 115 deletions

View File

@ -12,6 +12,10 @@ dependencies {
} }
compileOnly("org.spongepowered:configurate-yaml:4.2.0") // Configurate compileOnly("org.spongepowered:configurate-yaml:4.2.0") // Configurate
compileOnly("net.luckperms:api:5.5") // Luckperms compileOnly("net.luckperms:api:5.5") // Luckperms
//API validation
implementation("org.hibernate.validator:hibernate-validator:9.0.1.Final")
implementation("org.glassfish:jakarta.el:5.0.0-M1")
} }
publishing { publishing {

View File

@ -16,11 +16,14 @@ public class ChatLogQueries {
protected static void createChatLogTable() { protected static void createChatLogTable() {
String nicknamesTableQuery = """ String nicknamesTableQuery = """
CREATE TABLE chat_log ( CREATE TABLE IF NOT EXISTSq:Q chat_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGINT AUTO_INCREMENT PRIMARY KEY,
uuid CHAR(36) NOT NULL, uuid CHAR(36) NOT NULL,
time_stamp TIMESTAMP(6) NOT NULL, time_stamp TIMESTAMP(6) NOT NULL,
server VARCHAR(50) NOT NULL, server VARCHAR(50) NOT NULL,
type VARCHAR(16) NOT NULL DEFAULT 'public',
channel VARCHAR(36) DEFAULT NULL,
receiver CHAR(36) DEFAULT NULL,
chat_message VARCHAR(300) NOT NULL, chat_message VARCHAR(300) NOT NULL,
mini_message JSON, mini_message JSON,
blocked BOOLEAN NOT NULL DEFAULT FALSE, blocked BOOLEAN NOT NULL DEFAULT FALSE,
@ -38,7 +41,7 @@ public class ChatLogQueries {
} }
public static @NotNull CompletableFuture<Boolean> storeMessages(HashMap<UUID, List<ChatLog>> chatMessages) { public static @NotNull CompletableFuture<Boolean> storeMessages(HashMap<UUID, List<ChatLog>> chatMessages) {
String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, chat_message, mini_message, blocked) VALUES (?, ?, ?, ?, ?, ?)"; String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, type, channel, receiver, chat_message, mini_message, blocked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.createTransactionConnection()) { try (Connection connection = DatabaseConnection.createTransactionConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery); PreparedStatement preparedStatement = connection.prepareStatement(insertQuery);
@ -67,7 +70,7 @@ public class ChatLogQueries {
} }
public static @NotNull CompletableFuture<List<ChatLog>> retrieveMessages(ChatLogHandler chatLogHandler, UUID uuid, Duration duration, String server) { 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 = ?"; String query = "SELECT * FROM chat_log WHERE uuid = ? AND time_stamp > ? AND server = ? AND type = 'public'";
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.getConnection()) { try (Connection connection = DatabaseConnection.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(query); PreparedStatement preparedStatement = connection.prepareStatement(query);
@ -77,8 +80,7 @@ public class ChatLogQueries {
ResultSet resultSet = preparedStatement.executeQuery(); ResultSet resultSet = preparedStatement.executeQuery();
List<ChatLog> chatLogs = new ArrayList<>(); List<ChatLog> chatLogs = new ArrayList<>();
while (resultSet.next()) { while (resultSet.next()) {
ChatLog chatLog = chatLogHandler.loadFromResultSet(resultSet); chatLogHandler.loadFromResultSet(resultSet).ifPresent(chatLogs::add);
chatLogs.add(chatLog);
} }
return chatLogs; return chatLogs;
} catch (SQLException sqlException) { } catch (SQLException sqlException) {

View File

@ -1,6 +1,5 @@
package com.alttd.chat.database; package com.alttd.chat.database;
import com.alttd.chat.config.Config; import com.alttd.chat.config.Config;
import java.sql.Connection; import java.sql.Connection;
@ -27,6 +26,7 @@ public class DatabaseConnection {
/** /**
* Opens the connection if it's not already open. * Opens the connection if it's not already open.
*
* @throws SQLException If it can't create the connection. * @throws SQLException If it can't create the connection.
*/ */
public void openConnection() throws SQLException { public void openConnection() throws SQLException {
@ -45,14 +45,16 @@ public class DatabaseConnection {
} }
connection = DriverManager.getConnection( connection = DriverManager.getConnection(
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true"+ "jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true" +
"&useSSL=false", "&useSSL=false&preserveInstants=true",
Config.USERNAME, Config.PASSWORD); Config.USERNAME, Config.PASSWORD
);
} }
} }
/** /**
* Returns the connection for the database * Returns the connection for the database
*
* @return Returns the connection. * @return Returns the connection.
*/ */
public static Connection getConnection() { public static Connection getConnection() {
@ -69,13 +71,15 @@ public class DatabaseConnection {
* Creates a transactional database connection. * Creates a transactional database connection.
* *
* @return A {@code Connection} object representing the transactional database connection. * @return A {@code Connection} object representing the transactional database connection.
*
* @throws SQLException If there is an error creating the database connection. * @throws SQLException If there is an error creating the database connection.
*/ */
public static Connection createTransactionConnection() throws SQLException { public static Connection createTransactionConnection() throws SQLException {
connection = DriverManager.getConnection( connection = DriverManager.getConnection(
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true"+ "jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true" +
"&useSSL=false", "&useSSL=false&preserveInstants=true",
Config.USERNAME, Config.PASSWORD); Config.USERNAME, Config.PASSWORD
);
connection.setAutoCommit(false); connection.setAutoCommit(false);
return connection; return connection;
} }

View File

@ -18,8 +18,9 @@ public abstract class Toggleable {
public static Toggleable getToggleable(UUID uuid) { public static Toggleable getToggleable(UUID uuid) {
for (Toggleable toggleableClass : togglableClasses) { for (Toggleable toggleableClass : togglableClasses) {
if (toggleableClass.isToggled(uuid)) if (toggleableClass.isToggled(uuid)) {
return toggleableClass; return toggleableClass;
}
} }
return null; return null;
} }

View File

@ -1,6 +1,5 @@
package com.alttd.chat.objects.chat_log; package com.alttd.chat.objects.chat_log;
import com.alttd.altitudeweb.model.ChatMessageDto;
import com.alttd.chat.objects.BatchInsertable; import com.alttd.chat.objects.BatchInsertable;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@ -12,22 +11,20 @@ import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneOffset;
import java.util.UUID; import java.util.UUID;
@AllArgsConstructor @AllArgsConstructor
@Getter
public class ChatLog implements BatchInsertable { public class ChatLog implements BatchInsertable {
@Getter
private final UUID uuid; private final UUID uuid;
@Getter
private final Instant timestamp; private final Instant timestamp;
private final String server; private final String server;
@Getter private final ChatLogType type;
private final String channel;
private final String receiver;
private final String message; private final String message;
@Getter
private final Component miniMessage; private final Component miniMessage;
@Getter
private final boolean blocked; private final boolean blocked;
@Override @Override
@ -35,24 +32,13 @@ public class ChatLog implements BatchInsertable {
preparedStatement.setString(1, uuid.toString()); preparedStatement.setString(1, uuid.toString());
preparedStatement.setTimestamp(2, Timestamp.from(timestamp)); preparedStatement.setTimestamp(2, Timestamp.from(timestamp));
preparedStatement.setString(3, server); preparedStatement.setString(3, server);
preparedStatement.setString(4, message); preparedStatement.setString(4, ChatLogTypeMapper.toDb(type));
preparedStatement.setString(5, preparedStatement.setString(5, channel);
preparedStatement.setString(6, receiver);
preparedStatement.setString(7, message);
preparedStatement.setString(8,
miniMessage == null ? null : GsonComponentSerializer.gson().serialize(miniMessage) miniMessage == null ? null : GsonComponentSerializer.gson().serialize(miniMessage)
); );
preparedStatement.setInt(6, blocked ? 1 : 0); preparedStatement.setInt(9, blocked ? 1 : 0);
}
public ChatMessageDto toDto() {
ChatMessageDto chatMessageDto = new ChatMessageDto();
if (miniMessage == null) {
throw new IllegalArgumentException("MiniMessage cannot be null");
}
chatMessageDto.setMessage(GsonComponentSerializer.gson().serialize(miniMessage));
chatMessageDto.setChannel(ChatMessageDto.ChannelEnum.CHAT);
chatMessageDto.setServer(server);
chatMessageDto.setTimestamp(timestamp.atOffset(ZoneOffset.UTC));
chatMessageDto.setUuid(uuid);
chatMessageDto.setBlocked(blocked);
return chatMessageDto;
} }
} }

View File

@ -3,6 +3,7 @@ package com.alttd.chat.objects.chat_log;
import com.alttd.chat.config.Config; import com.alttd.chat.config.Config;
import com.alttd.chat.database.ChatLogQueries; import com.alttd.chat.database.ChatLogQueries;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -14,6 +15,7 @@ import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
@Slf4j
public class ChatLogHandler { public class ChatLogHandler {
private final ChatLogWebHandler chatLogWebHandler = new ChatLogWebHandler(); private final ChatLogWebHandler chatLogWebHandler = new ChatLogWebHandler();
@ -111,20 +113,48 @@ public class ChatLogHandler {
}); });
} }
public ChatLog loadFromResultSet(@NotNull ResultSet resultSet) throws SQLException { public Optional<ChatLog> loadFromResultSet(@NotNull ResultSet resultSet) throws SQLException {
UUID chatLogUUID = UUID.fromString(resultSet.getString("uuid")); UUID chatLogUUID = UUID.fromString(resultSet.getString("uuid"));
Instant chatTimestamp = resultSet.getTimestamp("time_stamp").toInstant(); Instant chatTimestamp = resultSet.getTimestamp("time_stamp").toInstant();
String server = resultSet.getString("server"); String server = resultSet.getString("server");
String stringType = resultSet.getString("type");
ChatLogType type;
try {
type = ChatLogTypeMapper.fromDb(stringType);
} catch (Exception e) {
log.error("Failed to load chat log from result set: {}", e.getMessage());
return Optional.empty();
}
String channel = resultSet.getString("channel");
String receiver = resultSet.getString("receiver");
String chatMessage = resultSet.getString("chat_message"); String chatMessage = resultSet.getString("chat_message");
String stringMiniMessage = resultSet.getString("mini_message"); String stringMiniMessage = resultSet.getString("mini_message");
Component miniMessage = stringMiniMessage == null ? null : GsonComponentSerializer.gson().deserialize( Component miniMessage = stringMiniMessage == null ? null : GsonComponentSerializer.gson().deserialize(
stringMiniMessage); stringMiniMessage);
boolean chatMessageBlocked = resultSet.getInt("blocked") == 1; boolean chatMessageBlocked = resultSet.getInt("blocked") == 1;
return new ChatLog(chatLogUUID, chatTimestamp, server, chatMessage, miniMessage, chatMessageBlocked); return Optional.of(new ChatLog(chatLogUUID,
chatTimestamp,
server,
type,
channel,
receiver,
chatMessage,
miniMessage,
chatMessageBlocked
));
} }
public void addChatLog(UUID uuid, String server, String message, Component miniMessage, boolean blocked) { public void addChatLog(UUID uuid, String server, String message, ChatLogType chatLogType, String channel, String receiver, Component miniMessage, boolean blocked) {
ChatLog chatLog = new ChatLog(uuid, Instant.now(), server, message, miniMessage, blocked); ChatLog chatLog = new ChatLog(uuid,
Instant.now(),
server,
chatLogType,
channel,
receiver,
message,
miniMessage,
blocked
);
addLog(chatLog); addLog(chatLog);
chatLogWebHandler.forwardChatLogToWeb(chatLog); chatLogWebHandler.forwardChatLogToWeb(chatLog);
} }

View File

@ -0,0 +1,50 @@
package com.alttd.chat.objects.chat_log;
import com.alttd.altitudeweb.model.ChatMessageDto;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import java.time.ZoneOffset;
import java.util.Optional;
import java.util.Set;
@Slf4j
@UtilityClass
public class ChatLogMapper {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
public Optional<ChatMessageDto> toDto(ChatLog chatLog) {
ChatMessageDto chatMessageDto = new ChatMessageDto();
if (chatLog.getMiniMessage() == null) {
throw new IllegalArgumentException("MiniMessage cannot be null");
}
chatMessageDto.setUuid(chatLog.getUuid());
chatMessageDto.setTimestamp(chatLog.getTimestamp().atOffset(ZoneOffset.UTC));
chatMessageDto.setServer(chatLog.getServer());
chatMessageDto.setType(ChatLogTypeMapper.toDto(chatLog.getType()));
chatMessageDto.setChannel(chatLog.getChannel());
chatMessageDto.setReceiver(chatLog.getReceiver());
chatMessageDto.setMessage(GsonComponentSerializer.gson().serialize(chatLog.getMiniMessage()));
chatMessageDto.setBlocked(chatLog.isBlocked());
if (checkIsInvalid(chatMessageDto)) {
return Optional.empty();
}
return Optional.of(chatMessageDto);
}
private boolean checkIsInvalid(ChatMessageDto chatMessageDto) {
Set<ConstraintViolation<ChatMessageDto>> violations = validator.validate(chatMessageDto);
if (violations.isEmpty()) {
return false;
}
log.error("Validation failed for chat message: {}, {}", chatMessageDto, violations);
return true;
}
}

View File

@ -0,0 +1,10 @@
package com.alttd.chat.objects.chat_log;
public enum ChatLogType {
PUBLIC,
GLOBAL,
PARTY,
GAC,
MSG,
CUSTOM;
}

View File

@ -0,0 +1,43 @@
package com.alttd.chat.objects.chat_log;
import com.alttd.altitudeweb.model.ChatMessageDto;
import lombok.experimental.UtilityClass;
@UtilityClass
public class ChatLogTypeMapper {
public static String toDb(ChatLogType chatLogType) {
return switch (chatLogType) {
case PUBLIC -> "public";
case GLOBAL -> "global";
case PARTY -> "party";
case GAC -> "gac";
case MSG -> "msg";
case CUSTOM -> "custom";
};
}
public ChatMessageDto.TypeEnum toDto(ChatLogType chatLogType) {
return switch (chatLogType) {
case PUBLIC -> ChatMessageDto.TypeEnum.PUBLIC;
case GLOBAL -> ChatMessageDto.TypeEnum.GLOBAL;
case PARTY -> ChatMessageDto.TypeEnum.PARTY;
case GAC -> ChatMessageDto.TypeEnum.GAC;
case MSG -> ChatMessageDto.TypeEnum.MSG;
case CUSTOM -> ChatMessageDto.TypeEnum.CUSTOM;
};
}
public ChatLogType fromDb(String dbType) {
return switch (dbType) {
case "public" -> ChatLogType.PUBLIC;
case "global" -> ChatLogType.GLOBAL;
case "party" -> ChatLogType.PARTY;
case "gac" -> ChatLogType.GAC;
case "msg" -> ChatLogType.MSG;
case "custom" -> ChatLogType.CUSTOM;
default -> throw new IllegalArgumentException("Invalid chat log type: " + dbType);
};
}
}

View File

@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -74,7 +75,11 @@ public class ChatLogWebHandler {
chatLogList.add(log); chatLogList.add(log);
} }
List<ChatMessageDto> batch = chatLogList.stream().map(ChatLog::toDto).toList(); List<ChatMessageDto> batch = chatLogList.stream()
.map(ChatLogMapper::toDto)
.filter(Optional::isPresent)
.map(Optional::get)
.toList();
if (!chatLogList.isEmpty()) { if (!chatLogList.isEmpty()) {
try { try {

View File

@ -35,11 +35,15 @@ public class ChatPlugin extends JavaPlugin {
instance = this; instance = this;
ALogger.init(getSLF4JLogger()); ALogger.init(getSLF4JLogger());
chatAPI = new ChatImplementation(); chatAPI = new ChatImplementation();
chatHandler = new ChatHandler(); ChatLogHandler chatLogHandler = ChatLogHandler.getInstance(true);
chatHandler = new ChatHandler(chatLogHandler);
DatabaseConnection.initialize(); DatabaseConnection.initialize();
serverConfig = new ServerConfig(ServerName.getServerName()); serverConfig = new ServerConfig(ServerName.getServerName());
ChatLogHandler chatLogHandler = ChatLogHandler.getInstance(true); registerListener(new PlayerListener(serverConfig),
registerListener(new PlayerListener(serverConfig), new ChatListener(chatLogHandler), new BookListener(), new ShutdownListener(chatLogHandler, this)); new ChatListener(chatLogHandler),
new BookListener(),
new ShutdownListener(chatLogHandler, this)
);
if (serverConfig.GLOBALCHAT) { if (serverConfig.GLOBALCHAT) {
registerCommand("globalchat", new GlobalChat()); registerCommand("globalchat", new GlobalChat());
registerCommand("toggleglobalchat", new ToggleGlobalChat()); registerCommand("toggleglobalchat", new ToggleGlobalChat());
@ -58,12 +62,15 @@ public class ChatPlugin extends JavaPlugin {
if (!(channel instanceof CustomChannel customChannel)) { if (!(channel instanceof CustomChannel customChannel)) {
continue; continue;
} }
this.getServer().getCommandMap().register(channel.getChannelName().toLowerCase(), new ChatChannel(customChannel)); this.getServer()
.getCommandMap()
.register(channel.getChannelName().toLowerCase(), new ChatChannel(customChannel));
} }
String messageChannel = Config.MESSAGECHANNEL; String messageChannel = Config.MESSAGECHANNEL;
getServer().getMessenger().registerOutgoingPluginChannel(this, messageChannel); getServer().getMessenger().registerOutgoingPluginChannel(this, messageChannel);
getServer().getMessenger().registerIncomingPluginChannel(this, messageChannel, new PluginMessage()); getServer().getMessenger()
.registerIncomingPluginChannel(this, messageChannel, new PluginMessage(chatLogHandler));
NicknamesEvents nicknamesEvents = new NicknamesEvents(); NicknamesEvents nicknamesEvents = new NicknamesEvents();
getServer().getMessenger().registerIncomingPluginChannel(this, messageChannel, nicknamesEvents); getServer().getMessenger().registerIncomingPluginChannel(this, messageChannel, nicknamesEvents);
@ -110,7 +117,9 @@ public class ChatPlugin extends JavaPlugin {
chatAPI.reloadConfig(); chatAPI.reloadConfig();
chatAPI.reloadChatFilters(); chatAPI.reloadChatFilters();
serverConfig = new ServerConfig(ServerName.getServerName()); serverConfig = new ServerConfig(ServerName.getServerName());
Bukkit.broadcast(Utility.parseMiniMessage("Reloaded ChatPlugin Config.").asComponent(), "command.chat.reloadchat"); Bukkit.broadcast(Utility.parseMiniMessage("Reloaded ChatPlugin Config.").asComponent(),
"command.chat.reloadchat"
);
ALogger.info("Reloaded ChatPlugin config."); ALogger.info("Reloaded ChatPlugin config.");
} }
} }

View File

@ -8,6 +8,8 @@ import com.alttd.chat.objects.ChatFilter;
import com.alttd.chat.objects.ChatUser; import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.ModifiableString; import com.alttd.chat.objects.ModifiableString;
import com.alttd.chat.objects.channels.CustomChannel; import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.objects.chat_log.ChatLogType;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.GalaxyUtility; import com.alttd.chat.util.GalaxyUtility;
import com.alttd.chat.util.ServerName; import com.alttd.chat.util.ServerName;
@ -28,6 +30,7 @@ import org.bukkit.Material;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -40,8 +43,10 @@ public class ChatHandler {
private final ChatPlugin plugin; private final ChatPlugin plugin;
private final ComponentLike GCNOTENABLED; private final ComponentLike GCNOTENABLED;
private final ChatLogHandler chatLogHandler;
public ChatHandler() { public ChatHandler(ChatLogHandler chatLogHandler) {
this.chatLogHandler = chatLogHandler;
plugin = ChatPlugin.getInstance(); plugin = ChatPlugin.getInstance();
GCNOTENABLED = Utility.parseMiniMessage(Config.GCNOTENABLED); GCNOTENABLED = Utility.parseMiniMessage(Config.GCNOTENABLED);
} }
@ -54,7 +59,7 @@ public class ChatHandler {
Placeholder.component("message", parseMessageContent(player, message)), Placeholder.component("message", parseMessageContent(player, message)),
Placeholder.component("sendername", player.name()), Placeholder.component("sendername", player.name()),
Placeholder.parsed("receivername", target) Placeholder.parsed("receivername", target)
); );
ComponentLike component = Utility.parseMiniMessage("<message>", placeholders); ComponentLike component = Utility.parseMiniMessage("<message>", placeholders);
@ -62,9 +67,10 @@ public class ChatHandler {
// todo a better way for this // todo a better way for this
if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "privatemessage")) { if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "privatemessage")) {
GalaxyUtility.sendBlockedNotification("DM Language", GalaxyUtility.sendBlockedNotification("DM Language",
player, player,
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())), Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
target); target
);
return; // the message was blocked return; // the message was blocked
} }
@ -73,7 +79,8 @@ public class ChatHandler {
sendPrivateMessage(player, target, "privatemessage", component.asComponent()); sendPrivateMessage(player, target, "privatemessage", component.asComponent());
ComponentLike spymessage = Utility.parseMiniMessage(Config.MESSAGESPY, placeholders); ComponentLike spymessage = Utility.parseMiniMessage(Config.MESSAGESPY, placeholders);
for (Player pl : Bukkit.getOnlinePlayers()) { for (Player pl : Bukkit.getOnlinePlayers()) {
if (pl.hasPermission(Config.SPYPERMISSION) && ChatUserManager.getChatUser(pl.getUniqueId()).isSpy() && !pl.equals(player) && !pl.getName().equalsIgnoreCase(target)) { if (pl.hasPermission(Config.SPYPERMISSION) && ChatUserManager.getChatUser(pl.getUniqueId())
.isSpy() && !pl.equals(player) && !pl.getName().equalsIgnoreCase(target)) {
pl.sendMessage(spymessage); pl.sendMessage(spymessage);
} }
} }
@ -88,15 +95,16 @@ public class ChatHandler {
Placeholder.component("message", messageComponent), Placeholder.component("message", messageComponent),
Placeholder.component("sendername", player.name()), Placeholder.component("sendername", player.name()),
Placeholder.parsed("receivername", target) Placeholder.parsed("receivername", target)
); );
ModifiableString modifiableString = new ModifiableString(messageComponent); ModifiableString modifiableString = new ModifiableString(messageComponent);
// todo a better way for this // todo a better way for this
if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "privatemessage")) { if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "privatemessage")) {
GalaxyUtility.sendBlockedNotification("DM Language", GalaxyUtility.sendBlockedNotification("DM Language",
player, player,
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())), Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
target); target
);
return; // the message was blocked return; // the message was blocked
} }
@ -108,7 +116,8 @@ public class ChatHandler {
sendPrivateMessage(player, target, "privatemessage", messageComponent); sendPrivateMessage(player, target, "privatemessage", messageComponent);
ComponentLike spymessage = Utility.parseMiniMessage(Config.MESSAGESPY, placeholders); ComponentLike spymessage = Utility.parseMiniMessage(Config.MESSAGESPY, placeholders);
for (Player pl : Bukkit.getOnlinePlayers()) { for (Player pl : Bukkit.getOnlinePlayers()) {
if (pl.hasPermission(Config.SPYPERMISSION) && ChatUserManager.getChatUser(pl.getUniqueId()).isSpy() && !pl.equals(player) && !pl.getName().equalsIgnoreCase(target)) { if (pl.hasPermission(Config.SPYPERMISSION) && ChatUserManager.getChatUser(pl.getUniqueId())
.isSpy() && !pl.equals(player) && !pl.getName().equalsIgnoreCase(target)) {
pl.sendMessage(spymessage); pl.sendMessage(spymessage);
} }
} }
@ -127,7 +136,9 @@ public class ChatHandler {
long timeLeft = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - user.getGcCooldown()); long timeLeft = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - user.getGcCooldown());
if (timeLeft <= Config.GCCOOLDOWN && !player.hasPermission("chat.globalchat.cooldownbypass")) { // player is on cooldown and should wait x seconds if (timeLeft <= Config.GCCOOLDOWN && !player.hasPermission("chat.globalchat.cooldownbypass")) { // player is on cooldown and should wait x seconds
player.sendRichMessage(Config.GCONCOOLDOWN, Placeholder.parsed("cooldown", Config.GCCOOLDOWN - timeLeft + "")); player.sendRichMessage(Config.GCONCOOLDOWN,
Placeholder.parsed("cooldown", Config.GCCOOLDOWN - timeLeft + "")
);
return; return;
} }
@ -138,7 +149,7 @@ public class ChatHandler {
Placeholder.component("prefix", prefix), Placeholder.component("prefix", prefix),
Placeholder.component("message", parseMessageContent(player, message)), Placeholder.component("message", parseMessageContent(player, message)),
Placeholder.parsed("server", ServerName.getServerName()) Placeholder.parsed("server", ServerName.getServerName())
); );
Component component = Utility.parseMiniMessage(Config.GCFORMAT, placeholders).asComponent(); Component component = Utility.parseMiniMessage(Config.GCFORMAT, placeholders).asComponent();
@ -146,13 +157,24 @@ public class ChatHandler {
// todo a better way for this // todo a better way for this
if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "globalchat")) { if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "globalchat")) {
GalaxyUtility.sendBlockedNotification("GC Language", GalaxyUtility.sendBlockedNotification("GC Language",
player, player,
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())), Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
""); ""
);
return; // the message was blocked return; // the message was blocked
} }
component = modifiableString.component(); component = modifiableString.component();
chatLogHandler.addChatLog(player.getUniqueId(),
Bukkit.getServerName(),
message,
ChatLogType.GLOBAL,
null,
null,
component,
false
);
user.setGcCooldown(System.currentTimeMillis()); user.setGcCooldown(System.currentTimeMillis());
sendPluginMessage(player, "globalchat", component); sendPluginMessage(player, "globalchat", component);
} }
@ -176,15 +198,20 @@ public class ChatHandler {
Placeholder.component("message", parseMessageContent(player, message)), Placeholder.component("message", parseMessageContent(player, message)),
Placeholder.parsed("server", ServerName.getServerName()), Placeholder.parsed("server", ServerName.getServerName()),
Placeholder.parsed("channel", channel.getChannelName()) Placeholder.parsed("channel", channel.getChannelName())
); );
Component component = Utility.parseMiniMessage(channel.getFormat(), placeholders).asComponent(); Component component = Utility.parseMiniMessage(channel.getFormat(), placeholders).asComponent();
ModifiableString modifiableString = new ModifiableString(component); ModifiableString modifiableString = new ModifiableString(component);
if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, channel.getChannelName())) { if (!RegexManager.filterText(player.getName(),
player.getUniqueId(),
modifiableString,
channel.getChannelName()
)) {
GalaxyUtility.sendBlockedNotification(channel.getChannelName() + " Language", GalaxyUtility.sendBlockedNotification(channel.getChannelName() + " Language",
player, player,
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())), Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
""); ""
);
ALogger.info("Refusing to send blocked chat message"); ALogger.info("Refusing to send blocked chat message");
return; return;
} }
@ -205,7 +232,7 @@ public class ChatHandler {
out.writeUTF(message); out.writeUTF(message);
out.writeUTF(GsonComponentSerializer.gson().serialize( out.writeUTF(GsonComponentSerializer.gson().serialize(
itemComponent(player.getInventory().getItemInMainHand()) itemComponent(player.getInventory().getItemInMainHand())
)); ));
player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray()); player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray());
// if (isMuted(player, message, "[" + party.getPartyName() + " Muted] ")) return; // if (isMuted(player, message, "[" + party.getPartyName() + " Muted] ")) return;
@ -255,16 +282,20 @@ public class ChatHandler {
if (!chatChannel.getServers().contains(ServerName.getServerName())) { if (!chatChannel.getServers().contains(ServerName.getServerName())) {
player.sendRichMessage("<red>Unable to send messages to <channel> in this server.</red>", player.sendRichMessage("<red>Unable to send messages to <channel> in this server.</red>",
Placeholder.parsed("channel", chatChannel.getChannelName())); Placeholder.parsed("channel", chatChannel.getChannelName())
);
ALogger.info(String.format("Not sending chat message due to [%s] not being in this channels config", ALogger.info(String.format("Not sending chat message due to [%s] not being in this channels config",
ServerName.getServerName())); ServerName.getServerName()
));
return; return;
} }
Stream<? extends Player> stream = Bukkit.getServer().getOnlinePlayers().stream() Stream<? extends Player> stream = Bukkit.getServer().getOnlinePlayers().stream()
.filter(p -> p.hasPermission(chatChannel.getPermission())); .filter(p -> p.hasPermission(chatChannel.getPermission()));
if (!player.hasPermission("chat.ignorebypass")) { if (!player.hasPermission("chat.ignorebypass")) {
stream = stream.filter(receiver -> !ChatUserManager.getChatUser(receiver.getUniqueId()).getIgnoredPlayers().contains(uuid) stream = stream.filter(receiver -> !ChatUserManager.getChatUser(receiver.getUniqueId())
.getIgnoredPlayers()
.contains(uuid)
|| receiver.hasPermission("chat.ignorebypass")); || receiver.hasPermission("chat.ignorebypass"));
} }
if (chatChannel.isLocal()) { if (chatChannel.isLocal()) {
@ -283,14 +314,39 @@ public class ChatHandler {
List<? extends Player> recipientPlayers = stream.toList(); List<? extends Player> recipientPlayers = stream.toList();
recipientPlayers.forEach(p -> p.sendMessage(component)); recipientPlayers.forEach(p -> p.sendMessage(component));
chatLogHandler.addChatLog(player.getUniqueId(),
Bukkit.getServerName(),
message,
ChatLogType.CUSTOM,
chatChannel.getChannelName(),
getLocationIfLocal(chatChannel, player),
component.asComponent(),
false
);
List<UUID> recipientUUIDs = recipientPlayers.stream().map(Entity::getUniqueId).toList(); List<UUID> recipientUUIDs = recipientPlayers.stream().map(Entity::getUniqueId).toList();
Bukkit.getServer().getOnlinePlayers().stream() Bukkit.getServer().getOnlinePlayers().stream()
.filter(onlinePlayer -> onlinePlayer.hasPermission(Config.SPYPERMISSION)) .filter(onlinePlayer -> onlinePlayer.hasPermission(Config.SPYPERMISSION))
.filter(onlinePlayer -> !recipientUUIDs.contains(onlinePlayer.getUniqueId())) .filter(onlinePlayer -> !recipientUUIDs.contains(onlinePlayer.getUniqueId()))
.forEach(onlinePlayer -> onlinePlayer.sendRichMessage(Config.CHANNEL_SPY, .forEach(onlinePlayer -> onlinePlayer.sendRichMessage(Config.CHANNEL_SPY,
Placeholder.component("sender", player.name()), Placeholder.component("sender", player.name()),
Placeholder.parsed("channel", chatChannel.getChannelName()), Placeholder.parsed("channel", chatChannel.getChannelName()),
Placeholder.parsed("message", message))); Placeholder.parsed("message", message)
));
}
private static @Nullable String getLocationIfLocal(CustomChannel chatChannel, Player player) {
if (!chatChannel.isLocal()) {
return null;
}
String format = String.format("%s;%s",
player.getLocation().getBlockX(),
player.getLocation().getBlockZ()
);
if (format.length() > 36) {
return null;
}
return format;
} }
private void sendPluginMessage(Player player, String channel, Component component) { private void sendPluginMessage(Player player, String channel, Component component) {
@ -326,9 +382,14 @@ public class ChatHandler {
if (user == null) { if (user == null) {
return false; return false;
} }
if (user.isMuted() || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission("chat.bypass-server-muted"))) { if (user.isMuted() || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission(
"chat.bypass-server-muted"))) {
// if (Database.get().isPlayerMuted(player.getUniqueId(), null) || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission("chat.bypass-server-muted"))) { // if (Database.get().isPlayerMuted(player.getUniqueId(), null) || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission("chat.bypass-server-muted"))) {
GalaxyUtility.sendBlockedNotification(prefix, player, Utility.parseMiniMessage(Utility.stripTokens(message)), ""); GalaxyUtility.sendBlockedNotification(prefix,
player,
Utility.parseMiniMessage(Utility.stripTokens(message)),
""
);
return true; return true;
} }
return false; return false;

View File

@ -7,6 +7,7 @@ import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.RegexManager; import com.alttd.chat.managers.RegexManager;
import com.alttd.chat.objects.*; import com.alttd.chat.objects.*;
import com.alttd.chat.objects.chat_log.ChatLogHandler; import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.objects.chat_log.ChatLogType;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.GalaxyUtility; import com.alttd.chat.util.GalaxyUtility;
import com.alttd.chat.util.ServerName; import com.alttd.chat.util.ServerName;
@ -129,6 +130,9 @@ public class ChatListener implements Listener {
chatLogHandler.addChatLog(uuid, chatLogHandler.addChatLog(uuid,
ServerName.getServerName(), ServerName.getServerName(),
originalMessage, originalMessage,
ChatLogType.PUBLIC,
null,
null,
render(player, inputComponent).asComponent(), render(player, inputComponent).asComponent(),
true true
); );
@ -162,6 +166,9 @@ public class ChatListener implements Listener {
chatLogHandler.addChatLog(uuid, chatLogHandler.addChatLog(uuid,
ServerName.getServerName(), ServerName.getServerName(),
modifiableString.string(), modifiableString.string(),
ChatLogType.PUBLIC,
null,
null,
input.asComponent(), input.asComponent(),
false false
); );

View File

@ -10,6 +10,8 @@ import com.alttd.chat.objects.Party;
import com.alttd.chat.objects.PartyUser; import com.alttd.chat.objects.PartyUser;
import com.alttd.chat.objects.channels.Channel; import com.alttd.chat.objects.channels.Channel;
import com.alttd.chat.objects.channels.CustomChannel; import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.objects.chat_log.ChatLogType;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.ServerName; import com.alttd.chat.util.ServerName;
import com.alttd.chat.util.Utility; import com.alttd.chat.util.Utility;
@ -18,6 +20,7 @@ import com.google.common.io.ByteStreams;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -29,6 +32,12 @@ import java.util.UUID;
public class PluginMessage implements PluginMessageListener { public class PluginMessage implements PluginMessageListener {
private final ChatLogHandler chatLogHandler;
public PluginMessage(ChatLogHandler chatLogHandler) {
this.chatLogHandler = chatLogHandler;
}
@Override @Override
public void onPluginMessageReceived(String channel, @NotNull Player ignored, byte[] bytes) { public void onPluginMessageReceived(String channel, @NotNull Player ignored, byte[] bytes) {
if (!channel.equals(Config.MESSAGECHANNEL)) { if (!channel.equals(Config.MESSAGECHANNEL)) {
@ -48,13 +57,25 @@ public class PluginMessage implements PluginMessageListener {
} }
ChatUser chatUser = ChatUserManager.getChatUser(uuid); ChatUser chatUser = ChatUserManager.getChatUser(uuid);
if (isTargetNotIgnored(chatUser, targetuuid)) { if (isTargetNotIgnored(chatUser, targetuuid)) {
player.sendMessage(GsonComponentSerializer.gson().deserialize(message)); Component component = GsonComponentSerializer.gson().deserialize(message);
player.sendMessage(component);
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1,
1); // todo load this from config 1
); // todo load this from config
ChatUser user = ChatUserManager.getChatUser(uuid); ChatUser user = ChatUserManager.getChatUser(uuid);
if (!user.getReplyContinueTarget().equalsIgnoreCase(target)) { if (!user.getReplyContinueTarget().equalsIgnoreCase(target)) {
user.setReplyTarget(target); user.setReplyTarget(target);
} }
// Handled here since this is after the ignore check and is only send once
chatLogHandler.addChatLog(uuid,
ServerName.getServerName(),
PlainTextComponentSerializer.plainText().serialize(component),
ChatLogType.MSG,
null,
player.getUniqueId().toString(),
component,
false
);
} }
} }
case "privatemessageout" -> { case "privatemessageout" -> {
@ -69,9 +90,18 @@ public class PluginMessage implements PluginMessageListener {
ChatUser chatUser = ChatUserManager.getChatUser(uuid); ChatUser chatUser = ChatUserManager.getChatUser(uuid);
if (isTargetNotIgnored(chatUser, targetuuid)) { if (isTargetNotIgnored(chatUser, targetuuid)) {
chatUser.setReplyTarget(target); chatUser.setReplyTarget(target);
player.sendMessage(GsonComponentSerializer.gson().deserialize(message)); Component component = GsonComponentSerializer.gson().deserialize(message);
// ChatUser user = ChatUserManager.getChatUser(uuid); player.sendMessage(component);
// user.setReplyTarget(target); // Handled here since this is after the ignore check and is only send once
chatLogHandler.addChatLog(uuid,
ServerName.getServerName(),
PlainTextComponentSerializer.plainText().serialize(component),
ChatLogType.MSG,
null,
player.getUniqueId().toString(),
component,
false
);
} }
} }
case "globalchat" -> { case "globalchat" -> {
@ -82,10 +112,11 @@ public class PluginMessage implements PluginMessageListener {
UUID uuid = UUID.fromString(in.readUTF()); UUID uuid = UUID.fromString(in.readUTF());
String message = in.readUTF(); String message = in.readUTF();
Component component = GsonComponentSerializer.gson().deserialize(message);
Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission(Config.GCPERMISSION)).forEach(p -> { Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission(Config.GCPERMISSION)).forEach(p -> {
ChatUser chatUser = ChatUserManager.getChatUser(p.getUniqueId()); ChatUser chatUser = ChatUserManager.getChatUser(p.getUniqueId());
if (isTargetNotIgnored(chatUser, uuid)) { if (isTargetNotIgnored(chatUser, uuid)) {
p.sendMessage(GsonComponentSerializer.gson().deserialize(message)); p.sendMessage(component);
} }
}); });
} }
@ -107,6 +138,7 @@ public class PluginMessage implements PluginMessageListener {
} }
chatChannel(in); chatChannel(in);
//TODO [Stijn] [2026-07-19]: handle custom channels
} }
case "tmppartyupdate" -> { case "tmppartyupdate" -> {
int id = Integer.parseInt(in.readUTF()); int id = Integer.parseInt(in.readUTF());

View File

@ -2,20 +2,20 @@ package com.alttd.velocitychat;
import com.alttd.chat.ChatAPI; import com.alttd.chat.ChatAPI;
import com.alttd.chat.ChatImplementation; import com.alttd.chat.ChatImplementation;
import com.alttd.chat.config.Config;
import com.alttd.chat.database.DatabaseConnection;
import com.alttd.chat.managers.ChatUserManager; import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.PartyManager; import com.alttd.chat.managers.PartyManager;
import com.alttd.chat.objects.ChatUser; import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.chat_log.ChatLogHandler; import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.util.ALogger;
import com.alttd.velocitychat.commands.*; import com.alttd.velocitychat.commands.*;
import com.alttd.chat.config.Config;
import com.alttd.chat.database.DatabaseConnection;
import com.alttd.velocitychat.handlers.ChatHandler; import com.alttd.velocitychat.handlers.ChatHandler;
import com.alttd.velocitychat.handlers.ServerHandler; import com.alttd.velocitychat.handlers.ServerHandler;
import com.alttd.velocitychat.listeners.ChatListener; import com.alttd.velocitychat.listeners.ChatListener;
import com.alttd.velocitychat.listeners.LiteBansListener; import com.alttd.velocitychat.listeners.LiteBansListener;
import com.alttd.velocitychat.listeners.ProxyPlayerListener;
import com.alttd.velocitychat.listeners.PluginMessageListener; import com.alttd.velocitychat.listeners.PluginMessageListener;
import com.alttd.chat.util.ALogger; import com.alttd.velocitychat.listeners.ProxyPlayerListener;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -36,7 +36,7 @@ import java.nio.file.Path;
description = "A chat plugin for Altitude Minecraft Server", description = "A chat plugin for Altitude Minecraft Server",
authors = {"destro174", "teri"}, authors = {"destro174", "teri"},
dependencies = {@Dependency(id = "luckperms"), @Dependency(id = "litebans"), @Dependency(id = "proxydiscordlink")} dependencies = {@Dependency(id = "luckperms"), @Dependency(id = "litebans"), @Dependency(id = "proxydiscordlink")}
) )
public class VelocityChat { public class VelocityChat {
private static VelocityChat plugin; private static VelocityChat plugin;
@ -67,7 +67,8 @@ public class VelocityChat {
PartyManager.initialize(); // load the parties from the db and add the previously loaded users to them PartyManager.initialize(); // load the parties from the db and add the previously loaded users to them
serverHandler = new ServerHandler(); serverHandler = new ServerHandler();
chatHandler = new ChatHandler(); ChatLogHandler chatLogHandler = new ChatLogHandler(true);
chatHandler = new ChatHandler(chatLogHandler);
server.getEventManager().register(this, new ChatListener()); server.getEventManager().register(this, new ChatListener());
server.getEventManager().register(this, new ProxyPlayerListener()); server.getEventManager().register(this, new ProxyPlayerListener());
new LiteBansListener().init(); // init the litebans api listeners new LiteBansListener().init(); // init the litebans api listeners
@ -89,7 +90,11 @@ public class VelocityChat {
ByteArrayDataOutput buf = ByteStreams.newDataOutput(); ByteArrayDataOutput buf = ByteStreams.newDataOutput();
buf.writeUTF("reloadconfig"); buf.writeUTF("reloadconfig");
ALogger.info("Reloaded ChatPlugin proxy config."); ALogger.info("Reloaded ChatPlugin proxy config.");
getProxy().getAllServers().stream().forEach(registeredServer -> registeredServer.sendPluginMessage(getChannelIdentifier(), buf.toByteArray())); getProxy().getAllServers()
.stream()
.forEach(registeredServer -> registeredServer.sendPluginMessage(getChannelIdentifier(),
buf.toByteArray()
));
} }
public File getDataDirectory() { public File getDataDirectory() {

View File

@ -6,6 +6,8 @@ import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.PartyManager; import com.alttd.chat.managers.PartyManager;
import com.alttd.chat.managers.RegexManager; import com.alttd.chat.managers.RegexManager;
import com.alttd.chat.objects.*; import com.alttd.chat.objects.*;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.objects.chat_log.ChatLogType;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.Utility; import com.alttd.chat.util.Utility;
import com.alttd.velocitychat.VelocityChat; import com.alttd.velocitychat.VelocityChat;
@ -32,6 +34,12 @@ import java.util.UUID;
public class ChatHandler { public class ChatHandler {
private final ChatLogHandler chatLogHandler;
public ChatHandler(ChatLogHandler chatLogHandler) {
this.chatLogHandler = chatLogHandler;
}
public void privateMessage(String sender, String target, String message) { public void privateMessage(String sender, String target, String message) {
UUID uuid = UUID.fromString(sender); UUID uuid = UUID.fromString(sender);
ChatUser senderUser = ChatUserManager.getChatUser(uuid); ChatUser senderUser = ChatUserManager.getChatUser(uuid);
@ -54,15 +62,23 @@ public class ChatHandler {
Placeholder.component("receiver", targetUser.getDisplayName()), Placeholder.component("receiver", targetUser.getDisplayName()),
Placeholder.unparsed("receivername", player2.getUsername()), Placeholder.unparsed("receivername", player2.getUsername()),
Placeholder.component("message", GsonComponentSerializer.gson().deserialize(message)), Placeholder.component("message", GsonComponentSerializer.gson().deserialize(message)),
Placeholder.unparsed("server", player.getCurrentServer().isPresent() ? player.getCurrentServer().get().getServerInfo().getName() : "Altitude")); Placeholder.unparsed("server",
player.getCurrentServer().isPresent() ? player.getCurrentServer()
.get()
.getServerInfo()
.getName() : "Altitude"
)
);
ServerConnection serverConnection; ServerConnection serverConnection;
//Log handled on receiving server since it is only received on one server and that's where the ignore check happens
if (player.getCurrentServer().isPresent() && player2.getCurrentServer().isPresent()) { if (player.getCurrentServer().isPresent() && player2.getCurrentServer().isPresent()) {
// redirect to the sender // redirect to the sender
serverConnection = player.getCurrentServer().get(); serverConnection = player.getCurrentServer().get();
Component component = Utility.parseMiniMessage(Config.MESSAGESENDER Component component = Utility.parseMiniMessage(Config.MESSAGESENDER
.replaceAll("<sendername>", player.getUsername()) .replaceAll("<sendername>", player.getUsername())
.replaceAll("<receivername>", player2.getUsername()), Placeholders).asComponent(); .replaceAll("<receivername>", player2.getUsername()), Placeholders
).asComponent();
ByteArrayDataOutput buf = ByteStreams.newDataOutput(); ByteArrayDataOutput buf = ByteStreams.newDataOutput();
buf.writeUTF("privatemessageout"); buf.writeUTF("privatemessageout");
buf.writeUTF(player.getUniqueId().toString()); buf.writeUTF(player.getUniqueId().toString());
@ -74,8 +90,9 @@ public class ChatHandler {
//redirect to the receiver //redirect to the receiver
serverConnection = player2.getCurrentServer().get(); serverConnection = player2.getCurrentServer().get();
component = Utility.parseMiniMessage(Config.MESSAGERECIEVER component = Utility.parseMiniMessage(Config.MESSAGERECIEVER
.replaceAll("<sendername>", player.getUsername()) .replaceAll("<sendername>", player.getUsername())
.replaceAll("<receivername>", player2.getUsername()), Placeholders).asComponent(); .replaceAll("<receivername>", player2.getUsername()), Placeholders
).asComponent();
buf = ByteStreams.newDataOutput(); buf = ByteStreams.newDataOutput();
buf.writeUTF("privatemessagein"); buf.writeUTF("privatemessagein");
buf.writeUTF(player2.getUniqueId().toString()); buf.writeUTF(player2.getUniqueId().toString());
@ -93,7 +110,7 @@ public class ChatHandler {
Placeholder.parsed("displayname", Utility.getDisplayName(player.getUniqueId(), player.getUsername())), Placeholder.parsed("displayname", Utility.getDisplayName(player.getUniqueId(), player.getUsername())),
Placeholder.unparsed("target", (target.isEmpty() ? " tried to say: " : " -> " + target + ": ")), Placeholder.unparsed("target", (target.isEmpty() ? " tried to say: " : " -> " + target + ": ")),
Placeholder.unparsed("input", input) Placeholder.unparsed("input", input)
); );
ComponentLike blockedNotification = Utility.parseMiniMessage(Config.NOTIFICATIONFORMAT, Placeholders); ComponentLike blockedNotification = Utility.parseMiniMessage(Config.NOTIFICATIONFORMAT, Placeholders);
serverConnection.getServer().getPlayersConnected().forEach(pl -> { serverConnection.getServer().getPlayersConnected().forEach(pl -> {
@ -134,15 +151,15 @@ public class ChatHandler {
} }
ComponentLike senderName = user.getDisplayName(); ComponentLike senderName = user.getDisplayName();
TagResolver Placeholders = TagResolver.resolver( TagResolver placeholders = TagResolver.resolver(
Placeholder.component("sender", senderName), Placeholder.component("sender", senderName),
Placeholder.component("sendername", senderName), Placeholder.component("sendername", senderName),
Placeholder.unparsed("partyname", party.getPartyName()), Placeholder.unparsed("partyname", party.getPartyName()),
Placeholder.component("message", parseMessageContent(player, message)), Placeholder.component("message", parseMessageContent(player, message)),
Placeholder.unparsed("server", serverConnection.getServer().getServerInfo().getName()) Placeholder.unparsed("server", serverConnection.getServer().getServerInfo().getName())
); );
Component partyMessage = Utility.parseMiniMessage(Config.PARTY_FORMAT, Placeholders).asComponent() Component partyMessage = Utility.parseMiniMessage(Config.PARTY_FORMAT, placeholders).asComponent()
.replaceText(TextReplacementConfig.builder().once().matchLiteral("[i]").replacement(item).build()); .replaceText(TextReplacementConfig.builder().once().matchLiteral("[i]").replacement(item).build());
ModifiableString modifiableString = new ModifiableString(partyMessage); ModifiableString modifiableString = new ModifiableString(partyMessage);
@ -155,7 +172,17 @@ public class ChatHandler {
sendPartyMessage(party, partyMessage, user.getIgnoredBy()); sendPartyMessage(party, partyMessage, user.getIgnoredBy());
ComponentLike spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, Placeholders); chatLogHandler.addChatLog(uuid,
serverConnection.getServer().getServerInfo().getName(),
PlainTextComponentSerializer.plainText().serialize(partyMessage),
ChatLogType.PARTY,
String.valueOf(party.getPartyId()),
null,
partyMessage,
false
);
ComponentLike spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, placeholders);
for (Player pl : serverConnection.getServer().getPlayersConnected()) { for (Player pl : serverConnection.getServer().getPlayersConnected()) {
if (pl.hasPermission(Config.SPYPERMISSION) && !party.getPartyUsersUuid().contains(pl.getUniqueId())) { if (pl.hasPermission(Config.SPYPERMISSION) && !party.getPartyUsersUuid().contains(pl.getUniqueId())) {
pl.sendMessage(spyMessage); pl.sendMessage(spyMessage);
@ -171,6 +198,17 @@ public class ChatHandler {
.stream() .stream()
.filter(target -> target.hasPermission("command.chat.globaladminchat")) .filter(target -> target.hasPermission("command.chat.globaladminchat"))
.forEach(target -> target.sendMessage(component)); .forEach(target -> target.sendMessage(component));
chatLogHandler.addChatLog(
new UUID(0, 0), //No origin uuid for GAC
"GAC", //No server for GAC
PlainTextComponentSerializer.plainText().serialize(component),
ChatLogType.GAC,
null,
null,
component,
false
);
} }
public void globalAdminChat(CommandSource commandSource, String message) { public void globalAdminChat(CommandSource commandSource, String message) {
@ -182,13 +220,17 @@ public class ChatHandler {
return; return;
} }
senderName = user.getDisplayName(); senderName = user.getDisplayName();
serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer().get().getServerInfo().getName() : "Altitude"; serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer()
.get()
.getServerInfo()
.getName() : "Altitude";
} }
TagResolver Placeholders = TagResolver.resolver( TagResolver Placeholders = TagResolver.resolver(
Placeholder.component("message", parseMessageContent(commandSource, message)), Placeholder.component("message", parseMessageContent(commandSource, message)),
Placeholder.component("sender", senderName), Placeholder.component("sender", senderName),
Placeholder.unparsed("server", serverName)); Placeholder.unparsed("server", serverName)
);
ComponentLike component = Utility.parseMiniMessage(Config.GACFORMAT, Placeholders); ComponentLike component = Utility.parseMiniMessage(Config.GACFORMAT, Placeholders);
@ -210,7 +252,8 @@ public class ChatHandler {
if (optionalPlayer.isEmpty()) { if (optionalPlayer.isEmpty()) {
targetUUID = ServerHandler.getPlayerUUID(recipient); targetUUID = ServerHandler.getPlayerUUID(recipient);
if (targetUUID == null) { if (targetUUID == null) {
commandSource.sendMessage(Utility.parseMiniMessage("<red>A player with this name hasn't logged in recently.")); // TOOD load from config commandSource.sendMessage(Utility.parseMiniMessage(
"<red>A player with this name hasn't logged in recently.")); // TOOD load from config
return; return;
} }
} else { } else {
@ -274,8 +317,10 @@ public class ChatHandler {
Placeholder.component("sender", chatUser.getDisplayName()), Placeholder.component("sender", chatUser.getDisplayName()),
Placeholder.component("message", Utility.parseMiniMessage(mail.getMessage())), Placeholder.component("message", Utility.parseMiniMessage(mail.getMessage())),
Placeholder.unparsed("date", date.toString()), Placeholder.unparsed("date", date.toString()),
Placeholder.unparsed("time_ago", getTimeAgo(Duration.between(date.toInstant(), new Date().toInstant()))) Placeholder.unparsed("time_ago",
); getTimeAgo(Duration.between(date.toInstant(), new Date().toInstant()))
)
);
ComponentLike mailMessage = Utility.parseMiniMessage(Config.mailBody, Placeholders); ComponentLike mailMessage = Utility.parseMiniMessage(Config.mailBody, Placeholders);
component = component.append(Component.newline()).append(mailMessage); component = component.append(Component.newline()).append(mailMessage);
} }

View File

@ -54,6 +54,7 @@ components:
- uuid - uuid
- name - name
- styledName - styledName
# TODO [Stijn] [2026-07-19]: Add who they ignore and who they are ignored by
properties: properties:
uuid: uuid:
@ -69,25 +70,33 @@ components:
required: required:
- uuid - uuid
- message - message
- channel
- server - server
- type
- timestamp - timestamp
- blocked - blocked
properties: properties:
uuid: uuid:
type: string type: string
format: uuid format: uuid
channel:
type: string
enum:
- CHAT
- PARTY
- AC
- GAC
message: message:
type: string type: string
server: server:
type: string type: string
type:
type: string
enum:
- PUBLIC
- GLOBAL
- PARTY
- GAC
- MSG
- CUSTOM
channel:
type: string
maxLength: 36
receiver:
type: string
maxLength: 36
timestamp: timestamp:
type: string type: string
format: date-time format: date-time