Introduce support for web chat integration.
- Replaced synchronous permission checks with asynchronous LuckPerms user loading. - Added `getOrLoadUser` utility to simplify user-related operations. - Introduced web-originated chat command processing via new `PunishmentCommandBuilder` and `PunishFromWebHandler`. - Updated `MuteServer`, `ToggleGlobalChat`, and `ToggleableForCustomChannel` to use async permission checks. - Added test suite for `PunishmentCommandBuilder`. - Expanded chat handling to process party messages from web events.
This commit is contained in:
@@ -14,10 +14,19 @@ dependencies {
|
||||
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()
|
||||
|
||||
@@ -10,6 +10,9 @@ 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.velocitychat.handlers.ChatHandler;
|
||||
import com.alttd.velocitychat.handlers.ServerHandler;
|
||||
@@ -50,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) {
|
||||
@@ -71,7 +75,7 @@ public class VelocityChat {
|
||||
WebHandler webHandler = new WebHandler();
|
||||
|
||||
ChatLogHandler chatLogHandler = new ChatLogHandler(webHandler, true);
|
||||
chatHandler = new ChatHandler(chatLogHandler);
|
||||
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
|
||||
@@ -84,6 +88,18 @@ public class VelocityChat {
|
||||
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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -16,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;
|
||||
@@ -24,7 +25,10 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.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;
|
||||
@@ -32,12 +36,15 @@ 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) {
|
||||
public ChatHandler(ChatLogHandler chatLogHandler, LuckPerms luckPerms) {
|
||||
this.chatLogHandler = chatLogHandler;
|
||||
this.luckPerms = luckPerms;
|
||||
}
|
||||
|
||||
public void privateMessage(String sender, String target, String message) {
|
||||
@@ -137,58 +144,125 @@ public class ChatHandler {
|
||||
});
|
||||
}
|
||||
|
||||
public void sendPartyMessageFromWeb(UUID uuid, Party party, Component senderName, String message) {
|
||||
Utility.getOrLoadUser(uuid).thenAccept(user -> sendPartyMessageFromWeb(uuid, party, senderName, message, user));
|
||||
}
|
||||
|
||||
public void sendPartyMessageFromWeb(UUID uuid, Party party, Component senderName, String message, User user) {
|
||||
Optional<ParsedPartyMessage> optionalParsedPartyMessage = getResult(uuid,
|
||||
message,
|
||||
null,
|
||||
null,
|
||||
senderName,
|
||||
party,
|
||||
user.getUsername(),
|
||||
user,
|
||||
null
|
||||
);
|
||||
|
||||
if (optionalParsedPartyMessage.isEmpty()) {
|
||||
log.error("Failed to parse party message: {}", message);
|
||||
return;
|
||||
}
|
||||
|
||||
ParsedPartyMessage parsedPartyMessage = optionalParsedPartyMessage.get();
|
||||
|
||||
chatLogHandler.addChatLog(uuid,
|
||||
"web",
|
||||
message,
|
||||
ChatLogType.PARTY,
|
||||
null,
|
||||
null,
|
||||
parsedPartyMessage.partyMessage(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public void sendPartyMessage(UUID uuid, String message, Component item, ServerConnection serverConnection) {
|
||||
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());
|
||||
|
||||
chatLogHandler.addChatLog(uuid,
|
||||
serverConnection.getServer().getServerInfo().getName(),
|
||||
PlainTextComponentSerializer.plainText().serialize(partyMessage),
|
||||
PlainTextComponentSerializer.plainText().serialize(parsedPartyMessage.partyMessage()),
|
||||
ChatLogType.PARTY,
|
||||
String.valueOf(party.getPartyId()),
|
||||
null,
|
||||
partyMessage,
|
||||
parsedPartyMessage.partyMessage(),
|
||||
false
|
||||
);
|
||||
|
||||
ComponentLike spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, placeholders);
|
||||
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) {
|
||||
@@ -215,11 +289,11 @@ public class ChatHandler {
|
||||
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();
|
||||
senderName = chatUser.getDisplayName();
|
||||
serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer()
|
||||
.get()
|
||||
.getServerInfo()
|
||||
@@ -347,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()) {
|
||||
@@ -367,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) {
|
||||
}
|
||||
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
package com.alttd.velocitychat.chat_web;
|
||||
|
||||
import com.alttd.chat.web.handler_class.PunishFromWeb;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class PunishmentCommandBuilderTest {
|
||||
|
||||
private PunishFromWeb baseEvent(String type, UUID executorUuid, UUID targetUuid) {
|
||||
PunishFromWeb event = new PunishFromWeb();
|
||||
event.setExecutor(executorUuid);
|
||||
event.setTarget(targetUuid);
|
||||
event.setType(type);
|
||||
return event;
|
||||
}
|
||||
|
||||
// ---- BAN ----
|
||||
|
||||
@Test
|
||||
public void testBuildBanCommandWithTime() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("ban", executorUuid, targetUuid);
|
||||
event.setTime("P7D"); // ISO-8601 duration: 7 days
|
||||
event.setReason("Griefing");
|
||||
|
||||
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||
|
||||
String expected = "ban " + targetUuid + " 7d --sender=ExecutorName --sender-uuid=" + executorUuid + " Griefing";
|
||||
assertEquals(expected, command);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildBanCommandPermanentWhenNoTime() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("ban", executorUuid, targetUuid);
|
||||
event.setTime(null);
|
||||
event.setReason("Griefing");
|
||||
|
||||
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||
|
||||
String expected = "ban " + targetUuid + " --sender=ExecutorName --sender-uuid=" + executorUuid + " Griefing";
|
||||
assertEquals(expected, command);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildBanCommandPermanentWhenBlankTime() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("ban", executorUuid, targetUuid);
|
||||
event.setTime(" ");
|
||||
event.setReason("Griefing");
|
||||
|
||||
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||
|
||||
String expected = "ban " + targetUuid + " --sender=ExecutorName --sender-uuid=" + executorUuid + " Griefing";
|
||||
assertEquals(expected, command);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildBanCommandMissingReasonThrows() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("ban", executorUuid, targetUuid);
|
||||
event.setTime(" ");
|
||||
event.setReason(" ");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||
);
|
||||
}
|
||||
|
||||
// ---- MUTE ----
|
||||
|
||||
@Test
|
||||
public void testBuildMuteCommand() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("mute", executorUuid, targetUuid);
|
||||
event.setTime("PT30M"); // 30 minutes
|
||||
event.setReason("Spamming");
|
||||
|
||||
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||
|
||||
String expected = "mute " + targetUuid + " 30m --sender=ExecutorName --sender-uuid=" + executorUuid + " Spamming";
|
||||
assertEquals(expected, command);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildMuteCommandNoTimeThrows() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("mute", executorUuid, targetUuid);
|
||||
event.setTime(null);
|
||||
event.setReason("Spamming");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildMuteCommandBlankTimeThrows() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("mute", executorUuid, targetUuid);
|
||||
event.setTime(" ");
|
||||
event.setReason("Spamming");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||
);
|
||||
}
|
||||
|
||||
// ---- WARN ----
|
||||
|
||||
@Test
|
||||
public void testBuildWarnCommandAlwaysUses30d() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("warn", executorUuid, targetUuid);
|
||||
event.setTime(null); // not supplied for warns, should default to 30d anyway
|
||||
event.setReason("Bad attitude");
|
||||
|
||||
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||
|
||||
String expected = "warn " + targetUuid + " 30d --sender=ExecutorName --sender-uuid=" + executorUuid + " Bad attitude";
|
||||
assertEquals(expected, command);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildWarnCommandIgnoresSuppliedTime() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("warn", executorUuid, targetUuid);
|
||||
event.setTime("P1D"); // should be ignored; warns are always 30d
|
||||
event.setReason("Bad attitude");
|
||||
|
||||
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||
|
||||
String expected = "warn " + targetUuid + " 30d --sender=ExecutorName --sender-uuid=" + executorUuid + " Bad attitude";
|
||||
assertEquals(expected, command);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildWarnCommandNoReasonThrows() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("warn", executorUuid, targetUuid);
|
||||
event.setTime(null);
|
||||
event.setReason("");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Type validation ----
|
||||
|
||||
@Test
|
||||
public void testInvalidTypeThrows() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("kick", executorUuid, targetUuid);
|
||||
event.setReason("Bad attitude");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> PunishmentCommandBuilder.buildCommand("ExecutorName", event)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeIsCaseInsensitive() {
|
||||
UUID executorUuid = UUID.randomUUID();
|
||||
UUID targetUuid = UUID.randomUUID();
|
||||
PunishFromWeb event = baseEvent("BAN", executorUuid, targetUuid);
|
||||
event.setTime(null);
|
||||
event.setReason("Griefing");
|
||||
|
||||
String command = PunishmentCommandBuilder.buildCommand("ExecutorName", event);
|
||||
|
||||
String expected = "ban " + targetUuid + " --sender=ExecutorName --sender-uuid=" + executorUuid + " Griefing";
|
||||
assertEquals(expected, command);
|
||||
}
|
||||
|
||||
// ---- Duration parsing ----
|
||||
|
||||
@Test
|
||||
public void testParseDurationDaysOnly() {
|
||||
assertEquals("7d", PunishmentCommandBuilder.parseDuration("P7D"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDurationMinutesOnly() {
|
||||
assertEquals("45m", PunishmentCommandBuilder.parseDuration("PT45M"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDurationTruncatesToDaysWhenDaysPresent() {
|
||||
// days take priority over everything smaller, which is dropped entirely
|
||||
assertEquals("1d", PunishmentCommandBuilder.parseDuration("P1DT2H3M4S"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDurationTruncatesToHoursWhenNoDays() {
|
||||
// hours take priority over minutes, which is dropped
|
||||
assertEquals("2h", PunishmentCommandBuilder.parseDuration("PT2H3M"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDurationInvalidFormatThrows() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> PunishmentCommandBuilder.parseDuration("7d")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDurationZeroThrows() {
|
||||
// duration must be positive
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> PunishmentCommandBuilder.parseDuration("PT0S")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDurationSubMinuteThrows() {
|
||||
// litebans has no second-level precision defaults to 1m
|
||||
assertEquals("1m", PunishmentCommandBuilder.parseDuration("PT30S"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user