Compare commits
11
Commits
421f6655fd
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f4045d20d | ||
|
|
d3aa424261 | ||
|
|
79ded52842 | ||
|
|
776ea90d7d | ||
|
|
b98dd65203 | ||
|
|
9e647f83a8 | ||
|
|
0d2ac3763f | ||
|
|
e72f79cb29 | ||
|
|
5eb470cfb6 | ||
|
|
d99ac4c597 | ||
|
|
5a4140c2e2 |
@@ -432,7 +432,8 @@ public final class Config {
|
|||||||
getList(key + "servers", Collections.EMPTY_LIST),
|
getList(key + "servers", Collections.EMPTY_LIST),
|
||||||
getList(key + "alias", Collections.EMPTY_LIST),
|
getList(key + "alias", Collections.EMPTY_LIST),
|
||||||
getBoolean(key + "proxy", false),
|
getBoolean(key + "proxy", false),
|
||||||
getBoolean(key + "local", false)
|
getBoolean(key + "local", false),
|
||||||
|
getBoolean(key + "web", false)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,8 +547,7 @@ public final class Config {
|
|||||||
|
|
||||||
public static String EMOTELIST_HEADER = "<bold>Available Chat Emotes</bold><newline>";
|
public static String EMOTELIST_HEADER = "<bold>Available Chat Emotes</bold><newline>";
|
||||||
public static String EMOTELIST_ITEM = "<insert:\"<regex>\"><gold><regex></gold> : <emote></insert><newline>";
|
public static String EMOTELIST_ITEM = "<insert:\"<regex>\"><gold><regex></gold> : <emote></insert><newline>";
|
||||||
public static String EMOTELIST_FOOTER =
|
public static String EMOTELIST_FOOTER = "<green>----<< <click:run_command:''/emoteslist prev''><gray>Prev</gray></click> <page> <gray>/</gray> <pages> <click:run_command:''/emoteslist next''><gray>Next</gray></click> >>----";
|
||||||
"<green>----<< <gray>Prev</gray> <page> <gray>/</gray> <pages> <gray>Next</gray> >>----";
|
|
||||||
|
|
||||||
private static void emoteListCommand() {
|
private static void emoteListCommand() {
|
||||||
EMOTELIST_HEADER = getString("commands.emotelist.header", EMOTELIST_HEADER);
|
EMOTELIST_HEADER = getString("commands.emotelist.header", EMOTELIST_HEADER);
|
||||||
|
|||||||
@@ -40,35 +40,124 @@ public class ChatLogQueries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull CompletableFuture<Boolean> storeMessages(HashMap<UUID, List<ChatLog>> chatMessages) {
|
public static @NotNull CompletableFuture<Boolean> storeMessages(
|
||||||
String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, type, channel, receiver, chat_message, mini_message, blocked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
HashMap<UUID, List<ChatLog>> chatMessages
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
) {
|
||||||
try (Connection connection = DatabaseConnection.createTransactionConnection()) {
|
String insertQuery = """
|
||||||
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery);
|
INSERT INTO chat_log
|
||||||
for (List<ChatLog> chatLogList : chatMessages.values()) {
|
(uuid, time_stamp, server, type, channel, receiver,
|
||||||
for (ChatLog chatLog : chatLogList) {
|
chat_message, mini_message, blocked)
|
||||||
chatLog.prepareStatement(preparedStatement);
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
preparedStatement.addBatch();
|
""";
|
||||||
}
|
|
||||||
}
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
int[] updatedRowsCount = preparedStatement.executeBatch();
|
try (Connection connection = DatabaseConnection.createTransactionConnection();
|
||||||
boolean isSuccess = Arrays.stream(updatedRowsCount).allMatch(i -> i >= 0);
|
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery)) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
addMessagesToBatch(chatMessages, preparedStatement);
|
||||||
|
|
||||||
|
int[] updatedRowsCount = preparedStatement.executeBatch();
|
||||||
|
|
||||||
|
boolean isSuccess = Arrays.stream(updatedRowsCount)
|
||||||
|
.allMatch(i -> i >= 0 || i == Statement.SUCCESS_NO_INFO);
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
connection.commit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (isSuccess) {
|
|
||||||
connection.commit();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
connection.rollback();
|
connection.rollback();
|
||||||
ALogger.warn("Failed to store messages");
|
} catch (BatchUpdateException exception) {
|
||||||
return false;
|
connection.rollback();
|
||||||
|
|
||||||
|
ALogger.error(
|
||||||
|
"Failed to store chat batch. Trying messages individually.",
|
||||||
|
exception
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (SQLException sqlException) {
|
|
||||||
ALogger.error("Failed to store chat messages", sqlException);
|
return storeMessagesIndividually(
|
||||||
throw new CompletionException("Failed to store chat messages", sqlException);
|
connection,
|
||||||
|
preparedStatement,
|
||||||
|
chatMessages
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (SQLException exception) {
|
||||||
|
ALogger.error("Failed to store chat messages", exception);
|
||||||
|
throw new CompletionException(
|
||||||
|
"Failed to store chat messages",
|
||||||
|
exception
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void addMessagesToBatch(
|
||||||
|
HashMap<UUID, List<ChatLog>> chatMessages,
|
||||||
|
PreparedStatement preparedStatement
|
||||||
|
) throws SQLException {
|
||||||
|
for (List<ChatLog> chatLogList : chatMessages.values()) {
|
||||||
|
for (ChatLog chatLog : chatLogList) {
|
||||||
|
chatLog.prepareStatement(preparedStatement);
|
||||||
|
preparedStatement.addBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean storeMessagesIndividually(
|
||||||
|
Connection connection,
|
||||||
|
PreparedStatement preparedStatement,
|
||||||
|
HashMap<UUID, List<ChatLog>> chatMessages
|
||||||
|
) throws SQLException {
|
||||||
|
boolean storedAny = false;
|
||||||
|
|
||||||
|
for (List<ChatLog> chatLogList : chatMessages.values()) {
|
||||||
|
Iterator<ChatLog> iterator = chatLogList.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
ChatLog chatLog = iterator.next();
|
||||||
|
|
||||||
|
if (storeMessage(preparedStatement, chatLog)) {
|
||||||
|
storedAny = true;
|
||||||
|
} else {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.commit();
|
||||||
|
|
||||||
|
return storedAny;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean storeMessage(
|
||||||
|
PreparedStatement preparedStatement,
|
||||||
|
ChatLog chatLog
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
preparedStatement.clearParameters();
|
||||||
|
|
||||||
|
chatLog.prepareStatement(preparedStatement);
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (SQLException exception) {
|
||||||
|
logInvalidMessage(chatLog, exception);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void logInvalidMessage(
|
||||||
|
ChatLog chatLog,
|
||||||
|
SQLException exception
|
||||||
|
) {
|
||||||
|
ALogger.error(
|
||||||
|
"Failed to store chat message. Removing offending message: " + chatLog,
|
||||||
|
exception
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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 = ? AND type = 'public'";
|
String query = "SELECT * FROM chat_log WHERE uuid = ? AND time_stamp > ? AND server = ? AND type = 'public'";
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package com.alttd.chat.objects;
|
|||||||
import com.alttd.chat.database.Queries;
|
import com.alttd.chat.database.Queries;
|
||||||
import com.alttd.chat.objects.channels.Channel;
|
import com.alttd.chat.objects.channels.Channel;
|
||||||
import com.alttd.chat.util.Utility;
|
import com.alttd.chat.util.Utility;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.ComponentLike;
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
|
import net.kyori.adventure.text.TextComponent;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -89,9 +92,15 @@ public class ChatUser {
|
|||||||
return Utility.getStaffPrefix(uuid);
|
return Utility.getStaffPrefix(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComponentLike getPrefixAll() {
|
public ComponentLike getPrefixAll(boolean isWebMessage) {
|
||||||
//return prefixAll;
|
//return prefixAll;
|
||||||
return Utility.getPrefix(uuid, false);
|
ComponentLike prefix = Utility.getPrefix(uuid, false);
|
||||||
|
if (isWebMessage) {
|
||||||
|
//TODO [Stijn] [2026-08-08]: Check icon
|
||||||
|
TextComponent webIcon = Component.text("\uD83D\uDEDC").color(NamedTextColor.DARK_AQUA);
|
||||||
|
prefix = webIcon.append(prefix);
|
||||||
|
}
|
||||||
|
return prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getReplyTarget() {
|
public String getReplyTarget() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class EmoteList {
|
|||||||
TagResolver placeholders = TagResolver.resolver(
|
TagResolver placeholders = TagResolver.resolver(
|
||||||
Placeholder.unparsed("page", String.valueOf(page)),
|
Placeholder.unparsed("page", String.valueOf(page)),
|
||||||
Placeholder.unparsed("pages", String.valueOf(pages))
|
Placeholder.unparsed("pages", String.valueOf(pages))
|
||||||
);
|
);
|
||||||
Component list = Utility.parseMiniMessage(Config.EMOTELIST_HEADER, placeholders).asComponent();
|
Component list = Utility.parseMiniMessage(Config.EMOTELIST_HEADER, placeholders).asComponent();
|
||||||
|
|
||||||
for (int i = startIndex; i < endIndex; i++) {
|
for (int i = startIndex; i < endIndex; i++) {
|
||||||
@@ -41,7 +41,7 @@ public class EmoteList {
|
|||||||
TagResolver emotes = TagResolver.resolver(
|
TagResolver emotes = TagResolver.resolver(
|
||||||
Placeholder.parsed("regex", emote.getRegex()),
|
Placeholder.parsed("regex", emote.getRegex()),
|
||||||
Placeholder.parsed("emote", emote.getReplacement())
|
Placeholder.parsed("emote", emote.getReplacement())
|
||||||
);
|
);
|
||||||
list = list.append(Utility.parseMiniMessage(Config.EMOTELIST_ITEM, emotes));
|
list = list.append(Utility.parseMiniMessage(Config.EMOTELIST_ITEM, emotes));
|
||||||
}
|
}
|
||||||
list = list.append(Utility.parseMiniMessage(Config.EMOTELIST_FOOTER, placeholders));
|
list = list.append(Utility.parseMiniMessage(Config.EMOTELIST_FOOTER, placeholders));
|
||||||
@@ -57,7 +57,6 @@ public class EmoteList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void prevPage() {
|
public void prevPage() {
|
||||||
this.page -= 1;
|
|
||||||
this.page = Math.max(page - 1, 0);
|
this.page = Math.max(page - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.alttd.chat.objects;
|
|||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import net.luckperms.api.model.user.User;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -60,4 +62,5 @@ public abstract class Toggleable {
|
|||||||
|
|
||||||
public abstract void sendMessage(Player player, String message);
|
public abstract void sendMessage(Player player, String message);
|
||||||
|
|
||||||
|
public abstract void sendMessage(User user, OfflinePlayer offlinePlayer, String message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,37 @@
|
|||||||
package com.alttd.chat.objects.channels;
|
package com.alttd.chat.objects.channels;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class Channel {
|
public class Channel {
|
||||||
|
|
||||||
public static HashMap<String, Channel> channels = new HashMap<>();
|
public static HashMap<String, Channel> channels = new HashMap<>();
|
||||||
protected String permission;
|
private final String permission;
|
||||||
protected String channelName;
|
private final String channelName;
|
||||||
protected String format;
|
private final String format;
|
||||||
protected boolean proxy;
|
private final boolean proxy;
|
||||||
protected boolean local;
|
private final boolean local;
|
||||||
|
private final boolean web;
|
||||||
|
private final String webPath;
|
||||||
|
|
||||||
public Channel(String channelName, String format, boolean proxy, boolean local) {
|
public Channel(String channelName, String format, boolean proxy, boolean local, boolean web) {
|
||||||
this.permission = "chat.channel." + channelName.toLowerCase();
|
this.permission = "chat.channel." + channelName.toLowerCase();
|
||||||
this.channelName = channelName;
|
this.channelName = channelName;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.local = local;
|
this.local = local;
|
||||||
channels.put(channelName.toLowerCase(), this);
|
channels.put(channelName.toLowerCase(), this);
|
||||||
|
this.web = web;
|
||||||
|
this.webPath = web ? "web_" + channelName + "_chat" : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<Channel> getChannels() {
|
public static Collection<Channel> getChannels() {
|
||||||
return channels.values();
|
return channels.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPermission() {
|
|
||||||
return permission;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChannelName() {
|
|
||||||
return channelName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFormat() {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isProxy() {
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLocal() {
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Channel getChatChannel(String channelName) {
|
public static Channel getChatChannel(String channelName) {
|
||||||
return channels.get(channelName.toLowerCase());
|
return channels.get(channelName.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
package com.alttd.chat.objects.channels;
|
package com.alttd.chat.objects.channels;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class CustomChannel extends Channel {
|
public class CustomChannel extends Channel {
|
||||||
private final List<String> servers;
|
private final List<String> servers;
|
||||||
private final List<String> aliases;
|
private final List<String> aliases;
|
||||||
|
|
||||||
public CustomChannel(String channelName, String format, List<String> servers, List<String> aliases, boolean proxy,
|
public CustomChannel(String channelName, String format, List<String> servers, List<String> aliases, boolean proxy,
|
||||||
boolean local) {
|
boolean local, boolean web) {
|
||||||
super(channelName, format, proxy, local);
|
super(channelName, format, proxy, local, web);
|
||||||
this.permission = "chat.channel." + channelName.toLowerCase();
|
|
||||||
this.channelName = channelName;
|
|
||||||
this.format = format;
|
|
||||||
this.servers = servers;
|
this.servers = servers;
|
||||||
this.proxy = proxy;
|
|
||||||
this.aliases = aliases;
|
this.aliases = aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getServers() {
|
|
||||||
return servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getAliases() {
|
|
||||||
return aliases;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.alttd.chat.objects.channels;
|
package com.alttd.chat.objects.channels;
|
||||||
|
|
||||||
public abstract class DefaultChannel extends Channel{
|
public abstract class DefaultChannel extends Channel {
|
||||||
public DefaultChannel(String channelName, String format, boolean proxy) {
|
public DefaultChannel(String channelName, String format, boolean proxy) {
|
||||||
super(channelName, format, proxy, false);
|
super(channelName, format, proxy, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 com.alttd.chat.objects.chat_log.mapper.chat_log.ChatLogType;
|
||||||
|
import com.alttd.chat.objects.chat_log.mapper.chat_log.ChatLogTypeMapper;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@@ -41,4 +43,19 @@ public class ChatLog implements BatchInsertable {
|
|||||||
);
|
);
|
||||||
preparedStatement.setInt(9, blocked ? 1 : 0);
|
preparedStatement.setInt(9, blocked ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ChatLog{" +
|
||||||
|
"uuid=" + uuid +
|
||||||
|
", timestamp=" + timestamp +
|
||||||
|
", server='" + server + '\'' +
|
||||||
|
", type=" + type.name() +
|
||||||
|
", channel='" + channel + '\'' +
|
||||||
|
", receiver='" + receiver + '\'' +
|
||||||
|
", message='" + message + '\'' +
|
||||||
|
", miniMessage=" + (miniMessage == null ? null : GsonComponentSerializer.gson().serialize(miniMessage)) +
|
||||||
|
", blocked=" + blocked +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,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.objects.chat_log.mapper.chat_log.ChatLogType;
|
||||||
|
import com.alttd.chat.objects.chat_log.mapper.chat_log.ChatLogTypeMapper;
|
||||||
import com.alttd.chat.util.ALogger;
|
import com.alttd.chat.util.ALogger;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@@ -18,13 +20,13 @@ import java.util.concurrent.*;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class ChatLogHandler {
|
public class ChatLogHandler {
|
||||||
|
|
||||||
private final ChatLogWebHandler chatLogWebHandler = new ChatLogWebHandler();
|
private final WebHandler webHandler;
|
||||||
private static ChatLogHandler instance = null;
|
private static ChatLogHandler instance = null;
|
||||||
private ScheduledExecutorService executorService = null;
|
private ScheduledExecutorService executorService = null;
|
||||||
|
|
||||||
public static ChatLogHandler getInstance(boolean enableLogging) {
|
public static ChatLogHandler getInstance(WebHandler webHandler, boolean enableLogging) {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = new ChatLogHandler(enableLogging);
|
instance = new ChatLogHandler(webHandler, enableLogging);
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -33,7 +35,8 @@ public class ChatLogHandler {
|
|||||||
private final Queue<ChatLog> chatLogQueue = new ConcurrentLinkedQueue<>();
|
private final Queue<ChatLog> chatLogQueue = new ConcurrentLinkedQueue<>();
|
||||||
private final HashMap<UUID, List<ChatLog>> chatLogs = new HashMap<>();
|
private final HashMap<UUID, List<ChatLog>> chatLogs = new HashMap<>();
|
||||||
|
|
||||||
public ChatLogHandler(boolean enableLogging) {
|
public ChatLogHandler(WebHandler webHandler, boolean enableLogging) {
|
||||||
|
this.webHandler = webHandler;
|
||||||
if (!enableLogging) {
|
if (!enableLogging) {
|
||||||
ALogger.info("Logging is not enabled on this server.");
|
ALogger.info("Logging is not enabled on this server.");
|
||||||
return;
|
return;
|
||||||
@@ -156,7 +159,7 @@ public class ChatLogHandler {
|
|||||||
blocked
|
blocked
|
||||||
);
|
);
|
||||||
addLog(chatLog);
|
addLog(chatLog);
|
||||||
chatLogWebHandler.forwardChatLogToWeb(chatLog);
|
webHandler.forwardChatLogToWeb(chatLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<List<ChatLog>> retrieveChatLogs(UUID uuid, Duration duration, String server) {
|
public CompletableFuture<List<ChatLog>> retrieveChatLogs(UUID uuid, Duration duration, String server) {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.alttd.chat.objects.chat_log;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.invoker.ApiCallback;
|
||||||
|
import com.alttd.altitudeweb.invoker.ApiException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class SilentApiHandler implements ApiCallback<Void> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(ApiException e, int statusCode, Map<String, List<String>> responseHeaders) {
|
||||||
|
log.error("Failed to update server states", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Void result, int statusCode, Map<String, List<String>> responseHeaders) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+15
-5
@@ -5,9 +5,11 @@ import com.alttd.altitudeweb.invoker.ApiClient;
|
|||||||
import com.alttd.altitudeweb.invoker.ApiException;
|
import com.alttd.altitudeweb.invoker.ApiException;
|
||||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||||
import com.alttd.chat.config.Config;
|
import com.alttd.chat.config.Config;
|
||||||
|
import com.alttd.chat.objects.chat_log.mapper.chat_log.ChatLogMapper;
|
||||||
|
import com.alttd.chat.objects.chat_log.mapper.server_state.ServerMapper;
|
||||||
|
import com.alttd.chat.objects.chat_log.mapper.server_state.WebPlayer;
|
||||||
import com.alttd.chat.util.ALogger;
|
import com.alttd.chat.util.ALogger;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -17,17 +19,17 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public class ChatLogWebHandler {
|
@Slf4j
|
||||||
|
public class WebHandler {
|
||||||
|
|
||||||
private static final int MAX_QUEUE_SIZE = 100;
|
private static final int MAX_QUEUE_SIZE = 100;
|
||||||
private static final Logger log = LoggerFactory.getLogger(ChatLogWebHandler.class);
|
|
||||||
|
|
||||||
private final Queue<ChatLog> queue = new ConcurrentLinkedQueue<>();
|
private final Queue<ChatLog> queue = new ConcurrentLinkedQueue<>();
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
private final ChatApi chatApi;
|
private final ChatApi chatApi;
|
||||||
|
|
||||||
public ChatLogWebHandler() {
|
public WebHandler() {
|
||||||
ApiClient apiClient = new ApiClient();
|
ApiClient apiClient = new ApiClient();
|
||||||
apiClient.setBasePath(Config.CHAT_WEB_SERVER_BASE_URL);
|
apiClient.setBasePath(Config.CHAT_WEB_SERVER_BASE_URL);
|
||||||
|
|
||||||
@@ -46,6 +48,14 @@ public class ChatLogWebHandler {
|
|||||||
triggerSend();
|
triggerSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateServerState(String serverName, List<WebPlayer> playerList) {
|
||||||
|
try {
|
||||||
|
chatApi.updateServerStatesAsync(ServerMapper.toDto(serverName, playerList), new SilentApiHandler());
|
||||||
|
} catch (ApiException e) {
|
||||||
|
log.error("Failed to update server state", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void triggerSend() {
|
private void triggerSend() {
|
||||||
if (sending) {
|
if (sending) {
|
||||||
return;
|
return;
|
||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
package com.alttd.chat.objects.chat_log;
|
package com.alttd.chat.objects.chat_log.mapper.chat_log;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||||
|
import com.alttd.chat.objects.chat_log.ChatLog;
|
||||||
import jakarta.validation.ConstraintViolation;
|
import jakarta.validation.ConstraintViolation;
|
||||||
import jakarta.validation.Validation;
|
import jakarta.validation.Validation;
|
||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.alttd.chat.objects.chat_log;
|
package com.alttd.chat.objects.chat_log.mapper.chat_log;
|
||||||
|
|
||||||
public enum ChatLogType {
|
public enum ChatLogType {
|
||||||
PUBLIC,
|
PUBLIC,
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.alttd.chat.objects.chat_log;
|
package com.alttd.chat.objects.chat_log.mapper.chat_log;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.alttd.chat.objects.chat_log.mapper.server_state;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.model.ServerDto;
|
||||||
|
import com.alttd.altitudeweb.model.ServerStateDto;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class ServerMapper {
|
||||||
|
|
||||||
|
public static ServerStateDto toDto(String serverName, List<WebPlayer> playerList) {
|
||||||
|
ServerStateDto serverStateDto = new ServerStateDto();
|
||||||
|
ServerDto serverDto = new ServerDto();
|
||||||
|
|
||||||
|
serverDto.setName(serverName);
|
||||||
|
serverDto.setPlayers(playerList.stream().map(WebPlayerMapper::toDto).toList());
|
||||||
|
serverStateDto.setServers(List.of(serverDto));
|
||||||
|
return serverStateDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.alttd.chat.objects.chat_log.mapper.server_state;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.model.UserDto;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class UserMapper {
|
||||||
|
public static UserDto toDto(Player player) {
|
||||||
|
UserDto userDto = new UserDto();
|
||||||
|
userDto.setName(player.getName());
|
||||||
|
userDto.setUuid(player.getUniqueId());
|
||||||
|
userDto.setStyledName(GsonComponentSerializer.gson().serialize(player.displayName()));
|
||||||
|
return userDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.alttd.chat.objects.chat_log.mapper.server_state;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class WebPlayer {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String name;
|
||||||
|
private String styledName;
|
||||||
|
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package com.alttd.chat.objects.chat_log.mapper.server_state;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.model.UserDto;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class WebPlayerMapper {
|
||||||
|
|
||||||
|
public static UserDto toDto(WebPlayer webPlayer) {
|
||||||
|
UserDto userDto = new UserDto();
|
||||||
|
userDto.setName(webPlayer.getName());
|
||||||
|
userDto.setUuid(webPlayer.getUuid());
|
||||||
|
userDto.setStyledName(webPlayer.getStyledName());
|
||||||
|
return userDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,11 +9,14 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
|||||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
import net.kyori.adventure.text.minimessage.tag.standard.StandardTags;
|
import net.kyori.adventure.text.minimessage.tag.standard.StandardTags;
|
||||||
import net.luckperms.api.LuckPerms;
|
import net.luckperms.api.LuckPerms;
|
||||||
|
import net.luckperms.api.context.ImmutableContextSet;
|
||||||
import net.luckperms.api.model.group.Group;
|
import net.luckperms.api.model.group.Group;
|
||||||
import net.luckperms.api.model.user.User;
|
import net.luckperms.api.model.user.User;
|
||||||
import net.luckperms.api.node.Node;
|
import net.luckperms.api.node.Node;
|
||||||
|
import net.luckperms.api.query.QueryOptions;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -180,13 +183,27 @@ public class Utility {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasPermission(UUID uuid, String permission) {
|
public static CompletableFuture<User> getOrLoadUser(UUID uuid) {
|
||||||
|
return ChatAPI.get().getLuckPerms().getUserManager().loadUser(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompletableFuture<Boolean> hasPermission(UUID uuid, String permission) {
|
||||||
LuckPerms luckPerms = ChatAPI.get().getLuckPerms();
|
LuckPerms luckPerms = ChatAPI.get().getLuckPerms();
|
||||||
User user = luckPerms.getUserManager().getUser(uuid);
|
User user = luckPerms.getUserManager().getUser(uuid);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return false;
|
return getOrLoadUser(uuid)
|
||||||
|
.thenApply(loadedUser -> hasPermission(loadedUser, permission))
|
||||||
|
.exceptionally(throwable -> false);
|
||||||
}
|
}
|
||||||
return user.getCachedData().getPermissionData().checkPermission(permission).asBoolean();
|
return CompletableFuture.completedFuture(hasPermission(user, permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasPermission(User user, String permission) {
|
||||||
|
LuckPerms luckPerms = ChatAPI.get().getLuckPerms();
|
||||||
|
return user.getCachedData()
|
||||||
|
.getPermissionData(QueryOptions.contextual(ImmutableContextSet.of("server", luckPerms.getServerName())))
|
||||||
|
.checkPermission(permission)
|
||||||
|
.asBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ComponentLike applyColor(String message) {
|
public static ComponentLike applyColor(String message) {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.alttd.chat.web.handler_class;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PartyChatFromWeb {
|
||||||
|
|
||||||
|
private UUID sender;
|
||||||
|
private String message;
|
||||||
|
private String partyId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.alttd.chat.web.handler_class;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PlayerListState {
|
||||||
|
|
||||||
|
private List<UUID> activePlayers;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.alttd.chat.web.handler_class;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PrivateChatFromWeb {
|
||||||
|
|
||||||
|
private UUID sender;
|
||||||
|
private String message;
|
||||||
|
private UUID recipient;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.alttd.chat.web.handler_class;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PunishFromWeb {
|
||||||
|
|
||||||
|
private UUID executor;
|
||||||
|
private UUID target;
|
||||||
|
private String type;
|
||||||
|
private String reason;
|
||||||
|
private String time;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ dependencies {
|
|||||||
compileOnly("org.apache.commons:commons-lang3:3.17.0") // needs an alternative, already removed from upstream api and will be removed in server
|
compileOnly("org.apache.commons:commons-lang3:3.17.0") // needs an alternative, already removed from upstream api and will be removed in server
|
||||||
compileOnly("net.luckperms:api:5.5") // Luckperms
|
compileOnly("net.luckperms:api:5.5") // Luckperms
|
||||||
implementation("com.alttd.inventory_gui:InventoryGUI:1.1.5-SNAPSHOT")
|
implementation("com.alttd.inventory_gui:InventoryGUI:1.1.5-SNAPSHOT")
|
||||||
|
compileOnly("com.github.NEZNAMY", "TAB-API", "6.1.2") // TAB
|
||||||
|
compileOnly("net.dmulloy2:ProtocolLib:5.4.0") // ProtocolLib
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.alttd.chat;
|
package com.alttd.chat;
|
||||||
|
|
||||||
import com.alttd.chat.chat_web.ChatMessageSender;
|
import com.alttd.chat.chat_web.ChatMessageSender;
|
||||||
|
import com.alttd.chat.chat_web.handlers.WebChannelChatHandler;
|
||||||
import com.alttd.chat.chat_web.handlers.WebChatHandler;
|
import com.alttd.chat.chat_web.handlers.WebChatHandler;
|
||||||
|
import com.alttd.chat.chat_web.handlers.WebUpdatePlayerList;
|
||||||
import com.alttd.chat.commands.*;
|
import com.alttd.chat.commands.*;
|
||||||
import com.alttd.chat.config.Config;
|
import com.alttd.chat.config.Config;
|
||||||
import com.alttd.chat.config.ServerConfig;
|
import com.alttd.chat.config.ServerConfig;
|
||||||
@@ -13,11 +15,13 @@ import com.alttd.chat.nicknames.NicknamesEvents;
|
|||||||
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.ChatLogHandler;
|
||||||
|
import com.alttd.chat.objects.chat_log.WebHandler;
|
||||||
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;
|
||||||
import com.alttd.chat.web.SseSubscribeClient;
|
import com.alttd.chat.web.SseSubscribeClient;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@@ -25,6 +29,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ChatPlugin extends JavaPlugin {
|
public class ChatPlugin extends JavaPlugin {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -42,12 +47,13 @@ public class ChatPlugin extends JavaPlugin {
|
|||||||
instance = this;
|
instance = this;
|
||||||
ALogger.init(getSLF4JLogger());
|
ALogger.init(getSLF4JLogger());
|
||||||
chatAPI = new ChatImplementation();
|
chatAPI = new ChatImplementation();
|
||||||
ChatLogHandler chatLogHandler = ChatLogHandler.getInstance(true);
|
WebHandler webHandler = new WebHandler();
|
||||||
|
ChatLogHandler chatLogHandler = ChatLogHandler.getInstance(webHandler, true);
|
||||||
chatHandler = new ChatHandler(chatLogHandler);
|
chatHandler = new ChatHandler(chatLogHandler);
|
||||||
DatabaseConnection.initialize();
|
DatabaseConnection.initialize();
|
||||||
serverConfig = new ServerConfig(ServerName.getServerName());
|
serverConfig = new ServerConfig(ServerName.getServerName());
|
||||||
ChatMessageSender chatMessageSender = new ChatMessageSender(chatLogHandler, chatAPI.getLuckPerms());
|
ChatMessageSender chatMessageSender = new ChatMessageSender(chatLogHandler, chatAPI.getLuckPerms());
|
||||||
registerListener(new PlayerListener(serverConfig),
|
registerListener(new PlayerListener(serverConfig, webHandler, this),
|
||||||
new ChatListener(chatMessageSender),
|
new ChatListener(chatMessageSender),
|
||||||
new BookListener(),
|
new BookListener(),
|
||||||
new ShutdownListener(chatLogHandler, this)
|
new ShutdownListener(chatLogHandler, this)
|
||||||
@@ -70,9 +76,13 @@ public class ChatPlugin extends JavaPlugin {
|
|||||||
if (!(channel instanceof CustomChannel customChannel)) {
|
if (!(channel instanceof CustomChannel customChannel)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
ChatChannel chatChannel = new ChatChannel(customChannel, chatAPI.getLuckPerms());
|
||||||
this.getServer()
|
this.getServer()
|
||||||
.getCommandMap()
|
.getCommandMap()
|
||||||
.register(channel.getChannelName().toLowerCase(), new ChatChannel(customChannel));
|
.register(channel.getChannelName().toLowerCase(), chatChannel);
|
||||||
|
if (customChannel.getWebPath() != null) {
|
||||||
|
sseSubscribeClient.register(customChannel.getWebPath(), new WebChannelChatHandler(chatChannel));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String messageChannel = Config.MESSAGECHANNEL;
|
String messageChannel = Config.MESSAGECHANNEL;
|
||||||
@@ -96,6 +106,8 @@ public class ChatPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
private void registerWebHandlers(ChatMessageSender chatMessageSender, SseSubscribeClient sseSubscribeClient) {
|
private void registerWebHandlers(ChatMessageSender chatMessageSender, SseSubscribeClient sseSubscribeClient) {
|
||||||
sseSubscribeClient.register("web_chat", new WebChatHandler(chatMessageSender));
|
sseSubscribeClient.register("web_chat", new WebChatHandler(chatMessageSender));
|
||||||
|
//TODO [Stijn] [2026-08-22]: send to velocity as well so it can add the players to the tab command
|
||||||
|
sseSubscribeClient.register("player_state", new WebUpdatePlayerList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alttd.chat.objects.ChatUser;
|
|||||||
import com.alttd.chat.objects.FilterType;
|
import com.alttd.chat.objects.FilterType;
|
||||||
import com.alttd.chat.objects.ModifiableString;
|
import com.alttd.chat.objects.ModifiableString;
|
||||||
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.objects.chat_log.mapper.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;
|
||||||
@@ -25,9 +25,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
|||||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
import net.luckperms.api.LuckPerms;
|
import net.luckperms.api.LuckPerms;
|
||||||
import net.luckperms.api.context.ImmutableContextSet;
|
|
||||||
import net.luckperms.api.model.user.User;
|
import net.luckperms.api.model.user.User;
|
||||||
import net.luckperms.api.query.QueryOptions;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
@@ -50,32 +48,33 @@ public class ChatMessageSender {
|
|||||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
public void sendMessage(UUID sender, String message) {
|
public void sendMessage(UUID sender, String message) {
|
||||||
|
//TODO [Stijn] [2026-08-02]: Mark as from website
|
||||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(sender);
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(sender);
|
||||||
Component component = miniMessage.deserialize(message);
|
Component component = miniMessage.deserialize(message);
|
||||||
sendMessage(offlinePlayer, component);
|
sendMessage(offlinePlayer, component, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMessage(@NotNull OfflinePlayer offlinePlayer, Component message) {
|
public void sendMessage(@NotNull OfflinePlayer offlinePlayer, Component message, boolean isFromWeb) {
|
||||||
UUID uuid = offlinePlayer.getUniqueId();
|
UUID uuid = offlinePlayer.getUniqueId();
|
||||||
if (luckPerms.getUserManager().isLoaded(uuid)) {
|
if (luckPerms.getUserManager().isLoaded(uuid)) {
|
||||||
User user = luckPerms.getUserManager().getUser(uuid);
|
User user = luckPerms.getUserManager().getUser(uuid);
|
||||||
sendMessage(user, offlinePlayer, message);
|
sendMessage(user, offlinePlayer, message, isFromWeb);
|
||||||
} else {
|
} else {
|
||||||
luckPerms.getUserManager().loadUser(uuid).whenComplete((user, throwable) -> {
|
luckPerms.getUserManager().loadUser(uuid).whenComplete((user, throwable) -> {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
ALogger.error("Failed to load user: " + uuid, throwable);
|
ALogger.error("Failed to load user: " + uuid, throwable);
|
||||||
}
|
}
|
||||||
sendMessage(user, offlinePlayer, message);
|
sendMessage(user, offlinePlayer, message, isFromWeb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(User user, OfflinePlayer offlinePlayer, Component message) {
|
private void sendMessage(User user, OfflinePlayer offlinePlayer, Component message, boolean isFromWeb) {
|
||||||
if (offlinePlayer == null) {
|
if (offlinePlayer == null) {
|
||||||
ALogger.error("OfflinePlayer is null");
|
ALogger.error("OfflinePlayer is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ChatPlugin.getInstance().serverMuted() && !hasPermission(user, "chat.bypass-server-muted")) {
|
if (ChatPlugin.getInstance().serverMuted() && !Utility.hasPermission(user, "chat.bypass-server-muted")) {
|
||||||
sendBlockNotifIfOnline(offlinePlayer, message);
|
sendBlockNotifIfOnline(offlinePlayer, message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -87,7 +86,7 @@ public class ChatMessageSender {
|
|||||||
|
|
||||||
ModifiableString modifiableString = new ModifiableString(inputComponent);
|
ModifiableString modifiableString = new ModifiableString(inputComponent);
|
||||||
|
|
||||||
if (parseMessage(offlinePlayer, uuid, modifiableString, inputComponent)) {
|
if (parseMessage(offlinePlayer, uuid, modifiableString, inputComponent, isFromWeb)) {
|
||||||
//Parse message failed likely due to something the player said, already logged
|
//Parse message failed likely due to something the player said, already logged
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -104,7 +103,7 @@ public class ChatMessageSender {
|
|||||||
Stream<Player> stream = Bukkit.getOnlinePlayers().stream()
|
Stream<Player> stream = Bukkit.getOnlinePlayers().stream()
|
||||||
.map(audience -> (Player) audience);
|
.map(audience -> (Player) audience);
|
||||||
|
|
||||||
if (!hasPermission(user, "chat.ignorebypass")) {
|
if (!Utility.hasPermission(user, "chat.ignorebypass")) {
|
||||||
stream = stream.filter(receiver -> {
|
stream = stream.filter(receiver -> {
|
||||||
boolean isPlayerIgnored = ChatUserManager
|
boolean isPlayerIgnored = ChatUserManager
|
||||||
.getChatUser(receiver.getUniqueId())
|
.getChatUser(receiver.getUniqueId())
|
||||||
@@ -118,7 +117,7 @@ public class ChatMessageSender {
|
|||||||
Set<Player> playersToPing = new HashSet<>();
|
Set<Player> playersToPing = new HashSet<>();
|
||||||
pingPlayers(playersToPing, modifiableString, offlinePlayer, user);
|
pingPlayers(playersToPing, modifiableString, offlinePlayer, user);
|
||||||
|
|
||||||
Optional<ComponentLike> render = render(offlinePlayer, modifiableString.component());
|
Optional<ComponentLike> render = render(offlinePlayer, modifiableString.component(), false);
|
||||||
if (render.isEmpty()) {
|
if (render.isEmpty()) {
|
||||||
//Already logged
|
//Already logged
|
||||||
return true;
|
return true;
|
||||||
@@ -142,7 +141,7 @@ public class ChatMessageSender {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean parseMessage(OfflinePlayer offlinePlayer, UUID uuid, ModifiableString modifiableString, Component inputComponent) {
|
private boolean parseMessage(OfflinePlayer offlinePlayer, UUID uuid, ModifiableString modifiableString, Component inputComponent, boolean isWebMessage) {
|
||||||
// todo a better way for this
|
// todo a better way for this
|
||||||
if (!RegexManager.filterText(offlinePlayer.getName(), uuid, modifiableString, true, "chat",
|
if (!RegexManager.filterText(offlinePlayer.getName(), uuid, modifiableString, true, "chat",
|
||||||
filterType -> punishOnlinePlayer(filterType, uuid, modifiableString)
|
filterType -> punishOnlinePlayer(filterType, uuid, modifiableString)
|
||||||
@@ -152,7 +151,10 @@ public class ChatMessageSender {
|
|||||||
""
|
""
|
||||||
);
|
);
|
||||||
String originalMessage = PlainTextComponentSerializer.plainText().serialize(inputComponent);
|
String originalMessage = PlainTextComponentSerializer.plainText().serialize(inputComponent);
|
||||||
Optional<Component> component = render(offlinePlayer, inputComponent).map(ComponentLike::asComponent);
|
Optional<Component> component = render(offlinePlayer,
|
||||||
|
inputComponent,
|
||||||
|
isWebMessage
|
||||||
|
).map(ComponentLike::asComponent);
|
||||||
if (component.isEmpty()) {
|
if (component.isEmpty()) {
|
||||||
//Already logged
|
//Already logged
|
||||||
return true;
|
return true;
|
||||||
@@ -201,7 +203,7 @@ public class ChatMessageSender {
|
|||||||
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId())
|
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId())
|
||||||
.getIgnoredPlayers()
|
.getIgnoredPlayers()
|
||||||
.contains(offlinePlayer.getUniqueId())
|
.contains(offlinePlayer.getUniqueId())
|
||||||
|| hasPermission(user, "chat.ignorebypass")) {
|
|| Utility.hasPermission(user, "chat.ignorebypass")) {
|
||||||
playersToPing.add(onlinePlayer);
|
playersToPing.add(onlinePlayer);
|
||||||
}
|
}
|
||||||
} else if (nickPattern.matcher(modifiableString.string()).find()) {
|
} else if (nickPattern.matcher(modifiableString.string()).find()) {
|
||||||
@@ -213,14 +215,14 @@ public class ChatMessageSender {
|
|||||||
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId())
|
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId())
|
||||||
.getIgnoredPlayers()
|
.getIgnoredPlayers()
|
||||||
.contains(offlinePlayer.getUniqueId())
|
.contains(offlinePlayer.getUniqueId())
|
||||||
|| hasPermission(user, "chat.ignorebypass")) {
|
|| Utility.hasPermission(user, "chat.ignorebypass")) {
|
||||||
playersToPing.add(onlinePlayer);
|
playersToPing.add(onlinePlayer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ComponentLike> render(@NotNull OfflinePlayer offlinePlayer, @NotNull Component message) {
|
private Optional<ComponentLike> render(@NotNull OfflinePlayer offlinePlayer, @NotNull Component message, boolean isWebMessage) {
|
||||||
if (offlinePlayer.getName() == null) {
|
if (offlinePlayer.getName() == null) {
|
||||||
ALogger.error("Invalid offline player");
|
ALogger.error("Invalid offline player");
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@@ -231,7 +233,7 @@ public class ChatMessageSender {
|
|||||||
Placeholder.component("sender", user.getDisplayName()),
|
Placeholder.component("sender", user.getDisplayName()),
|
||||||
Placeholder.parsed("sendername", offlinePlayer.getName()),
|
Placeholder.parsed("sendername", offlinePlayer.getName()),
|
||||||
Placeholder.component("prefix", user.getPrefix()),
|
Placeholder.component("prefix", user.getPrefix()),
|
||||||
Placeholder.component("prefixall", user.getPrefixAll()),
|
Placeholder.component("prefixall", user.getPrefixAll(isWebMessage)),
|
||||||
Placeholder.component("staffprefix", user.getStaffPrefix()),
|
Placeholder.component("staffprefix", user.getStaffPrefix()),
|
||||||
Placeholder.component("message", message)
|
Placeholder.component("message", message)
|
||||||
);
|
);
|
||||||
@@ -268,11 +270,4 @@ public class ChatMessageSender {
|
|||||||
GalaxyUtility.sendBlockedNotification("Chat Muted", player, message, "");
|
GalaxyUtility.sendBlockedNotification("Chat Muted", player, message, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasPermission(User user, String permission) {
|
|
||||||
return user.getCachedData()
|
|
||||||
.getPermissionData(QueryOptions.contextual(ImmutableContextSet.of("server", luckPerms.getServerName())))
|
|
||||||
.checkPermission(permission)
|
|
||||||
.asBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.alttd.chat.chat_web.handlers;
|
||||||
|
|
||||||
|
import com.alttd.chat.commands.ChatChannel;
|
||||||
|
import com.alttd.chat.web.WebHandler;
|
||||||
|
import com.alttd.chat.web.handler_class.ChatFromWeb;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebChannelChatHandler implements WebHandler<ChatFromWeb> {
|
||||||
|
|
||||||
|
private final ChatChannel chatChannel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ChatFromWeb> type() {
|
||||||
|
return ChatFromWeb.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(ChatFromWeb chatFromWeb) {
|
||||||
|
this.chatChannel.execute(chatFromWeb);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
package com.alttd.chat.chat_web.handlers;
|
||||||
|
|
||||||
|
import com.alttd.chat.managers.ChatUserManager;
|
||||||
|
import com.alttd.chat.objects.ChatUser;
|
||||||
|
import com.alttd.chat.web.WebHandler;
|
||||||
|
import com.alttd.chat.web.handler_class.PlayerListState;
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.comphenix.protocol.wrappers.*;
|
||||||
|
import com.destroystokyo.paper.profile.PlayerProfile;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.neznamy.tab.api.TabAPI;
|
||||||
|
import me.neznamy.tab.api.TabPlayer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public final class WebUpdatePlayerList implements WebHandler<PlayerListState> {
|
||||||
|
private final ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||||
|
private final Map<UUID, FakeTabPlayer> registeredPlayers = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<PlayerListState> type() {
|
||||||
|
return PlayerListState.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void handle(PlayerListState playerListState) {
|
||||||
|
Set<UUID> bukkitOnline = new HashSet<>();
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
bukkitOnline.add(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<UUID, FakeTabPlayer> desiredPlayers = createDesiredPlayers(playerListState, bukkitOnline);
|
||||||
|
|
||||||
|
removeInactiveOrOnlinePlayers(desiredPlayers);
|
||||||
|
|
||||||
|
createNewPlayers(desiredPlayers);
|
||||||
|
|
||||||
|
updateTab(desiredPlayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateTab(Map<UUID, FakeTabPlayer> desiredPlayers) {
|
||||||
|
TabAPI tabAPI;
|
||||||
|
try {
|
||||||
|
tabAPI = TabAPI.getInstance();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
log.error("Failed to get TabAPI instance", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (TabPlayer onlinePlayer : tabAPI.getOnlinePlayers()) {
|
||||||
|
if (!desiredPlayers.containsKey(onlinePlayer.getUniqueId())) {
|
||||||
|
if (onlinePlayer.getGroup().equals("web")) {
|
||||||
|
onlinePlayer.setTemporaryGroup(null);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (onlinePlayer.getGroup().equals("web")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
onlinePlayer.setTemporaryGroup("web");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Map<UUID, FakeTabPlayer> createDesiredPlayers(PlayerListState playerListState, Set<UUID> bukkitOnline) {
|
||||||
|
Map<UUID, FakeTabPlayer> desiredPlayers = new LinkedHashMap<>();
|
||||||
|
for (ChatUser chatUser : playerListState.getActivePlayers()
|
||||||
|
.stream()
|
||||||
|
.map(ChatUserManager::getChatUser)
|
||||||
|
.toList()) {
|
||||||
|
if (chatUser == null || chatUser.getUuid() == null) {
|
||||||
|
log.warn("PlayerListState contained a null ChatUser or ChatUser with null UUID");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID uuid = chatUser.getUuid();
|
||||||
|
|
||||||
|
if (bukkitOnline.contains(uuid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
desiredPlayers.putIfAbsent(
|
||||||
|
uuid,
|
||||||
|
new FakeTabPlayer(uuid, getOfflinePlayerName(uuid))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return desiredPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNewPlayers(Map<UUID, FakeTabPlayer> desiredPlayers) {
|
||||||
|
for (FakeTabPlayer desired : desiredPlayers.values()) {
|
||||||
|
FakeTabPlayer existing = registeredPlayers.get(desired.uuid());
|
||||||
|
|
||||||
|
if (existing == null) {
|
||||||
|
addFakePlayer(desired, Bukkit.getOnlinePlayers());
|
||||||
|
registeredPlayers.put(desired.uuid(), desired);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFakePlayers(Set.of(desired.uuid()), Bukkit.getOnlinePlayers());
|
||||||
|
addFakePlayer(desired, Bukkit.getOnlinePlayers());
|
||||||
|
registeredPlayers.put(desired.uuid(), desired);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeInactiveOrOnlinePlayers(Map<UUID, FakeTabPlayer> desiredPlayers) {
|
||||||
|
Set<UUID> toRemove = new HashSet<>(registeredPlayers.keySet());
|
||||||
|
toRemove.removeAll(desiredPlayers.keySet());
|
||||||
|
|
||||||
|
if (!toRemove.isEmpty()) {
|
||||||
|
removeFakePlayers(toRemove, Bukkit.getOnlinePlayers());
|
||||||
|
toRemove.forEach(registeredPlayers::remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFakePlayer(FakeTabPlayer fakePlayer, Collection<? extends Player> viewers) {
|
||||||
|
PacketContainer packet = protocolManager.createPacket(PacketType.Play.Server.PLAYER_INFO);
|
||||||
|
|
||||||
|
packet.getPlayerInfoActions().write(
|
||||||
|
0,
|
||||||
|
EnumSet.of(
|
||||||
|
EnumWrappers.PlayerInfoAction.ADD_PLAYER,
|
||||||
|
EnumWrappers.PlayerInfoAction.UPDATE_LISTED,
|
||||||
|
EnumWrappers.PlayerInfoAction.UPDATE_DISPLAY_NAME,
|
||||||
|
EnumWrappers.PlayerInfoAction.UPDATE_GAME_MODE,
|
||||||
|
EnumWrappers.PlayerInfoAction.UPDATE_LATENCY
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
WrappedGameProfile profile = new WrappedGameProfile(
|
||||||
|
fakePlayer.uuid(),
|
||||||
|
fakePlayer.name()
|
||||||
|
);
|
||||||
|
|
||||||
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(fakePlayer.uuid());
|
||||||
|
PlayerProfile playerProfile = offlinePlayer.getPlayerProfile();
|
||||||
|
|
||||||
|
URL skinUrl = playerProfile.getTextures().getSkin();
|
||||||
|
|
||||||
|
if (skinUrl != null) {
|
||||||
|
String textureJson = """
|
||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"SKIN": {
|
||||||
|
"url": "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(skinUrl);
|
||||||
|
|
||||||
|
String textureValue = Base64.getEncoder()
|
||||||
|
.encodeToString(textureJson.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
profile.getProperties().put(
|
||||||
|
"textures",
|
||||||
|
new WrappedSignedProperty("textures", textureValue, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerInfoData data = new PlayerInfoData(
|
||||||
|
fakePlayer.uuid(),
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
EnumWrappers.NativeGameMode.SURVIVAL,
|
||||||
|
profile,
|
||||||
|
WrappedChatComponent.fromText(fakePlayer.name())
|
||||||
|
);
|
||||||
|
|
||||||
|
writePlayerInfoData(packet, List.of(data));
|
||||||
|
send(packet, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFakePlayers(Set<UUID> uuids, Collection<? extends Player> viewers) {
|
||||||
|
if (uuids.isEmpty() || viewers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketContainer packet = protocolManager.createPacket(PacketType.Play.Server.PLAYER_INFO_REMOVE);
|
||||||
|
packet.getUUIDLists().write(0, new ArrayList<>(uuids));
|
||||||
|
send(packet, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writePlayerInfoData(PacketContainer packet, List<PlayerInfoData> data) {
|
||||||
|
StructureModifier<List<PlayerInfoData>> modifier = packet.getPlayerInfoDataLists();
|
||||||
|
if (modifier.size() == 0) {
|
||||||
|
throw new IllegalStateException("PLAYER_INFO packet has no PlayerInfoData list");
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier.write(modifier.size() - 1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(PacketContainer packet, Collection<? extends Player> viewers) {
|
||||||
|
for (Player viewer : viewers) {
|
||||||
|
protocolManager.sendServerPacket(viewer, packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getOfflinePlayerName(UUID uuid) {
|
||||||
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
return offlinePlayer.getName() == null ? offlinePlayer.getUniqueId().toString() : offlinePlayer.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record FakeTabPlayer(UUID uuid, String name) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,17 @@ package com.alttd.chat.commands;
|
|||||||
|
|
||||||
import com.alttd.chat.config.Config;
|
import com.alttd.chat.config.Config;
|
||||||
import com.alttd.chat.objects.channels.CustomChannel;
|
import com.alttd.chat.objects.channels.CustomChannel;
|
||||||
|
import com.alttd.chat.util.ALogger;
|
||||||
import com.alttd.chat.util.ToggleableForCustomChannel;
|
import com.alttd.chat.util.ToggleableForCustomChannel;
|
||||||
|
import com.alttd.chat.util.Utility;
|
||||||
|
import com.alttd.chat.web.handler_class.ChatFromWeb;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
|
import net.luckperms.api.LuckPerms;
|
||||||
|
import net.luckperms.api.model.user.User;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.command.defaults.BukkitCommand;
|
import org.bukkit.command.defaults.BukkitCommand;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@@ -13,16 +20,19 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ChatChannel extends BukkitCommand {
|
public class ChatChannel extends BukkitCommand {
|
||||||
|
|
||||||
CustomChannel channel;
|
CustomChannel channel;
|
||||||
String command;
|
String command;
|
||||||
ToggleableForCustomChannel toggleableForCustomChannel;
|
ToggleableForCustomChannel toggleableForCustomChannel;
|
||||||
|
private final LuckPerms luckPerms;
|
||||||
private static final List<ChatChannel> activeCommands = new ArrayList<>();
|
private static final List<ChatChannel> activeCommands = new ArrayList<>();
|
||||||
|
|
||||||
public ChatChannel(CustomChannel channel) {
|
public ChatChannel(CustomChannel channel, LuckPerms luckPerms) {
|
||||||
super(channel.getChannelName().toLowerCase());
|
super(channel.getChannelName().toLowerCase());
|
||||||
|
this.luckPerms = luckPerms;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.command = channel.getChannelName().toLowerCase();
|
this.command = channel.getChannelName().toLowerCase();
|
||||||
this.description = "Chat channel named " + channel.getChannelName() + ".";
|
this.description = "Chat channel named " + channel.getChannelName() + ".";
|
||||||
@@ -40,9 +50,12 @@ public class ChatChannel extends BukkitCommand {
|
|||||||
|
|
||||||
if (args.length == 0 && player.hasPermission(channel.getPermission())) {
|
if (args.length == 0 && player.hasPermission(channel.getPermission())) {
|
||||||
player.sendRichMessage(Config.CUSTOM_CHANNEL_TOGGLED, TagResolver.resolver(
|
player.sendRichMessage(Config.CUSTOM_CHANNEL_TOGGLED, TagResolver.resolver(
|
||||||
Placeholder.unparsed("channel", channel.getChannelName()),
|
Placeholder.unparsed("channel", channel.getChannelName()),
|
||||||
Placeholder.component("status", toggleableForCustomChannel.toggle(player.getUniqueId())
|
Placeholder.component("status", toggleableForCustomChannel.toggle(player.getUniqueId())
|
||||||
? Config.TOGGLED_ON : Config.TOGGLED_OFF)));
|
? Config.TOGGLED_ON : Config.TOGGLED_OFF
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,4 +65,28 @@ public class ChatChannel extends BukkitCommand {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void execute(ChatFromWeb chatFromWeb) {
|
||||||
|
UUID uuid = chatFromWeb.getSender();
|
||||||
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
if (luckPerms.getUserManager().isLoaded(uuid)) {
|
||||||
|
User user = luckPerms.getUserManager().getUser(uuid);
|
||||||
|
if (user == null) {
|
||||||
|
ALogger.error("Failed to load loaded user: " + uuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Utility.hasPermission(user, channel.getPermission())) {
|
||||||
|
ALogger.warn("Web user %s does not have permission to use this channel".formatted(uuid.toString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toggleableForCustomChannel.sendMessage(user, offlinePlayer, chatFromWeb.getMessage());
|
||||||
|
} else {
|
||||||
|
luckPerms.getUserManager().loadUser(uuid).whenComplete((user, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
ALogger.error("Failed to load user: " + uuid, throwable);
|
||||||
|
}
|
||||||
|
toggleableForCustomChannel.sendMessage(user, offlinePlayer, chatFromWeb.getMessage());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ import org.bukkit.command.Command;
|
|||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class MuteServer implements CommandExecutor {
|
public class MuteServer implements CommandExecutor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -21,27 +18,27 @@ public class MuteServer implements CommandExecutor {
|
|||||||
if (!(sender instanceof Player player)) { // must be a player
|
if (!(sender instanceof Player player)) { // must be a player
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
new BukkitRunnable() {
|
Utility.getOrLoadUser(player.getUniqueId()).thenAcceptAsync(user -> {
|
||||||
@Override
|
if (!Utility.hasPermission(user, Config.SERVERMUTEPERMISSION)) {
|
||||||
public void run() {
|
sender.sendRichMessage("<red>You don't have permission to use this command.</red>");
|
||||||
UUID uuid = player.getUniqueId();
|
return;
|
||||||
if (!Utility.hasPermission(uuid, Config.SERVERMUTEPERMISSION)) {
|
|
||||||
sender.sendRichMessage("<red>You don't have permission to use this command.</red>");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatPlugin.getInstance().toggleServerMuted();
|
|
||||||
|
|
||||||
ComponentLike component;
|
|
||||||
if (ChatPlugin.getInstance().serverMuted()) {
|
|
||||||
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(), player.getName()) + " <red>muted</red><white> chat.");
|
|
||||||
} else {
|
|
||||||
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(), player.getName()) + " <green>un-muted</green><white> chat.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(component));
|
|
||||||
}
|
}
|
||||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
|
||||||
|
ChatPlugin.getInstance().toggleServerMuted();
|
||||||
|
|
||||||
|
ComponentLike component;
|
||||||
|
if (ChatPlugin.getInstance().serverMuted()) {
|
||||||
|
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(),
|
||||||
|
player.getName()
|
||||||
|
) + " <red>muted</red><white> chat.");
|
||||||
|
} else {
|
||||||
|
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(),
|
||||||
|
player.getName()
|
||||||
|
) + " <green>un-muted</green><white> chat.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Bukkit.getOnlinePlayers().forEach(onlinePlayer -> onlinePlayer.sendMessage(component));
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import com.alttd.chat.config.Config;
|
|||||||
import com.alttd.chat.objects.Toggleable;
|
import com.alttd.chat.objects.Toggleable;
|
||||||
import com.alttd.chat.util.ALogger;
|
import com.alttd.chat.util.ALogger;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
|
import net.luckperms.api.model.user.User;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
@@ -70,4 +72,9 @@ public class PartyChat extends Toggleable implements CommandExecutor {
|
|||||||
}
|
}
|
||||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(User user, OfflinePlayer offlinePlayer, String message) {
|
||||||
|
//TODO [Stijn] [2026-08-09]: Implement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.alttd.chat.commands;
|
package com.alttd.chat.commands;
|
||||||
|
|
||||||
import com.alttd.chat.ChatPlugin;
|
|
||||||
import com.alttd.chat.config.Config;
|
import com.alttd.chat.config.Config;
|
||||||
import com.alttd.chat.managers.ChatUserManager;
|
import com.alttd.chat.managers.ChatUserManager;
|
||||||
import com.alttd.chat.util.Utility;
|
import com.alttd.chat.util.Utility;
|
||||||
@@ -8,7 +7,6 @@ import org.bukkit.command.Command;
|
|||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -17,18 +15,17 @@ public class ToggleGlobalChat implements CommandExecutor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||||
if (!(sender instanceof Player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
new BukkitRunnable() {
|
UUID uuid = player.getUniqueId();
|
||||||
@Override
|
Utility.getOrLoadUser(uuid).thenAcceptAsync(user -> {
|
||||||
public void run() {
|
ChatUserManager.getChatUser(uuid);
|
||||||
UUID uuid = ((Player) sender).getUniqueId();
|
Utility.flipPermission(uuid, Config.GCPERMISSION);
|
||||||
ChatUserManager.getChatUser(uuid);
|
sender.sendRichMessage("You have turned globalchat " + (!Utility.hasPermission(user,
|
||||||
Utility.flipPermission(uuid, Config.GCPERMISSION);
|
Config.GCPERMISSION
|
||||||
sender.sendRichMessage("You have turned globalchat " + (!Utility.hasPermission(uuid, Config.GCPERMISSION) ? "<green>on." : "<red>off.")); // TODO load from config and minimessage
|
) ? "<green>on." : "<red>off.")); // TODO load from config and minimessage
|
||||||
}
|
});
|
||||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ 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.ChatLogHandler;
|
||||||
import com.alttd.chat.objects.chat_log.ChatLogType;
|
import com.alttd.chat.objects.chat_log.mapper.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;
|
||||||
import com.alttd.chat.util.Utility;
|
import com.alttd.chat.util.Utility;
|
||||||
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 lombok.extern.slf4j.Slf4j;
|
||||||
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.TextReplacementConfig;
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
@@ -24,20 +25,24 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
|||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
|
import net.luckperms.api.model.user.User;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
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 org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ChatHandler {
|
public class ChatHandler {
|
||||||
|
|
||||||
private final ChatPlugin plugin;
|
private final ChatPlugin plugin;
|
||||||
@@ -52,11 +57,16 @@ public class ChatHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void continuePrivateMessage(Player player, String target, String message) {
|
public void continuePrivateMessage(Player player, String target, String message) {
|
||||||
|
Utility.getOrLoadUser(player.getUniqueId())
|
||||||
|
.thenAccept(user -> continuePrivateMessage(user, player, target, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void continuePrivateMessage(User user, Player player, String target, String message) {
|
||||||
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
||||||
// user.setReplyTarget(target);
|
// user.setReplyTarget(target);
|
||||||
|
|
||||||
TagResolver placeholders = TagResolver.resolver(
|
TagResolver placeholders = TagResolver.resolver(
|
||||||
Placeholder.component("message", parseMessageContent(player, message)),
|
Placeholder.component("message", parseMessageContent(user, player, message)),
|
||||||
Placeholder.component("sendername", player.name()),
|
Placeholder.component("sendername", player.name()),
|
||||||
Placeholder.parsed("receivername", target)
|
Placeholder.parsed("receivername", target)
|
||||||
);
|
);
|
||||||
@@ -87,10 +97,15 @@ public class ChatHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void privateMessage(Player player, String target, String message) {
|
public void privateMessage(Player player, String target, String message) {
|
||||||
|
Utility.getOrLoadUser(player.getUniqueId())
|
||||||
|
.thenAccept(user -> privateMessage(user, player, target, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void privateMessage(User user, Player player, String target, String message) {
|
||||||
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
||||||
// user.setReplyTarget(target);
|
// user.setReplyTarget(target);
|
||||||
|
|
||||||
Component messageComponent = parseMessageContent(player, message);
|
Component messageComponent = parseMessageContent(user, player, message);
|
||||||
TagResolver placeholders = TagResolver.resolver(
|
TagResolver placeholders = TagResolver.resolver(
|
||||||
Placeholder.component("message", messageComponent),
|
Placeholder.component("message", messageComponent),
|
||||||
Placeholder.component("sendername", player.name()),
|
Placeholder.component("sendername", player.name()),
|
||||||
@@ -124,17 +139,22 @@ public class ChatHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void globalChat(Player player, String message) {
|
public void globalChat(Player player, String message) {
|
||||||
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
Utility.getOrLoadUser(player.getUniqueId())
|
||||||
if (!Utility.hasPermission(player.getUniqueId(), Config.GCPERMISSION)) {
|
.thenAccept(user -> globalChat(user, player, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void globalChat(User user, Player player, String message) {
|
||||||
|
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
|
||||||
|
if (!Utility.hasPermission(user, Config.GCPERMISSION)) {
|
||||||
player.sendMessage(GCNOTENABLED);// GC IS OFF INFORM THEM ABOUT THIS and cancel
|
player.sendMessage(GCNOTENABLED);// GC IS OFF INFORM THEM ABOUT THIS and cancel
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMuted(player, message, "[GC Muted] ")) {
|
if (isMuted(user, player, message, "[GC Muted] ")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long timeLeft = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - user.getGcCooldown());
|
long timeLeft = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - chatUser.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,
|
player.sendRichMessage(Config.GCONCOOLDOWN,
|
||||||
Placeholder.parsed("cooldown", Config.GCCOOLDOWN - timeLeft + "")
|
Placeholder.parsed("cooldown", Config.GCCOOLDOWN - timeLeft + "")
|
||||||
@@ -142,12 +162,12 @@ public class ChatHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ComponentLike senderName = user.getDisplayName();
|
ComponentLike senderName = chatUser.getDisplayName();
|
||||||
ComponentLike prefix = user.getPrefix();
|
ComponentLike prefix = chatUser.getPrefix();
|
||||||
TagResolver placeholders = TagResolver.resolver(
|
TagResolver placeholders = TagResolver.resolver(
|
||||||
Placeholder.component("sender", senderName),
|
Placeholder.component("sender", senderName),
|
||||||
Placeholder.component("prefix", prefix),
|
Placeholder.component("prefix", prefix),
|
||||||
Placeholder.component("message", parseMessageContent(player, message)),
|
Placeholder.component("message", parseMessageContent(user, player, message)),
|
||||||
Placeholder.parsed("server", ServerName.getServerName())
|
Placeholder.parsed("server", ServerName.getServerName())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -175,40 +195,47 @@ public class ChatHandler {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
user.setGcCooldown(System.currentTimeMillis());
|
chatUser.setGcCooldown(System.currentTimeMillis());
|
||||||
sendPluginMessage(player, "globalchat", component);
|
sendPluginMessage(player, "globalchat", component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void chatChannel(Player player, CustomChannel channel, String message) {
|
public void chatChannel(User user, OfflinePlayer offlinePlayer, CustomChannel channel, String message) {
|
||||||
if (!player.hasPermission(channel.getPermission())) {
|
if (!Utility.hasPermission(user, channel.getPermission())) {
|
||||||
player.sendRichMessage("<red>You don't have permission to use this channel.</red>");
|
if (offlinePlayer.isOnline()) {
|
||||||
|
Player onlinePlayer = offlinePlayer.getPlayer();
|
||||||
|
if (onlinePlayer == null) {
|
||||||
|
log.error("Player is online but getPlayer() returned null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onlinePlayer.sendRichMessage("<red>You don't have permission to use this channel.</red>");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMuted(player, message, "[" + channel.getChannelName() + " Muted] ")) {
|
if (isMuted(user, offlinePlayer, message, "[" + channel.getChannelName() + " Muted] ")) {
|
||||||
ALogger.info("Refusing to send message by muted user");
|
ALogger.info("Refusing to send message by muted user");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
ChatUser chatUser = ChatUserManager.getChatUser(offlinePlayer.getUniqueId());
|
||||||
ComponentLike senderName = user.getDisplayName();
|
ComponentLike senderName = chatUser.getDisplayName();
|
||||||
|
|
||||||
TagResolver placeholders = TagResolver.resolver(
|
TagResolver placeholders = TagResolver.resolver(
|
||||||
Placeholder.component("sender", senderName),
|
Placeholder.component("sender", senderName),
|
||||||
Placeholder.component("message", parseMessageContent(player, message)),
|
Placeholder.component("message", parseMessageContent(user, offlinePlayer, 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(),
|
if (!RegexManager.filterText(offlinePlayer.getName(),
|
||||||
player.getUniqueId(),
|
offlinePlayer.getUniqueId(),
|
||||||
modifiableString,
|
modifiableString,
|
||||||
channel.getChannelName()
|
channel.getChannelName()
|
||||||
)) {
|
)) {
|
||||||
GalaxyUtility.sendBlockedNotification(channel.getChannelName() + " Language",
|
GalaxyUtility.sendBlockedNotification(channel.getChannelName() + " Language",
|
||||||
player,
|
offlinePlayer,
|
||||||
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
@@ -219,9 +246,9 @@ public class ChatHandler {
|
|||||||
component = modifiableString.component();
|
component = modifiableString.component();
|
||||||
|
|
||||||
if (channel.isProxy()) {
|
if (channel.isProxy()) {
|
||||||
sendChatChannelMessage(player, channel.getChannelName(), "chatchannel", component, message);
|
sendChatChannelMessage(offlinePlayer, channel.getChannelName(), "chatchannel", component, message);
|
||||||
} else {
|
} else {
|
||||||
sendChatChannelMessage(channel, player.getUniqueId(), component, message);
|
sendChatChannelMessage(channel, offlinePlayer.getUniqueId(), component, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,27 +393,36 @@ public class ChatHandler {
|
|||||||
player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray());
|
player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendChatChannelMessage(Player player, String chatChannelName, String channel, Component component, String ignored) {
|
public void sendChatChannelMessage(OfflinePlayer player, String chatChannelName, String channel, Component component, String ignored) {
|
||||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||||
out.writeUTF(channel);
|
out.writeUTF(channel);
|
||||||
out.writeUTF(chatChannelName);
|
out.writeUTF(chatChannelName);
|
||||||
out.writeUTF(player.getUniqueId().toString());
|
out.writeUTF(player.getUniqueId().toString());
|
||||||
out.writeUTF(GsonComponentSerializer.gson().serialize(component));
|
out.writeUTF(GsonComponentSerializer.gson().serialize(component));
|
||||||
player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray());
|
Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
|
||||||
|
if (player.isOnline() && player.getPlayer() != null) {
|
||||||
|
player.getPlayer().sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray());
|
||||||
|
} else {
|
||||||
|
//TODO [Stijn] [2026-08-09]: Validate that this works
|
||||||
|
onlinePlayers.stream().findFirst().ifPresent(p ->
|
||||||
|
p.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start - move these to util
|
// Start - move these to util
|
||||||
|
|
||||||
private boolean isMuted(Player player, String message, String prefix) {
|
private boolean isMuted(User user, OfflinePlayer offlinePlayer, String message, String prefix) {
|
||||||
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
ChatUser chatUser = ChatUserManager.getChatUser(offlinePlayer.getUniqueId());
|
||||||
if (user == null) {
|
if (chatUser == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (user.isMuted() || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission(
|
if (chatUser.isMuted() || (ChatPlugin.getInstance()
|
||||||
"chat.bypass-server-muted"))) {
|
.serverMuted() && !Utility.hasPermission(user,
|
||||||
|
"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,
|
GalaxyUtility.sendBlockedNotification(prefix,
|
||||||
player,
|
offlinePlayer,
|
||||||
Utility.parseMiniMessage(Utility.stripTokens(message)),
|
Utility.parseMiniMessage(Utility.stripTokens(message)),
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
@@ -437,11 +473,11 @@ public class ChatHandler {
|
|||||||
}
|
}
|
||||||
// end - move these to util
|
// end - move these to util
|
||||||
|
|
||||||
private Component parseMessageContent(Player player, String rawMessage) {
|
private Component parseMessageContent(User user, OfflinePlayer offlinePlayer, String rawMessage) {
|
||||||
TagResolver.Builder tagResolver = TagResolver.builder();
|
TagResolver.Builder tagResolver = TagResolver.builder();
|
||||||
|
|
||||||
Utility.formattingPerms.forEach((perm, pair) -> {
|
Utility.formattingPerms.forEach((perm, pair) -> {
|
||||||
if (player.hasPermission(perm)) {
|
if (Utility.hasPermission(user, perm)) {
|
||||||
tagResolver.resolver(pair.getX());
|
tagResolver.resolver(pair.getX());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -456,13 +492,16 @@ public class ChatHandler {
|
|||||||
.replacement(chatFilter.getReplacement()).build());
|
.replacement(chatFilter.getReplacement()).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
component = component
|
if (offlinePlayer.isOnline() && offlinePlayer.getPlayer() != null) {
|
||||||
.replaceText(
|
component = component
|
||||||
TextReplacementConfig.builder()
|
.replaceText(
|
||||||
.once()
|
TextReplacementConfig.builder()
|
||||||
.matchLiteral("[i]")
|
.once()
|
||||||
.replacement(ChatHandler.itemComponent(player.getInventory().getItemInMainHand()))
|
.matchLiteral("[i]")
|
||||||
.build());
|
.replacement(ChatHandler.itemComponent(offlinePlayer.getPlayer().getInventory()
|
||||||
|
.getItemInMainHand()))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class ChatListener implements Listener {
|
|||||||
toggleable.sendMessage(event.getPlayer(), event.message());
|
toggleable.sendMessage(event.getPlayer(), event.message());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chatMessageSender.sendMessage(event.getPlayer(), event.message());
|
chatMessageSender.sendMessage(event.getPlayer(), event.message(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComponentLike parseMessageContent(Player player, String rawMessage) {
|
private ComponentLike parseMessageContent(Player player, String rawMessage) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.alttd.chat.listeners;
|
package com.alttd.chat.listeners;
|
||||||
|
|
||||||
|
import com.alttd.chat.ChatPlugin;
|
||||||
import com.alttd.chat.config.Config;
|
import com.alttd.chat.config.Config;
|
||||||
import com.alttd.chat.config.ServerConfig;
|
import com.alttd.chat.config.ServerConfig;
|
||||||
import com.alttd.chat.database.Queries;
|
import com.alttd.chat.database.Queries;
|
||||||
@@ -8,8 +9,10 @@ import com.alttd.chat.managers.RegexManager;
|
|||||||
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.Toggleable;
|
import com.alttd.chat.objects.Toggleable;
|
||||||
|
import com.alttd.chat.objects.chat_log.WebHandler;
|
||||||
import com.alttd.chat.util.GalaxyUtility;
|
import com.alttd.chat.util.GalaxyUtility;
|
||||||
import com.alttd.chat.util.Utility;
|
import com.alttd.chat.util.Utility;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TextReplacementConfig;
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
import net.kyori.adventure.text.format.Style;
|
import net.kyori.adventure.text.format.Style;
|
||||||
@@ -30,18 +33,18 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class PlayerListener implements Listener {
|
public class PlayerListener implements Listener {
|
||||||
|
|
||||||
private final ServerConfig serverConfig;
|
private final ServerConfig serverConfig;
|
||||||
|
private final WebHandler webHandler;
|
||||||
public PlayerListener(ServerConfig serverConfig) {
|
private final ChatPlugin chatPlugin;
|
||||||
this.serverConfig = serverConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
private void onPlayerLogin(PlayerJoinEvent event) {
|
private void onPlayerLogin(PlayerJoinEvent event) {
|
||||||
@@ -50,19 +53,23 @@ public class PlayerListener implements Listener {
|
|||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
Toggleable.disableToggles(uuid);
|
Toggleable.disableToggles(uuid);
|
||||||
|
|
||||||
if (serverConfig.FIRST_JOIN_MESSAGES && (!player.hasPlayedBefore() || System.currentTimeMillis() - player.getFirstPlayed() < TimeUnit.SECONDS.toMillis(10))) {
|
if (serverConfig.FIRST_JOIN_MESSAGES && (!player.hasPlayedBefore() || System.currentTimeMillis() - player.getFirstPlayed() < TimeUnit.SECONDS.toMillis(
|
||||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Config.FIRST_JOIN, Placeholder.parsed("player", player.getName())));
|
10))) {
|
||||||
|
Bukkit.broadcast(MiniMessage.miniMessage()
|
||||||
|
.deserialize(Config.FIRST_JOIN, Placeholder.parsed("player", player.getName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||||
if(user != null) return;
|
if (user != null) {
|
||||||
|
updateServerState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// user failed to load - create a new one
|
// user failed to load - create a new one
|
||||||
ChatUser chatUser = new ChatUser(uuid, -1, null);
|
ChatUser chatUser = new ChatUser(uuid, -1, null);
|
||||||
ChatUserManager.addUser(chatUser);
|
ChatUserManager.addUser(chatUser);
|
||||||
Queries.saveUser(chatUser);
|
Queries.saveUser(chatUser);
|
||||||
|
updateServerState();
|
||||||
//TODO load player on other servers with plugin message?
|
//TODO load player on other servers with plugin message?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,8 +78,19 @@ public class PlayerListener implements Listener {
|
|||||||
UUID uuid = event.getPlayer().getUniqueId();
|
UUID uuid = event.getPlayer().getUniqueId();
|
||||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||||
ChatUserManager.removeUser(user);
|
ChatUserManager.removeUser(user);
|
||||||
|
updateServerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateServerState() {
|
||||||
|
Bukkit.getScheduler().runTaskLaterAsynchronously(chatPlugin, () -> {
|
||||||
|
ArrayList<Player> playerList = new ArrayList<>(Bukkit.getOnlinePlayers());
|
||||||
|
String name = Bukkit.getServerName();
|
||||||
|
webHandler.updateServerState(name,
|
||||||
|
playerList.stream().map(WebPlayerMapper::fromPlayer).toList()
|
||||||
|
);
|
||||||
|
}, 20
|
||||||
|
);//20 ticks aka 1 second delay
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true) // untested
|
@EventHandler(ignoreCancelled = true) // untested
|
||||||
public void onSignChangeE(SignChangeEvent event) {
|
public void onSignChangeE(SignChangeEvent event) {
|
||||||
@@ -88,7 +106,8 @@ public class PlayerListener implements Listener {
|
|||||||
GalaxyUtility.sendBlockedNotification("Sign Language",
|
GalaxyUtility.sendBlockedNotification("Sign Language",
|
||||||
player,
|
player,
|
||||||
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
||||||
"");
|
""
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
component = modifiableString.component() == null ? Component.empty() : modifiableString.component();
|
component = modifiableString.component() == null ? Component.empty() : modifiableString.component();
|
||||||
@@ -99,6 +118,7 @@ public class PlayerListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final HashMap<UUID, Stack<Instant>> sendPlayerDeaths = new HashMap<>();
|
private final HashMap<UUID, Stack<Instant>> sendPlayerDeaths = new HashMap<>();
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||||
public void onPlayerDeath(@NotNull PlayerDeathEvent event) {
|
public void onPlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||||
UUID uuid = event.getPlayer().getUniqueId();
|
UUID uuid = event.getPlayer().getUniqueId();
|
||||||
@@ -113,7 +133,7 @@ public class PlayerListener implements Listener {
|
|||||||
event.deathMessage(Component.empty());
|
event.deathMessage(Component.empty());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Component component = event.deathMessage();
|
Component component = event.deathMessage();
|
||||||
|
|
||||||
playerDeathsStack.push(Instant.now());
|
playerDeathsStack.push(Instant.now());
|
||||||
if (component == null) {
|
if (component == null) {
|
||||||
@@ -132,7 +152,9 @@ public class PlayerListener implements Listener {
|
|||||||
.build();
|
.build();
|
||||||
component = component.replaceText(killerReplacement);
|
component = component.replaceText(killerReplacement);
|
||||||
}
|
}
|
||||||
component = MiniMessage.miniMessage().deserialize("<dark_red>[</dark_red><red>☠</red><dark_red>]</dark_red> ").append(component);
|
component = MiniMessage.miniMessage()
|
||||||
|
.deserialize("<dark_red>[</dark_red><red>☠</red><dark_red>]</dark_red> ")
|
||||||
|
.append(component);
|
||||||
component = component.style(Style.style(TextColor.color(255, 155, 48), TextDecoration.ITALIC));
|
component = component.style(Style.style(TextColor.color(255, 155, 48), TextDecoration.ITALIC));
|
||||||
event.deathMessage(component);
|
event.deathMessage(component);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ 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.ChatLogHandler;
|
||||||
import com.alttd.chat.objects.chat_log.ChatLogType;
|
import com.alttd.chat.objects.chat_log.mapper.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;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.alttd.chat.listeners;
|
||||||
|
|
||||||
|
import com.alttd.chat.managers.ChatUserManager;
|
||||||
|
import com.alttd.chat.objects.ChatUser;
|
||||||
|
import com.alttd.chat.objects.chat_log.mapper.server_state.WebPlayer;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class WebPlayerMapper {
|
||||||
|
|
||||||
|
public static WebPlayer fromPlayer(Player player) {
|
||||||
|
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
|
||||||
|
WebPlayer webPlayer = new WebPlayer();
|
||||||
|
webPlayer.setUuid(player.getUniqueId());
|
||||||
|
webPlayer.setName(player.getName());
|
||||||
|
webPlayer.setStyledName(GsonComponentSerializer.gson().serialize(chatUser.getDisplayName().asComponent()));
|
||||||
|
return webPlayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package com.alttd.chat.util;
|
|||||||
import com.alttd.chat.ChatPlugin;
|
import com.alttd.chat.ChatPlugin;
|
||||||
import com.alttd.chat.objects.Toggleable;
|
import com.alttd.chat.objects.Toggleable;
|
||||||
import com.alttd.chat.objects.channels.CustomChannel;
|
import com.alttd.chat.objects.channels.CustomChannel;
|
||||||
|
import net.luckperms.api.model.user.User;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
||||||
@@ -38,11 +40,27 @@ public class ToggleableForCustomChannel extends Toggleable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(Player player, String message) {
|
public void sendMessage(Player player, String message) {
|
||||||
|
Utility.getOrLoadUser(player.getUniqueId()).thenAcceptAsync(user -> {
|
||||||
|
ALogger.info(String.format("%s sent %s message: %s",
|
||||||
|
player.getName(),
|
||||||
|
customChannel.getChannelName(),
|
||||||
|
message
|
||||||
|
));
|
||||||
|
ChatPlugin.getInstance().getChatHandler().chatChannel(user, player, customChannel, message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(User user, OfflinePlayer offlinePlayer, String message) {
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
ALogger.info(String.format("%s sent %s message: %s", player.getName(), customChannel.getChannelName(), message));
|
ALogger.info(String.format("%s sent %s message: %s",
|
||||||
ChatPlugin.getInstance().getChatHandler().chatChannel(player, customChannel, message);
|
offlinePlayer.getName(),
|
||||||
|
customChannel.getChannelName(),
|
||||||
|
message
|
||||||
|
));
|
||||||
|
ChatPlugin.getInstance().getChatHandler().chatChannel(user, offlinePlayer, customChannel, message);
|
||||||
}
|
}
|
||||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ name: ChatPlugin
|
|||||||
version: 2.0.0-SNAPSHOT
|
version: 2.0.0-SNAPSHOT
|
||||||
main: com.alttd.chat.ChatPlugin
|
main: com.alttd.chat.ChatPlugin
|
||||||
api-version: 1.19
|
api-version: 1.19
|
||||||
authors: [Destro, Teriuihi]
|
authors: [ Destro, Teriuihi ]
|
||||||
depend: [LuckPerms]
|
depend: [ LuckPerms, ProtocolLib ]
|
||||||
|
softdepend: [ TAB ]
|
||||||
loadbefore:
|
loadbefore:
|
||||||
- mcMMO
|
- mcMMO
|
||||||
- GriefPrevention
|
- GriefPrevention
|
||||||
@@ -18,7 +19,7 @@ commands:
|
|||||||
aliases: togglegc
|
aliases: togglegc
|
||||||
message:
|
message:
|
||||||
permission: command.chat.message
|
permission: command.chat.message
|
||||||
aliases: [msg, tell]
|
aliases: [ msg, tell ]
|
||||||
reply:
|
reply:
|
||||||
permission: command.chat.message
|
permission: command.chat.message
|
||||||
aliases: r
|
aliases: r
|
||||||
@@ -39,6 +40,6 @@ commands:
|
|||||||
permission: chat.command.clear-chat
|
permission: chat.command.clear-chat
|
||||||
emoteslist:
|
emoteslist:
|
||||||
permission: chat.command.emoteslist
|
permission: chat.command.emoteslist
|
||||||
aliases: [emotes]
|
aliases: [ emotes ]
|
||||||
nick:
|
nick:
|
||||||
permission: chat.command.nick
|
permission: chat.command.nick
|
||||||
@@ -14,10 +14,19 @@ dependencies {
|
|||||||
compileOnly("net.kyori:adventure-text-minimessage:4.23.0")
|
compileOnly("net.kyori:adventure-text-minimessage:4.23.0")
|
||||||
compileOnly("com.gitlab.ruany:LiteBansAPI:0.3.5")
|
compileOnly("com.gitlab.ruany:LiteBansAPI:0.3.5")
|
||||||
compileOnly("com.alttd.proxydiscordlink:ProxyDiscordLink:1.0.1-SNAPSHOT")
|
compileOnly("com.alttd.proxydiscordlink:ProxyDiscordLink:1.0.1-SNAPSHOT")
|
||||||
|
compileOnly("net.luckperms:api:5.5") // Luckperms
|
||||||
|
|
||||||
|
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
archiveFileName.set("${rootProject.name}-${project.name}-${project.version}.jar")
|
archiveFileName.set("${rootProject.name}-${project.name}-${project.version}.jar")
|
||||||
// minimize()
|
// minimize()
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ 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.objects.chat_log.WebHandler;
|
||||||
import com.alttd.chat.util.ALogger;
|
import com.alttd.chat.util.ALogger;
|
||||||
|
import com.alttd.chat.web.SseSubscribeClient;
|
||||||
|
import com.alttd.velocitychat.chat_web.handlers.PunishFromWebHandler;
|
||||||
|
import com.alttd.velocitychat.chat_web.handlers.WebPartyChatHandler;
|
||||||
import com.alttd.velocitychat.commands.*;
|
import com.alttd.velocitychat.commands.*;
|
||||||
import com.alttd.velocitychat.handlers.ChatHandler;
|
import com.alttd.velocitychat.handlers.ChatHandler;
|
||||||
import com.alttd.velocitychat.handlers.ServerHandler;
|
import com.alttd.velocitychat.handlers.ServerHandler;
|
||||||
@@ -49,6 +53,7 @@ public class VelocityChat {
|
|||||||
private ServerHandler serverHandler;
|
private ServerHandler serverHandler;
|
||||||
|
|
||||||
private ChannelIdentifier channelIdentifier;
|
private ChannelIdentifier channelIdentifier;
|
||||||
|
private SseSubscribeClient sseSubscribeClient;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VelocityChat(ProxyServer proxyServer, Logger proxyLogger, @DataDirectory Path proxydataDirectory) {
|
public VelocityChat(ProxyServer proxyServer, Logger proxyLogger, @DataDirectory Path proxydataDirectory) {
|
||||||
@@ -67,8 +72,10 @@ 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();
|
||||||
ChatLogHandler chatLogHandler = new ChatLogHandler(true);
|
WebHandler webHandler = new WebHandler();
|
||||||
chatHandler = new ChatHandler(chatLogHandler);
|
|
||||||
|
ChatLogHandler chatLogHandler = new ChatLogHandler(webHandler, true);
|
||||||
|
chatHandler = new ChatHandler(chatLogHandler, chatAPI.getLuckPerms());
|
||||||
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
|
||||||
@@ -76,11 +83,23 @@ public class VelocityChat {
|
|||||||
channelIdentifier = MinecraftChannelIdentifier.create(channels[0], channels[1]);
|
channelIdentifier = MinecraftChannelIdentifier.create(channels[0], channels[1]);
|
||||||
server.getChannelRegistrar().register(channelIdentifier);
|
server.getChannelRegistrar().register(channelIdentifier);
|
||||||
server.getEventManager().register(this, new PluginMessageListener(channelIdentifier));
|
server.getEventManager().register(this, new PluginMessageListener(channelIdentifier));
|
||||||
loadCommands();
|
loadCommands(webHandler);
|
||||||
// setup console chatuser
|
// setup console chatuser
|
||||||
ChatUser console = new ChatUser(Config.CONSOLEUUID, -1, null);
|
ChatUser console = new ChatUser(Config.CONSOLEUUID, -1, null);
|
||||||
console.setDisplayName(Config.CONSOLENAME);
|
console.setDisplayName(Config.CONSOLENAME);
|
||||||
ChatUserManager.addUser(console);
|
ChatUserManager.addUser(console);
|
||||||
|
sseSubscribeClient = new SseSubscribeClient(
|
||||||
|
Config.CHAT_WEB_REGISTER_TO_BASE_URL,
|
||||||
|
"proxy", //TODO [Stijn] [2026-08-09]: Make configurable if needed
|
||||||
|
Config.CHAT_WEB_TOKEN
|
||||||
|
);
|
||||||
|
new Thread(sseSubscribeClient).start();
|
||||||
|
registerWebHandlers(sseSubscribeClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerWebHandlers(SseSubscribeClient sseSubscribeClient) {
|
||||||
|
sseSubscribeClient.register("web_party_chat", new WebPartyChatHandler(chatHandler));
|
||||||
|
sseSubscribeClient.register("web_punish", new PunishFromWebHandler(server));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadConfig() {
|
public void reloadConfig() {
|
||||||
@@ -113,8 +132,8 @@ public class VelocityChat {
|
|||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadCommands() {
|
public void loadCommands(WebHandler webHandler) {
|
||||||
ChatLogHandler instance = ChatLogHandler.getInstance(false);
|
ChatLogHandler instance = ChatLogHandler.getInstance(webHandler, false);
|
||||||
new SilentJoinCommand(server);
|
new SilentJoinCommand(server);
|
||||||
new GlobalAdminChat(server);
|
new GlobalAdminChat(server);
|
||||||
new Reload(server);
|
new Reload(server);
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.alttd.velocitychat.chat_web;
|
||||||
|
|
||||||
|
import com.alttd.chat.web.handler_class.PunishFromWeb;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class PunishmentCommandBuilder {
|
||||||
|
|
||||||
|
private static final Set<String> ALLOWED_TYPES = Set.of("BAN", "MUTE", "WARN");
|
||||||
|
private static final String WARN_DURATION = "30d";
|
||||||
|
|
||||||
|
public static String buildCommand(String executorName, PunishFromWeb event) {
|
||||||
|
String rawType = event.getType();
|
||||||
|
UUID executorUuid = event.getExecutor();
|
||||||
|
String target = event.getTarget().toString();
|
||||||
|
String reason = event.getReason();
|
||||||
|
|
||||||
|
String type = validateType(rawType);
|
||||||
|
validateReason(reason);
|
||||||
|
String time = resolveTime(type, event.getTime());
|
||||||
|
|
||||||
|
StringBuilder commandBuilder = new StringBuilder();
|
||||||
|
commandBuilder.append(type).append(" ").append(target);
|
||||||
|
|
||||||
|
if (time != null) {
|
||||||
|
commandBuilder.append(" ").append(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
commandBuilder.append(" --sender=").append(executorName)
|
||||||
|
.append(" --sender-uuid=").append(executorUuid);
|
||||||
|
|
||||||
|
commandBuilder.append(" ").append(reason);
|
||||||
|
|
||||||
|
return commandBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String validateType(String rawType) {
|
||||||
|
if (rawType == null || !ALLOWED_TYPES.contains(rawType.toUpperCase(Locale.ROOT))) {
|
||||||
|
throw new IllegalArgumentException("Invalid punishment type: " + rawType
|
||||||
|
+ ". Allowed types are: " + ALLOWED_TYPES);
|
||||||
|
}
|
||||||
|
return rawType.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateReason(String reason) {
|
||||||
|
if (reason == null || reason.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("A reason is required for all punishments");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the litebans-formatted time argument for a given (already-normalized,
|
||||||
|
* lowercase) punishment type, applying the per-type rules:
|
||||||
|
* - warn: always 30d, regardless of what was supplied
|
||||||
|
* - mute: a duration is required
|
||||||
|
* - ban: optional, permanent (null) if not supplied
|
||||||
|
*/
|
||||||
|
private static String resolveTime(String type, String rawTime) {
|
||||||
|
switch (type) {
|
||||||
|
case "warn":
|
||||||
|
return WARN_DURATION;
|
||||||
|
case "mute":
|
||||||
|
if (rawTime == null || rawTime.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Mutes must have a duration");
|
||||||
|
}
|
||||||
|
return parseDuration(rawTime);
|
||||||
|
case "ban":
|
||||||
|
if (rawTime == null || rawTime.isBlank()) {
|
||||||
|
return null; // permanent ban
|
||||||
|
}
|
||||||
|
return parseDuration(rawTime);
|
||||||
|
default:
|
||||||
|
// unreachable, type is already validated before this is called
|
||||||
|
throw new IllegalArgumentException("Unsupported type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an ISO-8601 duration (e.g. "PT30M", "P7D", "P1DT2H3M4S") as sent by the
|
||||||
|
* OpenAPI spec's `time` field, and converts it into a litebans-style duration
|
||||||
|
* string. Litebans only accepts a single unit of d, h, or m (no combined units,
|
||||||
|
* no seconds), so this picks the single largest whole unit and drops the rest,
|
||||||
|
* e.g. "P1DT2H3M4S" -> "1d", "PT2H3M" -> "2h", "PT45M" -> "45m".
|
||||||
|
*/
|
||||||
|
static String parseDuration(String isoDuration) {
|
||||||
|
Duration duration;
|
||||||
|
try {
|
||||||
|
duration = Duration.parse(isoDuration);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid duration format: " + isoDuration, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
long totalSeconds = duration.getSeconds();
|
||||||
|
if (totalSeconds <= 0) {
|
||||||
|
throw new IllegalArgumentException("Duration must be positive: " + isoDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
long days = totalSeconds / 86400;
|
||||||
|
if (days > 0) {
|
||||||
|
return days + "d";
|
||||||
|
}
|
||||||
|
|
||||||
|
long hours = totalSeconds / 3600;
|
||||||
|
if (hours > 0) {
|
||||||
|
return hours + "h";
|
||||||
|
}
|
||||||
|
|
||||||
|
long minutes = totalSeconds / 60;
|
||||||
|
if (minutes > 0) {
|
||||||
|
return minutes + "m";
|
||||||
|
}
|
||||||
|
|
||||||
|
//Default minimum
|
||||||
|
return "1m";
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package com.alttd.velocitychat.chat_web.handlers;
|
||||||
|
|
||||||
|
import com.alttd.chat.util.Utility;
|
||||||
|
import com.alttd.chat.web.WebHandler;
|
||||||
|
import com.alttd.chat.web.handler_class.PunishFromWeb;
|
||||||
|
import com.alttd.velocitychat.chat_web.PunishmentCommandBuilder;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class PunishFromWebHandler implements WebHandler<PunishFromWeb> {
|
||||||
|
private final ProxyServer server;
|
||||||
|
|
||||||
|
public PunishFromWebHandler(ProxyServer server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<PunishFromWeb> type() {
|
||||||
|
return PunishFromWeb.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(PunishFromWeb event) {
|
||||||
|
String permission = "litebans." + event.getType().toLowerCase();
|
||||||
|
Utility.getOrLoadUser(event.getExecutor()).thenAccept(user -> {
|
||||||
|
if (user == null) {
|
||||||
|
log.warn("User {} does not exist", event.getExecutor());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Utility.hasPermission(user, permission)) {
|
||||||
|
log.warn("User {} does not have permission {}", user.getUsername(), permission);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String executorName = (user.getUsername() != null) ? user.getUsername() : event.getExecutor().toString();
|
||||||
|
server.getCommandManager()
|
||||||
|
.executeAsync(server.getConsoleCommandSource(),
|
||||||
|
PunishmentCommandBuilder.buildCommand(executorName, event)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package com.alttd.velocitychat.chat_web.handlers;
|
||||||
|
|
||||||
|
import com.alttd.chat.managers.ChatUserManager;
|
||||||
|
import com.alttd.chat.managers.PartyManager;
|
||||||
|
import com.alttd.chat.objects.ChatUser;
|
||||||
|
import com.alttd.chat.objects.Party;
|
||||||
|
import com.alttd.chat.web.WebHandler;
|
||||||
|
import com.alttd.chat.web.handler_class.PartyChatFromWeb;
|
||||||
|
import com.alttd.velocitychat.handlers.ChatHandler;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebPartyChatHandler implements WebHandler<PartyChatFromWeb> {
|
||||||
|
|
||||||
|
private final ChatHandler chatHandler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<PartyChatFromWeb> type() {
|
||||||
|
return PartyChatFromWeb.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(PartyChatFromWeb event) {
|
||||||
|
int partyId;
|
||||||
|
try {
|
||||||
|
partyId = Integer.parseInt(event.getPartyId());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.error("Invalid party id: {}", event.getPartyId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UUID sender = event.getSender();
|
||||||
|
String message = event.getMessage();
|
||||||
|
Party party = PartyManager.getParty(sender);
|
||||||
|
if (party == null) {
|
||||||
|
log.error("Party not found for sender: {}", sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (party.getPartyId() != partyId) {
|
||||||
|
log.error("Party id mismatch: {} != {}", party.getPartyId(), partyId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ChatUser chatUser = ChatUserManager.getChatUser(sender);//TODO [Stijn] [2026-08-09]: Async since it can do a query
|
||||||
|
chatHandler.sendPartyMessageFromWeb(sender, party, chatUser.getDisplayName().asComponent(), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ 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.ChatLogHandler;
|
||||||
import com.alttd.chat.objects.chat_log.ChatLogType;
|
import com.alttd.chat.objects.chat_log.mapper.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;
|
||||||
@@ -16,6 +16,7 @@ import com.google.common.io.ByteStreams;
|
|||||||
import com.velocitypowered.api.command.CommandSource;
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
import com.velocitypowered.api.proxy.Player;
|
import com.velocitypowered.api.proxy.Player;
|
||||||
import com.velocitypowered.api.proxy.ServerConnection;
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
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.TextReplacementConfig;
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
@@ -24,7 +25,10 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
|||||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
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 net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import net.luckperms.api.LuckPerms;
|
||||||
|
import net.luckperms.api.model.user.User;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -32,12 +36,15 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ChatHandler {
|
public class ChatHandler {
|
||||||
|
|
||||||
private final ChatLogHandler chatLogHandler;
|
private final ChatLogHandler chatLogHandler;
|
||||||
|
private final LuckPerms luckPerms;
|
||||||
|
|
||||||
public ChatHandler(ChatLogHandler chatLogHandler) {
|
public ChatHandler(ChatLogHandler chatLogHandler, LuckPerms luckPerms) {
|
||||||
this.chatLogHandler = chatLogHandler;
|
this.chatLogHandler = chatLogHandler;
|
||||||
|
this.luckPerms = luckPerms;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void privateMessage(String sender, String target, String message) {
|
public void privateMessage(String sender, String target, String message) {
|
||||||
@@ -137,58 +144,125 @@ public class ChatHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendPartyMessageFromWeb(UUID uuid, Party party, Component senderName, String message) {
|
||||||
|
Utility.getOrLoadUser(uuid).thenAccept(user -> sendPartyMessageFromWeb(uuid, party, senderName, message, user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPartyMessageFromWeb(UUID uuid, Party party, Component senderName, String message, User user) {
|
||||||
|
Optional<ParsedPartyMessage> optionalParsedPartyMessage = getResult(uuid,
|
||||||
|
message,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
senderName,
|
||||||
|
party,
|
||||||
|
user.getUsername(),
|
||||||
|
user,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (optionalParsedPartyMessage.isEmpty()) {
|
||||||
|
log.error("Failed to parse party message: {}", message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedPartyMessage parsedPartyMessage = optionalParsedPartyMessage.get();
|
||||||
|
|
||||||
|
chatLogHandler.addChatLog(uuid,
|
||||||
|
"web",
|
||||||
|
message,
|
||||||
|
ChatLogType.PARTY,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
parsedPartyMessage.partyMessage(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public void sendPartyMessage(UUID uuid, String message, Component item, ServerConnection serverConnection) {
|
public void sendPartyMessage(UUID uuid, String message, Component item, ServerConnection serverConnection) {
|
||||||
|
Utility.getOrLoadUser(uuid).thenAccept(user -> sendPartyMessage(uuid, message, item, serverConnection, user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPartyMessage(UUID uuid, String message, Component item, ServerConnection serverConnection, User user) {
|
||||||
Optional<Player> optionalPlayer = VelocityChat.getPlugin().getProxy().getPlayer(uuid);
|
Optional<Player> optionalPlayer = VelocityChat.getPlugin().getProxy().getPlayer(uuid);
|
||||||
if (optionalPlayer.isEmpty()) {
|
if (optionalPlayer.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Player player = optionalPlayer.get();
|
Player player = optionalPlayer.get();
|
||||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
|
||||||
Party party = PartyManager.getParty(user.getPartyId());
|
Party party = PartyManager.getParty(chatUser.getPartyId());
|
||||||
if (party == null) {
|
if (party == null) {
|
||||||
player.sendMessage(Utility.parseMiniMessage(Config.NOT_IN_A_PARTY));
|
player.sendMessage(Utility.parseMiniMessage(Config.NOT_IN_A_PARTY));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ComponentLike senderName = user.getDisplayName();
|
ComponentLike senderName = chatUser.getDisplayName();
|
||||||
|
|
||||||
TagResolver placeholders = TagResolver.resolver(
|
Optional<ParsedPartyMessage> optionalParsedPartyMessage = getResult(uuid,
|
||||||
Placeholder.component("sender", senderName),
|
message,
|
||||||
Placeholder.component("sendername", senderName),
|
item,
|
||||||
Placeholder.unparsed("partyname", party.getPartyName()),
|
serverConnection,
|
||||||
Placeholder.component("message", parseMessageContent(player, message)),
|
senderName,
|
||||||
Placeholder.unparsed("server", serverConnection.getServer().getServerInfo().getName())
|
party,
|
||||||
|
player.getUsername(),
|
||||||
|
user,
|
||||||
|
player
|
||||||
);
|
);
|
||||||
|
if (optionalParsedPartyMessage.isEmpty()) {
|
||||||
Component partyMessage = Utility.parseMiniMessage(Config.PARTY_FORMAT, placeholders).asComponent()
|
|
||||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("[i]").replacement(item).build());
|
|
||||||
|
|
||||||
ModifiableString modifiableString = new ModifiableString(partyMessage);
|
|
||||||
if (!RegexManager.filterText(player.getUsername(), uuid, modifiableString, "party")) {
|
|
||||||
sendBlockedNotification("Party Language", player, message, "", serverConnection);
|
|
||||||
return; // the message was blocked
|
return; // the message was blocked
|
||||||
}
|
}
|
||||||
|
|
||||||
partyMessage = modifiableString.component();
|
ParsedPartyMessage parsedPartyMessage = optionalParsedPartyMessage.get();
|
||||||
|
|
||||||
sendPartyMessage(party, partyMessage, user.getIgnoredBy());
|
sendPartyMessage(party, parsedPartyMessage.partyMessage(), chatUser.getIgnoredBy());
|
||||||
|
|
||||||
chatLogHandler.addChatLog(uuid,
|
chatLogHandler.addChatLog(uuid,
|
||||||
serverConnection.getServer().getServerInfo().getName(),
|
serverConnection.getServer().getServerInfo().getName(),
|
||||||
PlainTextComponentSerializer.plainText().serialize(partyMessage),
|
PlainTextComponentSerializer.plainText().serialize(parsedPartyMessage.partyMessage()),
|
||||||
ChatLogType.PARTY,
|
ChatLogType.PARTY,
|
||||||
String.valueOf(party.getPartyId()),
|
String.valueOf(party.getPartyId()),
|
||||||
null,
|
null,
|
||||||
partyMessage,
|
parsedPartyMessage.partyMessage(),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
ComponentLike spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, placeholders);
|
ComponentLike spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, parsedPartyMessage.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ALogger.info(PlainTextComponentSerializer.plainText().serialize(partyMessage));
|
ALogger.info(PlainTextComponentSerializer.plainText().serialize(parsedPartyMessage.partyMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ParsedPartyMessage> getResult(UUID uuid, String message, @Nullable Component item,
|
||||||
|
@Nullable ServerConnection serverConnection, ComponentLike senderName,
|
||||||
|
Party party, String playerName, User user, @Nullable Player player) {
|
||||||
|
TagResolver placeholders = TagResolver.resolver(
|
||||||
|
Placeholder.component("sender", senderName),
|
||||||
|
Placeholder.component("sendername", senderName),
|
||||||
|
Placeholder.unparsed("partyname", party.getPartyName()),
|
||||||
|
Placeholder.component("message", parseMessageContent(user, message)),
|
||||||
|
Placeholder.unparsed("server",
|
||||||
|
serverConnection != null ? serverConnection.getServer().getServerInfo().getName() : "web"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Component partyMessage;
|
||||||
|
if (item != null) {
|
||||||
|
partyMessage = Utility.parseMiniMessage(Config.PARTY_FORMAT, placeholders).asComponent()
|
||||||
|
.replaceText(TextReplacementConfig.builder().once().matchLiteral("[i]").replacement(item).build());
|
||||||
|
} else {
|
||||||
|
partyMessage = Utility.parseMiniMessage(Config.PARTY_FORMAT, placeholders).asComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
ModifiableString modifiableString = new ModifiableString(partyMessage);
|
||||||
|
if (!RegexManager.filterText(playerName, uuid, modifiableString, "party")) {
|
||||||
|
if (serverConnection != null && player != null) {
|
||||||
|
sendBlockedNotification("Party Language", player, message, "", serverConnection);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
partyMessage = modifiableString.component();
|
||||||
|
return Optional.of(new ParsedPartyMessage(placeholders, partyMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void globalAdminChat(String message) {
|
public void globalAdminChat(String message) {
|
||||||
@@ -215,11 +289,11 @@ public class ChatHandler {
|
|||||||
ComponentLike senderName = Component.text(Config.CONSOLENAME);
|
ComponentLike senderName = Component.text(Config.CONSOLENAME);
|
||||||
String serverName = "Altitude";
|
String serverName = "Altitude";
|
||||||
if (commandSource instanceof Player sender) {
|
if (commandSource instanceof Player sender) {
|
||||||
ChatUser user = ChatUserManager.getChatUser(sender.getUniqueId());
|
ChatUser chatUser = ChatUserManager.getChatUser(sender.getUniqueId());
|
||||||
if (user == null) {
|
if (chatUser == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
senderName = user.getDisplayName();
|
senderName = chatUser.getDisplayName();
|
||||||
serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer()
|
serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer()
|
||||||
.get()
|
.get()
|
||||||
.getServerInfo()
|
.getServerInfo()
|
||||||
@@ -347,15 +421,31 @@ public class ChatHandler {
|
|||||||
return stringBuilder.toString();
|
return stringBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component parseMessageContent(CommandSource source, String rawMessage) {
|
private Component parseMessageContent(User user, String rawMessage) {
|
||||||
TagResolver.Builder tagResolver = TagResolver.builder();
|
TagResolver.Builder tagResolver = TagResolver.builder();
|
||||||
|
|
||||||
Utility.formattingPerms.forEach((perm, pair) -> {
|
Utility.formattingPerms.forEach((perm, pair) -> {
|
||||||
if (source.hasPermission(perm)) {
|
if (Utility.hasPermission(user, perm)) {
|
||||||
tagResolver.resolver(pair.getX());
|
tagResolver.resolver(pair.getX());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return getComponent(rawMessage, tagResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component parseMessageContent(CommandSource commandSource, String rawMessage) {
|
||||||
|
TagResolver.Builder tagResolver = TagResolver.builder();
|
||||||
|
|
||||||
|
Utility.formattingPerms.forEach((perm, pair) -> {
|
||||||
|
if (commandSource.hasPermission(perm)) {
|
||||||
|
tagResolver.resolver(pair.getX());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return getComponent(rawMessage, tagResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Component getComponent(String rawMessage, TagResolver.Builder tagResolver) {
|
||||||
MiniMessage miniMessage = MiniMessage.builder().tags(tagResolver.build()).build();
|
MiniMessage miniMessage = MiniMessage.builder().tags(tagResolver.build()).build();
|
||||||
Component component = miniMessage.deserialize(rawMessage);
|
Component component = miniMessage.deserialize(rawMessage);
|
||||||
for (ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
|
for (ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
|
||||||
@@ -367,6 +457,5 @@ public class ChatHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.alttd.velocitychat.handlers;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
|
|
||||||
|
public record ParsedPartyMessage(TagResolver placeholders, Component partyMessage) {
|
||||||
|
}
|
||||||
|
|
||||||
+235
@@ -0,0 +1,235 @@
|
|||||||
|
package com.alttd.velocitychat.chat_web;
|
||||||
|
|
||||||
|
import com.alttd.chat.web.handler_class.PunishFromWeb;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
public class PunishmentCommandBuilderTest {
|
||||||
|
|
||||||
|
private PunishFromWeb baseEvent(String type, UUID executorUuid, UUID targetUuid) {
|
||||||
|
PunishFromWeb event = new PunishFromWeb();
|
||||||
|
event.setExecutor(executorUuid);
|
||||||
|
event.setTarget(targetUuid);
|
||||||
|
event.setType(type);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- BAN ----
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildBanCommandWithTime() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("ban", executorUuid, targetUuid);
|
||||||
|
event.setTime("P7D"); // ISO-8601 duration: 7 days
|
||||||
|
event.setReason("Griefing");
|
||||||
|
|
||||||
|
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||||
|
|
||||||
|
String expected = "ban " + targetUuid + " 7d --sender=ExecutorName --sender-uuid=" + executorUuid + " Griefing";
|
||||||
|
assertEquals(expected, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildBanCommandPermanentWhenNoTime() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("ban", executorUuid, targetUuid);
|
||||||
|
event.setTime(null);
|
||||||
|
event.setReason("Griefing");
|
||||||
|
|
||||||
|
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||||
|
|
||||||
|
String expected = "ban " + targetUuid + " --sender=ExecutorName --sender-uuid=" + executorUuid + " Griefing";
|
||||||
|
assertEquals(expected, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildBanCommandPermanentWhenBlankTime() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("ban", executorUuid, targetUuid);
|
||||||
|
event.setTime(" ");
|
||||||
|
event.setReason("Griefing");
|
||||||
|
|
||||||
|
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||||
|
|
||||||
|
String expected = "ban " + targetUuid + " --sender=ExecutorName --sender-uuid=" + executorUuid + " Griefing";
|
||||||
|
assertEquals(expected, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildBanCommandMissingReasonThrows() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("ban", executorUuid, targetUuid);
|
||||||
|
event.setTime(" ");
|
||||||
|
event.setReason(" ");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- MUTE ----
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildMuteCommand() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("mute", executorUuid, targetUuid);
|
||||||
|
event.setTime("PT30M"); // 30 minutes
|
||||||
|
event.setReason("Spamming");
|
||||||
|
|
||||||
|
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||||
|
|
||||||
|
String expected = "mute " + targetUuid + " 30m --sender=ExecutorName --sender-uuid=" + executorUuid + " Spamming";
|
||||||
|
assertEquals(expected, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildMuteCommandNoTimeThrows() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("mute", executorUuid, targetUuid);
|
||||||
|
event.setTime(null);
|
||||||
|
event.setReason("Spamming");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildMuteCommandBlankTimeThrows() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("mute", executorUuid, targetUuid);
|
||||||
|
event.setTime(" ");
|
||||||
|
event.setReason("Spamming");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- WARN ----
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildWarnCommandAlwaysUses30d() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("warn", executorUuid, targetUuid);
|
||||||
|
event.setTime(null); // not supplied for warns, should default to 30d anyway
|
||||||
|
event.setReason("Bad attitude");
|
||||||
|
|
||||||
|
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||||
|
|
||||||
|
String expected = "warn " + targetUuid + " 30d --sender=ExecutorName --sender-uuid=" + executorUuid + " Bad attitude";
|
||||||
|
assertEquals(expected, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildWarnCommandIgnoresSuppliedTime() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("warn", executorUuid, targetUuid);
|
||||||
|
event.setTime("P1D"); // should be ignored; warns are always 30d
|
||||||
|
event.setReason("Bad attitude");
|
||||||
|
|
||||||
|
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||||
|
|
||||||
|
String expected = "warn " + targetUuid + " 30d --sender=ExecutorName --sender-uuid=" + executorUuid + " Bad attitude";
|
||||||
|
assertEquals(expected, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildWarnCommandNoReasonThrows() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("warn", executorUuid, targetUuid);
|
||||||
|
event.setTime(null);
|
||||||
|
event.setReason("");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Type validation ----
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidTypeThrows() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("kick", executorUuid, targetUuid);
|
||||||
|
event.setReason("Bad attitude");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTypeIsCaseInsensitive() {
|
||||||
|
UUID executorUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
PunishFromWeb event = baseEvent("BAN", executorUuid, targetUuid);
|
||||||
|
event.setTime(null);
|
||||||
|
event.setReason("Griefing");
|
||||||
|
|
||||||
|
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||||
|
|
||||||
|
String expected = "ban " + targetUuid + " --sender=ExecutorName --sender-uuid=" + executorUuid + " Griefing";
|
||||||
|
assertEquals(expected, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Duration parsing ----
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseDurationDaysOnly() {
|
||||||
|
assertEquals("7d", PunishmentCommandBuilder.parseDuration("P7D"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseDurationMinutesOnly() {
|
||||||
|
assertEquals("45m", PunishmentCommandBuilder.parseDuration("PT45M"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseDurationTruncatesToDaysWhenDaysPresent() {
|
||||||
|
// days take priority over everything smaller, which is dropped entirely
|
||||||
|
assertEquals("1d", PunishmentCommandBuilder.parseDuration("P1DT2H3M4S"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseDurationTruncatesToHoursWhenNoDays() {
|
||||||
|
// hours take priority over minutes, which is dropped
|
||||||
|
assertEquals("2h", PunishmentCommandBuilder.parseDuration("PT2H3M"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseDurationInvalidFormatThrows() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PunishmentCommandBuilder.parseDuration("7d")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseDurationZeroThrows() {
|
||||||
|
// duration must be positive
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PunishmentCommandBuilder.parseDuration("PT0S")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseDurationSubMinuteThrows() {
|
||||||
|
// litebans has no second-level precision defaults to 1m
|
||||||
|
assertEquals("1m", PunishmentCommandBuilder.parseDuration("PT30S"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user