Compare commits
30
Commits
3bfd358e78
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f4045d20d | ||
|
|
d3aa424261 | ||
|
|
79ded52842 | ||
|
|
776ea90d7d | ||
|
|
b98dd65203 | ||
|
|
9e647f83a8 | ||
|
|
0d2ac3763f | ||
|
|
e72f79cb29 | ||
|
|
5eb470cfb6 | ||
|
|
d99ac4c597 | ||
|
|
5a4140c2e2 | ||
|
|
421f6655fd | ||
|
|
6db9816c85 | ||
|
|
9143642761 | ||
|
|
e600cc7331 | ||
|
|
2962a605ea | ||
|
|
7492dfa418 | ||
|
|
92979e9789 | ||
|
|
af6820d1bf | ||
|
|
8546bda28a | ||
|
|
28894ef245 | ||
|
|
5a508fdf28 | ||
|
|
dc513ac2f0 | ||
|
|
c48d4f5c86 | ||
|
|
7f00bc104b | ||
|
|
133cebd1ec | ||
|
|
fc20c9a276 | ||
|
|
ea8e952108 | ||
|
|
3d010e4139 | ||
|
|
7cc7cca129 |
@@ -3,12 +3,20 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":web-api")) // Web-API
|
||||
compileOnly("org.projectlombok:lombok:1.18.46")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.46")
|
||||
// Cosmos
|
||||
compileOnly("com.alttd.cosmos:cosmos-api:1.21.7-R0.1-SNAPSHOT") {
|
||||
compileOnly("com.alttd.cosmos:cosmos-api:1.21.8-R0.1-SNAPSHOT") {
|
||||
isChanging = true
|
||||
}
|
||||
compileOnly("org.spongepowered:configurate-yaml:4.2.0") // Configurate
|
||||
compileOnly("net.luckperms:api:5.5") // Luckperms
|
||||
|
||||
//API validation
|
||||
implementation("org.hibernate.validator:hibernate-validator:9.0.1.Final")
|
||||
implementation("org.glassfish:jakarta.el:5.0.0-M1")
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.22.1")
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
||||
@@ -183,11 +183,15 @@ public final class Config {
|
||||
private static void settings() {
|
||||
PREFIXGROUPS = getList("settings.prefix-groups",
|
||||
Lists.newArrayList("discord", "socialmedia", "eventteam", "eventleader", "youtube", "twitch",
|
||||
"developer"));
|
||||
"developer"
|
||||
)
|
||||
);
|
||||
CONFLICTINGPREFIXGROUPS = getList("settings.prefix-conflicts-groups",
|
||||
Lists.newArrayList("eventteam", "eventleader"));
|
||||
Lists.newArrayList("eventteam", "eventleader")
|
||||
);
|
||||
STAFFGROUPS = getList("settings.staff-groups",
|
||||
Lists.newArrayList("trainee", "moderator", "headmod", "admin", "manager", "owner"));
|
||||
Lists.newArrayList("trainee", "moderator", "headmod", "admin", "manager", "owner")
|
||||
);
|
||||
CONSOLENAME = getString("settings.console-name", CONSOLENAME);
|
||||
CONSOLEUUID = UUID.fromString(getString("settings.console-uuid", CONSOLEUUID.toString()));
|
||||
MINIMIUMSTAFFRANK = getString("settings.minimum-staff-rank", MINIMIUMSTAFFRANK);
|
||||
@@ -413,7 +417,8 @@ public final class Config {
|
||||
ConfigurationNode node = getNode("chat-channels");
|
||||
if (node.empty()) {
|
||||
getString("chat-channels.ac.format",
|
||||
"<white><gray><sender></gray> <hover:show_text:on <server>><yellow>to <channel></yellow></hover><gray>: <message>");
|
||||
"<white><gray><sender></gray> <hover:show_text:on <server>><yellow>to <channel></yellow></hover><gray>: <message>"
|
||||
);
|
||||
getList("chat-channels.ac.servers", List.of("lobby"));
|
||||
getBoolean("chat-channels.ac.proxy", false);
|
||||
node = getNode("chat-channels");
|
||||
@@ -427,7 +432,8 @@ public final class Config {
|
||||
getList(key + "servers", Collections.EMPTY_LIST),
|
||||
getList(key + "alias", Collections.EMPTY_LIST),
|
||||
getBoolean(key + "proxy", false),
|
||||
getBoolean(key + "local", false)
|
||||
getBoolean(key + "local", false),
|
||||
getBoolean(key + "web", false)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -541,8 +547,7 @@ public final class Config {
|
||||
|
||||
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_FOOTER =
|
||||
"<green>----<< <gray>Prev</gray> <page> <gray>/</gray> <pages> <gray>Next</gray> >>----";
|
||||
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> >>----";
|
||||
|
||||
private static void emoteListCommand() {
|
||||
EMOTELIST_HEADER = getString("commands.emotelist.header", EMOTELIST_HEADER);
|
||||
@@ -617,11 +622,15 @@ public final class Config {
|
||||
NICK_WAIT_TIME = getLong("nicknames.wait-time", NICK_WAIT_TIME);
|
||||
NICK_ITEM_LORE = getList("nicknames.item-lore",
|
||||
List.of("<aqua>New nick: <newnick>", "<aqua>Old nick: <oldnick>", "<aqua>Last changed: <lastchanged>",
|
||||
"<green>Left click to Accept <light_purple>| <red>Right click to Deny"));
|
||||
"<green>Left click to Accept <light_purple>| <red>Right click to Deny"
|
||||
)
|
||||
);
|
||||
NICK_BLOCKED_COLOR_CODESLIST = getList("nicknames.blocked-color-codes", List.of("&k", "&l", "&n", "&m", "&o"));
|
||||
NICK_ALLOWED_COLOR_CODESLIST = getList("nicknames.allowed-color-codes",
|
||||
List.of("&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f",
|
||||
"&r"));
|
||||
"&r"
|
||||
)
|
||||
);
|
||||
NICK_CURRENT = getString("nicknames.messages.nick-current", NICK_CURRENT);
|
||||
}
|
||||
|
||||
@@ -641,4 +650,14 @@ public final class Config {
|
||||
CHAT_LOG_DELETE_OLDER_THAN_DAYS = getLong("chat-log.delete-older-than-days", CHAT_LOG_DELETE_OLDER_THAN_DAYS);
|
||||
CHAT_LOG_SAVE_DELAY_MINUTES = getLong("chat-log.save-delay-minutes", CHAT_LOG_SAVE_DELAY_MINUTES);
|
||||
}
|
||||
|
||||
public static String CHAT_WEB_SERVER_BASE_URL = "http://10.0.0.121:8080";
|
||||
public static String CHAT_WEB_REGISTER_TO_BASE_URL = "https://alttd.com";
|
||||
public static String CHAT_WEB_TOKEN = "invalid-token";
|
||||
|
||||
private static void webServerSettings() {
|
||||
CHAT_WEB_SERVER_BASE_URL = getString("web-server.base-url", CHAT_WEB_SERVER_BASE_URL);
|
||||
CHAT_WEB_REGISTER_TO_BASE_URL = getString("web-server.register-to-base-url", CHAT_WEB_REGISTER_TO_BASE_URL);
|
||||
CHAT_WEB_TOKEN = getString("web-server.token", CHAT_WEB_TOKEN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,52 +15,151 @@ import java.util.concurrent.CompletionException;
|
||||
public class ChatLogQueries {
|
||||
|
||||
protected static void createChatLogTable() {
|
||||
String nicknamesTableQuery = "CREATE TABLE IF NOT EXISTS chat_log("
|
||||
+ "uuid CHAR(48) NOT NULL,"
|
||||
+ "time_stamp TIMESTAMP(6) NOT NULL, "
|
||||
+ "server VARCHAR(50) NOT NULL, "
|
||||
+ "chat_message VARCHAR(300) NOT NULL, "
|
||||
+ "blocked BIT(1) NOT NULL DEFAULT 0"
|
||||
+ ")";
|
||||
String nicknamesTableQuery = """
|
||||
CREATE TABLE IF NOT EXISTS chat_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
uuid CHAR(36) NOT NULL,
|
||||
time_stamp TIMESTAMP(6) NOT NULL,
|
||||
server VARCHAR(50) NOT NULL,
|
||||
type VARCHAR(16) NOT NULL DEFAULT 'public',
|
||||
channel VARCHAR(36) DEFAULT NULL,
|
||||
receiver CHAR(36) DEFAULT NULL,
|
||||
chat_message VARCHAR(300) NOT NULL,
|
||||
mini_message JSON,
|
||||
blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
try (PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(nicknamesTableQuery)) {
|
||||
INDEX idx_time_stamp (time_stamp)
|
||||
)
|
||||
""";
|
||||
|
||||
try (PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(
|
||||
nicknamesTableQuery)) {
|
||||
preparedStatement.executeUpdate();
|
||||
} catch (Throwable throwable) {
|
||||
ALogger.error("Failed to create chat log table", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull CompletableFuture<Boolean> storeMessages(HashMap<UUID, List<ChatLog>> chatMessages) {
|
||||
String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, chat_message, blocked) VALUES (?, ?, ?, ?, ?)";
|
||||
public static @NotNull CompletableFuture<Boolean> storeMessages(
|
||||
HashMap<UUID, List<ChatLog>> chatMessages
|
||||
) {
|
||||
String insertQuery = """
|
||||
INSERT INTO chat_log
|
||||
(uuid, time_stamp, server, type, channel, receiver,
|
||||
chat_message, mini_message, blocked)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Connection connection = DatabaseConnection.createTransactionConnection()) {
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery);
|
||||
try (Connection connection = DatabaseConnection.createTransactionConnection();
|
||||
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;
|
||||
}
|
||||
|
||||
connection.rollback();
|
||||
} catch (BatchUpdateException exception) {
|
||||
connection.rollback();
|
||||
|
||||
ALogger.error(
|
||||
"Failed to store chat batch. Trying messages individually.",
|
||||
exception
|
||||
);
|
||||
}
|
||||
|
||||
return storeMessagesIndividually(
|
||||
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();
|
||||
}
|
||||
}
|
||||
int[] updatedRowsCount = preparedStatement.executeBatch();
|
||||
boolean isSuccess = Arrays.stream(updatedRowsCount).allMatch(i -> i >= 0);
|
||||
}
|
||||
|
||||
if (isSuccess) {
|
||||
connection.commit();
|
||||
return true;
|
||||
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 {
|
||||
connection.rollback();
|
||||
ALogger.warn("Failed to store messages");
|
||||
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;
|
||||
}
|
||||
} catch (SQLException sqlException) {
|
||||
ALogger.error("Failed to store chat messages", sqlException);
|
||||
throw new CompletionException("Failed to store chat messages", sqlException);
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
String query = "SELECT * FROM chat_log WHERE uuid = ? AND time_stamp > ? AND server = ?";
|
||||
String query = "SELECT * FROM chat_log WHERE uuid = ? AND time_stamp > ? AND server = ? AND type = 'public'";
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Connection connection = DatabaseConnection.getConnection()) {
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(query);
|
||||
@@ -70,13 +169,14 @@ public class ChatLogQueries {
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
List<ChatLog> chatLogs = new ArrayList<>();
|
||||
while (resultSet.next()) {
|
||||
ChatLog chatLog = chatLogHandler.loadFromResultSet(resultSet);
|
||||
chatLogs.add(chatLog);
|
||||
chatLogHandler.loadFromResultSet(resultSet).ifPresent(chatLogs::add);
|
||||
}
|
||||
return chatLogs;
|
||||
} catch (SQLException sqlException) {
|
||||
ALogger.error(String.format("Failed to retrieve messages for user %s", uuid), sqlException);
|
||||
throw new CompletionException(String.format("Failed to retrieve messages for user %s", uuid), sqlException);
|
||||
throw new CompletionException(String.format("Failed to retrieve messages for user %s", uuid),
|
||||
sqlException
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -90,8 +190,13 @@ public class ChatLogQueries {
|
||||
preparedStatement.setTimestamp(1, Timestamp.from(Instant.now().minus(duration)));
|
||||
return preparedStatement.execute();
|
||||
} catch (SQLException sqlException) {
|
||||
ALogger.error(String.format("Failed to delete messages older than %s days", duration.toDays()), sqlException);
|
||||
throw new CompletionException(String.format("Failed to delete messages older than %s days", duration.toDays()), sqlException);
|
||||
ALogger.error(String.format("Failed to delete messages older than %s days", duration.toDays()),
|
||||
sqlException
|
||||
);
|
||||
throw new CompletionException(String.format("Failed to delete messages older than %s days",
|
||||
duration.toDays()
|
||||
), sqlException
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.alttd.chat.database;
|
||||
|
||||
|
||||
import com.alttd.chat.config.Config;
|
||||
|
||||
import java.sql.Connection;
|
||||
@@ -27,6 +26,7 @@ public class DatabaseConnection {
|
||||
|
||||
/**
|
||||
* Opens the connection if it's not already open.
|
||||
*
|
||||
* @throws SQLException If it can't create the connection.
|
||||
*/
|
||||
public void openConnection() throws SQLException {
|
||||
@@ -46,13 +46,15 @@ public class DatabaseConnection {
|
||||
|
||||
connection = DriverManager.getConnection(
|
||||
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true" +
|
||||
"&useSSL=false",
|
||||
Config.USERNAME, Config.PASSWORD);
|
||||
"&useSSL=false&preserveInstants=true",
|
||||
Config.USERNAME, Config.PASSWORD
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection for the database
|
||||
*
|
||||
* @return Returns the connection.
|
||||
*/
|
||||
public static Connection getConnection() {
|
||||
@@ -69,13 +71,15 @@ public class DatabaseConnection {
|
||||
* Creates a transactional database connection.
|
||||
*
|
||||
* @return A {@code Connection} object representing the transactional database connection.
|
||||
*
|
||||
* @throws SQLException If there is an error creating the database connection.
|
||||
*/
|
||||
public static Connection createTransactionConnection() throws SQLException {
|
||||
connection = DriverManager.getConnection(
|
||||
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true" +
|
||||
"&useSSL=false",
|
||||
Config.USERNAME, Config.PASSWORD);
|
||||
"&useSSL=false&preserveInstants=true",
|
||||
Config.USERNAME, Config.PASSWORD
|
||||
);
|
||||
connection.setAutoCommit(false);
|
||||
return connection;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package com.alttd.chat.objects;
|
||||
import com.alttd.chat.database.Queries;
|
||||
import com.alttd.chat.objects.channels.Channel;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import net.kyori.adventure.text.Component;
|
||||
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.List;
|
||||
@@ -89,9 +92,15 @@ public class ChatUser {
|
||||
return Utility.getStaffPrefix(uuid);
|
||||
}
|
||||
|
||||
public ComponentLike getPrefixAll() {
|
||||
public ComponentLike getPrefixAll(boolean isWebMessage) {
|
||||
//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() {
|
||||
|
||||
@@ -57,7 +57,6 @@ public class EmoteList {
|
||||
}
|
||||
|
||||
public void prevPage() {
|
||||
this.page -= 1;
|
||||
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.serializer.plain.PlainTextComponentSerializer;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -18,9 +20,10 @@ public abstract class Toggleable {
|
||||
|
||||
public static Toggleable getToggleable(UUID uuid) {
|
||||
for (Toggleable toggleableClass : togglableClasses) {
|
||||
if (toggleableClass.isToggled(uuid))
|
||||
if (toggleableClass.isToggled(uuid)) {
|
||||
return toggleableClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -59,4 +62,5 @@ public abstract class Toggleable {
|
||||
|
||||
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;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Getter
|
||||
public class Channel {
|
||||
|
||||
public static HashMap<String, Channel> channels = new HashMap<>();
|
||||
protected String permission;
|
||||
protected String channelName;
|
||||
protected String format;
|
||||
protected boolean proxy;
|
||||
protected boolean local;
|
||||
private final String permission;
|
||||
private final String channelName;
|
||||
private final String format;
|
||||
private final boolean proxy;
|
||||
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.channelName = channelName;
|
||||
this.format = format;
|
||||
this.proxy = proxy;
|
||||
this.local = local;
|
||||
channels.put(channelName.toLowerCase(), this);
|
||||
this.web = web;
|
||||
this.webPath = web ? "web_" + channelName + "_chat" : null;
|
||||
}
|
||||
|
||||
public static Collection<Channel> getChannels() {
|
||||
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) {
|
||||
return channels.get(channelName.toLowerCase());
|
||||
}
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
package com.alttd.chat.objects.channels;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class CustomChannel extends Channel {
|
||||
private final List<String> servers;
|
||||
private final List<String> aliases;
|
||||
|
||||
public CustomChannel(String channelName, String format, List<String> servers, List<String> aliases, boolean proxy,
|
||||
boolean local) {
|
||||
super(channelName, format, proxy, local);
|
||||
this.permission = "chat.channel." + channelName.toLowerCase();
|
||||
this.channelName = channelName;
|
||||
this.format = format;
|
||||
boolean local, boolean web) {
|
||||
super(channelName, format, proxy, local, web);
|
||||
this.servers = servers;
|
||||
this.proxy = proxy;
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
public List<String> getServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
public List<String> getAliases() {
|
||||
return aliases;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,6 @@ package com.alttd.chat.objects.channels;
|
||||
|
||||
public abstract class DefaultChannel extends Channel {
|
||||
public DefaultChannel(String channelName, String format, boolean proxy) {
|
||||
super(channelName, format, proxy, false);
|
||||
super(channelName, format, proxy, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package com.alttd.chat.objects.chat_log;
|
||||
|
||||
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.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
@@ -9,44 +15,47 @@ import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class ChatLog implements BatchInsertable {
|
||||
|
||||
private final UUID uuid;
|
||||
private final Instant timestamp;
|
||||
private final String server;
|
||||
private final ChatLogType type;
|
||||
private final String channel;
|
||||
private final String receiver;
|
||||
private final String message;
|
||||
private final Component miniMessage;
|
||||
private final boolean blocked;
|
||||
|
||||
protected ChatLog(UUID uuid, Instant timestamp, String server, String message, boolean blocked) {
|
||||
this.uuid = uuid;
|
||||
this.timestamp = timestamp;
|
||||
this.server = server;
|
||||
this.message = message;
|
||||
this.blocked = blocked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareStatement(@NotNull PreparedStatement preparedStatement) throws SQLException {
|
||||
preparedStatement.setString(1, uuid.toString());
|
||||
preparedStatement.setTimestamp(2, Timestamp.from(timestamp));
|
||||
preparedStatement.setString(3, server);
|
||||
preparedStatement.setString(4, message);
|
||||
preparedStatement.setInt(5, blocked ? 1 : 0);
|
||||
preparedStatement.setString(4, ChatLogTypeMapper.toDb(type));
|
||||
preparedStatement.setString(5, channel);
|
||||
preparedStatement.setString(6, receiver);
|
||||
preparedStatement.setString(7, message);
|
||||
preparedStatement.setString(8,
|
||||
miniMessage == null ? null : GsonComponentSerializer.gson().serialize(miniMessage)
|
||||
);
|
||||
preparedStatement.setInt(9, blocked ? 1 : 0);
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public Instant getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return blocked;
|
||||
@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,7 +2,12 @@ package com.alttd.chat.objects.chat_log;
|
||||
|
||||
import com.alttd.chat.config.Config;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
@@ -12,14 +17,17 @@ import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@Slf4j
|
||||
public class ChatLogHandler {
|
||||
|
||||
private final WebHandler webHandler;
|
||||
private static ChatLogHandler instance = null;
|
||||
private ScheduledExecutorService executorService = null;
|
||||
|
||||
public static ChatLogHandler getInstance(boolean enableLogging) {
|
||||
if (instance == null)
|
||||
instance = new ChatLogHandler(enableLogging);
|
||||
public static ChatLogHandler getInstance(WebHandler webHandler, boolean enableLogging) {
|
||||
if (instance == null) {
|
||||
instance = new ChatLogHandler(webHandler, enableLogging);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -27,7 +35,8 @@ public class ChatLogHandler {
|
||||
private final Queue<ChatLog> chatLogQueue = new ConcurrentLinkedQueue<>();
|
||||
private final HashMap<UUID, List<ChatLog>> chatLogs = new HashMap<>();
|
||||
|
||||
public ChatLogHandler(boolean enableLogging) {
|
||||
public ChatLogHandler(WebHandler webHandler, boolean enableLogging) {
|
||||
this.webHandler = webHandler;
|
||||
if (!enableLogging) {
|
||||
ALogger.info("Logging is not enabled on this server.");
|
||||
return;
|
||||
@@ -35,23 +44,30 @@ public class ChatLogHandler {
|
||||
Duration deleteThreshold = Duration.ofDays(Config.CHAT_LOG_DELETE_OLDER_THAN_DAYS);
|
||||
ChatLogQueries.deleteOldMessages(deleteThreshold).thenAccept(success -> {
|
||||
if (success) {
|
||||
ALogger.info(String.format("Deleted all messages older than %s days from chat log database.", deleteThreshold.toDays()));
|
||||
ALogger.info(String.format("Deleted all messages older than %s days from chat log database.",
|
||||
deleteThreshold.toDays()
|
||||
));
|
||||
} else {
|
||||
ALogger.warn(String.format("Failed to delete all messages older than %s days from chat log database.", deleteThreshold.toDays()));
|
||||
ALogger.warn(String.format("Failed to delete all messages older than %s days from chat log database.",
|
||||
deleteThreshold.toDays()
|
||||
));
|
||||
}
|
||||
});
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
executorService.scheduleAtFixedRate(() -> {
|
||||
saveToDatabase(false);
|
||||
ALogger.info(String.format("Running scheduler to save messages with a %d delay", Config.CHAT_LOG_SAVE_DELAY_MINUTES));
|
||||
ALogger.info(String.format("Running scheduler to save messages with a %d delay",
|
||||
Config.CHAT_LOG_SAVE_DELAY_MINUTES
|
||||
));
|
||||
},
|
||||
Config.CHAT_LOG_SAVE_DELAY_MINUTES, Config.CHAT_LOG_SAVE_DELAY_MINUTES, TimeUnit.MINUTES);
|
||||
Config.CHAT_LOG_SAVE_DELAY_MINUTES, Config.CHAT_LOG_SAVE_DELAY_MINUTES, TimeUnit.MINUTES
|
||||
);
|
||||
ALogger.info("Logging has started!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the executor service and saves the chat logs to the database.
|
||||
* Will throw an error if called on a ChatLogHandler that was started without logging
|
||||
* Shuts down the executor service and saves the chat logs to the database. Will throw an error if called on a
|
||||
* ChatLogHandler that was started without logging
|
||||
*/
|
||||
public void shutDown() {
|
||||
executorService.shutdown();
|
||||
@@ -66,7 +82,7 @@ public class ChatLogHandler {
|
||||
return isSaving;
|
||||
}
|
||||
|
||||
public synchronized void addLog(ChatLog chatLog) {
|
||||
private synchronized void addLog(ChatLog chatLog) {
|
||||
if (isBlocked()) {
|
||||
chatLogQueue.add(chatLog);
|
||||
} else {
|
||||
@@ -100,17 +116,50 @@ public class ChatLogHandler {
|
||||
});
|
||||
}
|
||||
|
||||
public ChatLog loadFromResultSet(@NotNull ResultSet resultSet) throws SQLException {
|
||||
public Optional<ChatLog> loadFromResultSet(@NotNull ResultSet resultSet) throws SQLException {
|
||||
UUID chatLogUUID = UUID.fromString(resultSet.getString("uuid"));
|
||||
Instant chatTimestamp = resultSet.getTimestamp("time_stamp").toInstant();
|
||||
String server = resultSet.getString("server");
|
||||
String stringType = resultSet.getString("type");
|
||||
ChatLogType type;
|
||||
try {
|
||||
type = ChatLogTypeMapper.fromDb(stringType);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load chat log from result set: {}", e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
String channel = resultSet.getString("channel");
|
||||
String receiver = resultSet.getString("receiver");
|
||||
String chatMessage = resultSet.getString("chat_message");
|
||||
String stringMiniMessage = resultSet.getString("mini_message");
|
||||
Component miniMessage = stringMiniMessage == null ? null : GsonComponentSerializer.gson().deserialize(
|
||||
stringMiniMessage);
|
||||
boolean chatMessageBlocked = resultSet.getInt("blocked") == 1;
|
||||
return new ChatLog(chatLogUUID, chatTimestamp, server, chatMessage, chatMessageBlocked);
|
||||
return Optional.of(new ChatLog(chatLogUUID,
|
||||
chatTimestamp,
|
||||
server,
|
||||
type,
|
||||
channel,
|
||||
receiver,
|
||||
chatMessage,
|
||||
miniMessage,
|
||||
chatMessageBlocked
|
||||
));
|
||||
}
|
||||
|
||||
public void addChatLog(UUID uuid, String server, String message, boolean blocked) {
|
||||
addLog(new ChatLog(uuid, Instant.now(), server, message, blocked));
|
||||
public void addChatLog(UUID uuid, String server, String message, ChatLogType chatLogType, String channel, String receiver, Component miniMessage, boolean blocked) {
|
||||
ChatLog chatLog = new ChatLog(uuid,
|
||||
Instant.now(),
|
||||
server,
|
||||
chatLogType,
|
||||
channel,
|
||||
receiver,
|
||||
message,
|
||||
miniMessage,
|
||||
blocked
|
||||
);
|
||||
addLog(chatLog);
|
||||
webHandler.forwardChatLogToWeb(chatLog);
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.alttd.chat.objects.chat_log;
|
||||
|
||||
import com.alttd.altitudeweb.api.ChatApi;
|
||||
import com.alttd.altitudeweb.invoker.ApiClient;
|
||||
import com.alttd.altitudeweb.invoker.ApiException;
|
||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Slf4j
|
||||
public class WebHandler {
|
||||
|
||||
private static final int MAX_QUEUE_SIZE = 100;
|
||||
|
||||
private final Queue<ChatLog> queue = new ConcurrentLinkedQueue<>();
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
private final ChatApi chatApi;
|
||||
|
||||
public WebHandler() {
|
||||
ApiClient apiClient = new ApiClient();
|
||||
apiClient.setBasePath(Config.CHAT_WEB_SERVER_BASE_URL);
|
||||
|
||||
chatApi = new ChatApi(apiClient);
|
||||
}
|
||||
|
||||
private volatile boolean sending;
|
||||
|
||||
public void forwardChatLogToWeb(ChatLog chatLog) {
|
||||
if (queue.size() >= MAX_QUEUE_SIZE) {
|
||||
queue.clear();
|
||||
log.error("Chat log queue overflow, is the web backend still running?");
|
||||
}
|
||||
|
||||
queue.add(chatLog);
|
||||
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() {
|
||||
if (sending) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (sending || queue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sending = true;
|
||||
}
|
||||
|
||||
executor.execute(this::sendBatch);
|
||||
}
|
||||
|
||||
private void sendBatch() {
|
||||
try {
|
||||
ArrayList<ChatLog> chatLogList = new ArrayList<>();
|
||||
|
||||
ChatLog log;
|
||||
while ((log = queue.poll()) != null) {
|
||||
if (log.getMiniMessage() == null) {
|
||||
ALogger.warn("No mini message for message, skipping");
|
||||
continue;
|
||||
}
|
||||
chatLogList.add(log);
|
||||
}
|
||||
|
||||
List<ChatMessageDto> batch = chatLogList.stream()
|
||||
.map(ChatLogMapper::toDto)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.toList();
|
||||
|
||||
if (!chatLogList.isEmpty()) {
|
||||
try {
|
||||
chatApi.sendChatMessages(batch);
|
||||
} catch (ApiException e) {
|
||||
ALogger.error("Failed to send chat messages to web backend, " +
|
||||
"adding messages back to queue for another try", e
|
||||
);
|
||||
queue.addAll(chatLogList);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
sending = false;
|
||||
}
|
||||
|
||||
triggerSend();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.alttd.chat.objects.chat_log.mapper.chat_log;
|
||||
|
||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||
import com.alttd.chat.objects.chat_log.ChatLog;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class ChatLogMapper {
|
||||
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
Validator validator = factory.getValidator();
|
||||
|
||||
public Optional<ChatMessageDto> toDto(ChatLog chatLog) {
|
||||
ChatMessageDto chatMessageDto = new ChatMessageDto();
|
||||
if (chatLog.getMiniMessage() == null) {
|
||||
throw new IllegalArgumentException("MiniMessage cannot be null");
|
||||
}
|
||||
chatMessageDto.setUuid(chatLog.getUuid());
|
||||
chatMessageDto.setTimestamp(chatLog.getTimestamp().atOffset(ZoneOffset.UTC));
|
||||
chatMessageDto.setServer(chatLog.getServer());
|
||||
chatMessageDto.setType(ChatLogTypeMapper.toDto(chatLog.getType()));
|
||||
chatMessageDto.setChannel(chatLog.getChannel());
|
||||
chatMessageDto.setReceiver(chatLog.getReceiver());
|
||||
chatMessageDto.setMessage(GsonComponentSerializer.gson().serialize(chatLog.getMiniMessage()));
|
||||
chatMessageDto.setBlocked(chatLog.isBlocked());
|
||||
if (checkIsInvalid(chatMessageDto)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(chatMessageDto);
|
||||
}
|
||||
|
||||
private boolean checkIsInvalid(ChatMessageDto chatMessageDto) {
|
||||
Set<ConstraintViolation<ChatMessageDto>> violations = validator.validate(chatMessageDto);
|
||||
if (violations.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
log.error("Validation failed for chat message: {}, {}", chatMessageDto, violations);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.alttd.chat.objects.chat_log.mapper.chat_log;
|
||||
|
||||
public enum ChatLogType {
|
||||
PUBLIC,
|
||||
GLOBAL,
|
||||
PARTY,
|
||||
GAC,
|
||||
MSG,
|
||||
CUSTOM;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.alttd.chat.objects.chat_log.mapper.chat_log;
|
||||
|
||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class ChatLogTypeMapper {
|
||||
|
||||
public static String toDb(ChatLogType chatLogType) {
|
||||
return switch (chatLogType) {
|
||||
case PUBLIC -> "public";
|
||||
case GLOBAL -> "global";
|
||||
case PARTY -> "party";
|
||||
case GAC -> "gac";
|
||||
case MSG -> "msg";
|
||||
case CUSTOM -> "custom";
|
||||
};
|
||||
}
|
||||
|
||||
public ChatMessageDto.TypeEnum toDto(ChatLogType chatLogType) {
|
||||
return switch (chatLogType) {
|
||||
case PUBLIC -> ChatMessageDto.TypeEnum.PUBLIC;
|
||||
case GLOBAL -> ChatMessageDto.TypeEnum.GLOBAL;
|
||||
case PARTY -> ChatMessageDto.TypeEnum.PARTY;
|
||||
case GAC -> ChatMessageDto.TypeEnum.GAC;
|
||||
case MSG -> ChatMessageDto.TypeEnum.MSG;
|
||||
case CUSTOM -> ChatMessageDto.TypeEnum.CUSTOM;
|
||||
};
|
||||
}
|
||||
|
||||
public ChatLogType fromDb(String dbType) {
|
||||
return switch (dbType) {
|
||||
case "public" -> ChatLogType.PUBLIC;
|
||||
case "global" -> ChatLogType.GLOBAL;
|
||||
case "party" -> ChatLogType.PARTY;
|
||||
case "gac" -> ChatLogType.GAC;
|
||||
case "msg" -> ChatLogType.MSG;
|
||||
case "custom" -> ChatLogType.CUSTOM;
|
||||
default -> throw new IllegalArgumentException("Invalid chat log type: " + dbType);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+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.standard.StandardTags;
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.model.group.Group;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.query.QueryOptions;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Matcher;
|
||||
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();
|
||||
User user = luckPerms.getUserManager().getUser(uuid);
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.alttd.chat.web;
|
||||
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Client for subscribing to: GET /api/chat/send/subscribe/{server} served as text/event-stream (SseEmitter) by
|
||||
* CommandsToServerController.
|
||||
* <p>
|
||||
* Handlers for individual SSE event types can be registered up front via {@link #register(String, WebHandler)}. Each
|
||||
* handler says "when an event named X arrives, deserialize its JSON payload as type T and pass it to this handler."
|
||||
* Unregistered event names are logged and skipped, not thrown.
|
||||
* <p>
|
||||
* If the connection fails or drops for any reason, the client logs it and retries once a minute, indefinitely, until
|
||||
* stop() is called or the thread is interrupted.
|
||||
* <p>
|
||||
* Server-side auth: the controller checks @AuthenticationPrincipal Token token, then token.getKey().equals(validToken).
|
||||
* That principal is populated from the "Authorization: Bearer <token>" header. validToken is currently hardcoded
|
||||
* server-side; swap TOKEN below for a config value once that TODO is resolved.
|
||||
*/
|
||||
public class SseSubscribeClient implements Runnable {
|
||||
|
||||
private static final Logger log = Logger.getLogger(SseSubscribeClient.class.getName());
|
||||
private static final Duration RETRY_DELAY = Duration.ofMinutes(1);
|
||||
|
||||
private final String baseUrl;
|
||||
private final String server;
|
||||
private final HttpClient httpClient;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final Map<String, WebHandler<?>> handlers = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile boolean running = false;
|
||||
|
||||
public SseSubscribeClient(String baseUrl, String server, String token) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.server = server;
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler for a given SSE event name. When an event with this name arrives, its data payload is
|
||||
* deserialized using the handler's type and passed to the handler.
|
||||
*/
|
||||
public <T> void register(String eventName, WebHandler<T> handler) {
|
||||
handlers.put(eventName, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the subscribe/retry loop on the calling thread. Blocks until stop() is called or the thread is interrupted.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
running = true;
|
||||
while (running && !Thread.currentThread().isInterrupted()) {
|
||||
String token = Config.CHAT_WEB_TOKEN;
|
||||
if (token.equals("invalid-token")) {
|
||||
log.warning("Invalid token provided for SSE connection as '" + server + "'");
|
||||
sleepBeforeRetry();
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
connectAndListen(token);
|
||||
log.warning("SSE stream as '" + server + "' ended; reconnecting in " + RETRY_DELAY.toMinutes() + " minute(s)");
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "SSE connection to '" + server + "' failed; retrying in "
|
||||
+ RETRY_DELAY.toMinutes() + " minute(s)", e
|
||||
);
|
||||
}
|
||||
|
||||
if (running) {
|
||||
sleepBeforeRetry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
private void connectAndListen(String token) throws Exception {
|
||||
HttpResponse<InputStream> response = openConnection(token);
|
||||
checkStatus(response);
|
||||
readEvents(response.body());
|
||||
}
|
||||
|
||||
private HttpResponse<InputStream> openConnection(String token) throws Exception {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + "/api/chat/server/send/subscribe/" + server))
|
||||
.header("X-Altitude-Token", token)
|
||||
.header("Accept", "text/event-stream")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
return httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
}
|
||||
|
||||
private void checkStatus(HttpResponse<InputStream> response) {
|
||||
int status = response.statusCode();
|
||||
if (status == 401) {
|
||||
throw new RuntimeException("Unauthorized - missing/invalid token");
|
||||
}
|
||||
if (status == 403) {
|
||||
throw new RuntimeException("Forbidden - token did not match server's validToken");
|
||||
}
|
||||
if (status != 200) {
|
||||
throw new RuntimeException("Unexpected status: " + status);
|
||||
}
|
||||
ALogger.info("SSE connection as '" + server + "' established");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the SSE stream line by line, accumulating one event at a time, and dispatches each complete event as it's
|
||||
* parsed.
|
||||
*/
|
||||
private void readEvents(InputStream body) throws Exception {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(body, StandardCharsets.UTF_8))) {
|
||||
String eventName = null;
|
||||
StringBuilder dataBuffer = new StringBuilder();
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty()) {
|
||||
if (!dataBuffer.isEmpty()) {
|
||||
dispatch(eventName, dataBuffer.toString());
|
||||
}
|
||||
eventName = null;
|
||||
dataBuffer.setLength(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("event:")) {
|
||||
eventName = line.substring(6).trim();
|
||||
} else if (line.startsWith("data:")) {
|
||||
if (!dataBuffer.isEmpty()) {
|
||||
dataBuffer.append('\n');
|
||||
}
|
||||
dataBuffer.append(line.substring(5).trim());
|
||||
}
|
||||
// lines starting with ":" are comments/heartbeats - ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatch(String eventName, String data) {
|
||||
if (eventName == null) {
|
||||
log.warning("Received event with no 'event:' name, ignoring. Data: " + data);
|
||||
return;
|
||||
}
|
||||
|
||||
WebHandler<?> handler = handlers.get(eventName);
|
||||
if (handler == null) {
|
||||
log.warning("No handler registered for event type '" + eventName + "', ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchTyped(eventName, handler, data);
|
||||
}
|
||||
|
||||
// Captures the wildcard type from the handler so parsing/casting is type-safe.
|
||||
private <T> void dispatchTyped(String eventName, WebHandler<T> handler, String data) {
|
||||
try {
|
||||
T event = objectMapper.readValue(data, handler.type());
|
||||
handler.handle(event);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Failed to parse/handle event '" + eventName + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sleepBeforeRetry() {
|
||||
try {
|
||||
Thread.sleep(RETRY_DELAY.toMillis());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alttd.chat.web;
|
||||
|
||||
public interface WebHandler<T> {
|
||||
|
||||
Class<T> type();
|
||||
|
||||
void handle(T event);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.alttd.chat.web.handler_class;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ChatFromWeb {
|
||||
|
||||
private UUID sender;
|
||||
private 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;
|
||||
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
id("io.github.goooler.shadow") version "8.1.8"
|
||||
id("com.gradleup.shadow") version "9.6.1"
|
||||
id("com.github.ben-manes.versions") version "0.52.0"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ subprojects {
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
languageVersion.set(JavaLanguageVersion.of(25))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-4
@@ -1,16 +1,22 @@
|
||||
plugins {
|
||||
`maven-publish`
|
||||
id("io.github.goooler.shadow")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
val nexusUser = providers.gradleProperty("alttdSnapshotUsername").orNull ?: System.getenv("NEXUS_USERNAME")
|
||||
val nexusPass = providers.gradleProperty("alttdSnapshotPassword").orNull ?: System.getenv("NEXUS_PASSWORD")
|
||||
|
||||
dependencies {
|
||||
implementation(project(":api")) // API
|
||||
compileOnly("com.alttd.cosmos:cosmos-api:1.21.6-R0.1-SNAPSHOT") {
|
||||
isChanging = true
|
||||
}
|
||||
compileOnly("com.alttd.cosmos:cosmos-api:26.2.build.17-stable")
|
||||
compileOnly("org.projectlombok:lombok:1.18.46")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.46")
|
||||
compileOnly("com.gitlab.ruany:LiteBansAPI:0.6.1") // move to proxy
|
||||
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
|
||||
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 {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package com.alttd.chat;
|
||||
|
||||
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.WebUpdatePlayerList;
|
||||
import com.alttd.chat.commands.*;
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.config.ServerConfig;
|
||||
@@ -11,9 +15,13 @@ import com.alttd.chat.nicknames.NicknamesEvents;
|
||||
import com.alttd.chat.objects.channels.Channel;
|
||||
import com.alttd.chat.objects.channels.CustomChannel;
|
||||
import com.alttd.chat.objects.chat_log.ChatLogHandler;
|
||||
import com.alttd.chat.objects.chat_log.WebHandler;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.alttd.chat.util.ServerName;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.alttd.chat.web.SseSubscribeClient;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -21,25 +29,35 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
public class ChatPlugin extends JavaPlugin {
|
||||
|
||||
@Getter
|
||||
private static ChatPlugin instance;
|
||||
|
||||
private ChatAPI chatAPI;
|
||||
@Getter
|
||||
private ChatHandler chatHandler;
|
||||
|
||||
private ServerConfig serverConfig;
|
||||
private SseSubscribeClient sseSubscribeClient;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
ALogger.init(getSLF4JLogger());
|
||||
chatAPI = new ChatImplementation();
|
||||
chatHandler = new ChatHandler();
|
||||
WebHandler webHandler = new WebHandler();
|
||||
ChatLogHandler chatLogHandler = ChatLogHandler.getInstance(webHandler, true);
|
||||
chatHandler = new ChatHandler(chatLogHandler);
|
||||
DatabaseConnection.initialize();
|
||||
serverConfig = new ServerConfig(ServerName.getServerName());
|
||||
ChatLogHandler chatLogHandler = ChatLogHandler.getInstance(true);
|
||||
registerListener(new PlayerListener(serverConfig), new ChatListener(chatLogHandler), new BookListener(), new ShutdownListener(chatLogHandler, this));
|
||||
ChatMessageSender chatMessageSender = new ChatMessageSender(chatLogHandler, chatAPI.getLuckPerms());
|
||||
registerListener(new PlayerListener(serverConfig, webHandler, this),
|
||||
new ChatListener(chatMessageSender),
|
||||
new BookListener(),
|
||||
new ShutdownListener(chatLogHandler, this)
|
||||
);
|
||||
if (serverConfig.GLOBALCHAT) {
|
||||
registerCommand("globalchat", new GlobalChat());
|
||||
registerCommand("toggleglobalchat", new ToggleGlobalChat());
|
||||
@@ -58,21 +76,43 @@ public class ChatPlugin extends JavaPlugin {
|
||||
if (!(channel instanceof CustomChannel customChannel)) {
|
||||
continue;
|
||||
}
|
||||
this.getServer().getCommandMap().register(channel.getChannelName().toLowerCase(), new ChatChannel(customChannel));
|
||||
ChatChannel chatChannel = new ChatChannel(customChannel, chatAPI.getLuckPerms());
|
||||
this.getServer()
|
||||
.getCommandMap()
|
||||
.register(channel.getChannelName().toLowerCase(), chatChannel);
|
||||
if (customChannel.getWebPath() != null) {
|
||||
sseSubscribeClient.register(customChannel.getWebPath(), new WebChannelChatHandler(chatChannel));
|
||||
}
|
||||
}
|
||||
|
||||
String messageChannel = Config.MESSAGECHANNEL;
|
||||
getServer().getMessenger().registerOutgoingPluginChannel(this, messageChannel);
|
||||
getServer().getMessenger().registerIncomingPluginChannel(this, messageChannel, new PluginMessage());
|
||||
getServer().getMessenger()
|
||||
.registerIncomingPluginChannel(this, messageChannel, new PluginMessage(chatLogHandler));
|
||||
|
||||
NicknamesEvents nicknamesEvents = new NicknamesEvents();
|
||||
getServer().getMessenger().registerIncomingPluginChannel(this, messageChannel, nicknamesEvents);
|
||||
getServer().getPluginManager().registerEvents(nicknamesEvents, this);
|
||||
registerCommand("nick", new Nicknames());
|
||||
|
||||
sseSubscribeClient = new SseSubscribeClient(
|
||||
Config.CHAT_WEB_REGISTER_TO_BASE_URL,
|
||||
getServer().getServerName(),
|
||||
Config.CHAT_WEB_TOKEN
|
||||
);
|
||||
new Thread(sseSubscribeClient).start();
|
||||
registerWebHandlers(chatMessageSender, sseSubscribeClient);
|
||||
}
|
||||
|
||||
private void registerWebHandlers(ChatMessageSender chatMessageSender, SseSubscribeClient sseSubscribeClient) {
|
||||
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
|
||||
public void onDisable() {
|
||||
sseSubscribeClient.stop();
|
||||
instance = null;
|
||||
}
|
||||
|
||||
@@ -86,14 +126,6 @@ public class ChatPlugin extends JavaPlugin {
|
||||
Objects.requireNonNull(getCommand(commandName)).setExecutor(commandExecutor);
|
||||
}
|
||||
|
||||
public static ChatPlugin getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ChatHandler getChatHandler() {
|
||||
return chatHandler;
|
||||
}
|
||||
|
||||
public boolean serverGlobalChatEnabled() {
|
||||
return serverConfig.GLOBALCHAT;
|
||||
}
|
||||
@@ -110,7 +142,9 @@ public class ChatPlugin extends JavaPlugin {
|
||||
chatAPI.reloadConfig();
|
||||
chatAPI.reloadChatFilters();
|
||||
serverConfig = new ServerConfig(ServerName.getServerName());
|
||||
Bukkit.broadcast(Utility.parseMiniMessage("Reloaded ChatPlugin Config.").asComponent(), "command.chat.reloadchat");
|
||||
Bukkit.broadcast(Utility.parseMiniMessage("Reloaded ChatPlugin Config.").asComponent(),
|
||||
"command.chat.reloadchat"
|
||||
);
|
||||
ALogger.info("Reloaded ChatPlugin config.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
package com.alttd.chat.chat_web;
|
||||
|
||||
import com.alttd.chat.ChatPlugin;
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.managers.ChatUserManager;
|
||||
import com.alttd.chat.managers.RegexManager;
|
||||
import com.alttd.chat.objects.ChatUser;
|
||||
import com.alttd.chat.objects.FilterType;
|
||||
import com.alttd.chat.objects.ModifiableString;
|
||||
import com.alttd.chat.objects.chat_log.ChatLogHandler;
|
||||
import com.alttd.chat.objects.chat_log.mapper.chat_log.ChatLogType;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.alttd.chat.util.GalaxyUtility;
|
||||
import com.alttd.chat.util.ServerName;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ChatMessageSender {
|
||||
|
||||
private final ChatLogHandler chatLogHandler;
|
||||
private final LuckPerms luckPerms;
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
public void sendMessage(UUID sender, String message) {
|
||||
//TODO [Stijn] [2026-08-02]: Mark as from website
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(sender);
|
||||
Component component = miniMessage.deserialize(message);
|
||||
sendMessage(offlinePlayer, component, true);
|
||||
}
|
||||
|
||||
public void sendMessage(@NotNull OfflinePlayer offlinePlayer, Component message, boolean isFromWeb) {
|
||||
UUID uuid = offlinePlayer.getUniqueId();
|
||||
if (luckPerms.getUserManager().isLoaded(uuid)) {
|
||||
User user = luckPerms.getUserManager().getUser(uuid);
|
||||
sendMessage(user, offlinePlayer, message, isFromWeb);
|
||||
} else {
|
||||
luckPerms.getUserManager().loadUser(uuid).whenComplete((user, throwable) -> {
|
||||
if (throwable != null) {
|
||||
ALogger.error("Failed to load user: " + uuid, throwable);
|
||||
}
|
||||
sendMessage(user, offlinePlayer, message, isFromWeb);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(User user, OfflinePlayer offlinePlayer, Component message, boolean isFromWeb) {
|
||||
if (offlinePlayer == null) {
|
||||
ALogger.error("OfflinePlayer is null");
|
||||
return;
|
||||
}
|
||||
if (ChatPlugin.getInstance().serverMuted() && !Utility.hasPermission(user, "chat.bypass-server-muted")) {
|
||||
sendBlockNotifIfOnline(offlinePlayer, message);
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = offlinePlayer.getUniqueId();
|
||||
|
||||
ComponentLike input = message.colorIfAbsent(NamedTextColor.WHITE);
|
||||
Component inputComponent = input.asComponent();
|
||||
|
||||
ModifiableString modifiableString = new ModifiableString(inputComponent);
|
||||
|
||||
if (parseMessage(offlinePlayer, uuid, modifiableString, inputComponent, isFromWeb)) {
|
||||
//Parse message failed likely due to something the player said, already logged
|
||||
return;
|
||||
}
|
||||
|
||||
if (sendMessageAndPing(user, offlinePlayer, uuid, modifiableString)) {
|
||||
//Send message and ping failed, likely due to the offlinePlayer not being valid, already logged
|
||||
return;
|
||||
}
|
||||
ALogger.info(PlainTextComponentSerializer.plainText().serialize(inputComponent));
|
||||
}
|
||||
|
||||
private boolean sendMessageAndPing(User user, OfflinePlayer offlinePlayer, UUID uuid, ModifiableString modifiableString) {
|
||||
ComponentLike input;
|
||||
Stream<Player> stream = Bukkit.getOnlinePlayers().stream()
|
||||
.map(audience -> (Player) audience);
|
||||
|
||||
if (!Utility.hasPermission(user, "chat.ignorebypass")) {
|
||||
stream = stream.filter(receiver -> {
|
||||
boolean isPlayerIgnored = ChatUserManager
|
||||
.getChatUser(receiver.getUniqueId())
|
||||
.getIgnoredPlayers()
|
||||
.contains(uuid);
|
||||
return !isPlayerIgnored || receiver.hasPermission("chat.ignorebypass");
|
||||
});
|
||||
}
|
||||
Set<Player> receivers = stream.collect(Collectors.toSet());
|
||||
|
||||
Set<Player> playersToPing = new HashSet<>();
|
||||
pingPlayers(playersToPing, modifiableString, offlinePlayer, user);
|
||||
|
||||
Optional<ComponentLike> render = render(offlinePlayer, modifiableString.component(), false);
|
||||
if (render.isEmpty()) {
|
||||
//Already logged
|
||||
return true;
|
||||
}
|
||||
input = render.get();
|
||||
for (Player receiver : receivers) {
|
||||
receiver.sendMessage(input);
|
||||
}
|
||||
for (Player pingPlayer : playersToPing) {
|
||||
pingPlayer.playSound(pingPlayer.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1);
|
||||
}
|
||||
chatLogHandler.addChatLog(uuid,
|
||||
ServerName.getServerName(),
|
||||
modifiableString.string(),
|
||||
ChatLogType.PUBLIC,
|
||||
null,
|
||||
null,
|
||||
input.asComponent(),
|
||||
false
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean parseMessage(OfflinePlayer offlinePlayer, UUID uuid, ModifiableString modifiableString, Component inputComponent, boolean isWebMessage) {
|
||||
// todo a better way for this
|
||||
if (!RegexManager.filterText(offlinePlayer.getName(), uuid, modifiableString, true, "chat",
|
||||
filterType -> punishOnlinePlayer(filterType, uuid, modifiableString)
|
||||
)) {
|
||||
GalaxyUtility.sendBlockedNotification("Language", offlinePlayer,
|
||||
modifiableString.component(),
|
||||
""
|
||||
);
|
||||
String originalMessage = PlainTextComponentSerializer.plainText().serialize(inputComponent);
|
||||
Optional<Component> component = render(offlinePlayer,
|
||||
inputComponent,
|
||||
isWebMessage
|
||||
).map(ComponentLike::asComponent);
|
||||
if (component.isEmpty()) {
|
||||
//Already logged
|
||||
return true;
|
||||
}
|
||||
chatLogHandler.addChatLog(uuid,
|
||||
ServerName.getServerName(),
|
||||
originalMessage,
|
||||
ChatLogType.PUBLIC,
|
||||
null,
|
||||
null,
|
||||
component.get(),
|
||||
true
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void pingPlayers(Set<Player> playersToPing, ModifiableString modifiableString, OfflinePlayer offlinePlayer, User user) {
|
||||
Component mention = MiniMessage.miniMessage().deserialize(Config.MENTIONPLAYERTAG);
|
||||
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
|
||||
String name = onlinePlayer.getName();
|
||||
String nickName = PlainTextComponentSerializer.plainText().serialize(onlinePlayer.displayName());
|
||||
|
||||
Pattern namePattern = Pattern.compile("\\b(?<!\\\\)" + name + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
// Pattern escapedNamePattern = Pattern.compile("\\b\\\\" + name + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
Pattern nickPattern = Pattern.compile("\\b(?<!\\\\)" + nickName + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
// Pattern escapedNickPattern = Pattern.compile("\\b\\\\" + nickName + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
ChatUser onlinePlayerUser = ChatUserManager.getChatUser(onlinePlayer.getUniqueId());
|
||||
if (namePattern.matcher(modifiableString.string()).find()) {
|
||||
modifiableString.replace(TextReplacementConfig.builder()
|
||||
.once()
|
||||
.match(namePattern)
|
||||
.replacement(mention.append(onlinePlayerUser.getDisplayName()))
|
||||
.build());
|
||||
//TODO replace all instances of \name with just name but using the match result so the capitalization doesn't change
|
||||
// modifiableString.replace(TextReplacementConfig.builder()
|
||||
// .once()
|
||||
// .match(escapedNamePattern)
|
||||
// .replacement((a, b) -> {
|
||||
// String substring = a.group().substring(1);
|
||||
// return ;
|
||||
// });
|
||||
|
||||
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId())
|
||||
.getIgnoredPlayers()
|
||||
.contains(offlinePlayer.getUniqueId())
|
||||
|| Utility.hasPermission(user, "chat.ignorebypass")) {
|
||||
playersToPing.add(onlinePlayer);
|
||||
}
|
||||
} else if (nickPattern.matcher(modifiableString.string()).find()) {
|
||||
modifiableString.replace(TextReplacementConfig.builder()
|
||||
.once()
|
||||
.match(nickPattern)
|
||||
.replacement(mention.append(onlinePlayerUser.getDisplayName()))
|
||||
.build());
|
||||
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId())
|
||||
.getIgnoredPlayers()
|
||||
.contains(offlinePlayer.getUniqueId())
|
||||
|| Utility.hasPermission(user, "chat.ignorebypass")) {
|
||||
playersToPing.add(onlinePlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<ComponentLike> render(@NotNull OfflinePlayer offlinePlayer, @NotNull Component message, boolean isWebMessage) {
|
||||
if (offlinePlayer.getName() == null) {
|
||||
ALogger.error("Invalid offline player");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
ChatUser user = ChatUserManager.getChatUser(offlinePlayer.getUniqueId());
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.component("sender", user.getDisplayName()),
|
||||
Placeholder.parsed("sendername", offlinePlayer.getName()),
|
||||
Placeholder.component("prefix", user.getPrefix()),
|
||||
Placeholder.component("prefixall", user.getPrefixAll(isWebMessage)),
|
||||
Placeholder.component("staffprefix", user.getStaffPrefix()),
|
||||
Placeholder.component("message", message)
|
||||
);
|
||||
|
||||
return Optional.of(Utility.parseMiniMessage(Config.CHATFORMAT, placeholders));
|
||||
}
|
||||
|
||||
private static void punishOnlinePlayer(FilterType filterType, UUID uuid, ModifiableString modifiableString) {
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
if (!filterType.equals(FilterType.PUNISH)) {
|
||||
ALogger.warn("Received another FilterType than punish when filtering chat and executing a filter action");
|
||||
return;
|
||||
}
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF("punish");
|
||||
out.writeUTF(player.getName());
|
||||
out.writeUTF(uuid.toString());
|
||||
out.writeUTF(modifiableString.string());
|
||||
player.sendPluginMessage(ChatPlugin.getInstance(), Config.MESSAGECHANNEL, out.toByteArray());
|
||||
}
|
||||
|
||||
private static void sendBlockNotifIfOnline(OfflinePlayer offlinePlayer, Component message) {
|
||||
if (!offlinePlayer.isOnline()) {
|
||||
return;
|
||||
}
|
||||
Player player = offlinePlayer.getPlayer();
|
||||
if (player == null) {
|
||||
ALogger.error("Failed to load player: " + offlinePlayer.getName());
|
||||
return;
|
||||
}
|
||||
GalaxyUtility.sendBlockedNotification("Chat Muted", player, message, "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,24 @@
|
||||
package com.alttd.chat.chat_web.handlers;
|
||||
|
||||
import com.alttd.chat.chat_web.ChatMessageSender;
|
||||
import com.alttd.chat.web.WebHandler;
|
||||
import com.alttd.chat.web.handler_class.ChatFromWeb;
|
||||
|
||||
public class WebChatHandler implements WebHandler<ChatFromWeb> {
|
||||
|
||||
private final ChatMessageSender chatMessageSender;
|
||||
|
||||
public WebChatHandler(ChatMessageSender chatMessageSender) {
|
||||
this.chatMessageSender = chatMessageSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ChatFromWeb> type() {
|
||||
return ChatFromWeb.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ChatFromWeb chatFromWeb) {
|
||||
this.chatMessageSender.sendMessage(chatFromWeb.getSender(), chatFromWeb.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -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.objects.channels.CustomChannel;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
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.TagResolver;
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.defaults.BukkitCommand;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -13,16 +20,19 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChatChannel extends BukkitCommand {
|
||||
|
||||
CustomChannel channel;
|
||||
String command;
|
||||
ToggleableForCustomChannel toggleableForCustomChannel;
|
||||
private final LuckPerms luckPerms;
|
||||
private static final List<ChatChannel> activeCommands = new ArrayList<>();
|
||||
|
||||
public ChatChannel(CustomChannel channel) {
|
||||
public ChatChannel(CustomChannel channel, LuckPerms luckPerms) {
|
||||
super(channel.getChannelName().toLowerCase());
|
||||
this.luckPerms = luckPerms;
|
||||
this.channel = channel;
|
||||
this.command = channel.getChannelName().toLowerCase();
|
||||
this.description = "Chat channel named " + channel.getChannelName() + ".";
|
||||
@@ -42,7 +52,10 @@ public class ChatChannel extends BukkitCommand {
|
||||
player.sendRichMessage(Config.CUSTOM_CHANNEL_TOGGLED, TagResolver.resolver(
|
||||
Placeholder.unparsed("channel", channel.getChannelName()),
|
||||
Placeholder.component("status", toggleableForCustomChannel.toggle(player.getUniqueId())
|
||||
? Config.TOGGLED_ON : Config.TOGGLED_OFF)));
|
||||
? Config.TOGGLED_ON : Config.TOGGLED_OFF
|
||||
)
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,4 +65,28 @@ public class ChatChannel extends BukkitCommand {
|
||||
|
||||
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.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class MuteServer implements CommandExecutor {
|
||||
|
||||
@Override
|
||||
@@ -21,11 +18,8 @@ public class MuteServer implements CommandExecutor {
|
||||
if (!(sender instanceof Player player)) { // must be a player
|
||||
return true;
|
||||
}
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
UUID uuid = player.getUniqueId();
|
||||
if (!Utility.hasPermission(uuid, Config.SERVERMUTEPERMISSION)) {
|
||||
Utility.getOrLoadUser(player.getUniqueId()).thenAcceptAsync(user -> {
|
||||
if (!Utility.hasPermission(user, Config.SERVERMUTEPERMISSION)) {
|
||||
sender.sendRichMessage("<red>You don't have permission to use this command.</red>");
|
||||
return;
|
||||
}
|
||||
@@ -34,14 +28,17 @@ public class MuteServer implements CommandExecutor {
|
||||
|
||||
ComponentLike component;
|
||||
if (ChatPlugin.getInstance().serverMuted()) {
|
||||
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(), player.getName()) + " <red>muted</red><white> chat.");
|
||||
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.");
|
||||
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());
|
||||
Bukkit.getOnlinePlayers().forEach(onlinePlayer -> onlinePlayer.sendMessage(component));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.objects.Toggleable;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -70,4 +72,9 @@ public class PartyChat extends Toggleable implements CommandExecutor {
|
||||
}
|
||||
}.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;
|
||||
|
||||
import com.alttd.chat.ChatPlugin;
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.managers.ChatUserManager;
|
||||
import com.alttd.chat.util.Utility;
|
||||
@@ -8,7 +7,6 @@ import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
@@ -17,18 +15,17 @@ public class ToggleGlobalChat implements CommandExecutor {
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
UUID uuid = ((Player) sender).getUniqueId();
|
||||
UUID uuid = player.getUniqueId();
|
||||
Utility.getOrLoadUser(uuid).thenAcceptAsync(user -> {
|
||||
ChatUserManager.getChatUser(uuid);
|
||||
Utility.flipPermission(uuid, Config.GCPERMISSION);
|
||||
sender.sendRichMessage("You have turned globalchat " + (!Utility.hasPermission(uuid, Config.GCPERMISSION) ? "<green>on." : "<red>off.")); // TODO load from config and minimessage
|
||||
}
|
||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
||||
sender.sendRichMessage("You have turned globalchat " + (!Utility.hasPermission(user,
|
||||
Config.GCPERMISSION
|
||||
) ? "<green>on." : "<red>off.")); // TODO load from config and minimessage
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,15 @@ import com.alttd.chat.objects.ChatFilter;
|
||||
import com.alttd.chat.objects.ChatUser;
|
||||
import com.alttd.chat.objects.ModifiableString;
|
||||
import com.alttd.chat.objects.channels.CustomChannel;
|
||||
import com.alttd.chat.objects.chat_log.ChatLogHandler;
|
||||
import com.alttd.chat.objects.chat_log.mapper.chat_log.ChatLogType;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.alttd.chat.util.GalaxyUtility;
|
||||
import com.alttd.chat.util.ServerName;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
@@ -22,36 +25,48 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
public class ChatHandler {
|
||||
|
||||
private final ChatPlugin plugin;
|
||||
|
||||
private final ComponentLike GCNOTENABLED;
|
||||
private final ChatLogHandler chatLogHandler;
|
||||
|
||||
public ChatHandler() {
|
||||
public ChatHandler(ChatLogHandler chatLogHandler) {
|
||||
this.chatLogHandler = chatLogHandler;
|
||||
plugin = ChatPlugin.getInstance();
|
||||
GCNOTENABLED = Utility.parseMiniMessage(Config.GCNOTENABLED);
|
||||
}
|
||||
|
||||
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());
|
||||
// user.setReplyTarget(target);
|
||||
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.component("message", parseMessageContent(player, message)),
|
||||
Placeholder.component("message", parseMessageContent(user, player, message)),
|
||||
Placeholder.component("sendername", player.name()),
|
||||
Placeholder.parsed("receivername", target)
|
||||
);
|
||||
@@ -64,7 +79,8 @@ public class ChatHandler {
|
||||
GalaxyUtility.sendBlockedNotification("DM Language",
|
||||
player,
|
||||
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
||||
target);
|
||||
target
|
||||
);
|
||||
return; // the message was blocked
|
||||
}
|
||||
|
||||
@@ -73,17 +89,23 @@ public class ChatHandler {
|
||||
sendPrivateMessage(player, target, "privatemessage", component.asComponent());
|
||||
ComponentLike spymessage = Utility.parseMiniMessage(Config.MESSAGESPY, placeholders);
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
if (pl.hasPermission(Config.SPYPERMISSION) && ChatUserManager.getChatUser(pl.getUniqueId()).isSpy() && !pl.equals(player) && !pl.getName().equalsIgnoreCase(target)) {
|
||||
if (pl.hasPermission(Config.SPYPERMISSION) && ChatUserManager.getChatUser(pl.getUniqueId())
|
||||
.isSpy() && !pl.equals(player) && !pl.getName().equalsIgnoreCase(target)) {
|
||||
pl.sendMessage(spymessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
// user.setReplyTarget(target);
|
||||
|
||||
Component messageComponent = parseMessageContent(player, message);
|
||||
Component messageComponent = parseMessageContent(user, player, message);
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.component("message", messageComponent),
|
||||
Placeholder.component("sendername", player.name()),
|
||||
@@ -96,7 +118,8 @@ public class ChatHandler {
|
||||
GalaxyUtility.sendBlockedNotification("DM Language",
|
||||
player,
|
||||
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
||||
target);
|
||||
target
|
||||
);
|
||||
return; // the message was blocked
|
||||
}
|
||||
|
||||
@@ -108,35 +131,43 @@ public class ChatHandler {
|
||||
sendPrivateMessage(player, target, "privatemessage", messageComponent);
|
||||
ComponentLike spymessage = Utility.parseMiniMessage(Config.MESSAGESPY, placeholders);
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
if (pl.hasPermission(Config.SPYPERMISSION) && ChatUserManager.getChatUser(pl.getUniqueId()).isSpy() && !pl.equals(player) && !pl.getName().equalsIgnoreCase(target)) {
|
||||
if (pl.hasPermission(Config.SPYPERMISSION) && ChatUserManager.getChatUser(pl.getUniqueId())
|
||||
.isSpy() && !pl.equals(player) && !pl.getName().equalsIgnoreCase(target)) {
|
||||
pl.sendMessage(spymessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void globalChat(Player player, String message) {
|
||||
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
||||
if (!Utility.hasPermission(player.getUniqueId(), Config.GCPERMISSION)) {
|
||||
Utility.getOrLoadUser(player.getUniqueId())
|
||||
.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
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMuted(player, message, "[GC Muted] ")) {
|
||||
if (isMuted(user, player, message, "[GC Muted] ")) {
|
||||
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
|
||||
player.sendRichMessage(Config.GCONCOOLDOWN, Placeholder.parsed("cooldown", Config.GCCOOLDOWN - timeLeft + ""));
|
||||
player.sendRichMessage(Config.GCONCOOLDOWN,
|
||||
Placeholder.parsed("cooldown", Config.GCCOOLDOWN - timeLeft + "")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentLike senderName = user.getDisplayName();
|
||||
ComponentLike prefix = user.getPrefix();
|
||||
ComponentLike senderName = chatUser.getDisplayName();
|
||||
ComponentLike prefix = chatUser.getPrefix();
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.component("sender", senderName),
|
||||
Placeholder.component("prefix", prefix),
|
||||
Placeholder.component("message", parseMessageContent(player, message)),
|
||||
Placeholder.component("message", parseMessageContent(user, player, message)),
|
||||
Placeholder.parsed("server", ServerName.getServerName())
|
||||
);
|
||||
|
||||
@@ -148,43 +179,66 @@ public class ChatHandler {
|
||||
GalaxyUtility.sendBlockedNotification("GC Language",
|
||||
player,
|
||||
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
||||
"");
|
||||
""
|
||||
);
|
||||
return; // the message was blocked
|
||||
}
|
||||
component = modifiableString.component();
|
||||
|
||||
user.setGcCooldown(System.currentTimeMillis());
|
||||
chatLogHandler.addChatLog(player.getUniqueId(),
|
||||
Bukkit.getServerName(),
|
||||
message,
|
||||
ChatLogType.GLOBAL,
|
||||
null,
|
||||
null,
|
||||
component,
|
||||
false
|
||||
);
|
||||
|
||||
chatUser.setGcCooldown(System.currentTimeMillis());
|
||||
sendPluginMessage(player, "globalchat", component);
|
||||
}
|
||||
|
||||
public void chatChannel(Player player, CustomChannel channel, String message) {
|
||||
if (!player.hasPermission(channel.getPermission())) {
|
||||
player.sendRichMessage("<red>You don't have permission to use this channel.</red>");
|
||||
public void chatChannel(User user, OfflinePlayer offlinePlayer, CustomChannel channel, String message) {
|
||||
if (!Utility.hasPermission(user, channel.getPermission())) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (isMuted(player, message, "[" + channel.getChannelName() + " Muted] ")) {
|
||||
if (isMuted(user, offlinePlayer, message, "[" + channel.getChannelName() + " Muted] ")) {
|
||||
ALogger.info("Refusing to send message by muted user");
|
||||
return;
|
||||
}
|
||||
|
||||
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
||||
ComponentLike senderName = user.getDisplayName();
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(offlinePlayer.getUniqueId());
|
||||
ComponentLike senderName = chatUser.getDisplayName();
|
||||
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.component("sender", senderName),
|
||||
Placeholder.component("message", parseMessageContent(player, message)),
|
||||
Placeholder.component("message", parseMessageContent(user, offlinePlayer, message)),
|
||||
Placeholder.parsed("server", ServerName.getServerName()),
|
||||
Placeholder.parsed("channel", channel.getChannelName())
|
||||
);
|
||||
Component component = Utility.parseMiniMessage(channel.getFormat(), placeholders).asComponent();
|
||||
|
||||
ModifiableString modifiableString = new ModifiableString(component);
|
||||
if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, channel.getChannelName())) {
|
||||
if (!RegexManager.filterText(offlinePlayer.getName(),
|
||||
offlinePlayer.getUniqueId(),
|
||||
modifiableString,
|
||||
channel.getChannelName()
|
||||
)) {
|
||||
GalaxyUtility.sendBlockedNotification(channel.getChannelName() + " Language",
|
||||
player,
|
||||
offlinePlayer,
|
||||
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
||||
"");
|
||||
""
|
||||
);
|
||||
ALogger.info("Refusing to send blocked chat message");
|
||||
return;
|
||||
}
|
||||
@@ -192,9 +246,9 @@ public class ChatHandler {
|
||||
component = modifiableString.component();
|
||||
|
||||
if (channel.isProxy()) {
|
||||
sendChatChannelMessage(player, channel.getChannelName(), "chatchannel", component, message);
|
||||
sendChatChannelMessage(offlinePlayer, channel.getChannelName(), "chatchannel", component, message);
|
||||
} else {
|
||||
sendChatChannelMessage(channel, player.getUniqueId(), component, message);
|
||||
sendChatChannelMessage(channel, offlinePlayer.getUniqueId(), component, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,16 +309,20 @@ public class ChatHandler {
|
||||
|
||||
if (!chatChannel.getServers().contains(ServerName.getServerName())) {
|
||||
player.sendRichMessage("<red>Unable to send messages to <channel> in this server.</red>",
|
||||
Placeholder.parsed("channel", chatChannel.getChannelName()));
|
||||
Placeholder.parsed("channel", chatChannel.getChannelName())
|
||||
);
|
||||
ALogger.info(String.format("Not sending chat message due to [%s] not being in this channels config",
|
||||
ServerName.getServerName()));
|
||||
ServerName.getServerName()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
Stream<? extends Player> stream = Bukkit.getServer().getOnlinePlayers().stream()
|
||||
.filter(p -> p.hasPermission(chatChannel.getPermission()));
|
||||
if (!player.hasPermission("chat.ignorebypass")) {
|
||||
stream = stream.filter(receiver -> !ChatUserManager.getChatUser(receiver.getUniqueId()).getIgnoredPlayers().contains(uuid)
|
||||
stream = stream.filter(receiver -> !ChatUserManager.getChatUser(receiver.getUniqueId())
|
||||
.getIgnoredPlayers()
|
||||
.contains(uuid)
|
||||
|| receiver.hasPermission("chat.ignorebypass"));
|
||||
}
|
||||
if (chatChannel.isLocal()) {
|
||||
@@ -283,6 +341,16 @@ public class ChatHandler {
|
||||
List<? extends Player> recipientPlayers = stream.toList();
|
||||
recipientPlayers.forEach(p -> p.sendMessage(component));
|
||||
|
||||
chatLogHandler.addChatLog(player.getUniqueId(),
|
||||
Bukkit.getServerName(),
|
||||
message,
|
||||
ChatLogType.CUSTOM,
|
||||
chatChannel.getChannelName(),
|
||||
getLocationIfLocal(chatChannel, player),
|
||||
component.asComponent(),
|
||||
false
|
||||
);
|
||||
|
||||
List<UUID> recipientUUIDs = recipientPlayers.stream().map(Entity::getUniqueId).toList();
|
||||
Bukkit.getServer().getOnlinePlayers().stream()
|
||||
.filter(onlinePlayer -> onlinePlayer.hasPermission(Config.SPYPERMISSION))
|
||||
@@ -290,7 +358,22 @@ public class ChatHandler {
|
||||
.forEach(onlinePlayer -> onlinePlayer.sendRichMessage(Config.CHANNEL_SPY,
|
||||
Placeholder.component("sender", player.name()),
|
||||
Placeholder.parsed("channel", chatChannel.getChannelName()),
|
||||
Placeholder.parsed("message", message)));
|
||||
Placeholder.parsed("message", message)
|
||||
));
|
||||
}
|
||||
|
||||
private static @Nullable String getLocationIfLocal(CustomChannel chatChannel, Player player) {
|
||||
if (!chatChannel.isLocal()) {
|
||||
return null;
|
||||
}
|
||||
String format = String.format("%s;%s",
|
||||
player.getLocation().getBlockX(),
|
||||
player.getLocation().getBlockZ()
|
||||
);
|
||||
if (format.length() > 36) {
|
||||
return null;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
private void sendPluginMessage(Player player, String channel, Component component) {
|
||||
@@ -310,25 +393,39 @@ public class ChatHandler {
|
||||
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();
|
||||
out.writeUTF(channel);
|
||||
out.writeUTF(chatChannelName);
|
||||
out.writeUTF(player.getUniqueId().toString());
|
||||
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
|
||||
|
||||
private boolean isMuted(Player player, String message, String prefix) {
|
||||
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
||||
if (user == null) {
|
||||
private boolean isMuted(User user, OfflinePlayer offlinePlayer, String message, String prefix) {
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(offlinePlayer.getUniqueId());
|
||||
if (chatUser == null) {
|
||||
return false;
|
||||
}
|
||||
if (user.isMuted() || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission("chat.bypass-server-muted"))) {
|
||||
if (chatUser.isMuted() || (ChatPlugin.getInstance()
|
||||
.serverMuted() && !Utility.hasPermission(user,
|
||||
"chat.bypass-server-muted"
|
||||
))) {
|
||||
// if (Database.get().isPlayerMuted(player.getUniqueId(), null) || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission("chat.bypass-server-muted"))) {
|
||||
GalaxyUtility.sendBlockedNotification(prefix, player, Utility.parseMiniMessage(Utility.stripTokens(message)), "");
|
||||
GalaxyUtility.sendBlockedNotification(prefix,
|
||||
offlinePlayer,
|
||||
Utility.parseMiniMessage(Utility.stripTokens(message)),
|
||||
""
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -376,11 +473,11 @@ public class ChatHandler {
|
||||
}
|
||||
// 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();
|
||||
|
||||
Utility.formattingPerms.forEach((perm, pair) -> {
|
||||
if (player.hasPermission(perm)) {
|
||||
if (Utility.hasPermission(user, perm)) {
|
||||
tagResolver.resolver(pair.getX());
|
||||
}
|
||||
});
|
||||
@@ -395,13 +492,16 @@ public class ChatHandler {
|
||||
.replacement(chatFilter.getReplacement()).build());
|
||||
}
|
||||
|
||||
if (offlinePlayer.isOnline() && offlinePlayer.getPlayer() != null) {
|
||||
component = component
|
||||
.replaceText(
|
||||
TextReplacementConfig.builder()
|
||||
.once()
|
||||
.matchLiteral("[i]")
|
||||
.replacement(ChatHandler.itemComponent(player.getInventory().getItemInMainHand()))
|
||||
.replacement(ChatHandler.itemComponent(offlinePlayer.getPlayer().getInventory()
|
||||
.getItemInMainHand()))
|
||||
.build());
|
||||
}
|
||||
|
||||
return component;
|
||||
|
||||
|
||||
@@ -1,52 +1,32 @@
|
||||
package com.alttd.chat.listeners;
|
||||
|
||||
import com.alttd.chat.ChatPlugin;
|
||||
import com.alttd.chat.chat_web.ChatMessageSender;
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.handler.ChatHandler;
|
||||
import com.alttd.chat.managers.ChatUserManager;
|
||||
import com.alttd.chat.managers.RegexManager;
|
||||
import com.alttd.chat.objects.*;
|
||||
import com.alttd.chat.objects.chat_log.ChatLogHandler;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.alttd.chat.util.GalaxyUtility;
|
||||
import com.alttd.chat.util.ServerName;
|
||||
import com.alttd.chat.objects.ChatFilter;
|
||||
import com.alttd.chat.objects.Toggleable;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent;
|
||||
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ChatListener implements Listener {
|
||||
|
||||
private final PlainTextComponentSerializer plainTextComponentSerializer = PlainTextComponentSerializer.plainText();
|
||||
private final ChatLogHandler chatLogHandler;
|
||||
|
||||
public ChatListener(ChatLogHandler chatLogHandler) {
|
||||
this.chatLogHandler = chatLogHandler;
|
||||
}
|
||||
private final ChatMessageSender chatMessageSender;
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onChatCommandDecorate(AsyncChatCommandDecorateEvent event) {
|
||||
@@ -55,9 +35,14 @@ public class ChatListener implements Listener {
|
||||
}
|
||||
|
||||
Component formatComponent = Component.text("%message%");
|
||||
ComponentLike message = parseMessageContent(event.player(), plainTextComponentSerializer.serialize(event.originalMessage()));
|
||||
ComponentLike message = parseMessageContent(event.player(),
|
||||
plainTextComponentSerializer.serialize(event.originalMessage())
|
||||
);
|
||||
|
||||
event.result(formatComponent.replaceText(TextReplacementConfig.builder().match("%message%").replacement(message).build()));
|
||||
event.result(formatComponent.replaceText(TextReplacementConfig.builder()
|
||||
.match("%message%")
|
||||
.replacement(message)
|
||||
.build()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
@@ -67,13 +52,16 @@ public class ChatListener implements Listener {
|
||||
}
|
||||
|
||||
Component formatComponent = Component.text("%message%");
|
||||
ComponentLike message = parseMessageContent(event.player(), plainTextComponentSerializer.serialize(event.originalMessage()));
|
||||
ComponentLike message = parseMessageContent(event.player(),
|
||||
plainTextComponentSerializer.serialize(event.originalMessage())
|
||||
);
|
||||
|
||||
event.result(formatComponent.replaceText(TextReplacementConfig.builder().match("%message%").replacement(message).build()));
|
||||
event.result(formatComponent.replaceText(TextReplacementConfig.builder()
|
||||
.match("%message%")
|
||||
.replacement(message)
|
||||
.build()));
|
||||
}
|
||||
|
||||
private final Component mention = MiniMessage.miniMessage().deserialize(Config.MENTIONPLAYERTAG);
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerChat(AsyncChatEvent event) {
|
||||
event.setCancelled(true); //Always cancel the event because we do not want to deal with Microsoft's stupid bans
|
||||
@@ -82,119 +70,7 @@ public class ChatListener implements Listener {
|
||||
toggleable.sendMessage(event.getPlayer(), event.message());
|
||||
return;
|
||||
}
|
||||
if (ChatPlugin.getInstance().serverMuted() && !event.getPlayer().hasPermission("chat.bypass-server-muted")) {
|
||||
Player player = event.getPlayer();
|
||||
GalaxyUtility.sendBlockedNotification("Chat Muted", player, event.message(), "");
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
UUID uuid = player.getUniqueId();
|
||||
|
||||
ComponentLike input = event.message().colorIfAbsent(NamedTextColor.WHITE);
|
||||
|
||||
ModifiableString modifiableString = new ModifiableString(input.asComponent());
|
||||
|
||||
// todo a better way for this
|
||||
if (!RegexManager.filterText(player.getName(), uuid, modifiableString, true, "chat", filterType -> {
|
||||
if (!filterType.equals(FilterType.PUNISH)) {
|
||||
ALogger.warn("Received another FilterType than punish when filtering chat and executing a filter action");
|
||||
return;
|
||||
}
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF("punish");
|
||||
out.writeUTF(player.getName());
|
||||
out.writeUTF(uuid.toString());
|
||||
out.writeUTF(modifiableString.string());
|
||||
player.sendPluginMessage(ChatPlugin.getInstance(), Config.MESSAGECHANNEL, out.toByteArray());
|
||||
})) {
|
||||
event.setCancelled(true);
|
||||
GalaxyUtility.sendBlockedNotification("Language", player,
|
||||
modifiableString.component(),
|
||||
"");
|
||||
chatLogHandler.addChatLog(uuid, ServerName.getServerName(), PlainTextComponentSerializer.plainText().serialize(input.asComponent()), true);
|
||||
return; // the message was blocked
|
||||
}
|
||||
|
||||
Stream<Player> stream = event.viewers().stream().filter(audience -> audience instanceof Player)
|
||||
.map(audience -> (Player) audience);
|
||||
|
||||
if (!player.hasPermission("chat.ignorebypass")) {
|
||||
stream = stream.filter(receiver -> !ChatUserManager.getChatUser(receiver.getUniqueId()).getIgnoredPlayers().contains(uuid)
|
||||
|| receiver.hasPermission("chat.ignorebypass"));
|
||||
}
|
||||
Set<Player> receivers = stream.collect(Collectors.toSet());
|
||||
|
||||
Set<Player> playersToPing = new HashSet<>();
|
||||
pingPlayers(playersToPing, modifiableString, player);
|
||||
|
||||
input = render(player, modifiableString.component());
|
||||
for (Player receiver : receivers) {
|
||||
receiver.sendMessage(input);
|
||||
}
|
||||
for (Player pingPlayer : playersToPing) {
|
||||
pingPlayer.playSound(pingPlayer.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1);
|
||||
}
|
||||
chatLogHandler.addChatLog(uuid, ServerName.getServerName(), modifiableString.string(), false);
|
||||
ALogger.info(PlainTextComponentSerializer.plainText().serialize(input.asComponent()));
|
||||
}
|
||||
|
||||
private void pingPlayers(Set<Player> playersToPing, ModifiableString modifiableString, Player player) {
|
||||
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
|
||||
String name = onlinePlayer.getName();
|
||||
String nickName = PlainTextComponentSerializer.plainText().serialize(onlinePlayer.displayName());
|
||||
|
||||
Pattern namePattern = Pattern.compile("\\b(?<!\\\\)" + name + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
// Pattern escapedNamePattern = Pattern.compile("\\b\\\\" + name + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
Pattern nickPattern = Pattern.compile("\\b(?<!\\\\)" + nickName + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
// Pattern escapedNickPattern = Pattern.compile("\\b\\\\" + nickName + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
ChatUser onlinePlayerUser = ChatUserManager.getChatUser(onlinePlayer.getUniqueId());
|
||||
if (namePattern.matcher(modifiableString.string()).find()) {
|
||||
modifiableString.replace(TextReplacementConfig.builder()
|
||||
.once()
|
||||
.match(namePattern)
|
||||
.replacement(mention.append(onlinePlayerUser.getDisplayName()))
|
||||
.build());
|
||||
//TODO replace all instances of \name with just name but using the match result so the capitalization doesn't change
|
||||
// modifiableString.replace(TextReplacementConfig.builder()
|
||||
// .once()
|
||||
// .match(escapedNamePattern)
|
||||
// .replacement((a, b) -> {
|
||||
// String substring = a.group().substring(1);
|
||||
// return ;
|
||||
// });
|
||||
|
||||
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId())
|
||||
|| player.hasPermission("chat.ignorebypass")) {
|
||||
playersToPing.add(onlinePlayer);
|
||||
}
|
||||
} else if (nickPattern.matcher(modifiableString.string()).find()) {
|
||||
modifiableString.replace(TextReplacementConfig.builder()
|
||||
.once()
|
||||
.match(nickPattern)
|
||||
.replacement(mention.append(onlinePlayerUser.getDisplayName()))
|
||||
.build());
|
||||
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId())
|
||||
|| player.hasPermission("chat.ignorebypass")) {
|
||||
playersToPing.add(onlinePlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull ComponentLike render(@NotNull Player player, @NotNull Component message) {
|
||||
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.component("sender", user.getDisplayName()),
|
||||
Placeholder.parsed("sendername", player.getName()),
|
||||
Placeholder.component("prefix", user.getPrefix()),
|
||||
Placeholder.component("prefixall", user.getPrefixAll()),
|
||||
Placeholder.component("staffprefix", user.getStaffPrefix()),
|
||||
Placeholder.component("message", message)
|
||||
);
|
||||
|
||||
return Utility.parseMiniMessage(Config.CHATFORMAT, placeholders);
|
||||
chatMessageSender.sendMessage(event.getPlayer(), event.message(), false);
|
||||
}
|
||||
|
||||
private ComponentLike parseMessageContent(Player player, String rawMessage) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.alttd.chat.listeners;
|
||||
|
||||
import com.alttd.chat.ChatPlugin;
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.config.ServerConfig;
|
||||
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.ModifiableString;
|
||||
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.Utility;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
@@ -30,18 +33,18 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Stack;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PlayerListener implements Listener {
|
||||
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
public PlayerListener(ServerConfig serverConfig) {
|
||||
this.serverConfig = serverConfig;
|
||||
}
|
||||
private final WebHandler webHandler;
|
||||
private final ChatPlugin chatPlugin;
|
||||
|
||||
@EventHandler
|
||||
private void onPlayerLogin(PlayerJoinEvent event) {
|
||||
@@ -50,19 +53,23 @@ public class PlayerListener implements Listener {
|
||||
UUID uuid = player.getUniqueId();
|
||||
Toggleable.disableToggles(uuid);
|
||||
|
||||
if (serverConfig.FIRST_JOIN_MESSAGES && (!player.hasPlayedBefore() || System.currentTimeMillis() - player.getFirstPlayed() < TimeUnit.SECONDS.toMillis(10))) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Config.FIRST_JOIN, Placeholder.parsed("player", player.getName())));
|
||||
if (serverConfig.FIRST_JOIN_MESSAGES && (!player.hasPlayedBefore() || System.currentTimeMillis() - player.getFirstPlayed() < TimeUnit.SECONDS.toMillis(
|
||||
10))) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage()
|
||||
.deserialize(Config.FIRST_JOIN, Placeholder.parsed("player", player.getName())));
|
||||
}
|
||||
|
||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||
if(user != null) return;
|
||||
|
||||
if (user != null) {
|
||||
updateServerState();
|
||||
return;
|
||||
}
|
||||
|
||||
// user failed to load - create a new one
|
||||
ChatUser chatUser = new ChatUser(uuid, -1, null);
|
||||
ChatUserManager.addUser(chatUser);
|
||||
Queries.saveUser(chatUser);
|
||||
|
||||
updateServerState();
|
||||
//TODO load player on other servers with plugin message?
|
||||
}
|
||||
|
||||
@@ -71,8 +78,19 @@ public class PlayerListener implements Listener {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||
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
|
||||
public void onSignChangeE(SignChangeEvent event) {
|
||||
@@ -88,7 +106,8 @@ public class PlayerListener implements Listener {
|
||||
GalaxyUtility.sendBlockedNotification("Sign Language",
|
||||
player,
|
||||
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
|
||||
"");
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
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<>();
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onPlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
@@ -132,7 +152,9 @@ public class PlayerListener implements Listener {
|
||||
.build();
|
||||
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));
|
||||
event.deathMessage(component);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.alttd.chat.objects.Party;
|
||||
import com.alttd.chat.objects.PartyUser;
|
||||
import com.alttd.chat.objects.channels.Channel;
|
||||
import com.alttd.chat.objects.channels.CustomChannel;
|
||||
import com.alttd.chat.objects.chat_log.ChatLogHandler;
|
||||
import com.alttd.chat.objects.chat_log.mapper.chat_log.ChatLogType;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.alttd.chat.util.ServerName;
|
||||
import com.alttd.chat.util.Utility;
|
||||
@@ -18,6 +20,7 @@ import com.google.common.io.ByteStreams;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -29,6 +32,12 @@ import java.util.UUID;
|
||||
|
||||
public class PluginMessage implements PluginMessageListener {
|
||||
|
||||
private final ChatLogHandler chatLogHandler;
|
||||
|
||||
public PluginMessage(ChatLogHandler chatLogHandler) {
|
||||
this.chatLogHandler = chatLogHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(String channel, @NotNull Player ignored, byte[] bytes) {
|
||||
if (!channel.equals(Config.MESSAGECHANNEL)) {
|
||||
@@ -48,13 +57,25 @@ public class PluginMessage implements PluginMessageListener {
|
||||
}
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
|
||||
if (isTargetNotIgnored(chatUser, targetuuid)) {
|
||||
player.sendMessage(GsonComponentSerializer.gson().deserialize(message));
|
||||
Component component = GsonComponentSerializer.gson().deserialize(message);
|
||||
player.sendMessage(component);
|
||||
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1,
|
||||
1); // todo load this from config
|
||||
1
|
||||
); // todo load this from config
|
||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||
if (!user.getReplyContinueTarget().equalsIgnoreCase(target)) {
|
||||
user.setReplyTarget(target);
|
||||
}
|
||||
// Handled here since this is after the ignore check and is only send once
|
||||
chatLogHandler.addChatLog(uuid,
|
||||
ServerName.getServerName(),
|
||||
PlainTextComponentSerializer.plainText().serialize(component),
|
||||
ChatLogType.MSG,
|
||||
null,
|
||||
player.getUniqueId().toString(),
|
||||
component,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
case "privatemessageout" -> {
|
||||
@@ -69,9 +90,18 @@ public class PluginMessage implements PluginMessageListener {
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
|
||||
if (isTargetNotIgnored(chatUser, targetuuid)) {
|
||||
chatUser.setReplyTarget(target);
|
||||
player.sendMessage(GsonComponentSerializer.gson().deserialize(message));
|
||||
// ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||
// user.setReplyTarget(target);
|
||||
Component component = GsonComponentSerializer.gson().deserialize(message);
|
||||
player.sendMessage(component);
|
||||
// Handled here since this is after the ignore check and is only send once
|
||||
chatLogHandler.addChatLog(uuid,
|
||||
ServerName.getServerName(),
|
||||
PlainTextComponentSerializer.plainText().serialize(component),
|
||||
ChatLogType.MSG,
|
||||
null,
|
||||
player.getUniqueId().toString(),
|
||||
component,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
case "globalchat" -> {
|
||||
@@ -82,10 +112,11 @@ public class PluginMessage implements PluginMessageListener {
|
||||
UUID uuid = UUID.fromString(in.readUTF());
|
||||
String message = in.readUTF();
|
||||
|
||||
Component component = GsonComponentSerializer.gson().deserialize(message);
|
||||
Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission(Config.GCPERMISSION)).forEach(p -> {
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(p.getUniqueId());
|
||||
if (isTargetNotIgnored(chatUser, uuid)) {
|
||||
p.sendMessage(GsonComponentSerializer.gson().deserialize(message));
|
||||
p.sendMessage(component);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -107,6 +138,7 @@ public class PluginMessage implements PluginMessageListener {
|
||||
}
|
||||
|
||||
chatChannel(in);
|
||||
//TODO [Stijn] [2026-07-19]: handle custom channels
|
||||
}
|
||||
case "tmppartyupdate" -> {
|
||||
int id = Integer.parseInt(in.readUTF());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -45,13 +45,83 @@ public class Nicknames implements CommandExecutor, TabCompleter {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) {
|
||||
if (sender instanceof Player player) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Console commands are disabled.");
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.ALL));
|
||||
return true;
|
||||
}
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "set":
|
||||
case "set" -> setNickname(sender, args, player);
|
||||
case "review" -> reviewNickname(sender, args, player);
|
||||
case "request" -> requestNickname(sender, args, player);
|
||||
case "try" -> tryNickname(sender, args, player);
|
||||
case "current" -> showCurrentNickname(sender, player);
|
||||
case "help" ->
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.ALL) + "For more info on nicknames and how to use rgb colors go to: <aqua>https://alttd.com/nicknames<white>");
|
||||
default -> sender.sendRichMessage(helpMessage(sender, HelpType.ALL));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showCurrentNickname(@NotNull CommandSender sender, Player player) {
|
||||
if (!hasPermission(sender, "chat.command.nick.current")) {
|
||||
return;
|
||||
}
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.component("nickname", chatUser.getDisplayName()),
|
||||
Placeholder.parsed("currentnickname", chatUser.getNickNameString())
|
||||
);
|
||||
player.sendRichMessage(Config.NICK_CURRENT, placeholders);
|
||||
}
|
||||
|
||||
private void tryNickname(@NotNull CommandSender sender, String[] args, Player player) {
|
||||
if (args.length != 2 || !hasPermission(sender, "chat.command.nick.try")) {
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.TRY));
|
||||
return;
|
||||
}
|
||||
LuckPerms api = ChatAPI.get().getLuckPerms();
|
||||
if (api == null) {
|
||||
sender.sendRichMessage(Config.NICK_NO_LUCKPERMS);
|
||||
return;
|
||||
}
|
||||
if (!NickUtilities.validNick(player, player, args[1])) {
|
||||
return;
|
||||
}
|
||||
sender.sendRichMessage(Config.NICK_TRYOUT,
|
||||
Placeholder.component("prefix", Utility.applyColor(api.getUserManager().getUser(player.getUniqueId())
|
||||
.getCachedData().getMetaData().getPrefix())), // TODO pull this from chatuser?
|
||||
Placeholder.component("nick", Utility.applyColor(args[1])),
|
||||
Placeholder.unparsed("nickrequest", args[1]));
|
||||
}
|
||||
|
||||
private void requestNickname(@NotNull CommandSender sender, String[] args, Player player) {
|
||||
if (args.length != 2 || !hasPermission(sender, "chat.command.nick.request")) {
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.REQUEST));
|
||||
return;
|
||||
}
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleNickRequest(player, args[1]);
|
||||
}
|
||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
||||
}
|
||||
|
||||
private void reviewNickname(@NotNull CommandSender sender, String[] args, Player player) {
|
||||
if (args.length != 1 || !hasPermission(sender, "chat.command.nick.review")) {
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.REVIEW));
|
||||
return;
|
||||
}
|
||||
NicknamesGui nicknamesGui = new NicknamesGui(player);
|
||||
ChatPlugin.getInstance().getServer().getPluginManager().registerEvents(nicknamesGui, ChatPlugin.getInstance());
|
||||
nicknamesGui.openInventory(player);
|
||||
}
|
||||
|
||||
private void setNickname(@NotNull CommandSender sender, String[] args, Player player) {
|
||||
if (args.length == 2 && hasPermission(sender, "chat.command.nick.set")) {
|
||||
handleNick(player, player, args[1]);
|
||||
} else if (args.length == 3 && hasPermission(sender, "chat.command.nick.set.others")) {
|
||||
@@ -65,67 +135,6 @@ public class Nicknames implements CommandExecutor, TabCompleter {
|
||||
} else if (args.length > 3) {
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.SET_SELF, HelpType.SET_OTHERS));
|
||||
}
|
||||
break;
|
||||
case "review":
|
||||
if (args.length == 1 && hasPermission(sender, "chat.command.nick.review")) {
|
||||
NicknamesGui nicknamesGui = new NicknamesGui();
|
||||
ChatPlugin.getInstance().getServer().getPluginManager().registerEvents(nicknamesGui, ChatPlugin.getInstance());
|
||||
nicknamesGui.openInventory(player);
|
||||
} else {
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.REVIEW));
|
||||
}
|
||||
break;
|
||||
case "request":
|
||||
if (args.length == 2 && hasPermission(sender, "chat.command.nick.request")) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleNickRequest(player, args[1]);
|
||||
}
|
||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
||||
} else {
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.REQUEST));
|
||||
}
|
||||
break;
|
||||
case "try":
|
||||
if (args.length == 2 && hasPermission(sender, "chat.command.nick.try")) {
|
||||
LuckPerms api = ChatAPI.get().getLuckPerms();
|
||||
if (api != null) {
|
||||
if (NickUtilities.validNick(player, player, args[1])) {
|
||||
sender.sendRichMessage(Config.NICK_TRYOUT,
|
||||
Placeholder.component("prefix", Utility.applyColor(api.getUserManager().getUser(player.getUniqueId())
|
||||
.getCachedData().getMetaData().getPrefix())), // TODO pull this from chatuser?
|
||||
Placeholder.component("nick", Utility.applyColor(args[1])),
|
||||
Placeholder.unparsed("nickrequest", args[1]));
|
||||
}
|
||||
} else {
|
||||
sender.sendRichMessage(Config.NICK_NO_LUCKPERMS);
|
||||
}
|
||||
} else {
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.TRY));
|
||||
}
|
||||
break;
|
||||
case "current":
|
||||
if (hasPermission(sender, "chat.command.nick.current")) {
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.component("nickname", chatUser.getDisplayName()),
|
||||
Placeholder.parsed("currentnickname", chatUser.getNickNameString())
|
||||
);
|
||||
player.sendRichMessage(Config.NICK_CURRENT, placeholders);
|
||||
}
|
||||
break;
|
||||
case "help":
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.ALL)
|
||||
+ "For more info on nicknames and how to use rgb colors go to: <aqua>https://alttd.com/nicknames<white>");
|
||||
break;
|
||||
default:
|
||||
sender.sendRichMessage(helpMessage(sender, HelpType.ALL));
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Console commands are disabled.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,6 +145,33 @@ public class Nicknames implements CommandExecutor, TabCompleter {
|
||||
}
|
||||
|
||||
if (args.length == 1) {
|
||||
tabCompleteArgLengthOne(sender, args, completions);
|
||||
} else if (args.length == 2) {
|
||||
tabCompleteArgLengthTwo(sender, args, completions);
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
|
||||
private static void tabCompleteArgLengthTwo(CommandSender sender, String[] args, List<String> completions) {
|
||||
if (!args[0].equalsIgnoreCase("set")) {
|
||||
return;
|
||||
}
|
||||
List<String> choices = new ArrayList<>();
|
||||
List<String> onlinePlayers = new ArrayList<>();
|
||||
Bukkit.getOnlinePlayers().forEach(a -> onlinePlayers.add(a.getName()));
|
||||
|
||||
if (sender.hasPermission("chat.command.nick.set.others")) {
|
||||
choices.addAll(onlinePlayers);
|
||||
}
|
||||
|
||||
for (String s : choices) {
|
||||
if (s.startsWith(args[1])) {
|
||||
completions.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void tabCompleteArgLengthOne(CommandSender sender, String[] args, List<String> completions) {
|
||||
List<String> choices = new ArrayList<>();
|
||||
if (sender.hasPermission("chat.command.nick.set")) {
|
||||
choices.add("set");
|
||||
@@ -159,24 +195,6 @@ public class Nicknames implements CommandExecutor, TabCompleter {
|
||||
completions.add(s);
|
||||
}
|
||||
}
|
||||
} else if (args.length == 2) {
|
||||
if (args[0].equalsIgnoreCase("set")) {
|
||||
List<String> choices = new ArrayList<>();
|
||||
List<String> onlinePlayers = new ArrayList<>();
|
||||
Bukkit.getOnlinePlayers().forEach(a -> onlinePlayers.add(a.getName()));
|
||||
|
||||
if (sender.hasPermission("chat.command.nick.set.others")) {
|
||||
choices.addAll(onlinePlayers);
|
||||
}
|
||||
|
||||
for (String s : choices) {
|
||||
if (s.startsWith(args[1])) {
|
||||
completions.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
|
||||
private void handleNickRequest(Player player, String nickName) {
|
||||
@@ -261,31 +279,13 @@ public class Nicknames implements CommandExecutor, TabCompleter {
|
||||
|
||||
private void handleNick(Player sender, OfflinePlayer target, final String nickName) {
|
||||
if (nickName.equalsIgnoreCase("off")) {
|
||||
|
||||
try {
|
||||
if (target.isOnline()) {
|
||||
resetNick(Objects.requireNonNull(target.getPlayer()));
|
||||
}
|
||||
Queries.removePlayerFromDataBase(target.getUniqueId());
|
||||
NickCache.remove(target.getUniqueId());
|
||||
nickCacheUpdate.add(target.getUniqueId());
|
||||
} catch (SQLException e) {
|
||||
ALogger.error("Failed to remove nickname from database", e);
|
||||
}
|
||||
|
||||
if (!sender.equals(target)) {
|
||||
sender.sendRichMessage(Config.NICK_RESET_OTHERS,
|
||||
Placeholder.unparsed("player", Objects.requireNonNull(target.getName())));
|
||||
}
|
||||
|
||||
if (target.isOnline() && target.getPlayer() != null) {
|
||||
target.getPlayer().sendRichMessage(Config.NICK_RESET);
|
||||
}
|
||||
|
||||
NickEvent nickEvent = new NickEvent(sender.getName(), target.getName(), null, NickEvent.NickEventType.RESET);
|
||||
nickEvent.callEvent();
|
||||
|
||||
handleNickOff(sender, target);
|
||||
} else if (NickUtilities.validNick(sender, target, nickName)) {
|
||||
setValidNick(sender, target, nickName);
|
||||
}
|
||||
}
|
||||
|
||||
private void setValidNick(Player sender, OfflinePlayer target, String nickName) {
|
||||
if (target.isOnline()) {
|
||||
setNick(target.getPlayer(), nickName);
|
||||
} else {
|
||||
@@ -322,6 +322,30 @@ public class Nicknames implements CommandExecutor, TabCompleter {
|
||||
Placeholder.unparsed("nickname", getNick(target.getPlayer())));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNickOff(Player sender, OfflinePlayer target) {
|
||||
try {
|
||||
if (target.isOnline()) {
|
||||
resetNick(Objects.requireNonNull(target.getPlayer()));
|
||||
}
|
||||
Queries.removePlayerFromDataBase(target.getUniqueId());
|
||||
NickCache.remove(target.getUniqueId());
|
||||
nickCacheUpdate.add(target.getUniqueId());
|
||||
} catch (SQLException e) {
|
||||
ALogger.error("Failed to remove nickname from database", e);
|
||||
}
|
||||
|
||||
if (!sender.equals(target)) {
|
||||
sender.sendRichMessage(Config.NICK_RESET_OTHERS,
|
||||
Placeholder.unparsed("player", Objects.requireNonNull(target.getName())));
|
||||
}
|
||||
|
||||
if (target.isOnline() && target.getPlayer() != null) {
|
||||
target.getPlayer().sendRichMessage(Config.NICK_RESET);
|
||||
}
|
||||
|
||||
NickEvent nickEvent = new NickEvent(sender.getName(), target.getName(), null, NickEvent.NickEventType.RESET);
|
||||
nickEvent.callEvent();
|
||||
}
|
||||
|
||||
private String helpMessage(final CommandSender sender, final HelpType... helpTypes) {
|
||||
|
||||
@@ -119,8 +119,7 @@ public class NicknamesEvents implements Listener, PluginMessageListener {
|
||||
switch (subChannel) {
|
||||
case "NickNameRequest":
|
||||
ComponentLike component = miniMessage.deserialize(Config.NICK_REQUEST_NEW, Placeholder.parsed("player", name))
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND,
|
||||
"/nick review"))
|
||||
.clickEvent(ClickEvent.runCommand("/nick review"))
|
||||
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||
miniMessage.deserialize("<gold>Click this text to review the request!")));
|
||||
|
||||
|
||||
@@ -5,24 +5,22 @@ import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.database.Queries;
|
||||
import com.alttd.chat.events.NickEvent;
|
||||
import com.alttd.chat.objects.Nick;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.alttd.inventory_gui.click.GuiItem;
|
||||
import com.alttd.inventory_gui.gui.InventoryGui;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
@@ -31,39 +29,45 @@ import org.bukkit.scheduler.BukkitRunnable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NicknamesGui implements Listener {
|
||||
|
||||
private final Inventory inv;
|
||||
public static final String UNKNOWN_PLAYER_NAME = "UNKNOWN PLAYER NAME";
|
||||
private final int currentPage;
|
||||
private final ChatPlugin plugin = ChatPlugin.getInstance();
|
||||
private final InventoryGui nicknamesGui;
|
||||
|
||||
public NicknamesGui() {
|
||||
// Create a new inventory, with no owner (as this isn't a real inventory)
|
||||
inv = Bukkit.createInventory(null, 36, Utility.parseMiniMessage("Nicknames GUI").asComponent());
|
||||
|
||||
// Put the items into the inventory
|
||||
public NicknamesGui(Player player) {
|
||||
nicknamesGui = InventoryGui.builder()
|
||||
.plugin(plugin)
|
||||
.title(Component.text("Nicknames GUI"))
|
||||
.rows(6)
|
||||
.build();
|
||||
currentPage = 1;
|
||||
setItems(currentPage);
|
||||
setItems(currentPage, player);
|
||||
}
|
||||
|
||||
public void setItems(int currentPage) {
|
||||
public void setItems(int currentPage, Player player) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
inv.clear();
|
||||
NickUtilities.updateCache();
|
||||
boolean hasNextPage = false;
|
||||
int i = (currentPage - 1) * 27; //TODO set to 1 or 2 to test
|
||||
int limit = i / 27;
|
||||
MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
for (Nick nick : Nicknames.getInstance().NickCache.values()) {
|
||||
if (nick.hasRequest()) {
|
||||
if (limit >= i / 27) {
|
||||
inv.setItem(i % 27, createPlayerSkull(nick, Config.NICK_ITEM_LORE));
|
||||
ItemStack playerSkull = createPlayerSkull(nick, Config.NICK_ITEM_LORE);
|
||||
nicknamesGui.getRoot().setItem(i % 27, GuiItem.clickable(playerSkull, inventoryClickEvent ->
|
||||
handleInventoryClick(nick, inventoryClickEvent, miniMessage, playerSkull)));
|
||||
ALogger.info("Added nick " + i + " to gui: " + nick.getUuid());
|
||||
i++;
|
||||
} else {
|
||||
ALogger.info("Reached end of nicknames gui page");
|
||||
hasNextPage = true;
|
||||
break;
|
||||
}
|
||||
@@ -71,20 +75,137 @@ public class NicknamesGui implements Listener {
|
||||
}
|
||||
|
||||
if (currentPage != 1) {
|
||||
inv.setItem(28, createGuiItem(Material.PAPER, "§bPrevious page",
|
||||
ItemStack itemStack = createGuiItem(Material.PAPER, "§bPrevious page",
|
||||
"§aCurrent page: %page%".replace("%page%", String.valueOf(currentPage)),
|
||||
"§aPrevious page: %previousPage%".replace("%previousPage%", String.valueOf(currentPage - 1))));
|
||||
"§aPrevious page: %previousPage%".replace("%previousPage%", String.valueOf(currentPage - 1)));
|
||||
GuiItem previousPage = GuiItem.clickable(itemStack, e -> setItems(currentPage - 1, player));
|
||||
nicknamesGui.getRoot().setItem(28, previousPage);
|
||||
}
|
||||
|
||||
if (hasNextPage) {
|
||||
inv.setItem(36, createGuiItem(Material.PAPER, "§bNext page",
|
||||
ItemStack itemStack = createGuiItem(Material.PAPER, "§bNext page",
|
||||
"§aCurrent page: %page%".replace("%page%", String.valueOf(currentPage)),
|
||||
"§aNext page: §b%nextPage%".replace("%nextPage%", String.valueOf(currentPage + 1))));
|
||||
"§aNext page: §b%nextPage%".replace("%nextPage%", String.valueOf(currentPage + 1)));
|
||||
GuiItem nextPage = GuiItem.clickable(itemStack, e -> setItems(currentPage + 1, player));
|
||||
nicknamesGui.getRoot().setItem(36, nextPage);
|
||||
}
|
||||
|
||||
nicknamesGui.render(player);
|
||||
}
|
||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
||||
}
|
||||
|
||||
private void handleInventoryClick(Nick nick, InventoryClickEvent inventoryClickEvent, MiniMessage miniMessage, ItemStack playerSkull) {
|
||||
final Player playerWhoClicked = (Player) inventoryClickEvent.getWhoClicked();
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(nick.getUuid());
|
||||
Component offlinePlayerName = miniMessage.deserialize(getOfflinePlayerName(offlinePlayer));
|
||||
if (!nick.hasRequest()) {
|
||||
playerWhoClicked.sendRichMessage(Config.NICK_ALREADY_HANDLED,
|
||||
Placeholder.component("targetplayer", offlinePlayerName));
|
||||
return;
|
||||
}
|
||||
if (inventoryClickEvent.isLeftClick()) {
|
||||
handleLeftClickPlayerSkull(nick, inventoryClickEvent, offlinePlayer, playerWhoClicked, offlinePlayerName, playerSkull);
|
||||
} else if (inventoryClickEvent.isRightClick()) {
|
||||
handleRightClickPlayerSkull(nick, inventoryClickEvent, offlinePlayer, playerWhoClicked, offlinePlayerName, playerSkull);
|
||||
}
|
||||
//TODO what do we do no click?
|
||||
}
|
||||
|
||||
private void handleRightClickPlayerSkull(Nick nick, InventoryClickEvent inventoryClickEvent, OfflinePlayer offlinePlayer, Player playerWhoClicked, Component offlinePlayerName, ItemStack playerSkull) {
|
||||
Queries.denyNewNickname(nick.getUuid());
|
||||
|
||||
String newNick = nick.getNewNick();
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NickEvent nickEvent = new NickEvent(inventoryClickEvent.getWhoClicked().getName(), getOfflinePlayerName(offlinePlayer), newNick, NickEvent.NickEventType.DENIED);
|
||||
nickEvent.callEvent();
|
||||
}
|
||||
}.runTask(ChatPlugin.getInstance());
|
||||
|
||||
playerWhoClicked.sendRichMessage(Config.NICK_DENIED,
|
||||
Placeholder.unparsed("targetplayer", getOfflinePlayerName(offlinePlayer)),
|
||||
Placeholder.component("newnick", Utility.applyColor(nick.getNewNick())),
|
||||
Placeholder.component("oldnick", Utility.applyColor(nick.getCurrentNick() == null ? getOfflinePlayerName(offlinePlayer) : nick.getCurrentNick())));
|
||||
|
||||
if (Nicknames.getInstance().NickCache.containsKey(nick.getUuid())
|
||||
&& Nicknames.getInstance().NickCache.get(nick.getUuid()).getCurrentNick() != null) {
|
||||
nick.setNewNick(null);
|
||||
nick.setRequestedDate(0);
|
||||
Nicknames.getInstance().NickCache.put(nick.getUuid(), nick);
|
||||
} else {
|
||||
Nicknames.getInstance().NickCache.remove(nick.getUuid());
|
||||
}
|
||||
|
||||
if (offlinePlayer.isOnline() && offlinePlayer.getPlayer() != null) {
|
||||
Nicknames.getInstance().setNick(offlinePlayer.getPlayer(), nick.getCurrentNick() == null ? getOfflinePlayerName(offlinePlayer) : nick.getCurrentNick());
|
||||
offlinePlayer.getPlayer().sendRichMessage(Config.NICK_NOT_CHANGED);
|
||||
}
|
||||
|
||||
NickUtilities.bungeeMessageHandled(nick.getUuid(), inventoryClickEvent.getWhoClicked().getServer().getPlayer(inventoryClickEvent.getWhoClicked().getName()), "Denied");
|
||||
final ComponentLike messageDenied = MiniMessage.miniMessage().deserialize("<red><name>'s nickname was denied!",
|
||||
Placeholder.unparsed("name", getOfflinePlayerName(offlinePlayer)));
|
||||
ChatPlugin.getInstance().getServer().getOnlinePlayers().stream()
|
||||
.filter(player -> player.hasPermission("chat.command.nick.review"))
|
||||
.forEach(player -> player.sendMessage(messageDenied));
|
||||
|
||||
ItemStack completedNickRequestItem = createCompletedNickRequestItem(offlinePlayerName, playerSkull);
|
||||
inventoryClickEvent.getInventory().setItem(inventoryClickEvent.getSlot(), completedNickRequestItem);
|
||||
nicknamesGui.render(playerWhoClicked);
|
||||
}
|
||||
|
||||
private void handleLeftClickPlayerSkull(Nick nick, InventoryClickEvent inventoryClickEvent, OfflinePlayer offlinePlayer, Player playerWhoClicked, Component offlinePlayerName, ItemStack playerSkull) {
|
||||
Queries.acceptNewNickname(nick.getUuid(), nick.getNewNick());
|
||||
|
||||
String newNick = nick.getNewNick();
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NickEvent nickEvent = new NickEvent(inventoryClickEvent.getWhoClicked().getName(), getOfflinePlayerName(offlinePlayer), newNick, NickEvent.NickEventType.ACCEPTED);
|
||||
nickEvent.callEvent();
|
||||
}
|
||||
}.runTask(ChatPlugin.getInstance());
|
||||
|
||||
playerWhoClicked.sendRichMessage(Config.NICK_ACCEPTED,
|
||||
Placeholder.component("targetplayer", offlinePlayerName),
|
||||
Placeholder.component("newnick", Utility.applyColor(nick.getNewNick())),
|
||||
Placeholder.component("oldnick", nick.getCurrentNick() == null ? offlinePlayerName : Utility.applyColor(nick.getCurrentNick())));
|
||||
|
||||
Player affectedPlayer = offlinePlayer.getPlayer();
|
||||
if (offlinePlayer.isOnline() && affectedPlayer != null) {
|
||||
Nicknames.getInstance().setNick(affectedPlayer, nick.getNewNick());
|
||||
}
|
||||
|
||||
NickUtilities.bungeeMessageHandled(nick.getUuid(), inventoryClickEvent.getWhoClicked().getServer().getPlayer(inventoryClickEvent.getWhoClicked().getName()), "Accepted");
|
||||
|
||||
nick.setCurrentNick(nick.getNewNick());
|
||||
nick.setLastChangedDate(new Date().getTime());
|
||||
nick.setNewNick(null);
|
||||
nick.setRequestedDate(0);
|
||||
|
||||
Nicknames.getInstance().NickCache.put(nick.getUuid(), nick);
|
||||
|
||||
ItemStack completedNickRequestItem = createCompletedNickRequestItem(offlinePlayerName, playerSkull);
|
||||
inventoryClickEvent.getInventory().setItem(inventoryClickEvent.getSlot(), completedNickRequestItem);
|
||||
nicknamesGui.render(playerWhoClicked);
|
||||
}
|
||||
|
||||
private static ItemStack createCompletedNickRequestItem(Component offlinePlayerName, ItemStack playerSkull) {
|
||||
ItemStack itemStack = new ItemStack(Material.SKELETON_SKULL);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
itemMeta.displayName(offlinePlayerName);
|
||||
itemMeta.lore(playerSkull.lore());
|
||||
itemStack.setItemMeta(itemMeta);
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
private static String getOfflinePlayerName(OfflinePlayer offlinePlayer) {
|
||||
return offlinePlayer.getName() == null ? UNKNOWN_PLAYER_NAME : offlinePlayer.getName();
|
||||
}
|
||||
|
||||
private ItemStack createPlayerSkull(Nick nick, List<String> lore) {
|
||||
MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
ItemStack playerHead = new ItemStack(Material.PLAYER_HEAD);
|
||||
@@ -94,9 +215,9 @@ public class NicknamesGui implements Listener {
|
||||
meta.setOwningPlayer(offlinePlayer);
|
||||
String name = offlinePlayer.getName();
|
||||
if (name == null) {
|
||||
meta.displayName(miniMessage.deserialize("UNKNOWN PLAYER NAME"));
|
||||
meta.displayName(miniMessage.deserialize("<red>" + getOfflinePlayerName(offlinePlayer) + "</red>"));
|
||||
} else {
|
||||
meta.displayName(miniMessage.deserialize(offlinePlayer.getName()));
|
||||
meta.displayName(miniMessage.deserialize(getOfflinePlayerName(offlinePlayer)));
|
||||
}
|
||||
|
||||
TagResolver resolver = TagResolver.resolver(
|
||||
@@ -128,185 +249,6 @@ public class NicknamesGui implements Listener {
|
||||
|
||||
// You can open the inventory with this
|
||||
public void openInventory(final HumanEntity ent) {//Possibly with a boolean to show if it should get from cache or update cache
|
||||
ent.openInventory(inv);
|
||||
}
|
||||
|
||||
// Check for clicks on items
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onInventoryClick(InventoryClickEvent e) {
|
||||
if (e.getInventory() != inv) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.setCancelled(true);
|
||||
|
||||
final ItemStack clickedItem = e.getCurrentItem();
|
||||
|
||||
if (clickedItem == null || clickedItem.getType() == Material.AIR) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Player p = (Player) e.getWhoClicked();
|
||||
|
||||
if (clickedItem.getType().equals(Material.PAPER)) {
|
||||
Component component = clickedItem.getItemMeta().displayName();
|
||||
if (component == null) {
|
||||
throw new IllegalStateException("Nicknames GUI: Item with no display name clicked!");
|
||||
}
|
||||
String serialize = PlainTextComponentSerializer.plainText().serialize(component);
|
||||
if (serialize.equals("Next Page")) {
|
||||
setItems(currentPage + 1);
|
||||
}
|
||||
} else if (clickedItem.getType().equals(Material.PLAYER_HEAD)) {
|
||||
ItemMeta itemMeta = clickedItem.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return;
|
||||
}
|
||||
SkullMeta meta = (SkullMeta) itemMeta;
|
||||
if (meta.hasEnchants()) {
|
||||
return;
|
||||
}
|
||||
OfflinePlayer owningPlayer = meta.getOwningPlayer();
|
||||
|
||||
if (owningPlayer == null) {
|
||||
p.sendRichMessage(Config.NICK_USER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NickUtilities.updateCache();
|
||||
|
||||
Nick nick;
|
||||
UUID uniqueId = owningPlayer.getUniqueId();
|
||||
if (Nicknames.getInstance().NickCache.containsKey(uniqueId)) {
|
||||
nick = Nicknames.getInstance().NickCache.get(uniqueId);
|
||||
} else {
|
||||
nick = Queries.getNick(uniqueId);
|
||||
}
|
||||
Component itemDisplayName = itemMeta.displayName();
|
||||
if (itemDisplayName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nick == null || !nick.hasRequest()) {
|
||||
p.sendRichMessage(Config.NICK_ALREADY_HANDLED,
|
||||
Placeholder.component("targetplayer", itemDisplayName))
|
||||
;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.isLeftClick()) {
|
||||
if (owningPlayer.hasPlayedBefore()) {
|
||||
Queries.acceptNewNickname(uniqueId, nick.getNewNick());
|
||||
|
||||
String newNick = nick.getNewNick();
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NickEvent nickEvent = new NickEvent(e.getWhoClicked().getName(), itemMeta.getDisplayName(), newNick, NickEvent.NickEventType.ACCEPTED);
|
||||
nickEvent.callEvent();
|
||||
}
|
||||
}.runTask(ChatPlugin.getInstance());
|
||||
|
||||
p.sendRichMessage(Config.NICK_ACCEPTED,
|
||||
Placeholder.component("targetplayer", itemDisplayName),
|
||||
Placeholder.component("newnick", Utility.applyColor(nick.getNewNick())),
|
||||
Placeholder.component("oldnick", Utility.applyColor(nick.getCurrentNick() == null ? itemMeta.getDisplayName() : nick.getCurrentNick())));
|
||||
|
||||
if (owningPlayer.isOnline() && owningPlayer.getPlayer() != null) {
|
||||
Nicknames.getInstance().setNick(owningPlayer.getPlayer(), nick.getNewNick());
|
||||
}
|
||||
|
||||
NickUtilities.bungeeMessageHandled(uniqueId, e.getWhoClicked().getServer().getPlayer(e.getWhoClicked().getName()), "Accepted");
|
||||
|
||||
nick.setCurrentNick(nick.getNewNick());
|
||||
nick.setLastChangedDate(new Date().getTime());
|
||||
nick.setNewNick(null);
|
||||
nick.setRequestedDate(0);
|
||||
|
||||
Nicknames.getInstance().NickCache.put(uniqueId, nick);
|
||||
|
||||
ItemStack itemStack = new ItemStack(Material.SKELETON_SKULL);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
itemMeta.displayName(itemMeta.displayName());
|
||||
itemMeta.lore(clickedItem.lore());
|
||||
itemStack.setItemMeta(itemMeta);
|
||||
e.getInventory().setItem(e.getSlot(), itemStack);
|
||||
p.updateInventory();
|
||||
} else {
|
||||
p.sendRichMessage(Config.NICK_PLAYER_NOT_ONLINE, Placeholder.component("player", itemDisplayName));
|
||||
}
|
||||
|
||||
} else if (e.isRightClick()) {
|
||||
if (owningPlayer.hasPlayedBefore()) {
|
||||
Queries.denyNewNickname(uniqueId);
|
||||
|
||||
String newNick = nick.getNewNick();
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NickEvent nickEvent = new NickEvent(e.getWhoClicked().getName(), itemMeta.getDisplayName(), newNick, NickEvent.NickEventType.DENIED);
|
||||
nickEvent.callEvent();
|
||||
}
|
||||
}.runTask(ChatPlugin.getInstance());
|
||||
|
||||
p.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_DENIED,
|
||||
Placeholder.unparsed("targetplayer", owningPlayer.getName()),
|
||||
Placeholder.component("newnick", Utility.applyColor(nick.getNewNick())),
|
||||
Placeholder.component("oldnick", Utility.applyColor(nick.getCurrentNick() == null ? owningPlayer.getName() : nick.getCurrentNick()))));
|
||||
|
||||
if (Nicknames.getInstance().NickCache.containsKey(uniqueId)
|
||||
&& Nicknames.getInstance().NickCache.get(uniqueId).getCurrentNick() != null) {
|
||||
nick.setNewNick(null);
|
||||
nick.setRequestedDate(0);
|
||||
Nicknames.getInstance().NickCache.put(uniqueId, nick);
|
||||
} else {
|
||||
Nicknames.getInstance().NickCache.remove(uniqueId);
|
||||
}
|
||||
|
||||
if (owningPlayer.isOnline() && owningPlayer.getPlayer() != null) {
|
||||
Nicknames.getInstance().setNick(owningPlayer.getPlayer(), nick.getCurrentNick() == null ? owningPlayer.getName() : nick.getCurrentNick());
|
||||
owningPlayer.getPlayer().sendRichMessage(Config.NICK_NOT_CHANGED);
|
||||
}
|
||||
|
||||
NickUtilities.bungeeMessageHandled(uniqueId, e.getWhoClicked().getServer().getPlayer(e.getWhoClicked().getName()), "Denied");
|
||||
final ComponentLike messageDenied = MiniMessage.miniMessage().deserialize("<red><name>'s nickname was denied!",
|
||||
Placeholder.unparsed("name", owningPlayer.getName()));
|
||||
ChatPlugin.getInstance().getServer().getOnlinePlayers().forEach(p -> {
|
||||
if (p.hasPermission("chat.command.nick.review")) {
|
||||
p.sendMessage(messageDenied);
|
||||
}
|
||||
});
|
||||
|
||||
ItemStack itemStack = new ItemStack(Material.SKELETON_SKULL);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
itemMeta.displayName(itemDisplayName);
|
||||
itemMeta.lore(clickedItem.lore());
|
||||
itemStack.setItemMeta(itemMeta);
|
||||
e.getInventory().setItem(e.getSlot(), itemStack);
|
||||
p.updateInventory();
|
||||
} else {
|
||||
if (itemDisplayName == null) {
|
||||
p.sendRichMessage(Config.NICK_PLAYER_NOT_ONLINE, Placeholder.parsed("player", "UNKNOWN PLAYER NAME"));
|
||||
} else {
|
||||
p.sendRichMessage(Config.NICK_PLAYER_NOT_ONLINE, Placeholder.component("player", itemDisplayName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel dragging in our inventory
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onInventoryClick(InventoryDragEvent e) {
|
||||
if (e.getInventory() == inv) {
|
||||
e.setCancelled(true);
|
||||
}
|
||||
nicknamesGui.open(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -13,10 +14,11 @@ import java.util.List;
|
||||
|
||||
public class GalaxyUtility {
|
||||
|
||||
public static void sendBlockedNotification(String prefix, Player player, ComponentLike input, String target) {
|
||||
public static void sendBlockedNotification(String prefix, OfflinePlayer offlinePlayer, ComponentLike input, String target) {
|
||||
String playerName = offlinePlayer.getName() == null ? "unknown_player" : offlinePlayer.getName();
|
||||
TagResolver placeholders = TagResolver.resolver(
|
||||
Placeholder.parsed("prefix", prefix),
|
||||
Placeholder.parsed("displayname", Utility.getDisplayName(player.getUniqueId(), player.getName())),
|
||||
Placeholder.parsed("displayname", Utility.getDisplayName(offlinePlayer.getUniqueId(), playerName)),
|
||||
Placeholder.parsed("target", (target.isEmpty() ? "tried to say:" : "-> " + target + ":")),
|
||||
Placeholder.component("input", input)
|
||||
);
|
||||
@@ -27,9 +29,16 @@ public class GalaxyUtility {
|
||||
a.sendMessage(blockedNotification);
|
||||
}
|
||||
});
|
||||
if (offlinePlayer.isOnline()) {
|
||||
Player player = offlinePlayer.getPlayer();
|
||||
if (player == null) {
|
||||
ALogger.error("Player is offline but isOnline() returned true");
|
||||
return;
|
||||
}
|
||||
player.sendRichMessage("<red>The language you used in your message is not allowed, " +
|
||||
"this constitutes as your only warning. Any further attempts at bypassing the filter will result in staff intervention.</red>");
|
||||
}
|
||||
}
|
||||
|
||||
public static void addAdditionalChatCompletions(Player player) {
|
||||
List<String> completions = new ArrayList<>(RegexManager.emotesList);
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.alttd.chat.util;
|
||||
import com.alttd.chat.ChatPlugin;
|
||||
import com.alttd.chat.objects.Toggleable;
|
||||
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.scheduler.BukkitRunnable;
|
||||
|
||||
@@ -38,11 +40,27 @@ public class ToggleableForCustomChannel extends Toggleable {
|
||||
|
||||
@Override
|
||||
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() {
|
||||
@Override
|
||||
public void run() {
|
||||
ALogger.info(String.format("%s sent %s message: %s", player.getName(), customChannel.getChannelName(), message));
|
||||
ChatPlugin.getInstance().getChatHandler().chatChannel(player, customChannel, message);
|
||||
ALogger.info(String.format("%s sent %s message: %s",
|
||||
offlinePlayer.getName(),
|
||||
customChannel.getChannelName(),
|
||||
message
|
||||
));
|
||||
ChatPlugin.getInstance().getChatHandler().chatChannel(user, offlinePlayer, customChannel, message);
|
||||
}
|
||||
}.runTaskAsynchronously(ChatPlugin.getInstance());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ version: 2.0.0-SNAPSHOT
|
||||
main: com.alttd.chat.ChatPlugin
|
||||
api-version: 1.19
|
||||
authors: [ Destro, Teriuihi ]
|
||||
depend: [LuckPerms]
|
||||
depend: [ LuckPerms, ProtocolLib ]
|
||||
softdepend: [ TAB ]
|
||||
loadbefore:
|
||||
- mcMMO
|
||||
- GriefPrevention
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -112,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -170,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -203,15 +203,14 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+13
-12
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+12
-5
@@ -9,24 +9,29 @@ val nexusPass = providers.gradleProperty("alttdSnapshotPassword").get()
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
// mavenLocal()
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven("https://repo.destro.xyz/snapshots") // Altitude - Galaxy
|
||||
maven("https://repo.alttd.com/snapshots") // Altitude - Galaxy
|
||||
maven("https://oss.sonatype.org/content/groups/public/") // Adventure
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots/") // Minimessage
|
||||
maven("https://nexus.velocitypowered.com/repository/") // Velocity
|
||||
maven("https://nexus.velocitypowered.com/repository/maven-public/") // Velocity
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://repo.spongepowered.org/maven") // Configurate
|
||||
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // Papi
|
||||
maven("https://jitpack.io")
|
||||
maven {
|
||||
name = "nexus"
|
||||
url = uri("https://repo.alttd.com/repository/alttd-snapshot/")
|
||||
credentials {
|
||||
username = nexusUser
|
||||
password = nexusPass
|
||||
}
|
||||
}
|
||||
maven {
|
||||
url = uri("https://repo.alttd.com/repository/alttd/")
|
||||
credentials {
|
||||
username = nexusUser
|
||||
password = nexusPass
|
||||
}
|
||||
}
|
||||
}
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
}
|
||||
@@ -36,3 +41,5 @@ pluginManagement {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
include("web-api")
|
||||
@@ -1,21 +1,32 @@
|
||||
plugins {
|
||||
`maven-publish`
|
||||
id("io.github.goooler.shadow")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":api")) // API
|
||||
compileOnly("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT")
|
||||
annotationProcessor("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT")
|
||||
compileOnly("org.projectlombok:lombok:1.18.46")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.46")
|
||||
compileOnly("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT")
|
||||
annotationProcessor("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT")
|
||||
implementation("mysql:mysql-connector-java:8.0.33") // mysql
|
||||
implementation("org.spongepowered", "configurate-yaml", "4.2.0")
|
||||
compileOnly("net.kyori:adventure-text-minimessage:4.23.0")
|
||||
compileOnly("com.gitlab.ruany:LiteBansAPI:0.3.5")
|
||||
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 {
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveFileName.set("${rootProject.name}-${project.name}-${project.version}.jar")
|
||||
// minimize()
|
||||
|
||||
@@ -2,20 +2,24 @@ package com.alttd.velocitychat;
|
||||
|
||||
import com.alttd.chat.ChatAPI;
|
||||
import com.alttd.chat.ChatImplementation;
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.database.DatabaseConnection;
|
||||
import com.alttd.chat.managers.ChatUserManager;
|
||||
import com.alttd.chat.managers.PartyManager;
|
||||
import com.alttd.chat.objects.ChatUser;
|
||||
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.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.chat.config.Config;
|
||||
import com.alttd.chat.database.DatabaseConnection;
|
||||
import com.alttd.velocitychat.handlers.ChatHandler;
|
||||
import com.alttd.velocitychat.handlers.ServerHandler;
|
||||
import com.alttd.velocitychat.listeners.ChatListener;
|
||||
import com.alttd.velocitychat.listeners.LiteBansListener;
|
||||
import com.alttd.velocitychat.listeners.ProxyPlayerListener;
|
||||
import com.alttd.velocitychat.listeners.PluginMessageListener;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.alttd.velocitychat.listeners.ProxyPlayerListener;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.inject.Inject;
|
||||
@@ -49,6 +53,7 @@ public class VelocityChat {
|
||||
private ServerHandler serverHandler;
|
||||
|
||||
private ChannelIdentifier channelIdentifier;
|
||||
private SseSubscribeClient sseSubscribeClient;
|
||||
|
||||
@Inject
|
||||
public VelocityChat(ProxyServer proxyServer, Logger proxyLogger, @DataDirectory Path proxydataDirectory) {
|
||||
@@ -67,7 +72,10 @@ public class VelocityChat {
|
||||
PartyManager.initialize(); // load the parties from the db and add the previously loaded users to them
|
||||
|
||||
serverHandler = new ServerHandler();
|
||||
chatHandler = new ChatHandler();
|
||||
WebHandler webHandler = new WebHandler();
|
||||
|
||||
ChatLogHandler chatLogHandler = new ChatLogHandler(webHandler, true);
|
||||
chatHandler = new ChatHandler(chatLogHandler, chatAPI.getLuckPerms());
|
||||
server.getEventManager().register(this, new ChatListener());
|
||||
server.getEventManager().register(this, new ProxyPlayerListener());
|
||||
new LiteBansListener().init(); // init the litebans api listeners
|
||||
@@ -75,11 +83,23 @@ public class VelocityChat {
|
||||
channelIdentifier = MinecraftChannelIdentifier.create(channels[0], channels[1]);
|
||||
server.getChannelRegistrar().register(channelIdentifier);
|
||||
server.getEventManager().register(this, new PluginMessageListener(channelIdentifier));
|
||||
loadCommands();
|
||||
loadCommands(webHandler);
|
||||
// setup console chatuser
|
||||
ChatUser console = new ChatUser(Config.CONSOLEUUID, -1, null);
|
||||
console.setDisplayName(Config.CONSOLENAME);
|
||||
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() {
|
||||
@@ -89,7 +109,11 @@ public class VelocityChat {
|
||||
ByteArrayDataOutput buf = ByteStreams.newDataOutput();
|
||||
buf.writeUTF("reloadconfig");
|
||||
ALogger.info("Reloaded ChatPlugin proxy config.");
|
||||
getProxy().getAllServers().stream().forEach(registeredServer -> registeredServer.sendPluginMessage(getChannelIdentifier(), buf.toByteArray()));
|
||||
getProxy().getAllServers()
|
||||
.stream()
|
||||
.forEach(registeredServer -> registeredServer.sendPluginMessage(getChannelIdentifier(),
|
||||
buf.toByteArray()
|
||||
));
|
||||
}
|
||||
|
||||
public File getDataDirectory() {
|
||||
@@ -108,8 +132,8 @@ public class VelocityChat {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void loadCommands() {
|
||||
ChatLogHandler instance = ChatLogHandler.getInstance(false);
|
||||
public void loadCommands(WebHandler webHandler) {
|
||||
ChatLogHandler instance = ChatLogHandler.getInstance(webHandler, false);
|
||||
new SilentJoinCommand(server);
|
||||
new GlobalAdminChat(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);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import com.alttd.chat.managers.ChatUserManager;
|
||||
import com.alttd.chat.managers.PartyManager;
|
||||
import com.alttd.chat.managers.RegexManager;
|
||||
import com.alttd.chat.objects.*;
|
||||
import com.alttd.chat.objects.chat_log.ChatLogHandler;
|
||||
import com.alttd.chat.objects.chat_log.mapper.chat_log.ChatLogType;
|
||||
import com.alttd.chat.util.ALogger;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import com.alttd.velocitychat.VelocityChat;
|
||||
@@ -14,6 +16,7 @@ import com.google.common.io.ByteStreams;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
@@ -22,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.serializer.gson.GsonComponentSerializer;
|
||||
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.jspecify.annotations.NonNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
@@ -30,8 +36,17 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
public class ChatHandler {
|
||||
|
||||
private final ChatLogHandler chatLogHandler;
|
||||
private final LuckPerms luckPerms;
|
||||
|
||||
public ChatHandler(ChatLogHandler chatLogHandler, LuckPerms luckPerms) {
|
||||
this.chatLogHandler = chatLogHandler;
|
||||
this.luckPerms = luckPerms;
|
||||
}
|
||||
|
||||
public void privateMessage(String sender, String target, String message) {
|
||||
UUID uuid = UUID.fromString(sender);
|
||||
ChatUser senderUser = ChatUserManager.getChatUser(uuid);
|
||||
@@ -54,15 +69,23 @@ public class ChatHandler {
|
||||
Placeholder.component("receiver", targetUser.getDisplayName()),
|
||||
Placeholder.unparsed("receivername", player2.getUsername()),
|
||||
Placeholder.component("message", GsonComponentSerializer.gson().deserialize(message)),
|
||||
Placeholder.unparsed("server", player.getCurrentServer().isPresent() ? player.getCurrentServer().get().getServerInfo().getName() : "Altitude"));
|
||||
Placeholder.unparsed("server",
|
||||
player.getCurrentServer().isPresent() ? player.getCurrentServer()
|
||||
.get()
|
||||
.getServerInfo()
|
||||
.getName() : "Altitude"
|
||||
)
|
||||
);
|
||||
|
||||
ServerConnection serverConnection;
|
||||
//Log handled on receiving server since it is only received on one server and that's where the ignore check happens
|
||||
if (player.getCurrentServer().isPresent() && player2.getCurrentServer().isPresent()) {
|
||||
// redirect to the sender
|
||||
serverConnection = player.getCurrentServer().get();
|
||||
Component component = Utility.parseMiniMessage(Config.MESSAGESENDER
|
||||
.replaceAll("<sendername>", player.getUsername())
|
||||
.replaceAll("<receivername>", player2.getUsername()), Placeholders).asComponent();
|
||||
.replaceAll("<receivername>", player2.getUsername()), Placeholders
|
||||
).asComponent();
|
||||
ByteArrayDataOutput buf = ByteStreams.newDataOutput();
|
||||
buf.writeUTF("privatemessageout");
|
||||
buf.writeUTF(player.getUniqueId().toString());
|
||||
@@ -75,7 +98,8 @@ public class ChatHandler {
|
||||
serverConnection = player2.getCurrentServer().get();
|
||||
component = Utility.parseMiniMessage(Config.MESSAGERECIEVER
|
||||
.replaceAll("<sendername>", player.getUsername())
|
||||
.replaceAll("<receivername>", player2.getUsername()), Placeholders).asComponent();
|
||||
.replaceAll("<receivername>", player2.getUsername()), Placeholders
|
||||
).asComponent();
|
||||
buf = ByteStreams.newDataOutput();
|
||||
buf.writeUTF("privatemessagein");
|
||||
buf.writeUTF(player2.getUniqueId().toString());
|
||||
@@ -120,48 +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) {
|
||||
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);
|
||||
if (optionalPlayer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Player player = optionalPlayer.get();
|
||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||
Party party = PartyManager.getParty(user.getPartyId());
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
|
||||
Party party = PartyManager.getParty(chatUser.getPartyId());
|
||||
if (party == null) {
|
||||
player.sendMessage(Utility.parseMiniMessage(Config.NOT_IN_A_PARTY));
|
||||
return;
|
||||
}
|
||||
ComponentLike senderName = user.getDisplayName();
|
||||
ComponentLike senderName = chatUser.getDisplayName();
|
||||
|
||||
TagResolver Placeholders = TagResolver.resolver(
|
||||
Placeholder.component("sender", senderName),
|
||||
Placeholder.component("sendername", senderName),
|
||||
Placeholder.unparsed("partyname", party.getPartyName()),
|
||||
Placeholder.component("message", parseMessageContent(player, message)),
|
||||
Placeholder.unparsed("server", serverConnection.getServer().getServerInfo().getName())
|
||||
Optional<ParsedPartyMessage> optionalParsedPartyMessage = getResult(uuid,
|
||||
message,
|
||||
item,
|
||||
serverConnection,
|
||||
senderName,
|
||||
party,
|
||||
player.getUsername(),
|
||||
user,
|
||||
player
|
||||
);
|
||||
|
||||
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);
|
||||
if (optionalParsedPartyMessage.isEmpty()) {
|
||||
return; // the message was blocked
|
||||
}
|
||||
|
||||
partyMessage = modifiableString.component();
|
||||
ParsedPartyMessage parsedPartyMessage = optionalParsedPartyMessage.get();
|
||||
|
||||
sendPartyMessage(party, partyMessage, user.getIgnoredBy());
|
||||
sendPartyMessage(party, parsedPartyMessage.partyMessage(), chatUser.getIgnoredBy());
|
||||
|
||||
ComponentLike spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, Placeholders);
|
||||
chatLogHandler.addChatLog(uuid,
|
||||
serverConnection.getServer().getServerInfo().getName(),
|
||||
PlainTextComponentSerializer.plainText().serialize(parsedPartyMessage.partyMessage()),
|
||||
ChatLogType.PARTY,
|
||||
String.valueOf(party.getPartyId()),
|
||||
null,
|
||||
parsedPartyMessage.partyMessage(),
|
||||
false
|
||||
);
|
||||
|
||||
ComponentLike spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, parsedPartyMessage.placeholders());
|
||||
for (Player pl : serverConnection.getServer().getPlayersConnected()) {
|
||||
if (pl.hasPermission(Config.SPYPERMISSION) && !party.getPartyUsersUuid().contains(pl.getUniqueId())) {
|
||||
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) {
|
||||
@@ -171,24 +272,39 @@ public class ChatHandler {
|
||||
.stream()
|
||||
.filter(target -> target.hasPermission("command.chat.globaladminchat"))
|
||||
.forEach(target -> target.sendMessage(component));
|
||||
|
||||
chatLogHandler.addChatLog(
|
||||
new UUID(0, 0), //No origin uuid for GAC
|
||||
"GAC", //No server for GAC
|
||||
PlainTextComponentSerializer.plainText().serialize(component),
|
||||
ChatLogType.GAC,
|
||||
null,
|
||||
null,
|
||||
component,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public void globalAdminChat(CommandSource commandSource, String message) {
|
||||
ComponentLike senderName = Component.text(Config.CONSOLENAME);
|
||||
String serverName = "Altitude";
|
||||
if (commandSource instanceof Player sender) {
|
||||
ChatUser user = ChatUserManager.getChatUser(sender.getUniqueId());
|
||||
if (user == null) {
|
||||
ChatUser chatUser = ChatUserManager.getChatUser(sender.getUniqueId());
|
||||
if (chatUser == null) {
|
||||
return;
|
||||
}
|
||||
senderName = user.getDisplayName();
|
||||
serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer().get().getServerInfo().getName() : "Altitude";
|
||||
senderName = chatUser.getDisplayName();
|
||||
serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer()
|
||||
.get()
|
||||
.getServerInfo()
|
||||
.getName() : "Altitude";
|
||||
}
|
||||
|
||||
TagResolver Placeholders = TagResolver.resolver(
|
||||
Placeholder.component("message", parseMessageContent(commandSource, message)),
|
||||
Placeholder.component("sender", senderName),
|
||||
Placeholder.unparsed("server", serverName));
|
||||
Placeholder.unparsed("server", serverName)
|
||||
);
|
||||
|
||||
ComponentLike component = Utility.parseMiniMessage(Config.GACFORMAT, Placeholders);
|
||||
|
||||
@@ -210,7 +326,8 @@ public class ChatHandler {
|
||||
if (optionalPlayer.isEmpty()) {
|
||||
targetUUID = ServerHandler.getPlayerUUID(recipient);
|
||||
if (targetUUID == null) {
|
||||
commandSource.sendMessage(Utility.parseMiniMessage("<red>A player with this name hasn't logged in recently.")); // TOOD load from config
|
||||
commandSource.sendMessage(Utility.parseMiniMessage(
|
||||
"<red>A player with this name hasn't logged in recently.")); // TOOD load from config
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -274,7 +391,9 @@ public class ChatHandler {
|
||||
Placeholder.component("sender", chatUser.getDisplayName()),
|
||||
Placeholder.component("message", Utility.parseMiniMessage(mail.getMessage())),
|
||||
Placeholder.unparsed("date", date.toString()),
|
||||
Placeholder.unparsed("time_ago", getTimeAgo(Duration.between(date.toInstant(), new Date().toInstant())))
|
||||
Placeholder.unparsed("time_ago",
|
||||
getTimeAgo(Duration.between(date.toInstant(), new Date().toInstant()))
|
||||
)
|
||||
);
|
||||
ComponentLike mailMessage = Utility.parseMiniMessage(Config.mailBody, Placeholders);
|
||||
component = component.append(Component.newline()).append(mailMessage);
|
||||
@@ -302,15 +421,31 @@ public class ChatHandler {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private Component parseMessageContent(CommandSource source, String rawMessage) {
|
||||
private Component parseMessageContent(User user, String rawMessage) {
|
||||
TagResolver.Builder tagResolver = TagResolver.builder();
|
||||
|
||||
Utility.formattingPerms.forEach((perm, pair) -> {
|
||||
if (source.hasPermission(perm)) {
|
||||
if (Utility.hasPermission(user, perm)) {
|
||||
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();
|
||||
Component component = miniMessage.deserialize(rawMessage);
|
||||
for (ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
|
||||
@@ -322,6 +457,5 @@ public class ChatHandler {
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.alttd.chat.util.Utility;
|
||||
import com.alttd.velocitychat.VelocityChat;
|
||||
import com.alttd.velocitychat.events.GlobalAdminChatEvent;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
@@ -19,7 +18,7 @@ public class ChatListener {
|
||||
plugin = VelocityChat.getPlugin();
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.FIRST)
|
||||
@Subscribe(priority = 0)
|
||||
public void onGlobalStaffChat(GlobalAdminChatEvent event) {
|
||||
String senderName = Config.CONSOLENAME;
|
||||
String serverName = "Altitude";
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.alttd.velocitychat.VelocityChat;
|
||||
import com.alttd.velocitychat.commands.vote_to_mute.ActiveVoteToMute;
|
||||
import com.alttd.velocitychat.data.ServerWrapper;
|
||||
import com.alttd.velocitychat.handlers.ServerHandler;
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
@@ -34,7 +33,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ProxyPlayerListener {
|
||||
|
||||
@Subscribe(order = PostOrder.FIRST)
|
||||
@Subscribe(priority = 0)
|
||||
public void onPlayerLogin(LoginEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID uuid = player.getUniqueId();
|
||||
@@ -57,7 +56,7 @@ public class ProxyPlayerListener {
|
||||
ServerHandler.addPlayerUUID(player.getUsername(), uuid);
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
@Subscribe(priority = 4)
|
||||
public void afterPlayerLogin(ServerPostConnectEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
RegisteredServer previousServer = event.getPreviousServer();
|
||||
|
||||
+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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
plugins {
|
||||
java
|
||||
id("org.openapi.generator") version "7.12.0"
|
||||
}
|
||||
|
||||
group = "com.alttd.chat"
|
||||
version = "2.0.0-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
implementation("io.swagger.core.v3:swagger-annotations:2.2.28")
|
||||
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
|
||||
|
||||
implementation("com.google.code.gson:gson:2.12.1")
|
||||
implementation("io.gsonfire:gson-fire:1.9.0")
|
||||
|
||||
implementation("javax.annotation:javax.annotation-api:1.3.2")
|
||||
implementation("jakarta.annotation:jakarta.annotation-api:2.1.1")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir("${projectDir}/build/generated-sources/client/src/main/java")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<org.openapitools.generator.gradle.plugin.tasks.GenerateTask>("openApiGenerate") {
|
||||
generatorName.set("java")
|
||||
inputSpec.set("${projectDir}/src/main/resources/chat-api.yml")
|
||||
configFile.set("${projectDir}/src/main/resources/config_backend.json")
|
||||
outputDir.set("${projectDir}/build/generated-sources/client")
|
||||
|
||||
apiPackage.set("com.alttd.altitudeweb.api")
|
||||
modelPackage.set("com.alttd.altitudeweb.model")
|
||||
|
||||
modelNameSuffix.set("Dto")
|
||||
|
||||
additionalProperties.set(
|
||||
mapOf(
|
||||
"dateLibrary" to "java8",
|
||||
"library" to "okhttp-gson"
|
||||
)
|
||||
)
|
||||
|
||||
generateApiTests.set(false)
|
||||
generateApiDocumentation.set(false)
|
||||
generateModelTests.set(false)
|
||||
generateModelDocumentation.set(false)
|
||||
|
||||
generateAliasAsModel.set(true)
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir(layout.buildDirectory.dir("generated/openapi/src/main/java"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
dependsOn("openApiGenerate")
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
openapi: 3.0.3
|
||||
|
||||
info:
|
||||
title: Minecraft Network API
|
||||
version: 1.0.0
|
||||
|
||||
servers:
|
||||
- url: http://localhost:8080/api
|
||||
|
||||
tags:
|
||||
- name: chat
|
||||
description: Data for displaying Chat messages to clients
|
||||
|
||||
paths:
|
||||
/chat/send/chat/message:
|
||||
post:
|
||||
tags:
|
||||
- chat
|
||||
summary: Sends one or more chat messages
|
||||
operationId: sendChatMessages
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ChatMessage"
|
||||
responses:
|
||||
"202":
|
||||
description: Accepted
|
||||
|
||||
/chat/send/servers/state:
|
||||
post:
|
||||
tags:
|
||||
- chat
|
||||
summary: Update server states
|
||||
operationId: updateServerStates
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ServerState"
|
||||
responses:
|
||||
"202":
|
||||
description: Accepted
|
||||
|
||||
components:
|
||||
schemas:
|
||||
User:
|
||||
type: object
|
||||
required:
|
||||
- uuid
|
||||
- name
|
||||
- styledName
|
||||
# TODO [Stijn] [2026-07-19]: Add who they ignore and who they are ignored by
|
||||
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
styledName:
|
||||
type: string
|
||||
|
||||
ChatMessage:
|
||||
type: object
|
||||
required:
|
||||
- uuid
|
||||
- message
|
||||
- server
|
||||
- type
|
||||
- timestamp
|
||||
- blocked
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
message:
|
||||
type: string
|
||||
server:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- PUBLIC
|
||||
- GLOBAL
|
||||
- PARTY
|
||||
- GAC
|
||||
- MSG
|
||||
- CUSTOM
|
||||
channel:
|
||||
type: string
|
||||
maxLength: 36
|
||||
receiver:
|
||||
type: string
|
||||
maxLength: 36
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
blocked:
|
||||
type: boolean
|
||||
|
||||
PrivateMessage:
|
||||
type: object
|
||||
required:
|
||||
- sender
|
||||
- receiver
|
||||
- message
|
||||
- senderServer
|
||||
- receiverServer
|
||||
properties:
|
||||
sender:
|
||||
type: string
|
||||
format: uuid
|
||||
receiver:
|
||||
type: string
|
||||
format: uuid
|
||||
message:
|
||||
type: string
|
||||
senderServer:
|
||||
type: string
|
||||
receiverServer:
|
||||
type: string
|
||||
|
||||
Server:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
players:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/User"
|
||||
|
||||
ServerState:
|
||||
type: object
|
||||
properties:
|
||||
servers:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Server"
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"library": "okhttp-gson",
|
||||
"hideGenerationTimestamp": true,
|
||||
"modelPackage": "com.alttd.altitudeweb.model",
|
||||
"apiPackage": "com.alttd.altitudeweb.api",
|
||||
"invokerPackage": "com.alttd.altitudeweb.invoker",
|
||||
"serializableModel": true,
|
||||
"openApiNullable": false,
|
||||
"useTags": true,
|
||||
"generateApis": true,
|
||||
"generateApiTests": false,
|
||||
"generateApiDocumentation": false,
|
||||
"generateModels": true,
|
||||
"generateModelTests": false,
|
||||
"generateSupportingFiles": true,
|
||||
"modelNameSuffix": "Dto",
|
||||
"generateTests": false,
|
||||
"dateLibrary": "java8",
|
||||
"enumUnknownDefaultCase": true,
|
||||
"disallowAdditionalPropertiesIfNotPresent": false,
|
||||
"useJakartaEe": true
|
||||
}
|
||||
Reference in New Issue
Block a user