Add support for miniMessage serialization in chat logs
This commit is contained in:
parent
c48d4f5c86
commit
dc513ac2f0
|
|
@ -15,15 +15,22 @@ import java.util.concurrent.CompletionException;
|
||||||
public class ChatLogQueries {
|
public class ChatLogQueries {
|
||||||
|
|
||||||
protected static void createChatLogTable() {
|
protected static void createChatLogTable() {
|
||||||
String nicknamesTableQuery = "CREATE TABLE IF NOT EXISTS chat_log("
|
String nicknamesTableQuery = """
|
||||||
+ "uuid CHAR(48) NOT NULL,"
|
CREATE TABLE chat_log (
|
||||||
+ "time_stamp TIMESTAMP(6) NOT NULL, "
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
+ "server VARCHAR(50) NOT NULL, "
|
uuid CHAR(36) NOT NULL,
|
||||||
+ "chat_message VARCHAR(300) NOT NULL, "
|
time_stamp TIMESTAMP(6) NOT NULL,
|
||||||
+ "blocked BIT(1) NOT NULL DEFAULT 0"
|
server VARCHAR(50) NOT NULL,
|
||||||
+ ")";
|
chat_message VARCHAR(300) NOT NULL,
|
||||||
|
mini_message JSON,
|
||||||
|
blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
|
||||||
try (PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(nicknamesTableQuery)) {
|
INDEX idx_time_stamp (time_stamp)
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(
|
||||||
|
nicknamesTableQuery)) {
|
||||||
preparedStatement.executeUpdate();
|
preparedStatement.executeUpdate();
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
ALogger.error("Failed to create chat log table", throwable);
|
ALogger.error("Failed to create chat log table", throwable);
|
||||||
|
|
@ -31,7 +38,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, blocked) VALUES (?, ?, ?, ?, ?)";
|
String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, 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);
|
||||||
|
|
@ -76,7 +83,9 @@ public class ChatLogQueries {
|
||||||
return chatLogs;
|
return chatLogs;
|
||||||
} catch (SQLException sqlException) {
|
} catch (SQLException sqlException) {
|
||||||
ALogger.error(String.format("Failed to retrieve messages for user %s", uuid), 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);
|
throw new CompletionException(String.format("Failed to retrieve messages for user %s", uuid),
|
||||||
|
sqlException
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -90,8 +99,13 @@ public class ChatLogQueries {
|
||||||
preparedStatement.setTimestamp(1, Timestamp.from(Instant.now().minus(duration)));
|
preparedStatement.setTimestamp(1, Timestamp.from(Instant.now().minus(duration)));
|
||||||
return preparedStatement.execute();
|
return preparedStatement.execute();
|
||||||
} catch (SQLException sqlException) {
|
} catch (SQLException sqlException) {
|
||||||
ALogger.error(String.format("Failed to delete messages older than %s days", duration.toDays()), sqlException);
|
ALogger.error(String.format("Failed to delete messages older than %s days", duration.toDays()),
|
||||||
throw new CompletionException(String.format("Failed to delete messages older than %s days", duration.toDays()), sqlException);
|
sqlException
|
||||||
|
);
|
||||||
|
throw new CompletionException(String.format("Failed to delete messages older than %s days",
|
||||||
|
duration.toDays()
|
||||||
|
), sqlException
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.alttd.chat.objects.chat_log;
|
package com.alttd.chat.objects.chat_log;
|
||||||
|
|
||||||
import com.alttd.chat.objects.BatchInsertable;
|
import com.alttd.chat.objects.BatchInsertable;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
|
|
@ -15,13 +17,15 @@ public class ChatLog implements BatchInsertable {
|
||||||
private final Instant timestamp;
|
private final Instant timestamp;
|
||||||
private final String server;
|
private final String server;
|
||||||
private final String message;
|
private final String message;
|
||||||
|
private final Component miniMessage;
|
||||||
private final boolean blocked;
|
private final boolean blocked;
|
||||||
|
|
||||||
protected ChatLog(UUID uuid, Instant timestamp, String server, String message, boolean blocked) {
|
protected ChatLog(UUID uuid, Instant timestamp, String server, String message, Component miniMessage, boolean blocked) {
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
this.miniMessage = miniMessage;
|
||||||
this.blocked = blocked;
|
this.blocked = blocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,7 +35,10 @@ public class ChatLog implements BatchInsertable {
|
||||||
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, message);
|
||||||
preparedStatement.setInt(5, blocked ? 1 : 0);
|
preparedStatement.setString(5,
|
||||||
|
miniMessage == null ? null : GsonComponentSerializer.gson().serialize(miniMessage)
|
||||||
|
);
|
||||||
|
preparedStatement.setInt(6, blocked ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getUuid() {
|
public UUID getUuid() {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ 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 net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
|
|
@ -18,8 +20,9 @@ public class ChatLogHandler {
|
||||||
private ScheduledExecutorService executorService = null;
|
private ScheduledExecutorService executorService = null;
|
||||||
|
|
||||||
public static ChatLogHandler getInstance(boolean enableLogging) {
|
public static ChatLogHandler getInstance(boolean enableLogging) {
|
||||||
if (instance == null)
|
if (instance == null) {
|
||||||
instance = new ChatLogHandler(enableLogging);
|
instance = new ChatLogHandler(enableLogging);
|
||||||
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,23 +38,30 @@ public class ChatLogHandler {
|
||||||
Duration deleteThreshold = Duration.ofDays(Config.CHAT_LOG_DELETE_OLDER_THAN_DAYS);
|
Duration deleteThreshold = Duration.ofDays(Config.CHAT_LOG_DELETE_OLDER_THAN_DAYS);
|
||||||
ChatLogQueries.deleteOldMessages(deleteThreshold).thenAccept(success -> {
|
ChatLogQueries.deleteOldMessages(deleteThreshold).thenAccept(success -> {
|
||||||
if (success) {
|
if (success) {
|
||||||
ALogger.info(String.format("Deleted all messages older than %s days from chat log database.", deleteThreshold.toDays()));
|
ALogger.info(String.format("Deleted all messages older than %s days from chat log database.",
|
||||||
|
deleteThreshold.toDays()
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
ALogger.warn(String.format("Failed to delete all messages older than %s days from chat log database.", deleteThreshold.toDays()));
|
ALogger.warn(String.format("Failed to delete all messages older than %s days from chat log database.",
|
||||||
|
deleteThreshold.toDays()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
executorService.scheduleAtFixedRate(() -> {
|
executorService.scheduleAtFixedRate(() -> {
|
||||||
saveToDatabase(false);
|
saveToDatabase(false);
|
||||||
ALogger.info(String.format("Running scheduler to save messages with a %d delay", Config.CHAT_LOG_SAVE_DELAY_MINUTES));
|
ALogger.info(String.format("Running scheduler to save messages with a %d delay",
|
||||||
|
Config.CHAT_LOG_SAVE_DELAY_MINUTES
|
||||||
|
));
|
||||||
},
|
},
|
||||||
Config.CHAT_LOG_SAVE_DELAY_MINUTES, Config.CHAT_LOG_SAVE_DELAY_MINUTES, TimeUnit.MINUTES);
|
Config.CHAT_LOG_SAVE_DELAY_MINUTES, Config.CHAT_LOG_SAVE_DELAY_MINUTES, TimeUnit.MINUTES
|
||||||
|
);
|
||||||
ALogger.info("Logging has started!");
|
ALogger.info("Logging has started!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuts down the executor service and saves the chat logs to the database.
|
* Shuts down the executor service and saves the chat logs to the database. Will throw an error if called on a
|
||||||
* Will throw an error if called on a ChatLogHandler that was started without logging
|
* ChatLogHandler that was started without logging
|
||||||
*/
|
*/
|
||||||
public void shutDown() {
|
public void shutDown() {
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
|
|
@ -66,7 +76,7 @@ public class ChatLogHandler {
|
||||||
return isSaving;
|
return isSaving;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void addLog(ChatLog chatLog) {
|
private synchronized void addLog(ChatLog chatLog) {
|
||||||
if (isBlocked()) {
|
if (isBlocked()) {
|
||||||
chatLogQueue.add(chatLog);
|
chatLogQueue.add(chatLog);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -105,12 +115,15 @@ public class ChatLogHandler {
|
||||||
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 chatMessage = resultSet.getString("chat_message");
|
String chatMessage = resultSet.getString("chat_message");
|
||||||
|
String stringMiniMessage = resultSet.getString("mini_message");
|
||||||
|
Component miniMessage = stringMiniMessage == null ? null : GsonComponentSerializer.gson().deserialize(
|
||||||
|
stringMiniMessage);
|
||||||
boolean chatMessageBlocked = resultSet.getInt("blocked") == 1;
|
boolean chatMessageBlocked = resultSet.getInt("blocked") == 1;
|
||||||
return new ChatLog(chatLogUUID, chatTimestamp, server, chatMessage, chatMessageBlocked);
|
return new ChatLog(chatLogUUID, chatTimestamp, server, chatMessage, miniMessage, chatMessageBlocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addChatLog(UUID uuid, String server, String message, boolean blocked) {
|
public void addChatLog(UUID uuid, String server, String message, Component miniMessage, boolean blocked) {
|
||||||
addLog(new ChatLog(uuid, Instant.now(), server, message, blocked));
|
addLog(new ChatLog(uuid, Instant.now(), server, message, miniMessage, blocked));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<List<ChatLog>> retrieveChatLogs(UUID uuid, Duration duration, String server) {
|
public CompletableFuture<List<ChatLog>> retrieveChatLogs(UUID uuid, Duration duration, String server) {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,9 @@ public class ChatListener implements Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component formatComponent = Component.text("%message%");
|
Component formatComponent = Component.text("%message%");
|
||||||
ComponentLike message = parseMessageContent(event.player(), plainTextComponentSerializer.serialize(event.originalMessage()));
|
ComponentLike message = parseMessageContent(event.player(),
|
||||||
|
plainTextComponentSerializer.serialize(event.originalMessage())
|
||||||
|
);
|
||||||
|
|
||||||
event.result(formatComponent.replaceText(TextReplacementConfig.builder().match("%message%").replacement(message).build()));
|
event.result(formatComponent.replaceText(TextReplacementConfig.builder().match("%message%").replacement(message).build()));
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +69,9 @@ public class ChatListener implements Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component formatComponent = Component.text("%message%");
|
Component formatComponent = Component.text("%message%");
|
||||||
ComponentLike message = parseMessageContent(event.player(), plainTextComponentSerializer.serialize(event.originalMessage()));
|
ComponentLike message = parseMessageContent(event.player(),
|
||||||
|
plainTextComponentSerializer.serialize(event.originalMessage())
|
||||||
|
);
|
||||||
|
|
||||||
event.result(formatComponent.replaceText(TextReplacementConfig.builder().match("%message%").replacement(message).build()));
|
event.result(formatComponent.replaceText(TextReplacementConfig.builder().match("%message%").replacement(message).build()));
|
||||||
}
|
}
|
||||||
|
|
@ -92,27 +96,31 @@ public class ChatListener implements Listener {
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
|
|
||||||
ComponentLike input = event.message().colorIfAbsent(NamedTextColor.WHITE);
|
ComponentLike input = event.message().colorIfAbsent(NamedTextColor.WHITE);
|
||||||
|
Component inputComponent = input.asComponent();
|
||||||
|
|
||||||
ModifiableString modifiableString = new ModifiableString(input.asComponent());
|
ModifiableString modifiableString = new ModifiableString(inputComponent);
|
||||||
|
|
||||||
// todo a better way for this
|
// todo a better way for this
|
||||||
if (!RegexManager.filterText(player.getName(), uuid, modifiableString, true, "chat", filterType -> {
|
if (!RegexManager.filterText(player.getName(), uuid, modifiableString, true, "chat", filterType -> {
|
||||||
if (!filterType.equals(FilterType.PUNISH)) {
|
if (!filterType.equals(FilterType.PUNISH)) {
|
||||||
ALogger.warn("Received another FilterType than punish when filtering chat and executing a filter action");
|
ALogger.warn("Received another FilterType than punish when filtering chat and executing a filter action");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||||
out.writeUTF("punish");
|
out.writeUTF("punish");
|
||||||
out.writeUTF(player.getName());
|
out.writeUTF(player.getName());
|
||||||
out.writeUTF(uuid.toString());
|
out.writeUTF(uuid.toString());
|
||||||
out.writeUTF(modifiableString.string());
|
out.writeUTF(modifiableString.string());
|
||||||
player.sendPluginMessage(ChatPlugin.getInstance(), Config.MESSAGECHANNEL, out.toByteArray());
|
player.sendPluginMessage(ChatPlugin.getInstance(), Config.MESSAGECHANNEL, out.toByteArray());
|
||||||
})) {
|
}
|
||||||
|
)) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
GalaxyUtility.sendBlockedNotification("Language", player,
|
GalaxyUtility.sendBlockedNotification("Language", player,
|
||||||
modifiableString.component(),
|
modifiableString.component(),
|
||||||
"");
|
""
|
||||||
chatLogHandler.addChatLog(uuid, ServerName.getServerName(), PlainTextComponentSerializer.plainText().serialize(input.asComponent()), true);
|
);
|
||||||
|
String originalMessage = PlainTextComponentSerializer.plainText().serialize(inputComponent);
|
||||||
|
chatLogHandler.addChatLog(uuid, ServerName.getServerName(), originalMessage, inputComponent, true);
|
||||||
return; // the message was blocked
|
return; // the message was blocked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +128,8 @@ public class ChatListener implements Listener {
|
||||||
.map(audience -> (Player) audience);
|
.map(audience -> (Player) audience);
|
||||||
|
|
||||||
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"));
|
||||||
}
|
}
|
||||||
Set<Player> receivers = stream.collect(Collectors.toSet());
|
Set<Player> receivers = stream.collect(Collectors.toSet());
|
||||||
|
|
@ -135,8 +144,13 @@ public class ChatListener implements Listener {
|
||||||
for (Player pingPlayer : playersToPing) {
|
for (Player pingPlayer : playersToPing) {
|
||||||
pingPlayer.playSound(pingPlayer.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1);
|
pingPlayer.playSound(pingPlayer.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1);
|
||||||
}
|
}
|
||||||
chatLogHandler.addChatLog(uuid, ServerName.getServerName(), modifiableString.string(), false);
|
chatLogHandler.addChatLog(uuid,
|
||||||
ALogger.info(PlainTextComponentSerializer.plainText().serialize(input.asComponent()));
|
ServerName.getServerName(),
|
||||||
|
modifiableString.string(),
|
||||||
|
modifiableString.component(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
ALogger.info(PlainTextComponentSerializer.plainText().serialize(inputComponent));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pingPlayers(Set<Player> playersToPing, ModifiableString modifiableString, Player player) {
|
private void pingPlayers(Set<Player> playersToPing, ModifiableString modifiableString, Player player) {
|
||||||
|
|
@ -152,10 +166,10 @@ public class ChatListener implements Listener {
|
||||||
ChatUser onlinePlayerUser = ChatUserManager.getChatUser(onlinePlayer.getUniqueId());
|
ChatUser onlinePlayerUser = ChatUserManager.getChatUser(onlinePlayer.getUniqueId());
|
||||||
if (namePattern.matcher(modifiableString.string()).find()) {
|
if (namePattern.matcher(modifiableString.string()).find()) {
|
||||||
modifiableString.replace(TextReplacementConfig.builder()
|
modifiableString.replace(TextReplacementConfig.builder()
|
||||||
.once()
|
.once()
|
||||||
.match(namePattern)
|
.match(namePattern)
|
||||||
.replacement(mention.append(onlinePlayerUser.getDisplayName()))
|
.replacement(mention.append(onlinePlayerUser.getDisplayName()))
|
||||||
.build());
|
.build());
|
||||||
//TODO replace all instances of \name with just name but using the match result so the capitalization doesn't change
|
//TODO replace all instances of \name with just name but using the match result so the capitalization doesn't change
|
||||||
// modifiableString.replace(TextReplacementConfig.builder()
|
// modifiableString.replace(TextReplacementConfig.builder()
|
||||||
// .once()
|
// .once()
|
||||||
|
|
@ -171,10 +185,10 @@ public class ChatListener implements Listener {
|
||||||
}
|
}
|
||||||
} else if (nickPattern.matcher(modifiableString.string()).find()) {
|
} else if (nickPattern.matcher(modifiableString.string()).find()) {
|
||||||
modifiableString.replace(TextReplacementConfig.builder()
|
modifiableString.replace(TextReplacementConfig.builder()
|
||||||
.once()
|
.once()
|
||||||
.match(nickPattern)
|
.match(nickPattern)
|
||||||
.replacement(mention.append(onlinePlayerUser.getDisplayName()))
|
.replacement(mention.append(onlinePlayerUser.getDisplayName()))
|
||||||
.build());
|
.build());
|
||||||
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId())
|
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId())
|
||||||
|| player.hasPermission("chat.ignorebypass")) {
|
|| player.hasPermission("chat.ignorebypass")) {
|
||||||
playersToPing.add(onlinePlayer);
|
playersToPing.add(onlinePlayer);
|
||||||
|
|
@ -192,7 +206,7 @@ public class ChatListener implements Listener {
|
||||||
Placeholder.component("prefixall", user.getPrefixAll()),
|
Placeholder.component("prefixall", user.getPrefixAll()),
|
||||||
Placeholder.component("staffprefix", user.getStaffPrefix()),
|
Placeholder.component("staffprefix", user.getStaffPrefix()),
|
||||||
Placeholder.component("message", message)
|
Placeholder.component("message", message)
|
||||||
);
|
);
|
||||||
|
|
||||||
return Utility.parseMiniMessage(Config.CHATFORMAT, placeholders);
|
return Utility.parseMiniMessage(Config.CHATFORMAT, placeholders);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user