45 Commits
Author SHA1 Message Date
stijn 0f4045d20d Fix EmoteList formatting issues and improve navigation command handling 2026-09-05 20:25:22 +02:00
stijn d3aa424261 Update WebUpdatePlayerList to fetch and apply player skin textures for fake players. 2026-08-26 18:27:11 +02:00
stijn 79ded52842 Refactor storeMessages to improve batch handling and error recovery, add per-message fallback, and enhance logging. 2026-08-25 22:44:36 +02:00
stijn 776ea90d7d Get the right servername and player name 2026-08-24 20:36:01 +02:00
stijn b98dd65203 Refactor PlayerListener and add delayed server state updates for web integration. 2026-08-22 21:08:31 +02:00
stijn 9e647f83a8 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.
2026-08-09 19:39:55 +02:00
stijn 0d2ac3763f Update server state handling and mapping for web player integration.
- Introduced `WebPlayer` class for representing web players.
- Added `WebPlayerMapper` for converting between `WebPlayer` and DTOs.
- Updated `ServerMapper` and `WebHandler` to support `WebPlayer`.
- Refactored `PlayerListener` to include `ChatUser` in server state updates.
2026-08-09 02:07:05 +02:00
stijn e72f79cb29 Add server state mapping for web integration. 2026-08-09 00:28:21 +02:00
stijn 5eb470cfb6 Add server state mapping for web integration. 2026-08-09 00:24:52 +02:00
stijn d99ac4c597 Add support for marking and processing web-originated chat messages. 2026-08-08 23:54:15 +02:00
stijn 5a4140c2e2 Add support for marking and processing web-originated chat messages. 2026-08-08 23:54:03 +02:00
stijn 421f6655fd Add SSE client for web chat integration and enable event-based message handling.
- Introduced `SseSubscribeClient` to connect to the external web server and process events via SSE (Server-Sent Events).
- Added `WebHandler` interface for type-safe event deserialization and processing.
- Included `WebChatHandler` to handle incoming web chat messages and forward them to the chat system.
- Updated `Config` for new configurable values: `CHAT_WEB_REGISTER_TO_BASE_URL` and `CHAT_WEB_TOKEN`.
2026-08-02 17:39:25 +02:00
stijn 6db9816c85 Refactor chat message handling. It now supports sending chat messages with just a UUID and a string message. (For support for web messages) 2026-08-02 15:46:51 +02:00
Len 9143642761 Update to 26.2 2026-07-27 02:14:13 -05:00
Len e600cc7331 Update gradle 2026-07-27 02:05:03 -05:00
stijn 2962a605ea Fix typo in CREATE TABLE query within ChatLogQueries to ensure table creation works correctly. 2026-07-19 23:12:36 +02:00
stijn 7492dfa418 Integrate ChatLogHandler across chat modules and add centralized message logging with validation through ChatLogMapper. 2026-07-19 23:00:40 +02:00
stijn 92979e9789 Refactor ChatListener to improve readability and formatting consistency across message handling and ignored players logic. 2026-07-19 01:03:07 +02:00
stijn af6820d1bf Remove custom OffsetDateTime mappings in OpenAPI config and ensure timestamps are converted to UTC in ChatLog. 2026-07-18 21:04:54 +02:00
stijn 8546bda28a Replace hardcoded web server URL with configurable value from Config. 2026-07-18 20:51:32 +02:00
stijn 28894ef245 Improve string formatting consistency in Config.java and add webServerSettings method for web server configuration 2026-07-18 20:51:04 +02:00
stijn 5a508fdf28 Add web-api module with OpenAPI integration, enable chat logs forwarding to web backend, and update dependencies across modules. 2026-07-18 20:21:25 +02:00
stijn dc513ac2f0 Add support for miniMessage serialization in chat logs 2026-07-18 19:11:53 +02:00
stijn c48d4f5c86 Update InventoryGUI to version 1.1.5-SNAPSHOT and refine Nicknames GUI handling logic. 2026-02-02 03:07:45 +01:00
stijn 7f00bc104b Update InventoryGUI to version 1.1.3-SNAPSHOT in build.gradle.kts. 2026-02-01 03:48:31 +01:00
stijn 133cebd1ec Rerender gui when needed 2026-02-01 03:36:06 +01:00
stijn fc20c9a276 Update velocity-api to 3.5.0-SNAPSHOT, adjust event priority annotations, and reconfigure repository URLs in settings.gradle.kts. 2026-01-31 23:45:19 +01:00
stijn ea8e952108 Refactor Nicknames GUI to handle nicknames even if the player is unknown and use InventoryGui library, improving modularity and readability. Update velocity-api version and add InventoryGUI dependency. 2026-01-31 23:25:11 +01:00
stijn 3d010e4139 extract methods for shorter functions and use early returns 2026-01-31 18:52:14 +01:00
stijn 7cc7cca129 Update cosmos-api to version 1.21.8 and adjust repository URL in settings.gradle.kts. 2025-07-29 22:54:06 +02:00
stijn 3bfd358e78 Add support for custom channel aliases and improve string formatting consistency 2025-07-29 19:11:14 +02:00
stijn eb30750134 Refactor switch statements to use enhanced -> syntax and update dependencies to include cosmos-api. 2025-07-29 19:10:20 +02:00
auto fc7860145f Updated to 1.21 by switching to cosmos 2025-06-21 00:53:55 +02:00
auto 1dd19f0543 Update dependencies and build configuration 2025-06-20 23:26:54 +02:00
auto bbd421c423 switched to ' instead of " 2025-06-20 22:53:03 +02:00
auto 5fe5a305a5 Corrected insecure usage of environment variables 2025-06-20 22:50:59 +02:00
auto bbfc9c407c Corrected credentials id 2025-06-20 22:47:48 +02:00
auto 880108d344 Fix formatting 2025-06-20 22:46:59 +02:00
auto 61e705b088 Fix formatting 2025-06-20 22:45:53 +02:00
auto 38228ea585 Add credentials to Jenkinsfile 2025-06-20 22:34:40 +02:00
auto b65fcdb1c7 Update URL detection and formatting logic in Utility
Enhanced `DEFAULT_URL_PATTERN` for improved URL detection and added `formatUrl` method to ensure URLs include a valid scheme. Adjusted `formatText` to use clickable and underlined URLs in the configured format. Removed unused imports and cleaned up comments.
2025-06-20 22:34:22 +02:00
auto e74361a7b7 Add "local" chat channels with proximity-based messaging
Introduced a "local" channel type to restrict chat visibility based on player proximity, configurable via `LOCAL_DISTANCE`. Added channel spy functionality to monitor "local" messages. Updated constructors and configuration to support this new feature.
2025-03-22 18:19:25 +01:00
auto 769859a617 Fix bypass permission logic for ignored players in chat
Updated the filtering logic to correctly handle the "chat.ignorebypass" permission, ensuring players with bypass permission are not excluded due to ignore settings. Adjusted related conditionals in ChatHandler and ChatListener, and refactored patterns without altering functionality.
2025-03-22 02:44:42 +01:00
auto cb0bda8d5b Prevent sending mail to ignored players.
Added a check to block users from sending mail to players who have ignored them. A message is displayed to inform the sender, ensuring clearer communication and respecting player preferences.
2025-03-21 22:56:10 +01:00
auto 5212954946 Refactor config reload and server name handling.
Replaced inconsistent `ReloadConfig` usage with `reloadConfig` across the codebase for better naming consistency. Introduced `ServerName` utility to standardize server name retrieval and improve maintainability. Added logging enhancements for better debugging of muted users and blocked messages.
2025-03-21 22:55:57 +01:00
96 changed files with 4244 additions and 1828 deletions
Vendored
+5 -2
View File
@@ -1,9 +1,12 @@
pipeline {
agent any
environment {
NEXUS_CREDS = credentials('alttd-snapshot-user')
}
stages {
stage('Gradle') {
steps {
sh './gradlew build'
sh './gradlew build -PalttdSnapshotUsername=$NEXUS_CREDS_USR -PalttdSnapshotPassword=$NEXUS_CREDS_PSW'
}
}
stage('Archive') {
@@ -17,4 +20,4 @@ pipeline {
}
}
}
}
}
+15 -7
View File
@@ -3,12 +3,20 @@ plugins {
}
dependencies {
// compileOnly("com.alttd:Galaxy-API:1.21-R0.1-SNAPSHOT") {
// exclude("net.kyori")
// }
compileOnly("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
compileOnly("org.spongepowered:configurate-yaml:4.1.2") // Configurate
compileOnly("net.luckperms:api:5.3") // Luckperms
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.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 {
@@ -18,7 +26,7 @@ publishing {
}
}
repositories{
repositories {
maven {
name = "maven"
url = uri("https://repo.destro.xyz/snapshots")
@@ -15,9 +15,9 @@ public abstract interface ChatAPI {
DatabaseConnection getDataBase();
void ReloadConfig();
void reloadConfig();
void ReloadChatFilters();
void reloadChatFilters();
HashMap<String, String> getPrefixes();
@@ -5,15 +5,12 @@ import com.alttd.chat.config.PrefixConfig;
import com.alttd.chat.database.DatabaseConnection;
import com.alttd.chat.database.Queries;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.PartyManager;
import com.alttd.chat.managers.RegexManager;
import com.alttd.chat.util.ALogger;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.group.Group;
import java.util.HashMap;
import java.util.Map;
public class ChatImplementation implements ChatAPI{
@@ -26,7 +23,7 @@ public class ChatImplementation implements ChatAPI{
public ChatImplementation() {
instance = this;
ReloadConfig();
reloadConfig();
luckPerms = getLuckPerms();
databaseConnection = getDataBase();
@@ -57,13 +54,13 @@ public class ChatImplementation implements ChatAPI{
}
@Override
public void ReloadConfig() {
public void reloadConfig() {
Config.init();
loadPrefixes();
}
@Override
public void ReloadChatFilters() {
public void reloadChatFilters() {
RegexManager.initialize();
}
@@ -1,10 +1,11 @@
package com.alttd.chat.config;
import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.Utility;
import com.google.common.collect.Lists;
import io.leangen.geantyref.TypeToken;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.serialize.SerializationException;
@@ -19,6 +20,7 @@ import java.lang.reflect.Modifier;
import java.util.*;
import java.util.regex.Pattern;
@SuppressWarnings("unused")
public final class Config {
private static final Pattern PATH_PATTERN = Pattern.compile("\\.");
private static final String HEADER = "";
@@ -31,6 +33,7 @@ public final class Config {
static boolean verbose;
public static File CONFIGPATH;
public static void init() {
CONFIGPATH = new File(File.separator + "mnt" + File.separator + "configs" + File.separator + "ChatPlugin");
CONFIG_FILE = new File(CONFIGPATH, "config.yml");
@@ -39,24 +42,24 @@ public final class Config {
.nodeStyle(NodeStyle.BLOCK)
.build();
if (!CONFIG_FILE.getParentFile().exists()) {
if(!CONFIG_FILE.getParentFile().mkdirs()) {
if (!CONFIG_FILE.getParentFile().mkdirs()) {
return;
}
}
if (!CONFIG_FILE.exists()) {
try {
if(!CONFIG_FILE.createNewFile()) {
if (!CONFIG_FILE.createNewFile()) {
return;
}
} catch (IOException error) {
error.printStackTrace();
ALogger.error(error.getMessage(), error);
}
}
try {
config = configLoader.load(ConfigurationOptions.defaults().header(HEADER).shouldCopyDefaults(false));
} catch (IOException e) {
e.printStackTrace();
ALogger.error(e.getMessage(), e);
}
verbose = getBoolean("verbose", true);
@@ -66,7 +69,7 @@ public final class Config {
try {
configLoader.save(config);
} catch (IOException e) {
e.printStackTrace();
ALogger.error(e.getMessage(), e);
}
}
@@ -78,7 +81,7 @@ public final class Config {
method.setAccessible(true);
method.invoke(instance);
} catch (InvocationTargetException | IllegalAccessException ex) {
ex.printStackTrace();
ALogger.error(ex.getMessage(), ex);
}
}
}
@@ -86,7 +89,7 @@ public final class Config {
try {
configLoader.save(config);
} catch (IOException ex) {
ex.printStackTrace();
ALogger.error(ex.getMessage(), ex);
}
}
@@ -94,7 +97,7 @@ public final class Config {
try {
configLoader.save(config);
} catch (IOException ex) {
ex.printStackTrace();
ALogger.error(ex.getMessage(), ex);
}
}
@@ -103,21 +106,22 @@ public final class Config {
}
private static void set(String path, Object def) {
if(config.node(splitPath(path)).virtual()) {
if (config.node(splitPath(path)).virtual()) {
try {
config.node(splitPath(path)).set(def);
} catch (SerializationException e) {
e.printStackTrace();
ALogger.error(e.getMessage(), e);
}
}
}
private static void setString(String path, String def) {
try {
if(config.node(splitPath(path)).virtual())
if (config.node(splitPath(path)).virtual()) {
config.node(splitPath(path)).set(io.leangen.geantyref.TypeToken.get(String.class), def);
} catch(SerializationException ex) {
ex.printStackTrace();
}
} catch (SerializationException ex) {
ALogger.error(ex.getMessage(), ex);
}
}
@@ -150,21 +154,23 @@ public final class Config {
try {
set(path, def);
return config.node(splitPath(path)).getList(TypeToken.get(String.class));
} catch(SerializationException ex) {
ex.printStackTrace();
} catch (SerializationException ex) {
ALogger.error(ex.getMessage(), ex);
}
return new ArrayList<>();
}
private static ConfigurationNode getNode(String path) {
if(config.node(splitPath(path)).virtual()) {
if (config.node(splitPath(path)).virtual()) {
//new RegexConfig("Dummy");
}
config.childrenMap();
return config.node(splitPath(path));
}
/** ONLY EDIT ANYTHING BELOW THIS LINE **/
/**
* ONLY EDIT ANYTHING BELOW THIS LINE
**/
public static List<String> PREFIXGROUPS = new ArrayList<>();
public static List<String> CONFLICTINGPREFIXGROUPS = new ArrayList<>();
public static List<String> STAFFGROUPS = new ArrayList<>();
@@ -173,13 +179,19 @@ public final class Config {
public static UUID CONSOLEUUID = UUID.randomUUID();
public static int EMOTELIMIT = 3;
public static String MENTIONPLAYERTAG = "<aqua>@</aqua>";
private static void settings() {
PREFIXGROUPS = getList("settings.prefix-groups",
Lists.newArrayList("discord", "socialmedia", "eventteam", "eventleader", "youtube", "twitch", "developer"));
Lists.newArrayList("discord", "socialmedia", "eventteam", "eventleader", "youtube", "twitch",
"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);
@@ -189,10 +201,13 @@ public final class Config {
public static List<String> MESSAGECOMMANDALIASES = new ArrayList<>();
public static List<String> REPLYCOMMANDALIASES = new ArrayList<>();
public static String MESSAGESENDER = "<hover:show_text:Click to reply><click:suggest_command:/msg <receivername> ><light_purple>(Me -> <gray><receiver></gray>)</hover> <message>";
public static String MESSAGERECIEVER = "<hover:show_text:Click to reply><click:suggest_command:/msg <sendername> ><light_purple>(<gray><sender></gray> on <server> -> Me)</hover> <message>";
public static String MESSAGESENDER =
"<hover:show_text:Click to reply><click:suggest_command:/msg <receivername> ><light_purple>(Me -> <gray><receiver></gray>)</hover> <message>";
public static String MESSAGERECIEVER =
"<hover:show_text:Click to reply><click:suggest_command:/msg <sendername> ><light_purple>(<gray><sender></gray> on <server> -> Me)</hover> <message>";
public static String MESSAGESPY = "<gray>(<gray><sendername></gray> -> <receivername>) <message>";
public static String RECEIVER_DOES_NOT_EXIST = "<red><player> is not a valid player.</red>";
private static void messageCommand() {
MESSAGECOMMANDALIASES.clear();
REPLYCOMMANDALIASES.clear();
@@ -204,12 +219,14 @@ public final class Config {
RECEIVER_DOES_NOT_EXIST = getString("commands.message.receiver-does-not-exist", RECEIVER_DOES_NOT_EXIST);
}
public static String GCFORMAT = "<white><light_purple><prefix></light_purple> <gray><sender></gray> <hover:show_text:on <server>><yellow>to Global</yellow></hover><gray>: <message>";
public static String GCFORMAT =
"<white><light_purple><prefix></light_purple> <gray><sender></gray> <hover:show_text:on <server>><yellow>to Global</yellow></hover><gray>: <message>";
public static String GCPERMISSION = "proxy.globalchat";
public static List<String> GCALIAS = new ArrayList<>();
public static String GCNOTENABLED = "You don't have global chat enabled.";
public static String GCONCOOLDOWN = "You have to wait <cooldown> seconds before using this feature again.";
public static int GCCOOLDOWN = 30;
private static void globalChat() {
GCFORMAT = getString("commands.globalchat.format", GCFORMAT);
GCPERMISSION = getString("commands.globalchat.view-chat-permission", GCPERMISSION);
@@ -219,26 +236,32 @@ public final class Config {
GCCOOLDOWN = getInt("commands.globalchat.cooldown", GCCOOLDOWN);
}
public static String CHATFORMAT = "<white><light_purple><prefixall> <gray><hover:show_text:Click to message <sendername>><click:suggest_command:/msg <sendername> ><sender></hover>: <white><message>";
public static String CHATFORMAT =
"<white><light_purple><prefixall> <gray><hover:show_text:Click to message <sendername>><click:suggest_command:/msg <sendername> ><sender></hover>: <white><message>";
public static String URLFORMAT = "<click:OPEN_URL:<clickurl>><url></click>";
private static void Chat() {
CHATFORMAT = getString("chat.format", CHATFORMAT);
URLFORMAT = getString("chat.urlformat", URLFORMAT);
}
public static List<String> GACECOMMANDALIASES = new ArrayList<>();
public static String GACFORMAT = "<hover:show_text:Click to reply><click:suggest_command:/acg ><yellow>(<sender> on <server> -> Team)</hover> <message>";
public static String GACFORMAT =
"<hover:show_text:Click to reply><click:suggest_command:/acg ><yellow>(<sender> on <server> -> Team)</hover> <message>";
private static void globalAdminChat() {
GACECOMMANDALIASES = getList("commands.globaladminchat.aliases", Lists.newArrayList("acg"));
GACFORMAT = getString("commands.globaladminchat.format", GACFORMAT);
}
public static String MESSAGECHANNEL = "altitude:chatplugin";
private static void messageChannels() {
MESSAGECHANNEL = getString("settings.message-channel", MESSAGECHANNEL);
}
public static ConfigurationNode REGEXNODE = null;
private static void RegexNOde() {
REGEXNODE = getNode("regex-settings");
}
@@ -247,6 +270,7 @@ public final class Config {
public static String SERVERSWTICHMESSAGETO = "<gray>* <player> leaves to <to_server>...";
public static String SERVERJOINMESSAGE = "<green>* <player> appears from thin air...";
public static String SERVERLEAVEMESSAGE = "<red>* <player> vanishes in the mist...";
private static void JoinLeaveMessages() {
SERVERSWTICHMESSAGEFROM = getString("messages.switch-server-from", SERVERSWTICHMESSAGEFROM);
SERVERSWTICHMESSAGETO = getString("messages.switch-server-to", SERVERSWTICHMESSAGETO);
@@ -255,12 +279,14 @@ public final class Config {
}
public static String PARTY_FORMAT = "<dark_aqua>(<gray><sender></gray><hover:show_text:\"on <server>\"> → <party></hover>) <message>";
public static String PARTY_SPY = "<i><gray>PC:</gray><dark_gray> <dark_gray><username></dark_gray>: <dark_gray><party></dark_gray> <message></dark_gray></i>";
public static String PARTY_FORMAT =
"<dark_aqua>(<gray><sender></gray><hover:show_text:\"on <server>\"> → <party></hover>) <message>";
public static String PARTY_SPY =
"<i><gray>PC:</gray><dark_gray> <dark_gray><username></dark_gray>: <dark_gray><party></dark_gray> <message></dark_gray></i>";
public static String NO_PERMISSION = "<red>You don't have permission to use this command.</red>";
public static String NO_CONSOLE = "<red>This command can not be used by console</red>";
public static String CREATED_PARTY = "<green>You created a chat party called: " +
"'<gold><party_name></gold>' with the password: '<gold><party_password></gold>'</green>";
"'<gold><party_name></gold>' with the password: '<gold><party_password></gold>'</green>";
public static String NOT_IN_A_PARTY = "<red>You're not in a chat party.</red>";
public static String NOT_YOUR_PARTY = "<red>You don't own this chat party.</red>";
public static String NOT_A_PARTY = "<red>This chat party does not exist.</red>";
@@ -270,9 +296,11 @@ public final class Config {
public static String INVALID_PASSWORD = "<red>Invalid password.</red>";
public static String JOINED_PARTY = "<green>You joined <party_name>!</green>";
public static String PLAYER_JOINED_PARTY = "<green><player_name> joined <party_name>!</green>";
public static String NOTIFY_FINDING_NEW_OWNER = "<dark_aqua>Since you own this chat party a new party owner will be chosen.<dark_aqua>";
public static String NOTIFY_FINDING_NEW_OWNER =
"<dark_aqua>Since you own this chat party a new party owner will be chosen.<dark_aqua>";
public static String LEFT_PARTY = "<green>You have left the chat party!</green>";
public static String OWNER_LEFT_PARTY = "<dark_aqua>[ChatParty]: <old_owner> left the chat party, the new party owner is <new_owner></dark_aqua>";
public static String OWNER_LEFT_PARTY =
"<dark_aqua>[ChatParty]: <old_owner> left the chat party, the new party owner is <new_owner></dark_aqua>";
public static String PLAYER_LEFT_PARTY = "<dark_aqua>[ChatParty]: <player_name> left the chat party!</dark_aqua>";
public static String NEW_PARTY_OWNER = "<dark_aqua>[ChatParty]: <old_owner> transferred the party to <new_owner>!";
public static String CANT_REMOVE_PARTY_OWNER = "<red>You can't remove yourself, please leave instead.</red>";
@@ -283,23 +311,26 @@ public final class Config {
public static String ALREADY_IN_THIS_PARTY = "<red>You're already in <party>!</red>";
public static String SENT_PARTY_INV = "<green>You send a chat party invite to <player>!</green>";
public static String JOIN_PARTY_CLICK_MESSAGE = "<click:run_command:'/party join <party> <party_password>'>" +
"<dark_aqua>You received an invite to join <party>, click this message to accept.</dark_aqua></click>";
"<dark_aqua>You received an invite to join <party>, click this message to accept.</dark_aqua></click>";
public static String PARTY_MEMBER_LOGGED_ON = "<dark_aqua>[ChatParty] <player> joined Altitude...</dark_aqua>";
public static String PARTY_MEMBER_LOGGED_OFF = "<dark_aqua>[ChatParty] <player> left Altitude...</dark_aqua>";
public static String RENAMED_PARTY = "<dark_aqua>[ChatParty] <owner> changed the party name from <old_name> to <new_name>!</dark_aqua>";
public static String RENAMED_PARTY =
"<dark_aqua>[ChatParty] <owner> changed the party name from <old_name> to <new_name>!</dark_aqua>";
public static String CHANGED_PASSWORD = "<green>Password was set to <password></green>";
public static String DISBAND_PARTY_CONFIRM = "<green><bold>Are you sure you want to disband your party?</bold> " +
"Type <gold>/party disband confirm <party></gold> to confirm.";
public static String DISBANDED_PARTY = "<dark_aqua>[ChatParty] <owner> has disbanded <party>, everyone has been removed.</dark_aqua>";
"Type <gold>/party disband confirm <party></gold> to confirm.";
public static String DISBANDED_PARTY =
"<dark_aqua>[ChatParty] <owner> has disbanded <party>, everyone has been removed.</dark_aqua>";
public static String PARTY_INFO = """
<gold><bold>Chat party info</bold>:
</gold><green>Name: <dark_aqua><party></dark_aqua>
Password: <dark_aqua><password></dark_aqua>
Owner: <owner>
Members: <members>""";
public static Component ONLINE_PREFIX = null;
public static Component OFFLINE_PREFIX = null;
<gold><bold>Chat party info</bold>:
</gold><green>Name: <dark_aqua><party></dark_aqua>
Password: <dark_aqua><password></dark_aqua>
Owner: <owner>
Members: <members>""";
public static ComponentLike ONLINE_PREFIX = null;
public static ComponentLike OFFLINE_PREFIX = null;
public static String PARTY_TOGGLED = "<dark_aqua>Party chat toggled <status>.</dark_aqua>";
private static void party() {
PARTY_FORMAT = getString("party.format", PARTY_FORMAT);
PARTY_SPY = getString("party.spy", PARTY_SPY);
@@ -338,17 +369,27 @@ public final class Config {
public static String PARTY_HELP_WRAPPER = "<gold>ChatParty help:\n<commands></gold>";
public static String PARTY_HELP_HELP = "<green>Show this menu: <gold>/party help</gold></green>";
public static String PARTY_HELP_CREATE = "<green>Create a party: <gold>/party create <party_name> <party_password></gold></green>";
public static String PARTY_HELP_INFO = "<green>Show info about your current party: <gold>/party info</gold></green>";
public static String PARTY_HELP_INVITE = "<green>Invite a user to your party: <gold>/party invite <username></gold></green>";
public static String PARTY_HELP_JOIN = "<green>Join a party: <gold>/party join <party_name> <party_password></gold></green>";
public static String PARTY_HELP_CREATE =
"<green>Create a party: <gold>/party create <party_name> <party_password></gold></green>";
public static String PARTY_HELP_INFO =
"<green>Show info about your current party: <gold>/party info</gold></green>";
public static String PARTY_HELP_INVITE =
"<green>Invite a user to your party: <gold>/party invite <username></gold></green>";
public static String PARTY_HELP_JOIN =
"<green>Join a party: <gold>/party join <party_name> <party_password></gold></green>";
public static String PARTY_HELP_LEAVE = "<green>Leave your current party: <gold>/party leave</gold></green>";
public static String PARTY_HELP_NAME = "<green>Change the name of your party: <gold>/party name <new_name></gold></green>";
public static String PARTY_HELP_OWNER = "<green>Change the owner of your party: <gold>/party owner <new_owner_name></gold></green>";
public static String PARTY_HELP_PASSWORD = "<green>Change the password of your party: <gold>/party password <new_password></gold></green>";
public static String PARTY_HELP_REMOVE = "<green>Remove a member from your party: <gold>/party remove <member_name></gold></green>";
public static String PARTY_HELP_DISBAND = "<green>Remove everyone from your party and disband it: <gold>/party disband</gold></green>";
public static String PARTY_HELP_NAME =
"<green>Change the name of your party: <gold>/party name <new_name></gold></green>";
public static String PARTY_HELP_OWNER =
"<green>Change the owner of your party: <gold>/party owner <new_owner_name></gold></green>";
public static String PARTY_HELP_PASSWORD =
"<green>Change the password of your party: <gold>/party password <new_password></gold></green>";
public static String PARTY_HELP_REMOVE =
"<green>Remove a member from your party: <gold>/party remove <member_name></gold></green>";
public static String PARTY_HELP_DISBAND =
"<green>Remove everyone from your party and disband it: <gold>/party disband</gold></green>";
public static String PARTY_HELP_CHAT = "<green>Talk in party chat: <gold>/p <message></gold></green>";
private static void partyHelp() {
PARTY_HELP_WRAPPER = getString("party.help.wrapper", PARTY_HELP_WRAPPER);
PARTY_HELP_HELP = getString("party.help.help", PARTY_HELP_HELP);
@@ -366,12 +407,18 @@ public final class Config {
}
public static String CUSTOM_CHANNEL_TOGGLED = "<yellow>Toggled <channel> <status>.</yellow>";
public static Component TOGGLED_ON = null;
public static Component TOGGLED_OFF = null;
public static ComponentLike TOGGLED_ON = null;
public static ComponentLike TOGGLED_OFF = null;
public static double LOCAL_DISTANCE;
public static String CHANNEL_SPY =
"<i><gray>SPY:</gray> <dark_gray>(<dark_gray><sender> → <channel>) <message></dark_gray>";
private static void chatChannels() {
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>");
getString("chat-channels.ac.format",
"<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");
@@ -383,16 +430,24 @@ public final class Config {
new CustomChannel(channelName,
getString(key + "format", ""),
getList(key + "servers", Collections.EMPTY_LIST),
getBoolean(key + "proxy", false));
getList(key + "alias", Collections.EMPTY_LIST),
getBoolean(key + "proxy", false),
getBoolean(key + "local", false),
getBoolean(key + "web", false)
);
}
CUSTOM_CHANNEL_TOGGLED = getString("chat-channels-messages.channel-toggled", CUSTOM_CHANNEL_TOGGLED);
TOGGLED_ON = Utility.parseMiniMessage(getString("chat-channels-messages.channel-on", "<green>on</green><gray>"));
TOGGLED_ON =
Utility.parseMiniMessage(getString("chat-channels-messages.channel-on", "<green>on</green><gray>"));
TOGGLED_OFF = Utility.parseMiniMessage(getString("chat-channels-messages.channel-off", "<red>off</red><gray>"));
LOCAL_DISTANCE = getDouble("chat-channels-messages.local-distance", 200.0);
CHANNEL_SPY = getString("chat-channels-messages.spy", CHANNEL_SPY);
}
public static String SERVERMUTEPERMISSION = "chat.command.mute-server";
public static String SPYPERMISSION = "chat.socialspy";
private static void permissions() {
SERVERMUTEPERMISSION = getString("permissions.server-mute", SERVERMUTEPERMISSION);
SPYPERMISSION = getString("permissions.spy-permission", SPYPERMISSION);
@@ -403,6 +458,7 @@ public final class Config {
public static String DATABASE = "database";
public static String USERNAME = "root";
public static String PASSWORD = "root";
private static void database() {
IP = getString("database.ip", IP);
PORT = getString("database.port", PORT);
@@ -412,19 +468,25 @@ public final class Config {
}
public static String NOTIFICATIONFORMAT = "<red>[<prefix>] <displayname> <target> <input>";
private static void notificationSettings() {
NOTIFICATIONFORMAT = getString("settings.blockedmessage-notification", NOTIFICATIONFORMAT);
}
public static String mailHeader = "===== List Mails ====='";
public static String mailBody = "<white>From:</white> [<staffprefix>] <sender> <white><hover:show_text:'<date>'><time_ago> day(s) ago</hover>: </white><message>";
public static String mailBody =
"<white>From:</white> [<staffprefix>] <sender> <white><hover:show_text:'<date>'><time_ago> day(s) ago</hover>: </white><message>";
public static String mailFooter = "======================";
public static String mailNoUser = "<red>A player with this name hasn't logged in recently.";
public static String mailReceived = "<yellow><click:run_command:/mail list unread>New mail from <sender>, click to view</click></yellow>";
public static String mailUnread = "<green><click:run_command:/mail list unread>You have <amount> unread mail, click to view it.</click></green>";
public static String mailSent = "<green>Successfully send mail to <player_name></green>: <#2e8b57><message></#2e8b57>";
public static String mailReceived =
"<yellow><click:run_command:/mail list unread>New mail from <sender>, click to view</click></yellow>";
public static String mailUnread =
"<green><click:run_command:/mail list unread>You have <amount> unread mail, click to view it.</click></green>";
public static String mailSent =
"<green>Successfully send mail to <player_name></green>: <#2e8b57><message></#2e8b57>";
public static List<String> mailCommandAlias = new ArrayList<>();
public static int mailDisplayDelay = 5;
private static void mailSettings() {
mailHeader = getString("settings.mail.header", mailHeader);
mailBody = getString("settings.mail.message", mailBody);
@@ -437,8 +499,11 @@ public final class Config {
}
public static HashMap<String, Long> serverChannelId = new HashMap<>();
public static String REPORT_SENT = "<green>Your report was sent, staff will contact you asap to help resolve your issue!</green>";
public static String REPORT_TOO_SHORT = "<red>Please ensure your report is descriptive. We require at least 3 words per report</red>";
public static String REPORT_SENT =
"<green>Your report was sent, staff will contact you asap to help resolve your issue!</green>";
public static String REPORT_TOO_SHORT =
"<red>Please ensure your report is descriptive. We require at least 3 words per report</red>";
private static void loadChannelIds() {
serverChannelId.clear();
serverChannelId.put("general", getLong("discord-channel-id.general", (long) -1));
@@ -446,8 +511,9 @@ public final class Config {
Map<Object, ? extends ConfigurationNode> objectMap = node.childrenMap();
for (Object o : objectMap.keySet()) {
String key = (String) o;
if (key.equalsIgnoreCase("general"))
if (key.equalsIgnoreCase("general")) {
continue;
}
ConfigurationNode configurationNode = objectMap.get(o);
long channelId = configurationNode.getLong();
serverChannelId.put(key.toLowerCase(), channelId);
@@ -471,7 +537,9 @@ public final class Config {
}
public static String HELP_REPORT = "<red>/report <message></red>";
public static String FIRST_JOIN = "<green>* Welcome <light_purple><player></light_purple> to Altitude! They've joined for the first time.</green>";
public static String FIRST_JOIN =
"<green>* Welcome <light_purple><player></light_purple> to Altitude! They've joined for the first time.</green>";
private static void loadMessages() {
HELP_REPORT = getString("settings.mail.mail-sent", HELP_REPORT);
FIRST_JOIN = getString("settings.first-join.message", FIRST_JOIN);
@@ -479,7 +547,8 @@ 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);
EMOTELIST_ITEM = getString("commands.emotelist.item", EMOTELIST_ITEM);
@@ -490,31 +559,42 @@ public final class Config {
public static String NICK_CHANGED = "<yellow>Your nickname was changed to <nickname><yellow>.";
public static String NICK_NOT_CHANGED = "<yellow>Your nickname request was denied.";
public static String NICK_RESET = "<yellow>Nickname changed back to normal.";
public static String NICK_CHANGED_OTHERS = "<gold><targetplayer><yellow>'s nickname was changed to <nickname><yellow>.";
public static String NICK_TARGET_NICK_CHANGE = "<yellow>Your nickname was changed to <nickname> <yellow>by <sendernick><yellow>";
public static String NICK_CHANGED_OTHERS =
"<gold><targetplayer><yellow>'s nickname was changed to <nickname><yellow>.";
public static String NICK_TARGET_NICK_CHANGE =
"<yellow>Your nickname was changed to <nickname> <yellow>by <sendernick><yellow>";
public static String NICK_RESET_OTHERS = "<gold><player><gold>'s <yellow>nickname was reset back to normal.";
public static String NICK_INVALID_CHARACTERS = "<yellow>You can only use letters and numbers in nicknames.";
public static String NICK_INVALID_LENGTH = "<yellow>Nicknames need to be between 3 to 16 characters long.";
public static String NICK_PLAYER_NOT_ONLINE = "<red>That player is not online.";
public static String NICK_BLOCKED_COLOR_CODES = "<yellow>You have blocked color codes in that nickname.";
public static String NICK_USER_NOT_FOUND = "<red>Failed to set nickname from player, try again from a server this player has been on before.";
public static String NICK_ACCEPTED = "<green>You accepted <targetplayer><green>'s nickname. They are now called <newnick><green>.";
public static String NICK_DENIED = "<green>You denied <targetplayer><green>'s nickname. They are still called <oldnick><green>.";
public static String NICK_USER_NOT_FOUND =
"<red>Failed to set nickname from player, try again from a server this player has been on before.";
public static String NICK_ACCEPTED =
"<green>You accepted <targetplayer><green>'s nickname. They are now called <newnick><green>.";
public static String NICK_DENIED =
"<green>You denied <targetplayer><green>'s nickname. They are still called <oldnick><green>.";
public static String NICK_ALREADY_HANDLED = "<red><targetplayer><red>'s nickname was already accepted or denied.";
public static String NICK_NO_LUCKPERMS = "<red>Due to an issue with LuckPerms /nick try won't work at the moment.";
public static String NICK_TOO_SOON = "<red>Please wait <time><red> until requesting a new nickname";
public static String NICK_REQUEST_PLACED = "<green>Replaced your previous request <oldrequestednick><green> with <newrequestednick><green>.";
public static String NICK_REQUEST_PLACED =
"<green>Replaced your previous request <oldrequestednick><green> with <newrequestednick><green>.";
public static String NICK_REQUEST_NEW = "<green>New nickname request by <player><green>!";
public static String NICK_TRYOUT = "<white><prefix><white> <nick><gray>: <white><click:suggest_command:/nick request <nickrequest>>Hi, this is what my new nickname could look like! Click this message to request.";
public static String NICK_REQUESTED = "<green>Your requested to be nicknamed <nick><green> has been received. Staff will accept or deny this request asap!";
public static String NICK_TRYOUT =
"<white><prefix><white> <nick><gray>: <white><click:suggest_command:/nick request <nickrequest>>Hi, this is what my new nickname could look like! Click this message to request.";
public static String NICK_REQUESTED =
"<green>Your requested to be nicknamed <nick><green> has been received. Staff will accept or deny this request asap!";
public static String NICK_REVIEW_WAITING = "<green>There are <amount> nicknames waiting for review!";
public static String NICK_TAKEN = "<red>Someone else already has this nickname, or has this name as their username.";
public static String NICK_TAKEN =
"<red>Someone else already has this nickname, or has this name as their username.";
public static String NICK_REQUESTS_ON_LOGIN = "<green>Current nick requests: <amount>";
public static long NICK_WAIT_TIME = 86400000;
public static List<String> NICK_ITEM_LORE = new ArrayList<>();
public static List<String> NICK_BLOCKED_COLOR_CODESLIST = new ArrayList<>();
public static List<String> NICK_ALLOWED_COLOR_CODESLIST = new ArrayList<>();
public static String NICK_CURRENT = "<gold>Current nickname: <nickname><white>(<insert:\"<currentnickname>\"><currentnickname></insert>)";
public static String NICK_CURRENT =
"<gold>Current nickname: <nickname><white>(<insert:\"<currentnickname>\"><currentnickname></insert>)";
private static void nicknameSettings() {
NICK_CHANGED = getString("nicknames.messages.nick-changed", NICK_CHANGED);
NICK_NOT_CHANGED = getString("nicknames.messages.nick-not-changed", NICK_NOT_CHANGED);
@@ -540,9 +620,17 @@ public final class Config {
NICK_TAKEN = getString("nicknames.messages.nick-taken", NICK_TAKEN);
NICK_REQUESTS_ON_LOGIN = getString("nicknames.messages.nick-reauests-on-login", NICK_REQUESTS_ON_LOGIN);
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"));
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"
)
);
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"));
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"
)
);
NICK_CURRENT = getString("nicknames.messages.nick-current", NICK_CURRENT);
}
@@ -551,7 +639,8 @@ public final class Config {
private static void deathMessagesSettings() {
DEATH_MESSAGES_MAX_PER_PERIOD = getInt("death-messages.max-per-period", DEATH_MESSAGES_MAX_PER_PERIOD);
DEATH_MESSAGES_LIMIT_PERIOD_MINUTES = getInt("death-messages.limit-period-minutes", DEATH_MESSAGES_LIMIT_PERIOD_MINUTES);
DEATH_MESSAGES_LIMIT_PERIOD_MINUTES =
getInt("death-messages.limit-period-minutes", DEATH_MESSAGES_LIMIT_PERIOD_MINUTES);
}
public static long CHAT_LOG_DELETE_OLDER_THAN_DAYS = 31;
@@ -561,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,
INDEX idx_time_stamp (time_stamp)
)
""";
try (PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(nicknamesTableQuery)) {
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 (?, ?, ?, ?, ?)";
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.createTransactionConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery);
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);
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 {
addMessagesToBatch(chatMessages, preparedStatement);
int[] updatedRowsCount = preparedStatement.executeBatch();
boolean isSuccess = Arrays.stream(updatedRowsCount)
.allMatch(i -> i >= 0 || i == Statement.SUCCESS_NO_INFO);
if (isSuccess) {
connection.commit();
return true;
}
if (isSuccess) {
connection.commit();
return true;
} else {
connection.rollback();
ALogger.warn("Failed to store messages");
return false;
} catch (BatchUpdateException exception) {
connection.rollback();
ALogger.error(
"Failed to store chat batch. Trying messages individually.",
exception
);
}
} catch (SQLException sqlException) {
ALogger.error("Failed to store chat messages", sqlException);
throw new CompletionException("Failed to store chat messages", sqlException);
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();
}
}
}
private static boolean storeMessagesIndividually(
Connection connection,
PreparedStatement preparedStatement,
HashMap<UUID, List<ChatLog>> chatMessages
) throws SQLException {
boolean storedAny = false;
for (List<ChatLog> chatLogList : chatMessages.values()) {
Iterator<ChatLog> iterator = chatLogList.iterator();
while (iterator.hasNext()) {
ChatLog chatLog = iterator.next();
if (storeMessage(preparedStatement, chatLog)) {
storedAny = true;
} else {
iterator.remove();
}
}
}
connection.commit();
return storedAny;
}
private static boolean storeMessage(
PreparedStatement preparedStatement,
ChatLog chatLog
) {
try {
preparedStatement.clearParameters();
chatLog.prepareStatement(preparedStatement);
preparedStatement.executeUpdate();
return true;
} catch (SQLException exception) {
logInvalidMessage(chatLog, exception);
return false;
}
}
private static void logInvalidMessage(
ChatLog chatLog,
SQLException exception
) {
ALogger.error(
"Failed to store chat message. Removing offending message: " + chatLog,
exception
);
}
public static @NotNull CompletableFuture<List<ChatLog>> retrieveMessages(ChatLogHandler chatLogHandler, UUID uuid, Duration duration, String server) {
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 {
@@ -45,14 +45,16 @@ public class DatabaseConnection {
}
connection = DriverManager.getConnection(
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true"+
"&useSSL=false",
Config.USERNAME, Config.PASSWORD);
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true" +
"&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);
"jdbc:mysql://" + Config.IP + ":" + Config.PORT + "/" + Config.DATABASE + "?autoReconnect=true" +
"&useSSL=false&preserveInstants=true",
Config.USERNAME, Config.PASSWORD
);
connection.setAutoCommit(false);
return connection;
}
@@ -88,4 +92,4 @@ public class DatabaseConnection {
return connection != null;
}
}
}
@@ -4,6 +4,9 @@ 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;
@@ -13,20 +16,20 @@ import java.util.stream.Collectors;
public class ChatUser {
private final UUID uuid; // player uuid
private int partyId; // the party they are in
private Channel toggledChannel;
private final Channel toggledChannel;
private String name; // the nickname, doesn't need to be saved with the chatuser object, could be saved but we can get it from the nicknamesview
private Component displayName; // the nickname, doesn't need to be saved with the chatuser object, could be saved but we can get it from the nicknamesview
// private Component prefix; // doesn't need saving, we get this from luckperms
// private Component staffPrefix; // doesn't need saving, we get this from luckperms
// private Component prefixAll; // doesn't need saving, we get this from luckperms
private ComponentLike displayName; // the nickname, doesn't need to be saved with the chatuser object, could be saved but we can get it from the nicknamesview
// private Component prefix; // doesn't need saving, we get this from luckperms
// private Component staffPrefix; // doesn't need saving, we get this from luckperms
// private Component prefixAll; // doesn't need saving, we get this from luckperms
//private boolean toggleGc; // should be saved, this toggles if the player can see and use global chat
private String replyTarget; // reply target for use in /msg i don't mind setting this to null on login, feedback?
private String replyContinueTarget; // reply target for use in /c
private long gcCooldown; // the time when they last used gc, is used for the cooldown, i wouldn't save this, but setting this to the login time means they can't use gc for 30 seconds after logging in
private boolean spy;
private List<Mail> mails; // mails aren't finalized yet, so for now a table sender, reciever, sendtime, readtime(if emtpy mail isn't read yet?, could also do a byte to control this), the actual message
private List<UUID> ignoredPlayers; // a list of UUID, a new table non unique, where one is is the player select * from ignores where ignoredby = thisplayer? where the result is the uuid of the player ignored by this player?
private List<UUID> ignoredBy; // a list of UUID, same table as above but select * from ignores where ignored = thisplayer? result should be the other user that ignored this player?
private final List<Mail> mails; // mails aren't finalized yet, so for now a table sender, reciever, sendtime, readtime(if emtpy mail isn't read yet?, could also do a byte to control this), the actual message
private final List<UUID> ignoredPlayers; // a list of UUID, a new table non unique, where one is is the player select * from ignores where ignoredby = thisplayer? where the result is the uuid of the player ignored by this player?
private final List<UUID> ignoredBy; // a list of UUID, same table as above but select * from ignores where ignored = thisplayer? result should be the other user that ignored this player?
private boolean isMuted;
public ChatUser(UUID uuid, int partyId, Channel toggledChannel) {
@@ -40,10 +43,10 @@ public class ChatUser {
}
setDisplayName(name);
// prefix = Utility.getPrefix(uuid, true); // TODO we need to update this, so cache and update when needed or always request it?
// staffPrefix = Utility.getStaffPrefix(uuid);
//
// prefixAll = Utility.getPrefix(uuid, false);
// prefix = Utility.getPrefix(uuid, true); // TODO we need to update this, so cache and update when needed or always request it?
// staffPrefix = Utility.getStaffPrefix(uuid);
//
// prefixAll = Utility.getPrefix(uuid, false);
replyTarget = "";
replyContinueTarget = "";
@@ -71,12 +74,7 @@ public class ChatUser {
return toggledChannel;
}
public void setToggledChannel(Channel channel) {
toggledChannel = channel;
Queries.setToggledChannel(toggledChannel, uuid); //TODO: Async pls - no CompleteableFuture<>!
}
public Component getDisplayName() {
public ComponentLike getDisplayName() {
return displayName;
}
@@ -84,19 +82,25 @@ public class ChatUser {
this.displayName = Utility.applyColor(displayName);
}
public Component getPrefix() {
public ComponentLike getPrefix() {
//return prefix;
return Utility.getPrefix(uuid, true); // No longer cache this data
}
public Component getStaffPrefix() {
public ComponentLike getStaffPrefix() {
//return staffPrefix;
return Utility.getStaffPrefix(uuid);
}
public Component 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() {
@@ -145,10 +149,6 @@ public class ChatUser {
return ignoredBy;
}
public void addIgnoredBy(UUID uuid) {
ignoredBy.add(uuid);
}
public long getGcCooldown() {
return gcCooldown;
}
@@ -4,9 +4,9 @@ import com.alttd.chat.config.Config;
import com.alttd.chat.managers.RegexManager;
import com.alttd.chat.util.Utility;
import net.kyori.adventure.text.Component;
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 java.util.HashMap;
import java.util.Map;
@@ -16,7 +16,7 @@ public class EmoteList {
public static final Map<UUID, EmoteList> emoteLists = new HashMap<>();
public static final int pageSize = 7;
// public static int pages = RegexManager.getEmoteFilters().size() / pageSize;// TODO reload this when config is reloaded.
// public static int pages = RegexManager.getEmoteFilters().size() / pageSize;// TODO reload this when config is reloaded.
public static EmoteList getEmoteList(UUID uuid) {
synchronized (emoteLists) {
@@ -25,7 +25,8 @@ public class EmoteList {
}
private int page;
public Component showEmotePage() {
public ComponentLike showEmotePage() {
int startIndex = page * pageSize;
int pages = RegexManager.getEmoteFilters().size() / pageSize;
int endIndex = Math.min(startIndex + pageSize, RegexManager.getEmoteFilters().size());
@@ -33,7 +34,7 @@ public class EmoteList {
Placeholder.unparsed("page", String.valueOf(page)),
Placeholder.unparsed("pages", String.valueOf(pages))
);
Component list = Utility.parseMiniMessage(Config.EMOTELIST_HEADER, placeholders);
Component list = Utility.parseMiniMessage(Config.EMOTELIST_HEADER, placeholders).asComponent();
for (int i = startIndex; i < endIndex; i++) {
ChatFilter emote = RegexManager.getEmoteFilters().get(i);
@@ -56,9 +57,7 @@ public class EmoteList {
}
public void prevPage() {
this.page -= 1;
this.page = Math.max(page - 1, 0);
}
}
@@ -3,10 +3,6 @@ package com.alttd.chat.objects;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.intellij.lang.annotations.RegExp;
import javax.annotation.RegEx;
import java.util.regex.Pattern;
public class ModifiableString {
private Component text;
@@ -1,21 +1,21 @@
package com.alttd.chat.objects;
import com.alttd.chat.util.Utility;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import java.util.UUID;
public class PartyUser {
protected UUID uuid;
protected Component displayName;
protected ComponentLike displayName;
protected String playerName;
public PartyUser(UUID uuid, String displayName, String playerName) {
this(uuid, Utility.applyColor(displayName), playerName);
}
public PartyUser(UUID uuid, Component displayName, String playerName) {
public PartyUser(UUID uuid, ComponentLike displayName, String playerName) {
this.uuid = uuid;
this.displayName = displayName;
this.playerName = playerName;
@@ -25,7 +25,7 @@ public class PartyUser {
return uuid;
}
public Component getDisplayName() {
public ComponentLike getDisplayName() {
return displayName;
}
@@ -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,8 +20,9 @@ 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,44 +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;
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) {
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 static Channel getChatChannel(String channelName) {
return channels.get(channelName.toLowerCase());
}
@@ -1,20 +1,19 @@
package com.alttd.chat.objects.channels;
import java.util.*;
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, boolean proxy) {
super(channelName, format, proxy);
this.permission = "chat.channel." + channelName.toLowerCase();
this.channelName = channelName;
this.format = format;
public CustomChannel(String channelName, String format, List<String> servers, List<String> aliases, boolean proxy,
boolean local, boolean web) {
super(channelName, format, proxy, local, web);
this.servers = servers;
this.proxy = proxy;
channels.put(channelName.toLowerCase(), this);
}
public List<String> getServers() {
return servers;
this.aliases = aliases;
}
}
@@ -1,7 +1,7 @@
package com.alttd.chat.objects.channels;
public abstract class DefaultChannel extends Channel{
public abstract class DefaultChannel extends Channel {
public DefaultChannel(String channelName, String format, boolean proxy) {
super(channelName, format, proxy);
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;
}
@@ -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);
};
}
}
@@ -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;
}
@@ -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;
}
}
@@ -3,33 +3,33 @@ package com.alttd.chat.util;
import com.alttd.chat.ChatAPI;
import com.alttd.chat.config.Config;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.TextDecoration;
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.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
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 org.bukkit.permissions.Permission;
import net.luckperms.api.query.QueryOptions;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class Utility {
private static final List<String> EMPTY_LIST = new ArrayList<>();
static final Pattern DEFAULT_URL_PATTERN = Pattern.compile("(?:(https?)://)?([-\\w_.]+\\.\\w{2,})(/\\S*)?");
static final Pattern URL_SCHEME_PATTERN = Pattern.compile("^[a-z][a-z0-9+\\-.]*:");
static final Pattern DEFAULT_URL_PATTERN = Pattern.compile("(https?://)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)");
private static MiniMessage miniMessage = null;
public static String stringRegen = "\\{#[A-Fa-f0-9]{6}(<)?(>)?}";
public static HashMap<String, String> colors;
private static LegacyComponentSerializer legacySerializer;
static { // this might be in minimessage already?
colors = new HashMap<>();
colors.put("&0", "<black>");
@@ -49,41 +49,44 @@ public class Utility {
colors.put("&e", "<yellow>");
colors.put("&f", "<white>");
}
public static HashMap<String, Pair<TagResolver, List<String>>> formattingPerms = new HashMap<>();
static {
formattingPerms.put("chat.format.color",
new Pair<>(StandardTags.color(), colors.values().stream().toList()));
new Pair<>(StandardTags.color(), colors.values().stream().toList()));
formattingPerms.put("chat.format.bold",
new Pair<>(StandardTags.decorations(TextDecoration.BOLD), List.of("<bold>", "<b>")));
new Pair<>(StandardTags.decorations(TextDecoration.BOLD), List.of("<bold>", "<b>")));
formattingPerms.put("chat.format.italic",
new Pair<>(StandardTags.decorations(TextDecoration.ITALIC), List.of("<italic>", "<i>")));
new Pair<>(StandardTags.decorations(TextDecoration.ITALIC), List.of("<italic>", "<i>")));
formattingPerms.put("chat.format.underlined",
new Pair<>(StandardTags.decorations(TextDecoration.UNDERLINED), List.of("<underlined>", "<u>")));
new Pair<>(StandardTags.decorations(TextDecoration.UNDERLINED), List.of("<underlined>", "<u>")));
formattingPerms.put("chat.format.strikethrough",
new Pair<>(StandardTags.decorations(TextDecoration.STRIKETHROUGH), List.of("<strikethrough>", "<st>")));
new Pair<>(StandardTags.decorations(TextDecoration.STRIKETHROUGH), List.of("<strikethrough>", "<st>")));
formattingPerms.put("chat.format.obfuscated",
new Pair<>(StandardTags.decorations(TextDecoration.OBFUSCATED), List.of("<obfuscated>", "<obf>")));
new Pair<>(StandardTags.decorations(TextDecoration.OBFUSCATED), List.of("<obfuscated>", "<obf>")));
formattingPerms.put("chat.format.gradient",
new Pair<>(StandardTags.gradient(), EMPTY_LIST));
new Pair<>(StandardTags.gradient(), EMPTY_LIST));
formattingPerms.put("chat.format.font",
new Pair<>(StandardTags.font(), EMPTY_LIST));
new Pair<>(StandardTags.font(), EMPTY_LIST));
formattingPerms.put("chat.format.rainbow",
new Pair<>(StandardTags.rainbow(), List.of("<rainbow>")));
new Pair<>(StandardTags.rainbow(), List.of("<rainbow>")));
formattingPerms.put("chat.format.hover",
new Pair<>(StandardTags.hoverEvent(), EMPTY_LIST));
new Pair<>(StandardTags.hoverEvent(), EMPTY_LIST));
formattingPerms.put("chat.format.click",
new Pair<>(StandardTags.clickEvent(), EMPTY_LIST));
new Pair<>(StandardTags.clickEvent(), EMPTY_LIST));
formattingPerms.put("chat.format.transition",
new Pair<>(StandardTags.transition(), EMPTY_LIST));
new Pair<>(StandardTags.transition(), EMPTY_LIST));
formattingPerms.put("chat.format.reset",
new Pair<>(StandardTags.reset(), List.of("<reset>", "<r>")));
new Pair<>(StandardTags.reset(), List.of("<reset>", "<r>")));
formattingPerms.put("chat.format.newline",
new Pair<>(StandardTags.newline(), List.of("<newline>")));
new Pair<>(StandardTags.newline(), List.of("<newline>")));
}
public static String parseColors(String message) {
if (message == null) return "";
if (message == null) {
return "";
}
// split string in sections and check those vs looping hashmap?:/
// think this is better, but will check numbers on this
for (String key : colors.keySet()) {
@@ -94,15 +97,17 @@ public class Utility {
return message;
}
public static Component getPrefix(UUID uuid, boolean single) {
public static ComponentLike getPrefix(UUID uuid, boolean single) {
StringBuilder prefix = new StringBuilder();
LuckPerms luckPerms = ChatAPI.get().getLuckPerms();
User user = luckPerms.getUserManager().getUser(uuid);
List<String> prefixGroups = Config.PREFIXGROUPS;
if(user == null) return Component.empty();
if(!single) {
if (user == null) {
return Component.empty();
}
if (!single) {
Collection<Group> inheritedGroups = user.getInheritedGroups(user.getQueryOptions());
if(inheritedGroups.stream().map(Group::getName).collect(Collectors.toList()).contains("eventleader")) {
if (inheritedGroups.stream().anyMatch(group -> group.getName().equals("eventleader"))) {
prefixGroups.remove("eventteam"); // hardcoded for now, new prefix system would load this from config
}
inheritedGroups.stream()
@@ -114,21 +119,23 @@ public class Utility {
});
}
prefix.append(getUserPrefix(user));
// prefix.append(user.getCachedData().getMetaData().getPrefix());
return applyColor(prefix.toString());
}
public static Component getStaffPrefix(UUID uuid) {
public static ComponentLike getStaffPrefix(UUID uuid) {
StringBuilder prefix = new StringBuilder();
LuckPerms luckPerms = ChatAPI.get().getLuckPerms();
User user = luckPerms.getUserManager().getUser(uuid);
if(user == null) return Component.empty();
if(user.getCachedData().getPermissionData().checkPermission("group." + Config.MINIMIUMSTAFFRANK).asBoolean()) {
if (user == null) {
return Component.empty();
}
if (user.getCachedData().getPermissionData().checkPermission("group." + Config.MINIMIUMSTAFFRANK).asBoolean()) {
Group group = luckPerms.getGroupManager().getGroup(user.getPrimaryGroup());
if(group != null)
if (group != null) {
prefix.append(getGroupPrefix(group));
// prefix.append(group.getCachedData().getMetaData().getPrefix());
}
// prefix.append(group.getCachedData().getMetaData().getPrefix());
}
return applyColor(prefix.toString());
}
@@ -139,26 +146,32 @@ public class Utility {
if (group == null) {
return "";
}
return ChatAPI.get().getPrefixes().get(group.getName()).replace("<prefix>", user.getCachedData().getMetaData().getPrefix());
}
public static String getGroupPrefix(String groupName) {
Group group = ChatAPI.get().getLuckPerms().getGroupManager().getGroup(groupName);
if (group == null) {
String prefix = user.getCachedData().getMetaData().getPrefix();
if (prefix == null) {
ALogger.warn("User " + user.getUsername() + " has no prefix set!");
return "";
}
return getGroupPrefix(group);
return ChatAPI.get().getPrefixes().get(group.getName()).replace("<prefix>", prefix);
}
public static String getGroupPrefix(Group group) {
return ChatAPI.get().getPrefixes().get(group.getName()).replace("<prefix>", group.getCachedData().getMetaData().getPrefix());
String prefix = group.getCachedData().getMetaData().getPrefix();
if (prefix == null) {
ALogger.warn("Group " + group.getName() + " has no prefix set!");
return "";
}
return ChatAPI.get().getPrefixes().get(group.getName()).replace("<prefix>", prefix);
}
public static String getDisplayName(UUID uuid, String playerName) {
if (!playerName.isBlank()) return playerName;
if (!playerName.isBlank()) {
return playerName;
}
LuckPerms luckPerms = ChatAPI.get().getLuckPerms();
User user = luckPerms.getUserManager().getUser(uuid);
if(user == null) return "";
if (user == null) {
return "";
}
return user.getUsername();
}
@@ -166,20 +179,36 @@ public class Utility {
ChatAPI.get().getLuckPerms().getUserManager().modifyUser(uuid, user -> {
// Add the permission
user.data().add(Node.builder(permission)
.value(!user.getCachedData().getPermissionData().checkPermission(permission).asBoolean()).build());
.value(!user.getCachedData().getPermissionData().checkPermission(permission).asBoolean()).build());
});
}
public static boolean hasPermission(UUID uuid, String permission) {
LuckPerms luckPerms = ChatAPI.get().getLuckPerms();
User user = luckPerms.getUserManager().getUser(uuid);
if(user == null) return false;
return user.getCachedData().getPermissionData().checkPermission(permission).asBoolean();
public static CompletableFuture<User> getOrLoadUser(UUID uuid) {
return ChatAPI.get().getLuckPerms().getUserManager().loadUser(uuid);
}
public static Component applyColor(String message) {
public static CompletableFuture<Boolean> hasPermission(UUID uuid, String permission) {
LuckPerms luckPerms = ChatAPI.get().getLuckPerms();
User user = luckPerms.getUserManager().getUser(uuid);
if (user == null) {
return getOrLoadUser(uuid)
.thenApply(loadedUser -> hasPermission(loadedUser, permission))
.exceptionally(throwable -> false);
}
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) {
String hexColor1 = "";
String hexColor2 = "";
String hexColor2;
StringBuilder stringBuilder = new StringBuilder();
message = parseColors(message);
boolean startsWithColor = false;
@@ -197,7 +226,7 @@ public class Utility {
for (String s : split) {
nextIndex += s.length();
int tmp = message.indexOf("}", nextIndex);
if (tmp < message.length() && tmp>=0) {
if (tmp < message.length() && tmp >= 0) {
list.add(message.substring(nextIndex, tmp + 1));
nextIndex = tmp + 1;
}
@@ -221,7 +250,7 @@ public class Utility {
} else if (bigger || lesser) {
hexColor2 = s.substring(1, s.length() - 2);
} else {
hexColor2 = s.substring(1, s.length() -1);
hexColor2 = s.substring(1, s.length() - 1);
}
if (firstLoop) {
@@ -242,12 +271,12 @@ public class Utility {
lastColorMatters = bigger;
i++;
}
if (split.length > i){
if (split.length > i) {
stringBuilder.append("<").append(hexColor1).append(">").append(split[i]);
}
}
return stringBuilder.length() == 0 ? Utility.parseMiniMessage(message)
: Utility.parseMiniMessage(stringBuilder.toString());
return stringBuilder.isEmpty() ? Utility.parseMiniMessage(message)
: Utility.parseMiniMessage(stringBuilder.toString());
}
public static boolean checkNickBrightEnough(String nickname) {
@@ -292,35 +321,29 @@ public class Utility {
}
public static String formatText(String message) {
/*
.match(pattern)
.replacement(url -> {
String clickUrl = url.content();
if (!URL_SCHEME_PATTERN.matcher(clickUrl).find()) {
clickUrl = "http://" + clickUrl;
}
return (style == null ? url : url.style(style)).clickEvent(ClickEvent.openUrl(clickUrl));
})
.build();
*/
Matcher matcher = DEFAULT_URL_PATTERN.matcher(message);
while (matcher.find()) {
String url = matcher.group();
String clickUrl = url;
String urlFormat = Config.URLFORMAT;
// if (!URL_SCHEME_PATTERN.matcher(clickUrl).find()) {
// clickUrl = "http://" + clickUrl;
// }
message = message.replace(url, urlFormat.replaceAll("<url>", url).replaceAll("<clickurl>", clickUrl));
message = message.replace(url, urlFormat
.replaceAll("<url>", "<u>" + url + "</u>")
.replaceAll("<clickurl>", formatUrl(url)));
}
return message;
}
public static Component parseMiniMessage(String message) {
private static String formatUrl(String url) {
if (url.startsWith("http://") || url.startsWith("https://")) {
return url;
}
return "https://" + url;
}
public static ComponentLike parseMiniMessage(String message) {
return getMiniMessage().deserialize(message);
}
public static Component parseMiniMessage(String message, TagResolver placeholders) {
public static ComponentLike parseMiniMessage(String message, TagResolver placeholders) {
if (placeholders == null) {
return getMiniMessage().deserialize(message);
} else {
@@ -328,7 +351,7 @@ public class Utility {
}
}
public static Component parseMiniMessage(String message, TagResolver ... placeholders) {
public static ComponentLike parseMiniMessage(String message, TagResolver... placeholders) {
if (placeholders == null) {
return getMiniMessage().deserialize(message);
} else {
@@ -341,7 +364,9 @@ public class Utility {
}
public static MiniMessage getMiniMessage() {
if (miniMessage == null) miniMessage = MiniMessage.miniMessage();
if (miniMessage == null) {
miniMessage = MiniMessage.miniMessage();
}
return miniMessage;
}
@@ -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;
}
+4 -3
View File
@@ -1,6 +1,7 @@
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"
}
allprojects {
@@ -14,7 +15,7 @@ subprojects {
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
languageVersion.set(JavaLanguageVersion.of(25))
}
}
@@ -67,4 +68,4 @@ tasks {
jar {
enabled = false
}
}
}
+13 -6
View File
@@ -1,15 +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:Galaxy-API:1.21-R0.1-SNAPSHOT") // Galaxy
compileOnly("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
compileOnly("com.gitlab.ruany:LiteBansAPI:0.3.5") // move to proxy
compileOnly("org.apache.commons:commons-lang3:3.12.0") // needs an alternative, already removed from upstream api and will be removed in server
compileOnly("net.luckperms:api:5.3") // Luckperms
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,37 +15,50 @@ 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.command.PluginCommand;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.List;
import java.util.Objects;
@Slf4j
public class ChatPlugin extends JavaPlugin {
@Getter
private static ChatPlugin instance;
private ChatAPI chatAPI;
@Getter
private ChatHandler chatHandler;
private String messageChannel;
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(Bukkit.getServer().getName());
ChatLogHandler chatLogHandler = ChatLogHandler.getInstance(true);
registerListener(new PlayerListener(serverConfig), new ChatListener(chatLogHandler), new BookListener(), new ShutdownListener(chatLogHandler, this));
if(serverConfig.GLOBALCHAT) {
serverConfig = new ServerConfig(ServerName.getServerName());
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());
}
@@ -53,26 +70,49 @@ public class ChatPlugin extends JavaPlugin {
registerCommand("muteserver", new MuteServer());
registerCommand("spy", new Spy());
registerCommand("chatclear", new ChatClear());
// registerCommand("chatparty", new ChatParty());
registerCommand("p", new PartyChat());
registerCommand("emotes", new Emotes());
for (Channel channel : Channel.getChannels()) {
if (!(channel instanceof CustomChannel customChannel)) continue;
this.getServer().getCommandMap().register(channel.getChannelName().toLowerCase(), new ChatChannel(customChannel));
if (!(channel instanceof CustomChannel customChannel)) {
continue;
}
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));
}
}
messageChannel = Config.MESSAGECHANNEL;
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;
}
@@ -83,25 +123,7 @@ public class ChatPlugin extends JavaPlugin {
}
public void registerCommand(String commandName, CommandExecutor commandExecutor) {
getCommand(commandName).setExecutor(commandExecutor);
}
public void registerCommand(String commandName, CommandExecutor commandExecutor, List<String> aliases) {
PluginCommand command = getCommand(commandName);
command.setAliases(aliases);
command.setExecutor(commandExecutor);
}
public static ChatPlugin getInstance() {
return instance;
}
public ChatAPI getChatAPI() {
return chatAPI;
}
public ChatHandler getChatHandler() {
return chatHandler;
Objects.requireNonNull(getCommand(commandName)).setExecutor(commandExecutor);
}
public boolean serverGlobalChatEnabled() {
@@ -116,11 +138,13 @@ public class ChatPlugin extends JavaPlugin {
serverConfig.MUTED = !serverConfig.MUTED;
}
public void ReloadConfig() {
chatAPI.ReloadConfig();
chatAPI.ReloadChatFilters();
serverConfig = new ServerConfig(Bukkit.getServer().getName());
Bukkit.broadcast(Utility.parseMiniMessage("Reloaded ChatPlugin Config."), "command.chat.reloadchat");
public void reloadConfig() {
chatAPI.reloadConfig();
chatAPI.reloadChatFilters();
serverConfig = new ServerConfig(ServerName.getServerName());
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,46 +2,60 @@ 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;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ChatChannel extends BukkitCommand {
CustomChannel channel;
String command;
ToggleableForCustomChannel toggleableForCustomChannel;
private static List<ChatChannel> activeCommands = new ArrayList<>();
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() + ".";
this.usageMessage = "/" + command + " <message>";
this.setAliases(Collections.emptyList());
this.setAliases(channel.getAliases());
activeCommands.add(this);
this.toggleableForCustomChannel = new ToggleableForCustomChannel(channel);
}
@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String command, @NotNull String[] args) {
if(!(sender instanceof Player player)) { // must be a player
if (!(sender instanceof Player player)) { // must be a player
return true;
}
if(args.length == 0 && player.hasPermission(channel.getPermission())) {
if (args.length == 0 && player.hasPermission(channel.getPermission())) {
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)));
Placeholder.unparsed("channel", channel.getChannelName()),
Placeholder.component("status", toggleableForCustomChannel.toggle(player.getUniqueId())
? Config.TOGGLED_ON : Config.TOGGLED_OFF
)
)
);
return false;
}
@@ -51,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());
});
}
}
}
@@ -1,6 +1,5 @@
package com.alttd.chat.commands;
import com.alttd.chat.util.Utility;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
@@ -9,26 +8,29 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class ChatClear implements CommandExecutor {
private static final Component component = MiniMessage.miniMessage().deserialize("\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n");
private static final Component component = MiniMessage.miniMessage().deserialize("\n".repeat(100));
MiniMessage miniMessage = MiniMessage.miniMessage();
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!sender.hasPermission("chat.command.clear-chat")) {
sender.sendMessage(Utility.parseMiniMessage("<red>You don't have permission to use this command.</red>"));
sender.sendRichMessage("<red>You don't have permission to use this command.</red>");
return true;
}
for (Player player : Bukkit.getOnlinePlayers())
if (!player.hasPermission("chat.clear-bypass"))
for (Player player : Bukkit.getOnlinePlayers()) {
if (!player.hasPermission("chat.clear-bypass")) {
player.sendMessage(component);
}
}
Bukkit.getServer().sendMessage(miniMessage.deserialize(
"<gold><player> cleared chat.</gold>",
Placeholder.component("player",sender.name()))
);
"<gold><player> cleared chat.</gold>",
Placeholder.component("player", sender.name()))
);
return true;
}
}
@@ -8,17 +8,22 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class Continue implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player player)) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
return true;
}
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
if (user.getReplyContinueTarget() == null) return false;
if(args.length == 0) return false; // todo error message or command info
if (user.getReplyContinueTarget() == null) {
return false;
}
if (args.length == 0) {
return false; // todo error message or command info
}
String message = StringUtils.join(args, " ", 0, args.length);
ChatPlugin.getInstance().getChatHandler().continuePrivateMessage(player, user.getReplyContinueTarget(), message);
@@ -7,15 +7,18 @@ 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;
public class GlobalChat implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player player)) { // must be a player
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) { // must be a player
return true;
}
if(args.length == 0) return false;
if (args.length == 0) {
return false;
}
String message = StringUtils.join(args, " ", 0, args.length);
@@ -5,19 +5,17 @@ import com.alttd.chat.config.Config;
import com.alttd.chat.database.Queries;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.util.Utility;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
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.HashSet;
import java.util.List;
import java.util.UUID;
public class Ignore implements CommandExecutor {
@@ -29,11 +27,13 @@ public class Ignore implements CommandExecutor {
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player player)) { // must be a player
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
return true;
}
if(args.length > 1) return false; // todo error message or command info
if (args.length > 1) {
return false; // todo error message or command info
}
String targetName = args[0];
if (targetName.equals("?")) {
new BukkitRunnable() {
@@ -44,7 +44,7 @@ public class Ignore implements CommandExecutor {
StringBuilder ignoredMessage = new StringBuilder();
if (userNames.isEmpty()) {
player.sendMessage(Utility.parseMiniMessage("You don't have anyone ignored!")); //TODO load from config
player.sendRichMessage("You don't have anyone ignored!"); //TODO load from config
return;
}
@@ -52,21 +52,21 @@ public class Ignore implements CommandExecutor {
userNames.forEach(username -> ignoredMessage.append(username).append("\n"));
ignoredMessage.delete(ignoredMessage.length() - 1, ignoredMessage.length());
player.sendMessage(Utility.parseMiniMessage(ignoredMessage.toString()));
player.sendRichMessage(ignoredMessage.toString());
}
}.runTaskAsynchronously(plugin);
return false;
}
Player targetPlayer = Bukkit.getPlayer(targetName);
if(targetPlayer == null) { // can't ignore offline players
if (targetPlayer == null) { // can't ignore offline players
sender.sendMessage("You can't ignore offline players");
//sender.sendMessage("Target not found..."); // TODO load from config and minimessage
return false;
}
UUID target = targetPlayer.getUniqueId();
if(targetPlayer.hasPermission("chat.ignorebypass") || target.equals(player.getUniqueId())) {
if (targetPlayer.hasPermission("chat.ignorebypass") || target.equals(player.getUniqueId())) {
sender.sendMessage("You can't ignore this player"); // TODO load from config and minimessage
return false;
}
@@ -74,9 +74,9 @@ public class Ignore implements CommandExecutor {
@Override
public void run() {
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
if(!chatUser.getIgnoredPlayers().contains(target)) {
if (!chatUser.getIgnoredPlayers().contains(target)) {
chatUser.addIgnoredPlayers(target);
Queries.ignoreUser(((Player) sender).getUniqueId(), target);
Queries.ignoreUser(player.getUniqueId(), target);
sender.sendMessage("You have ignored " + targetName + "."); // TODO load from config and minimessage
sendPluginMessage("ignore", player, target);
} else {
@@ -8,15 +8,18 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class Message implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player player)) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
return true;
}
if(args.length < 2) return false; // todo error message or command info
if (args.length < 2) {
return false; // todo error message or command info
}
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
user.setReplyContinueTarget(args[0]);
@@ -3,45 +3,42 @@ package com.alttd.chat.commands;
import com.alttd.chat.ChatPlugin;
import com.alttd.chat.config.Config;
import com.alttd.chat.util.Utility;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.ComponentLike;
import org.bukkit.Bukkit;
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 java.util.UUID;
import org.jetbrains.annotations.NotNull;
public class MuteServer implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player player)) { // must be a player
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
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)) {
sender.sendMessage(Utility.parseMiniMessage("<red>You don't have permission to use this command.</red>"));
return;
}
ChatPlugin.getInstance().toggleServerMuted();
Component component;
if (ChatPlugin.getInstance().serverMuted()) {
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(), player.getName()) + " <red>muted</red><white> chat.");
} else {
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(), player.getName()) + " <green>un-muted</green><white> chat.");
}
Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(component));
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;
}
}.runTaskAsynchronously(ChatPlugin.getInstance());
ChatPlugin.getInstance().toggleServerMuted();
ComponentLike component;
if (ChatPlugin.getInstance().serverMuted()) {
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(),
player.getName()
) + " <red>muted</red><white> chat.");
} else {
component = Utility.parseMiniMessage(Utility.getDisplayName(player.getUniqueId(),
player.getName()
) + " <green>un-muted</green><white> chat.");
}
Bukkit.getOnlinePlayers().forEach(onlinePlayer -> onlinePlayer.sendMessage(component));
});
return false;
}
@@ -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
}
}
@@ -8,18 +8,22 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class Reply implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player)) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
return true;
}
Player player = (Player) sender;
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
if (user.getReplyTarget() == null) return false;
if(args.length == 0) return false; // todo error message or command info
if (user.getReplyTarget() == null) {
return false;
}
if (args.length == 0) {
return false; // todo error message or command info
}
String message = StringUtils.join(args, " ", 0, args.length);
ChatPlugin.getInstance().getChatHandler().privateMessage(player, user.getReplyTarget(), message);
@@ -1,30 +1,29 @@
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.objects.ChatUser;
import com.alttd.chat.util.Utility;
import net.kyori.adventure.text.minimessage.MiniMessage;
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 Spy implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player)) { // must be a player
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
return true;
}
new BukkitRunnable() {
@Override
public void run() {
UUID uuid = ((Player) sender).getUniqueId();
UUID uuid = player.getUniqueId();
ChatUser user = ChatUserManager.getChatUser(uuid);
user.toggleSpy();
sender.sendMessage(Utility.parseMiniMessage("You have turned spy " + (user.isSpy() ? "<green>on." : "<red>off."))); // TODO load from config and minimessage
@@ -1,41 +1,31 @@
package com.alttd.chat.commands;
import com.alttd.chat.ChatPlugin;
import com.alttd.chat.config.Config;
import com.alttd.chat.database.Queries;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.util.Utility;
import jdk.jshell.execution.Util;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.apache.commons.lang3.StringUtils;
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.Objects;
import java.util.UUID;
public class ToggleGlobalChat implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player)) { // must be a player
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
return true;
}
new BukkitRunnable() {
@Override
public void run() {
UUID uuid = ((Player) sender).getUniqueId();
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
//chatUser.toggleGc();
Utility.flipPermission(uuid, Config.GCPERMISSION);
//Queries.setGlobalChatState(chatUser.isGcOn(), chatUser.getUuid());
sender.sendMessage(Utility.parseMiniMessage("You have turned globalchat " + (!Utility.hasPermission(uuid, Config.GCPERMISSION) ? "<green>on." : "<red>off."))); // TODO load from config and minimessage
}
}.runTaskAsynchronously(ChatPlugin.getInstance());
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(user,
Config.GCPERMISSION
) ? "<green>on." : "<red>off.")); // TODO load from config and minimessage
});
return false;
}
@@ -3,21 +3,17 @@ package com.alttd.chat.commands;
import com.alttd.chat.ChatPlugin;
import com.alttd.chat.config.Config;
import com.alttd.chat.database.Queries;
import com.alttd.chat.listeners.PluginMessage;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.objects.ChatUser;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@@ -30,22 +26,20 @@ public class Unignore implements CommandExecutor {
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player player)) { // must be a player
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) { // must be a player
return true;
}
if(args.length > 1) return false; // todo error message or command info
if (args.length > 1) {
return false; // todo error message or command info
}
String targetName = args[0];
UUID target = Bukkit.getOfflinePlayer(targetName).getUniqueId();
if(target == null) {
//sender.sendMessage("Target not found..."); // TODO load from config and minimessage
return false;
}
new BukkitRunnable() {
@Override
public void run() {
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
if(chatUser.getIgnoredPlayers().contains(target)) {
if (chatUser.getIgnoredPlayers().contains(target)) {
chatUser.removeIgnoredPlayers(target);
Queries.unIgnoreUser(player.getUniqueId(), target);
sender.sendMessage("You no longer ignore " + targetName + "."); // TODO load from config and minimessage
@@ -8,75 +8,104 @@ 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;
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.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.*;
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 Component GCNOTENABLED;
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) {
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
// user.setReplyTarget(target);
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)
);
Component component = Utility.parseMiniMessage("<message>", placeholders);
ComponentLike component = Utility.parseMiniMessage("<message>", placeholders);
ModifiableString modifiableString = new ModifiableString(component);
ModifiableString modifiableString = new ModifiableString(component.asComponent());
// todo a better way for this
if(!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "privatemessage")) {
if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "privatemessage")) {
GalaxyUtility.sendBlockedNotification("DM Language",
player,
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
target);
target
);
return; // the message was blocked
}
component = modifiableString.component();
sendPrivateMessage(player, target, "privatemessage", component);
Component 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)) {
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)) {
pl.sendMessage(spymessage);
}
}
}
public void privateMessage(Player player, String target, String message) {
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
// user.setReplyTarget(target);
Utility.getOrLoadUser(player.getUniqueId())
.thenAccept(user -> privateMessage(user, player, target, message));
}
Component messageComponent = parseMessageContent(player, message);
public void privateMessage(User user, Player player, String target, String message) {
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
// user.setReplyTarget(target);
Component messageComponent = parseMessageContent(user, player, message);
TagResolver placeholders = TagResolver.resolver(
Placeholder.component("message", messageComponent),
Placeholder.component("sendername", player.name()),
@@ -85,56 +114,64 @@ public class ChatHandler {
ModifiableString modifiableString = new ModifiableString(messageComponent);
// todo a better way for this
if(!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "privatemessage")) {
if (!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "privatemessage")) {
GalaxyUtility.sendBlockedNotification("DM Language",
player,
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
target);
target
);
return; // the message was blocked
}
messageComponent = modifiableString.component();
// Component component = Utility.parseMiniMessage("<message>", placeholders)
// .replaceText(TextReplacementConfig.builder().once().matchLiteral("[i]").replacement(ChatHandler.itemComponent(player.getInventory().getItemInMainHand())).build());
// Component component = Utility.parseMiniMessage("<message>", placeholders)
// .replaceText(TextReplacementConfig.builder().once().matchLiteral("[i]").replacement(ChatHandler.itemComponent(player.getInventory().getItemInMainHand())).build());
sendPrivateMessage(player, target, "privatemessage", messageComponent);
Component 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)) {
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)) {
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());
if(timeLeft <= Config.GCCOOLDOWN && !player.hasPermission("chat.globalchat.cooldownbypass")) { // player is on cooldown and should wait x seconds
player.sendMessage(Utility.parseMiniMessage(Config.GCONCOOLDOWN, Placeholder.parsed("cooldown", Config.GCCOOLDOWN-timeLeft+"")));
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 + "")
);
return;
}
Component senderName = user.getDisplayName();
Component 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.parsed("server", Bukkit.getServer().getName())
Placeholder.component("message", parseMessageContent(user, player, message)),
Placeholder.parsed("server", ServerName.getServerName())
);
Component component = Utility.parseMiniMessage(Config.GCFORMAT, placeholders);
Component component = Utility.parseMiniMessage(Config.GCFORMAT, placeholders).asComponent();
ModifiableString modifiableString = new ModifiableString(component);
// todo a better way for this
@@ -142,49 +179,76 @@ 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.sendMessage(Utility.parseMiniMessage("<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] ")) return;
if (isMuted(user, offlinePlayer, message, "[" + channel.getChannelName() + " Muted] ")) {
ALogger.info("Refusing to send message by muted user");
return;
}
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
Component 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.parsed("server", Bukkit.getServer().getName()),
Placeholder.component("message", parseMessageContent(user, offlinePlayer, message)),
Placeholder.parsed("server", ServerName.getServerName()),
Placeholder.parsed("channel", channel.getChannelName())
);
Component component = Utility.parseMiniMessage(channel.getFormat(), placeholders);
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())),
"");
return; // the message was blocked
""
);
ALogger.info("Refusing to send blocked chat message");
return;
}
component = modifiableString.component();
if (channel.isProxy()) {
sendChatChannelMessage(player, channel.getChannelName(), "chatchannel", component);
sendChatChannelMessage(offlinePlayer, channel.getChannelName(), "chatchannel", component, message);
} else {
sendChatChannelMessage(channel, player.getUniqueId(), component);
sendChatChannelMessage(channel, offlinePlayer.getUniqueId(), component, message);
}
}
@@ -198,61 +262,118 @@ public class ChatHandler {
));
player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray());
// if (isMuted(player, message, "[" + party.getPartyName() + " Muted] ")) return;
//
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
// Component senderName = user.getDisplayName();
//
// String updatedMessage = RegexManager.replaceText(player.getName(), player.getUniqueId(), message);
// if(updatedMessage == null) {
// GalaxyUtility.sendBlockedNotification("Party Language", player, message, "");
// return; // the message was blocked
// }
//
// if(!player.hasPermission("chat.format")) {
// updatedMessage = Utility.stripTokens(updatedMessage);
// }
//
// if(updatedMessage.contains("[i]")) updatedMessage = updatedMessage.replaceFirst("[i]", "<item>");
//
// updatedMessage = Utility.formatText(updatedMessage);
//
// List<Placeholder> Placeholders = new ArrayList<>(List.of(
// Placeholder.miniMessage("sender", senderName),
// Placeholder.miniMessage("sendername", senderName),
// Placeholder.miniMessage("partyname", party.getPartyName()),
// Placeholder.miniMessage("message", updatedMessage),
// Placeholder.miniMessage("server", Bukkit.getServerName()),
// Placeholder.miniMessage("[i]", itemComponent(player.getInventory().getItemInMainHand()))));
//
// Component component = Utility.parseMiniMessage(Config.PARTY_FORMAT, Placeholders);
//// sendPartyMessage(player, party.getPartyId(), component);
//
// Component spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, Placeholders);
// for(Player pl : Bukkit.getOnlinePlayers()) {
// if(pl.hasPermission(Config.SPYPERMISSION) && !party.getPartyUsersUuid().contains(pl.getUniqueId())) {
// pl.sendMessage(spyMessage);
// }
// }
// if (isMuted(player, message, "[" + party.getPartyName() + " Muted] ")) return;
//
// ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
// Component senderName = user.getDisplayName();
//
// String updatedMessage = RegexManager.replaceText(player.getName(), player.getUniqueId(), message);
// if(updatedMessage == null) {
// GalaxyUtility.sendBlockedNotification("Party Language", player, message, "");
// return; // the message was blocked
// }
//
// if(!player.hasPermission("chat.format")) {
// updatedMessage = Utility.stripTokens(updatedMessage);
// }
//
// if(updatedMessage.contains("[i]")) updatedMessage = updatedMessage.replaceFirst("[i]", "<item>");
//
// updatedMessage = Utility.formatText(updatedMessage);
//
// List<Placeholder> Placeholders = new ArrayList<>(List.of(
// Placeholder.miniMessage("sender", senderName),
// Placeholder.miniMessage("sendername", senderName),
// Placeholder.miniMessage("partyname", party.getPartyName()),
// Placeholder.miniMessage("message", updatedMessage),
// Placeholder.miniMessage("server", Bukkit.getServerName()),
// Placeholder.miniMessage("[i]", itemComponent(player.getInventory().getItemInMainHand()))));
//
// Component component = Utility.parseMiniMessage(Config.PARTY_FORMAT, Placeholders);
//// sendPartyMessage(player, party.getPartyId(), component);
//
// Component spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, Placeholders);
// for(Player pl : Bukkit.getOnlinePlayers()) {
// if(pl.hasPermission(Config.SPYPERMISSION) && !party.getPartyUsersUuid().contains(pl.getUniqueId())) {
// pl.sendMessage(spyMessage);
// }
// }
}
private void sendChatChannelMessage(CustomChannel chatChannel, UUID uuid, Component component) {
if (!chatChannel.getServers().contains(Bukkit.getServer().getName())) {
private void sendChatChannelMessage(CustomChannel chatChannel, UUID uuid, ComponentLike component, String message) {
Player player = Bukkit.getPlayer(uuid);
if (player == null) {
ALogger.warn("Failed to send chat message from non existent player");
return;
}
Player player = Bukkit.getPlayer(uuid);
if (player == null) {
if (!chatChannel.getServers().contains(ServerName.getServerName())) {
player.sendRichMessage("<red>Unable to send messages to <channel> in this server.</red>",
Placeholder.parsed("channel", chatChannel.getChannelName())
);
ALogger.info(String.format("Not sending chat message due to [%s] not being in this channels config",
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(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers().contains(uuid) && !p.hasPermission("chat.ignorebypass"));
stream = stream.filter(receiver -> !ChatUserManager.getChatUser(receiver.getUniqueId())
.getIgnoredPlayers()
.contains(uuid)
|| receiver.hasPermission("chat.ignorebypass"));
}
stream.forEach(p -> p.sendMessage(component));
if (chatChannel.isLocal()) {
Location location = player.getLocation();
stream = stream.filter(receiver -> {
Player receiverPlayer = Bukkit.getPlayer(receiver.getUniqueId());
if (receiverPlayer == null) {
return false;
}
if (!location.getWorld().getUID().equals(receiverPlayer.getLocation().getWorld().getUID())) {
return false;
}
return !(receiverPlayer.getLocation().distance(location) > Config.LOCAL_DISTANCE);
});
}
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))
.filter(onlinePlayer -> !recipientUUIDs.contains(onlinePlayer.getUniqueId()))
.forEach(onlinePlayer -> onlinePlayer.sendRichMessage(Config.CHANNEL_SPY,
Placeholder.component("sender", player.name()),
Placeholder.parsed("channel", chatChannel.getChannelName()),
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) {
@@ -272,23 +393,39 @@ public class ChatHandler {
player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray());
}
public void sendChatChannelMessage(Player player, String chatChannelName, String channel, Component component) {
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) return false;
if (user.isMuted() || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission("chat.bypass-server-muted"))) {
// if (Database.get().isPlayerMuted(player.getUniqueId(), null) || (ChatPlugin.getInstance().serverMuted() && !player.hasPermission("chat.bypass-server-muted"))) {
GalaxyUtility.sendBlockedNotification(prefix, player, Utility.parseMiniMessage(Utility.stripTokens(message)), "");
private boolean isMuted(User user, OfflinePlayer offlinePlayer, String message, String prefix) {
ChatUser chatUser = ChatUserManager.getChatUser(offlinePlayer.getUniqueId());
if (chatUser == null) {
return false;
}
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,
offlinePlayer,
Utility.parseMiniMessage(Utility.stripTokens(message)),
""
);
return true;
}
return false;
@@ -296,11 +433,12 @@ public class ChatHandler {
public static Component itemComponent(ItemStack item) {
Component component = Component.text("[i]", NamedTextColor.AQUA);
if(item.getType().equals(Material.AIR))
if (item.getType().equals(Material.AIR)) {
return component.color(NamedTextColor.WHITE);
}
boolean dname = item.hasItemMeta() && item.getItemMeta().hasDisplayName();
if(dname) {
component = component.append(item.getItemMeta().displayName());
if (dname) {
component = component.append(Objects.requireNonNull(item.getItemMeta().displayName()));
} else {
component = component.append(Component.text(materialToName(item.getType()), NamedTextColor.WHITE));
}
@@ -322,10 +460,12 @@ public class ChatHandler {
int loc = sb.lastIndexOf(split);
char charLoc = sb.charAt(loc);
if (!(split.equalsIgnoreCase("of") || split.equalsIgnoreCase("and") ||
split.equalsIgnoreCase("with") || split.equalsIgnoreCase("on")))
split.equalsIgnoreCase("with") || split.equalsIgnoreCase("on"))) {
sb.setCharAt(loc, Character.toUpperCase(charLoc));
if (pos != splits.length - 1)
}
if (pos != splits.length - 1) {
sb.append(' ');
}
++pos;
}
@@ -333,18 +473,18 @@ 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());
}
});
MiniMessage miniMessage = MiniMessage.builder().tags(tagResolver.build()).build();
Component component = miniMessage.deserialize(rawMessage);
for(ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
for (ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
component = component.replaceText(
TextReplacementConfig.builder()
.times(Config.EMOTELIMIT)
@@ -352,13 +492,16 @@ public class ChatHandler {
.replacement(chatFilter.getReplacement()).build());
}
component = component
.replaceText(
TextReplacementConfig.builder()
.once()
.matchLiteral("[i]")
.replacement(ChatHandler.itemComponent(player.getInventory().getItemInMainHand()))
.build());
if (offlinePlayer.isOnline() && offlinePlayer.getPlayer() != null) {
component = component
.replaceText(
TextReplacementConfig.builder()
.once()
.matchLiteral("[i]")
.replacement(ChatHandler.itemComponent(offlinePlayer.getPlayer().getInventory()
.getItemInMainHand()))
.build());
}
return component;
@@ -1,73 +1,67 @@
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.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) {
if (event.player() == null) return;
if (event.player() == null) {
return;
}
Component formatComponent = Component.text("%message%");
Component 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)
public void onChatDecorate(AsyncChatDecorateEvent event) {
if (event.player() == null) return;
if (event.player() == null) {
return;
}
Component formatComponent = Component.text("%message%");
Component 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
@@ -76,119 +70,10 @@ 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();
Component input = event.message().colorIfAbsent(NamedTextColor.WHITE);
ModifiableString modifiableString = new ModifiableString(input);
// 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, player.getServer().getName(), PlainTextComponentSerializer.plainText().serialize(input), 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, player.getServer().getName(), modifiableString.string(), false);
ALogger.info(PlainTextComponentSerializer.plainText().serialize(input));
chatMessageSender.sendMessage(event.getPlayer(), event.message(), false);
}
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 Component 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);
}
private Component parseMessageContent(Player player, String rawMessage) {
private ComponentLike parseMessageContent(Player player, String rawMessage) {
TagResolver.Builder tagResolver = TagResolver.builder();
Utility.formattingPerms.forEach((perm, pair) -> {
@@ -199,7 +84,7 @@ public class ChatListener implements Listener {
MiniMessage miniMessage = MiniMessage.builder().tags(tagResolver.build()).build();
Component component = miniMessage.deserialize(Utility.formatText(rawMessage));
for(ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
for (ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
component = component.replaceText(
TextReplacementConfig.builder()
.times(Config.EMOTELIMIT)
@@ -209,11 +94,11 @@ public class ChatListener implements Listener {
component = component
.replaceText(
TextReplacementConfig.builder()
.once()
.matchLiteral("[i]")
.replacement(ChatHandler.itemComponent(player.getInventory().getItemInMainHand()))
.build());
TextReplacementConfig.builder()
.once()
.matchLiteral("[i]")
.replacement(ChatHandler.itemComponent(player.getInventory().getItemInMainHand()))
.build());
return component;
@@ -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();
@@ -113,7 +133,7 @@ public class PlayerListener implements Listener {
event.deathMessage(Component.empty());
return;
}
Component component = event.deathMessage();
Component component = event.deathMessage();
playerDeathsStack.push(Instant.now());
if (component == null) {
@@ -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);
}
@@ -5,17 +5,22 @@ import com.alttd.chat.config.Config;
import com.alttd.chat.database.Queries;
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.objects.PartyUser;
import com.alttd.chat.objects.channels.Channel;
import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.objects.ChatUser;
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;
import com.google.common.io.ByteArrayDataInput;
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;
@@ -27,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)) {
@@ -35,7 +46,7 @@ public class PluginMessage implements PluginMessageListener {
ByteArrayDataInput in = ByteStreams.newDataInput(bytes);
String subChannel = in.readUTF();
switch (subChannel) {
case "privatemessagein": {
case "privatemessagein" -> {
UUID uuid = UUID.fromString(in.readUTF());
String target = in.readUTF();
Player player = Bukkit.getPlayer(uuid);
@@ -46,14 +57,28 @@ public class PluginMessage implements PluginMessageListener {
}
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
if (isTargetNotIgnored(chatUser, targetuuid)) {
player.sendMessage(GsonComponentSerializer.gson().deserialize(message));
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1); // todo load this from config
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
ChatUser user = ChatUserManager.getChatUser(uuid);
if (!user.getReplyContinueTarget().equalsIgnoreCase(target))
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": {
case "privatemessageout" -> {
UUID uuid = UUID.fromString(in.readUTF());
String target = in.readUTF();
Player player = Bukkit.getPlayer(uuid);
@@ -65,47 +90,57 @@ 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
);
}
break;
}
case "globalchat": {
if (!ChatPlugin.getInstance().serverGlobalChatEnabled() || ChatPlugin.getInstance().serverMuted()) break;
case "globalchat" -> {
if (!ChatPlugin.getInstance().serverGlobalChatEnabled() || ChatPlugin.getInstance().serverMuted()) {
break;
}
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);
}
});
break;
}
case "ignore": {
case "ignore" -> {
ChatUser chatUser = ChatUserManager.getChatUser(UUID.fromString(in.readUTF()));
UUID targetUUID = UUID.fromString(in.readUTF());
if(!chatUser.getIgnoredPlayers().contains(targetUUID)) {
if (!chatUser.getIgnoredPlayers().contains(targetUUID)) {
chatUser.addIgnoredPlayers(targetUUID);
}
break;
}
case "unignore": {
case "unignore" -> {
ChatUser chatUser = ChatUserManager.getChatUser(UUID.fromString(in.readUTF()));
chatUser.removeIgnoredPlayers(UUID.fromString(in.readUTF()));
break;
}
case "chatchannel": {
if (ChatPlugin.getInstance().serverMuted()) break;
case "chatchannel" -> {
if (ChatPlugin.getInstance().serverMuted()) {
break;
}
chatChannel(in);
break;
//TODO [Stijn] [2026-07-19]: handle custom channels
}
case "tmppartyupdate" : {
case "tmppartyupdate" -> {
int id = Integer.parseInt(in.readUTF());
new BukkitRunnable() {
@Override
@@ -113,9 +148,8 @@ public class PluginMessage implements PluginMessageListener {
Queries.loadPartyUsers(id);
}
}.runTaskAsynchronously(ChatPlugin.getInstance());
break;
}
case "partylogin": {
case "partylogin" -> {
int id = Integer.parseInt(in.readUTF());
Party party = PartyManager.getParty(id);
if (party == null) {
@@ -127,19 +161,20 @@ public class PluginMessage implements PluginMessageListener {
@Override
public void run() {
PartyUser user = party.getPartyUser(uuid);
if(user != null) {
Component component = Utility.parseMiniMessage("<dark_aqua>* " + user.getPlayerName() + " logged in to Altitude.");
if (user != null) {
ComponentLike component = Utility.parseMiniMessage(
"<dark_aqua>* " + user.getPlayerName() + " logged in to Altitude.");
Bukkit.getOnlinePlayers().stream()
.filter(p -> party.getPartyUsersUuid().contains(p.getUniqueId()))
.filter(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers().contains(uuid))
.filter(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers()
.contains(uuid))
.forEach(p -> p.sendMessage(component));
}
}
}.runTaskAsynchronously(ChatPlugin.getInstance());
break;
}
case "partylogout": {
case "partylogout" -> {
int id = Integer.parseInt(in.readUTF());
Party party = PartyManager.getParty(id);
if (party == null) {
@@ -151,30 +186,31 @@ public class PluginMessage implements PluginMessageListener {
@Override
public void run() {
PartyUser user = party.getPartyUser(uuid);
if(user != null) {
Component component = Utility.parseMiniMessage("<dark_aqua>* " + user.getPlayerName() + " logged out of Altitude.");
if (user != null) {
ComponentLike component = Utility.parseMiniMessage(
"<dark_aqua>* " + user.getPlayerName() + " logged out of Altitude.");
Bukkit.getOnlinePlayers().stream()
.filter(p -> party.getPartyUsersUuid().contains(p.getUniqueId()))
.filter(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers().contains(uuid))
.filter(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers()
.contains(uuid))
.forEach(p -> p.sendMessage(component));
}
}
}.runTaskAsynchronously(ChatPlugin.getInstance());
break;
}
case "reloadconfig":
ChatPlugin.getInstance().ReloadConfig();
break;
case "chatpunishments":
case "reloadconfig" -> ChatPlugin.getInstance().reloadConfig();
case "chatpunishments" -> {
UUID uuid = UUID.fromString(in.readUTF());
boolean mute = in.readBoolean();
ChatUser user = ChatUserManager.getChatUser(uuid);
if (user == null) return;
if (user == null) {
return;
}
user.setMuted(mute);
break;
default:
break;
}
default -> {
}
}
}
@@ -186,15 +222,15 @@ public class PluginMessage implements PluginMessageListener {
chatChannel = (CustomChannel) Channel.getChatChannel(in.readUTF());
uuid = UUID.fromString(in.readUTF());
component = GsonComponentSerializer.gson().deserialize(in.readUTF());
} catch (Exception e) { //Idk the exception for reading too far into in.readUTF()
e.printStackTrace();
} catch (Exception e) {
ALogger.error("Failed to read ChatChannel message.", e);
}
if (chatChannel == null) {
ALogger.warn("Received ChatChannel message for non existent channel.");
return;
}
if (!chatChannel.getServers().contains(Bukkit.getServer().getName())) {
if (!chatChannel.getServers().contains(ServerName.getServerName())) {
ALogger.warn("Received ChatChannel message for the wrong server.");
return;
}
@@ -211,7 +247,8 @@ public class PluginMessage implements PluginMessageListener {
public void run() {
Bukkit.getOnlinePlayers().stream()
.filter(p -> p.hasPermission(finalChatChannel.getPermission()))
.filter(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers().contains(finalUuid))
.filter(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers()
.contains(finalUuid))
.forEach(p -> p.sendMessage(finalComponent));
}
}.runTaskAsynchronously(ChatPlugin.getInstance());
@@ -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;
}
}
@@ -4,100 +4,21 @@ import com.alttd.chat.ChatPlugin;
import com.alttd.chat.config.Config;
import com.alttd.chat.database.Queries;
import com.alttd.chat.objects.Nick;
import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.Utility;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyFormat;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;
public class NickUtilities
{
public class NickUtilities {
public static String stringRegen;
public static String applyColor(String message) {
ChatColor hexColor1 = null;
ChatColor hexColor2;
StringBuilder stringBuilder = new StringBuilder();
message = ChatColor.translateAlternateColorCodes('&', message);
boolean startsWithColor = false;
boolean lastColorMatters = false;
if (message.matches(".*" + NickUtilities.stringRegen + ".*")) {
String[] split = message.split(NickUtilities.stringRegen);
ArrayList<String> list = new ArrayList<>();
int nextIndex = 0;
if (message.indexOf("}") <= 11) {
startsWithColor = true;
list.add(message.substring(0, message.indexOf("}") + 1));
}
for (String s : split) {
nextIndex += s.length();
int tmp = message.indexOf("}", nextIndex);
if (tmp < message.length() && tmp>=0) {
list.add(message.substring(nextIndex, tmp + 1));
nextIndex = tmp + 1;
}
}
int i;
boolean firstLoop = true;
if (startsWithColor) {
i = -1;
} else {
i = 0;
stringBuilder.append(split[i]);
}
for (String s : list) {
boolean lesser = s.contains("<");
boolean bigger = s.contains(">");
if (bigger && lesser) {
hexColor2 = ChatColor.of(s.substring(1, s.length() - 3));
} else if (bigger || lesser) {
hexColor2 = ChatColor.of(s.substring(1, s.length() - 2));
} else {
hexColor2 = ChatColor.of(s.substring(1, s.length() -1));
}
if (firstLoop) {
lastColorMatters = bigger;
hexColor1 = hexColor2;
firstLoop = false;
i++;
continue;
}
if (lesser && lastColorMatters) {
stringBuilder.append(hexGradient(hexColor1.getColor(), hexColor2.getColor(), split[i]));
} else {
stringBuilder.append(hexColor1).append(split[i]);
}
hexColor1 = hexColor2;
lastColorMatters = bigger;
i++;
}
if (split.length > i){
stringBuilder.append(hexColor1).append(split[i]);
}
}
return stringBuilder.length()==0 ? message : stringBuilder.toString();
}
public static String removeAllColors(String string) {
for (final String colorCodes : Config.NICK_ALLOWED_COLOR_CODESLIST) {
@@ -111,34 +32,11 @@ public class NickUtilities
NickUtilities.stringRegen = "\\{#[A-Fa-f0-9]{6}(<)?(>)?}";
}
public static String hexGradient(Color color1, Color color2, String text){
double r = color1.getRed();
double g = color1.getGreen();
double b = color1.getBlue();
double rDifference = (color1.getRed() - color2.getRed()) / ((double) text.length() - 1);
double gDifference = (color1.getGreen() - color2.getGreen()) / ((double) text.length() - 1);
double bDifference = (color1.getBlue() - color2.getBlue()) / ((double) text.length() - 1);
StringBuilder stringBuilder = new StringBuilder();
char[] chars = text.toCharArray();
for (int i = 0; i < text.length(); i++) {
if (i > 0) {
r = r - rDifference;
g = g - gDifference;
b = b - bDifference;
}
stringBuilder.append(ChatColor.of(new Color((int) r, (int) g, (int) b))).append(chars[i]);
}
return stringBuilder.toString();
}
public static void updateCache() {
if (!Nicknames.getInstance().nickCacheUpdate.isEmpty()){
Nicknames.getInstance().nickCacheUpdate.forEach(uuid ->{
if (!Nicknames.getInstance().nickCacheUpdate.isEmpty()) {
Nicknames.getInstance().nickCacheUpdate.forEach(uuid -> {
Nick nick = Queries.getNick(uuid);
if (nick == null){
if (nick == null) {
Nicknames.getInstance().NickCache.remove(uuid);
} else {
Nicknames.getInstance().NickCache.put(uuid, nick);
@@ -174,13 +72,13 @@ public class NickUtilities
return true;
}
for (Nick nick : Nicknames.getInstance().NickCache.values()){
for (Nick nick : Nicknames.getInstance().NickCache.values()) {
if (!nick.getUuid().equals(target.getUniqueId())
&& ((nick.getCurrentNickNoColor() != null && nick.getCurrentNickNoColor().equalsIgnoreCase(cleanNick))
|| (nick.getNewNickNoColor() != null && nick.getNewNickNoColor().equalsIgnoreCase(cleanNick)))){
&& ((nick.getCurrentNickNoColor() != null && nick.getCurrentNickNoColor().equalsIgnoreCase(cleanNick))
|| (nick.getNewNickNoColor() != null && nick.getNewNickNoColor().equalsIgnoreCase(cleanNick)))) {
UUID uuid = nick.getUuid();
UUID uniqueId = target.getUniqueId();
if (uniqueId.equals(uuid)){
if (uniqueId.equals(uuid)) {
ChatPlugin.getInstance().getLogger().info(uuid + " " + uniqueId);
}
sender.sendRichMessage(Config.NICK_TAKEN);
@@ -204,16 +102,16 @@ public class NickUtilities
public static void bungeeMessageHandled(UUID uniqueId, Player player, String channel) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
// out.writeUTF("Forward"); // So BungeeCord knows to forward it
// out.writeUTF("ALL");
// out.writeUTF("Forward"); // So BungeeCord knows to forward it
// out.writeUTF("ALL");
out.writeUTF("NickName" + channel); // The channel name to check if this your data
ByteArrayOutputStream msgbytes = new ByteArrayOutputStream();
DataOutputStream msgout = new DataOutputStream(msgbytes);
try {
msgout.writeUTF(uniqueId.toString());
} catch (IOException exception){
exception.printStackTrace();
} catch (IOException exception) {
ALogger.error("Failed to write UUID to byte array", exception);
return;
}
byte[] bytes = msgbytes.toByteArray();
@@ -8,6 +8,7 @@ import com.alttd.chat.events.NickEvent;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.Nick;
import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.Utility;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
@@ -21,7 +22,6 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
@@ -45,138 +45,158 @@ 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 (args.length == 0) {
sender.sendMessage(Utility.parseMiniMessage(helpMessage(sender, HelpType.ALL)));
return true;
}
switch (args[0].toLowerCase()) {
case "set":
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")) {
OfflinePlayer offlinePlayer = sender.getServer().getOfflinePlayer(args[1]);
if (offlinePlayer.isOnline() || offlinePlayer.hasPlayedBefore()) {
handleNick(player, offlinePlayer, args[2]);
} else {
sender.sendMessage(Utility.parseMiniMessage(helpMessage(sender, HelpType.SET_OTHERS)));
}
} else if (args.length > 3) {
sender.sendMessage(Utility.parseMiniMessage(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.sendMessage(Utility.parseMiniMessage(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.sendMessage(Utility.parseMiniMessage(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.sendMessage(Utility.parseMiniMessage(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.sendMessage(Utility.parseMiniMessage(Config.NICK_NO_LUCKPERMS));
}
} else {
sender.sendMessage(Utility.parseMiniMessage(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.sendMessage(Utility.parseMiniMessage(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.sendMessage(Utility.parseMiniMessage(helpMessage(sender, HelpType.ALL)));
}
} else {
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" -> 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")) {
OfflinePlayer offlinePlayer = sender.getServer().getOfflinePlayer(args[1]);
if (offlinePlayer.isOnline() || offlinePlayer.hasPlayedBefore()) {
handleNick(player, offlinePlayer, args[2]);
} else {
sender.sendRichMessage(helpMessage(sender, HelpType.SET_OTHERS));
}
} else if (args.length > 3) {
sender.sendRichMessage(helpMessage(sender, HelpType.SET_SELF, HelpType.SET_OTHERS));
}
}
@Override
public List<String> onTabComplete(CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
List<String> completions = new ArrayList<>();
if (!sender.hasPermission("chat.command.nick")) return completions;
if (!sender.hasPermission("chat.command.nick")) {
return completions;
}
if (args.length == 1) {
List<String> choices = new ArrayList<>();
if (sender.hasPermission("chat.command.nick.set")) {
choices.add("set");
}
if (sender.hasPermission("chat.command.nick.review")) {
choices.add("review");
}
if (sender.hasPermission("chat.command.nick.request")) {
choices.add("request");
}
if (sender.hasPermission("chat.command.nick.try")) {
choices.add("try");
}
if (sender.hasPermission("chat.command.nick.current")) {
choices.add("current");
}
choices.add("help");
for (String s : choices) {
if (s.startsWith(args[0])) {
completions.add(s);
}
}
tabCompleteArgLengthOne(sender, args, completions);
} 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);
}
}
}
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");
}
if (sender.hasPermission("chat.command.nick.review")) {
choices.add("review");
}
if (sender.hasPermission("chat.command.nick.request")) {
choices.add("request");
}
if (sender.hasPermission("chat.command.nick.try")) {
choices.add("try");
}
if (sender.hasPermission("chat.command.nick.current")) {
choices.add("current");
}
choices.add("help");
for (String s : choices) {
if (s.startsWith(args[0])) {
completions.add(s);
}
}
}
private void handleNickRequest(Player player, String nickName) {
if (!NickUtilities.validNick(player, player, nickName)) {
return;
@@ -191,15 +211,15 @@ public class Nicknames implements CommandExecutor, TabCompleter {
long waitTime = Config.NICK_WAIT_TIME;
if (timeSinceLastChange > waitTime || player.hasPermission("chat.command.nick.bypasswaittime")) {
if (nick.hasRequest()) {
player.sendMessage(Utility.parseMiniMessage(Config.NICK_REQUEST_PLACED,
Placeholder.component("oldrequestednick", Utility.applyColor(nick.getNewNick())),
Placeholder.component("newrequestednick", Utility.applyColor(nickName))));
player.sendRichMessage(Config.NICK_REQUEST_PLACED,
Placeholder.component("oldrequestednick", Utility.applyColor(nick.getNewNick())),
Placeholder.component("newrequestednick", Utility.applyColor(nickName)));
}
nick.setNewNick(nickName);
nick.setRequestedDate(new Date().getTime());
} else {
player.sendMessage(Utility.parseMiniMessage(Config.NICK_TOO_SOON,
Placeholder.unparsed("time", formatTime((timeSinceLastChange-waitTime)*-1))));
player.sendRichMessage(Config.NICK_TOO_SOON,
Placeholder.unparsed("time", formatTime((timeSinceLastChange - waitTime) * -1)));
return;
}
} else {
@@ -207,8 +227,8 @@ public class Nicknames implements CommandExecutor, TabCompleter {
}
Queries.newNicknameRequest(uniqueId, nickName);
bungeeMessageRequest(player);
player.sendMessage(Utility.parseMiniMessage(Config.NICK_REQUESTED,
Placeholder.component("nick", Utility.applyColor(nickName))));
player.sendRichMessage(Config.NICK_REQUESTED,
Placeholder.component("nick", Utility.applyColor(nickName)));
}
private void bungeeMessageRequest(Player player) {
@@ -216,8 +236,8 @@ public class Nicknames implements CommandExecutor, TabCompleter {
UUID uniqueId = player.getUniqueId();
// out.writeUTF("Forward"); // So BungeeCord knows to forward it
// out.writeUTF("ALL");
// out.writeUTF("Forward"); // So BungeeCord knows to forward it
// out.writeUTF("ALL");
out.writeUTF("NickNameRequest"); // The channel name to check if this your data
ByteArrayOutputStream msgbytes = new ByteArrayOutputStream();
@@ -225,7 +245,7 @@ public class Nicknames implements CommandExecutor, TabCompleter {
try {
msgout.writeUTF(uniqueId.toString());
} catch (IOException exception) {
exception.printStackTrace();
ALogger.error("Failed to write UUID to ByteArrayOutputStream", exception);
return;
}
byte[] bytes = msgbytes.toByteArray();
@@ -244,13 +264,13 @@ public class Nicknames implements CommandExecutor, TabCompleter {
long days = (timeInMillis / (1000 * 60 * 60 * 24));
StringBuilder stringBuilder = new StringBuilder();
if (days!=0) {
if (days != 0) {
stringBuilder.append(days).append(" days ");
}
if (days!=0 || hour!=0) {
if (days != 0 || hour != 0) {
stringBuilder.append(hour).append(" hours ");
}
if (days!=0 || hour!=0 || minute != 0) {
if (days != 0 || hour != 0 || minute != 0) {
stringBuilder.append(minute).append(" minutes and ");
}
stringBuilder.append(second).append(" seconds");
@@ -259,67 +279,75 @@ 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(target.getPlayer());
}
Queries.removePlayerFromDataBase(target.getUniqueId());
NickCache.remove(target.getUniqueId());
nickCacheUpdate.add(target.getUniqueId());
} catch (SQLException e) {
e.printStackTrace();
}
if (!sender.equals(target)) {
sender.sendMessage(Utility.parseMiniMessage(Config.NICK_RESET_OTHERS,
Placeholder.unparsed("player", target.getName())));
}
if (target.isOnline() && target.getPlayer() != null) {
target.getPlayer().sendMessage(Utility.parseMiniMessage(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)) {
if (target.isOnline()) {
setNick(target.getPlayer(), nickName);
} else {
NickUtilities.bungeeMessageHandled(target.getUniqueId(), sender, "Set");
}
Queries.setNicknameInDatabase(target.getUniqueId(), nickName);
NickEvent nickEvent = new NickEvent(sender.getName(), target.getName(), nickName, NickEvent.NickEventType.SET);
nickEvent.callEvent();
if (NickCache.containsKey(target.getUniqueId())) {
Nick nick = NickCache.get(target.getUniqueId());
nick.setCurrentNick(nickName);
nick.setLastChangedDate(new Date().getTime());
setNick(target.getPlayer(), nickName);
} else {
NickCache.put(target.getUniqueId(), new Nick(target.getUniqueId(), nickName, new Date().getTime()));
}
if (!sender.equals(target)) {
sender.sendMessage(Utility.parseMiniMessage(Config.NICK_CHANGED_OTHERS,
Placeholder.unparsed("targetplayer", target.getName()),
Placeholder.unparsed("nickname", nickName)));
if (target.isOnline()) {
target.getPlayer().sendMessage(Utility.parseMiniMessage(Config.NICK_TARGET_NICK_CHANGE,
Placeholder.unparsed("nickname", getNick(target.getPlayer())),
Placeholder.unparsed("sendernick", getNick(sender)),
Placeholder.unparsed("player", target.getName())));
}
} else if (target.isOnline()) {
target.getPlayer().sendMessage(Utility.parseMiniMessage(Config.NICK_CHANGED,
Placeholder.unparsed("nickname", getNick(target.getPlayer()))));
}
setValidNick(sender, target, nickName);
}
}
private void setValidNick(Player sender, OfflinePlayer target, String nickName) {
if (target.isOnline()) {
setNick(target.getPlayer(), nickName);
} else {
NickUtilities.bungeeMessageHandled(target.getUniqueId(), sender, "Set");
}
Queries.setNicknameInDatabase(target.getUniqueId(), nickName);
NickEvent nickEvent = new NickEvent(sender.getName(), target.getName(), nickName, NickEvent.NickEventType.SET);
nickEvent.callEvent();
if (NickCache.containsKey(target.getUniqueId())) {
Nick nick = NickCache.get(target.getUniqueId());
nick.setCurrentNick(nickName);
nick.setLastChangedDate(new Date().getTime());
setNick(target.getPlayer(), nickName);
} else {
NickCache.put(target.getUniqueId(), new Nick(target.getUniqueId(), nickName, new Date().getTime()));
}
if (!sender.equals(target)) {
sender.sendMessage(Utility.parseMiniMessage(Config.NICK_CHANGED_OTHERS,
Placeholder.unparsed("targetplayer", Objects.requireNonNull(target.getName())),
Placeholder.unparsed("nickname", nickName)));
if (target.isOnline()) {
Objects.requireNonNull(target.getPlayer())
.sendRichMessage(Config.NICK_TARGET_NICK_CHANGE,
Placeholder.unparsed("nickname", getNick(target.getPlayer())),
Placeholder.unparsed("sendernick", getNick(sender)),
Placeholder.unparsed("player", target.getName()));
}
} else if (target.isOnline()) {
Objects.requireNonNull(target.getPlayer())
.sendRichMessage(Config.NICK_CHANGED,
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) {
StringBuilder message = new StringBuilder();
for (HelpType helpType : helpTypes) {
@@ -353,8 +381,10 @@ public class Nicknames implements CommandExecutor, TabCompleter {
break;
case REQUEST:
if (sender.hasPermission("chat.command.nick.request")) {
message.append("<gold>/nick request <nickname><white> - Requests a username to be reviewed by staff.\n" +
" <gray>Try using <dark_gray>/nick try <nickname><gray> to see if you like the name, you can only change it once per day!\n");
message.append("""
<gold>/nick request <nickname><white> - Requests a username to be reviewed by staff.
<gray>Try using <dark_gray>/nick try <nickname><gray> to see if you like the name, you can only change it once per day!
""");
}
break;
case REVIEW:
@@ -381,8 +411,8 @@ public class Nicknames implements CommandExecutor, TabCompleter {
public void resetNick(final Player player) {
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
user.setDisplayName(player.getName());
player.displayName(user.getDisplayName());
// updateCMIUser(player, null);
player.displayName(user.getDisplayName().asComponent());
// updateCMIUser(player, null);
}
public String getNick(final Player player) {
@@ -391,42 +421,13 @@ public class Nicknames implements CommandExecutor, TabCompleter {
}
public void setNick(final Player player, final String nickName) {
if (player == null)
if (player == null) {
return;
}
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
user.setDisplayName(nickName);
player.displayName(user.getDisplayName());
// updateCMIUser(player, nickName);
}
// public static String format(final String m) {
// return NickUtilities.applyColor(m);
// }
// public void updateCMIUser(Player player, String nickName) {
// if (!isCMIEnabled())
// return;
//
// CMIUser cmiUser = CMI.getInstance().getPlayerManager().getUser(player);
// if (nickName == null){
// cmiUser.setNickName(null, true);
// } else {
// cmiUser.setNickName(NickUtilities.applyColor(nickName), true);
// }
// cmiUser.updateDisplayName();
// }
private Boolean isCMIEnabled = null;
private Boolean isCMIEnabled() {
if (!(isCMIEnabled == null))
return isCMIEnabled;
Plugin plugin = Bukkit.getPluginManager().getPlugin("CMI");
if (plugin != null && plugin.isEnabled())
return isCMIEnabled = true;
return isCMIEnabled = false;
player.displayName(user.getDisplayName().asComponent());
}
public static Nicknames getInstance() {
@@ -5,9 +5,11 @@ import com.alttd.chat.config.Config;
import com.alttd.chat.database.Queries;
import com.alttd.chat.objects.Nick;
import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.Utility;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.minimessage.MiniMessage;
@@ -29,7 +31,6 @@ import java.util.UUID;
public class NicknamesEvents implements Listener, PluginMessageListener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onPlayerJoin(PlayerJoinEvent e) {
@@ -54,9 +55,9 @@ public class NicknamesEvents implements Listener, PluginMessageListener {
strippedNick = MiniMessage.miniMessage().stripTags(Nicknames.getInstance().getNick(player));
} catch (NullPointerException ignored) {
}
// final String strippedNick = CMIChatColor.stripColor(Nicknames.getInstance().getNick(player));
// final String strippedNick = CMIChatColor.stripColor(Nicknames.getInstance().getNick(player));
// final String cmiNick = Util.CMIChatColor.deColorize(Nicknames.getInstance().getNick(player));
// final String cmiNick = Util.CMIChatColor.deColorize(Nicknames.getInstance().getNick(player));
if (nickName == null) {
Nicknames.getInstance().resetNick(player);
@@ -75,8 +76,8 @@ public class NicknamesEvents implements Listener, PluginMessageListener {
}
if (i > 0) {
player.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_REQUESTS_ON_LOGIN,
Placeholder.unparsed("amount", String.valueOf(i))));
player.sendRichMessage(Config.NICK_REQUESTS_ON_LOGIN,
Placeholder.unparsed("amount", String.valueOf(i)));
}
}
}
@@ -93,7 +94,7 @@ public class NicknamesEvents implements Listener, PluginMessageListener {
String subChannel = in.readUTF();
ALogger.info(channel + ": " + subChannel);
if (!subChannel.equals("NickNameRequest") && !subChannel.equals("NickNameAccepted")
&& !subChannel.equals("NickNameDenied") && !subChannel.equals("NickNameSet")) {
&& !subChannel.equals("NickNameDenied") && !subChannel.equals("NickNameSet")) {
return;
}
UUID playerUUID;
@@ -110,18 +111,17 @@ public class NicknamesEvents implements Listener, PluginMessageListener {
name = offlinePlayer.getName() == null ? playerUUID.toString() : offlinePlayer.getName();
} catch (Exception e) {
e.printStackTrace();
ALogger.error("Failed to read plugin message", e);
return;
}
MiniMessage miniMessage = MiniMessage.miniMessage();
switch (subChannel) {
case "NickNameRequest":
Component component = miniMessage.deserialize(Config.NICK_REQUEST_NEW, Placeholder.parsed("player", name))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND,
"/nick review"))
ComponentLike component = miniMessage.deserialize(Config.NICK_REQUEST_NEW, Placeholder.parsed("player", name))
.clickEvent(ClickEvent.runCommand("/nick review"))
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
miniMessage.deserialize("<gold>Click this text to review the request!")));
miniMessage.deserialize("<gold>Click this text to review the request!")));
ChatPlugin.getInstance().getServer().getOnlinePlayers().forEach(p -> {
if (p.hasPermission("chat.command.nick.review")) {
@@ -138,8 +138,9 @@ public class NicknamesEvents implements Listener, PluginMessageListener {
}
break;
case "NickNameAccepted":
Component deserialize = miniMessage.deserialize("<green><name>'s nickname was accepted!",
Placeholder.unparsed("name", name));
ComponentLike deserialize = Utility.parseMiniMessage("<green><name>'s nickname was accepted!",
Placeholder.unparsed("name", name));
ChatPlugin.getInstance().getServer().getOnlinePlayers().forEach(p -> {
if (p.hasPermission("chat.command.nick.review")) {
p.sendMessage(deserialize);
@@ -153,14 +154,14 @@ public class NicknamesEvents implements Listener, PluginMessageListener {
Player target = Bukkit.getPlayer(playerUUID);
if (target != null && nick != null && nick.getCurrentNick() != null) {
Nicknames.getInstance().setNick(target, nick.getCurrentNick());
target.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_CHANGED,
Placeholder.unparsed("nickname", nick.getCurrentNick())));
target.sendRichMessage(Config.NICK_CHANGED,
Placeholder.unparsed("nickname", nick.getCurrentNick()));
}
}
break;
case "NickNameDenied":
final Component messageDenied = miniMessage.deserialize("<red><name>'s nickname was denied",
Placeholder.unparsed("name", name));
Placeholder.unparsed("name", name));
Nick nick = Nicknames.getInstance().NickCache.get(playerUUID);
ChatPlugin.getInstance().getServer().getOnlinePlayers().forEach(p -> {
@@ -181,9 +182,11 @@ public class NicknamesEvents implements Listener, PluginMessageListener {
if (offlinePlayer.isOnline()) {
Player target = Bukkit.getPlayer(playerUUID);
if (target == null) break;
target.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_NOT_CHANGED,
Placeholder.unparsed("nickname", nick.getCurrentNick())));
if (target == null) {
break;
}
target.sendRichMessage(Config.NICK_NOT_CHANGED,
Placeholder.unparsed("nickname", nick.getCurrentNick()));
}
break;
}
@@ -5,23 +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;
@@ -30,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"));
// 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;
}
@@ -70,20 +75,137 @@ public class NicknamesGui implements Listener {
}
if (currentPage != 1) {
inv.setItem(28, createGuiItem(Material.PAPER, "§bPrevious page",
"§aCurrent page: %page%".replace("%page%", String.valueOf(currentPage)),
"§aPrevious page: %previousPage%".replace("%previousPage%", String.valueOf(currentPage - 1))));
ItemStack itemStack = createGuiItem(Material.PAPER, "§bPrevious page",
"§aCurrent page: %page%".replace("%page%", String.valueOf(currentPage)),
"§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",
"§aCurrent page: %page%".replace("%page%", String.valueOf(currentPage)),
"§aNext page: §b%nextPage%".replace("%nextPage%", String.valueOf(currentPage + 1))));
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)));
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);
@@ -92,10 +214,11 @@ public class NicknamesGui implements Listener {
meta.setOwningPlayer(offlinePlayer);
String name = offlinePlayer.getName();
if (name == null)
meta.displayName(miniMessage.deserialize("UNKNOWN PLAYER NAME"));
else
meta.displayName(miniMessage.deserialize(offlinePlayer.getName()));
if (name == null) {
meta.displayName(miniMessage.deserialize("<red>" + getOfflinePlayerName(offlinePlayer) + "</red>"));
} else {
meta.displayName(miniMessage.deserialize(getOfflinePlayerName(offlinePlayer)));
}
TagResolver resolver = TagResolver.resolver(
Placeholder.component("newnick", Utility.applyColor(nick.getNewNick())),
@@ -126,172 +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)) {
String serialize = PlainTextComponentSerializer.plainText().serialize(clickedItem.getItemMeta().displayName());
if (serialize.equals("Next Page")) {
setItems(currentPage + 1);
}
} else if (clickedItem.getType().equals(Material.PLAYER_HEAD)) {
SkullMeta meta = (SkullMeta) clickedItem.getItemMeta();
if (meta.hasEnchants()) {
return;
}
OfflinePlayer owningPlayer = meta.getOwningPlayer();
if (owningPlayer == null) {
p.sendMessage(MiniMessage.miniMessage().deserialize(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);
}
if (nick == null || !nick.hasRequest()) {
p.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_ALREADY_HANDLED,
Placeholder.component("targetplayer", clickedItem.getItemMeta().displayName())));
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(), clickedItem.getItemMeta().getDisplayName(), newNick, NickEvent.NickEventType.ACCEPTED);
nickEvent.callEvent();
}
}.runTask(ChatPlugin.getInstance());
p.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_ACCEPTED,
Placeholder.component("targetplayer", clickedItem.getItemMeta().displayName()),
Placeholder.component("newnick", Utility.applyColor(nick.getNewNick())),
Placeholder.component("oldnick", Utility.applyColor(nick.getCurrentNick() == null ? clickedItem.getItemMeta().getDisplayName() : nick.getCurrentNick()))));
if (owningPlayer.isOnline() && owningPlayer.getPlayer() != null) {
Nicknames.getInstance().setNick(owningPlayer.getPlayer(), nick.getNewNick());
// owningPlayer.getPlayer().sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_CHANGED // This message is also send when the plugin message is received
// .replace("%nickname%", 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(clickedItem.getItemMeta().displayName());
itemMeta.lore(clickedItem.lore());
itemStack.setItemMeta(itemMeta);
e.getInventory().setItem(e.getSlot(), itemStack);
p.updateInventory();
} else {
p.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_PLAYER_NOT_ONLINE,
Placeholder.component("playerName", clickedItem.getItemMeta().displayName())));
}
} else if (e.isRightClick()) {
Component displayName = clickedItem.getItemMeta().displayName();
if (owningPlayer.hasPlayedBefore()) {
Queries.denyNewNickname(uniqueId);
String newNick = nick.getNewNick();
new BukkitRunnable() {
@Override
public void run() {
NickEvent nickEvent = new NickEvent(e.getWhoClicked().getName(), clickedItem.getItemMeta().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().sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_NOT_CHANGED));
}
NickUtilities.bungeeMessageHandled(uniqueId, e.getWhoClicked().getServer().getPlayer(e.getWhoClicked().getName()), "Denied");
final Component 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(displayName);
itemMeta.lore(clickedItem.lore());
itemStack.setItemMeta(itemMeta);
e.getInventory().setItem(e.getSlot(), itemStack);
p.updateInventory();
} else {
if (displayName == null)
p.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_PLAYER_NOT_ONLINE, Placeholder.parsed("playerName", "UNKNOWN PLAYER NAME")));
else
p.sendMessage(MiniMessage.miniMessage().deserialize(Config.NICK_PLAYER_NOT_ONLINE, Placeholder.component("playerName", displayName)));
}
}
}
}.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);
}
}
@@ -2,13 +2,11 @@ package com.alttd.chat.util;
import com.alttd.chat.config.Config;
import com.alttd.chat.managers.RegexManager;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
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 net.kyori.adventure.text.minimessage.tag.standard.StandardTags;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.ArrayList;
@@ -16,40 +14,30 @@ import java.util.List;
public class GalaxyUtility {
public static void sendBlockedNotification(String prefix, Player player, String 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("target", (target.isEmpty() ? "tried to say:" : "-> " + target + ":")),
Placeholder.parsed("input", input)
);
Component blockedNotification = Utility.parseMiniMessage(Config.NOTIFICATIONFORMAT, placeholders);
Bukkit.getOnlinePlayers().forEach(a ->{
if (a.hasPermission("chat.alert-blocked")) {
a.sendMessage(blockedNotification);
}
});
player.sendMessage(Utility.parseMiniMessage("<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 sendBlockedNotification(String prefix, Player player, Component input, String target) {
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)
);
Component blockedNotification = Utility.parseMiniMessage(Config.NOTIFICATIONFORMAT, placeholders);
ComponentLike blockedNotification = Utility.parseMiniMessage(Config.NOTIFICATIONFORMAT, placeholders);
Bukkit.getOnlinePlayers().forEach(a ->{
Bukkit.getOnlinePlayers().forEach(a -> {
if (a.hasPermission("chat.alert-blocked")) {
a.sendMessage(blockedNotification);
}
});
player.sendMessage(Utility.parseMiniMessage("<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>"));
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) {
@@ -0,0 +1,41 @@
package com.alttd.chat.util;
import org.bukkit.Bukkit;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class ServerName {
private static final String serverName = loadServerName();
private static String loadServerName() {
String serverName = "unknown";
try {
File serverPropertiesFile = new File(Bukkit.getWorldContainer().getParentFile(), "server.properties");
if (!serverPropertiesFile.exists()) {
ALogger.warn(String.format("server.properties file not found in %s!", serverPropertiesFile.getAbsolutePath()));
return serverName;
}
Properties properties = new Properties();
FileInputStream fis = new FileInputStream(serverPropertiesFile);
properties.load(fis);
fis.close();
serverName = properties.getProperty("server-name", serverName);
ALogger.info(String.format("Found server name [%s]", serverName));
} catch (IOException e) {
ALogger.error("Failed to read server.properties", e);
}
return serverName;
}
public static String getServerName() {
return serverName;
}
}
@@ -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());
}
+6 -5
View File
@@ -2,8 +2,9 @@ name: ChatPlugin
version: 2.0.0-SNAPSHOT
main: com.alttd.chat.ChatPlugin
api-version: 1.19
authors: [Destro, Teriuihi]
depend: [LuckPerms]
authors: [ Destro, Teriuihi ]
depend: [ LuckPerms, ProtocolLib ]
softdepend: [ TAB ]
loadbefore:
- mcMMO
- GriefPrevention
@@ -18,7 +19,7 @@ commands:
aliases: togglegc
message:
permission: command.chat.message
aliases: [msg, tell]
aliases: [ msg, tell ]
reply:
permission: command.chat.message
aliases: r
@@ -39,6 +40,6 @@ commands:
permission: chat.command.clear-chat
emoteslist:
permission: chat.command.emoteslist
aliases: [emotes]
aliases: [ emotes ]
nick:
permission: chat.command.nick
permission: chat.command.nick
Binary file not shown.
+1 -1
View File
@@ -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
Vendored
+7 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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")
+17 -6
View File
@@ -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")
implementation("mysql:mysql-connector-java:8.0.27") // mysql
implementation("org.spongepowered", "configurate-yaml", "4.1.2")
compileOnly("net.kyori:adventure-text-minimessage:4.10.1")
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;
@@ -24,7 +28,6 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Dependency;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
@@ -37,7 +40,7 @@ import java.nio.file.Path;
description = "A chat plugin for Altitude Minecraft Server",
authors = {"destro174", "teri"},
dependencies = {@Dependency(id = "luckperms"), @Dependency(id = "litebans"), @Dependency(id = "proxydiscordlink")}
)
)
public class VelocityChat {
private static VelocityChat plugin;
@@ -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) {
@@ -68,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
@@ -76,21 +83,37 @@ 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);
}
public void ReloadConfig() {
chatAPI.ReloadConfig();
chatAPI.ReloadChatFilters();
private void registerWebHandlers(SseSubscribeClient sseSubscribeClient) {
sseSubscribeClient.register("web_party_chat", new WebPartyChatHandler(chatHandler));
sseSubscribeClient.register("web_punish", new PunishFromWebHandler(server));
}
public void reloadConfig() {
chatAPI.reloadConfig();
chatAPI.reloadChatFilters();
serverHandler.cleanup();
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() {
@@ -109,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";
}
}
@@ -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)
);
});
}
}
@@ -0,0 +1,49 @@
package com.alttd.velocitychat.chat_web.handlers;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.PartyManager;
import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.Party;
import com.alttd.chat.web.WebHandler;
import com.alttd.chat.web.handler_class.PartyChatFromWeb;
import com.alttd.velocitychat.handlers.ChatHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.UUID;
@Slf4j
@RequiredArgsConstructor
public class WebPartyChatHandler implements WebHandler<PartyChatFromWeb> {
private final ChatHandler chatHandler;
@Override
public Class<PartyChatFromWeb> type() {
return PartyChatFromWeb.class;
}
@Override
public void handle(PartyChatFromWeb event) {
int partyId;
try {
partyId = Integer.parseInt(event.getPartyId());
} catch (NumberFormatException e) {
log.error("Invalid party id: {}", event.getPartyId());
return;
}
UUID sender = event.getSender();
String message = event.getMessage();
Party party = PartyManager.getParty(sender);
if (party == null) {
log.error("Party not found for sender: {}", sender);
return;
}
if (party.getPartyId() != partyId) {
log.error("Party id mismatch: {} != {}", party.getPartyId(), partyId);
return;
}
ChatUser chatUser = ChatUserManager.getChatUser(sender);//TODO [Stijn] [2026-08-09]: Async since it can do a query
chatHandler.sendPartyMessageFromWeb(sender, party, chatUser.getDisplayName().asComponent(), message);
}
}
@@ -7,7 +7,7 @@ import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.util.ArrayList;
@@ -39,12 +39,13 @@ public class PartyCommand implements SimpleCommand {
CommandSource source = invocation.source();
if (args.length < 1) {
if (!source.hasPermission("party.use"))
if (!source.hasPermission("party.use")) {
source.sendMessage(Utility.parseMiniMessage(Config.NO_PERMISSION));
else if (source instanceof Player)
} else if (source instanceof Player) {
source.sendMessage(getHelpMessage(source));
else
} else {
source.sendMessage(Utility.parseMiniMessage(Config.NO_CONSOLE));
}
return;
}
@@ -52,11 +53,12 @@ public class PartyCommand implements SimpleCommand {
.filter(subCommand -> subCommand.getName().equalsIgnoreCase(args[0]))
.findFirst()
.ifPresentOrElse(subCommand -> {
if (source.hasPermission(subCommand.getPermission()))
if (source.hasPermission(subCommand.getPermission())) {
subCommand.execute(args, source);
else
} else {
source.sendMessage(Utility.parseMiniMessage(Config.NO_PERMISSION));
}, () -> source.sendMessage(getHelpMessage(source)));
}
}, () -> source.sendMessage(getHelpMessage(source)));
}
@Override
@@ -64,9 +66,9 @@ public class PartyCommand implements SimpleCommand {
String[] args = invocation.arguments();
List<String> suggest = new ArrayList<>();
if (!invocation.source().hasPermission("party.use"))
if (!invocation.source().hasPermission("party.use")) {
return suggest;
else if (args.length == 0) {
} else if (args.length == 0) {
subCommands.stream()
.filter(subCommand -> invocation.source().hasPermission(subCommand.getPermission()))
.forEach(subCommand -> suggest.add(subCommand.getName()));
@@ -83,10 +85,11 @@ public class PartyCommand implements SimpleCommand {
.ifPresent(subCommand -> suggest.addAll(subCommand.suggest(args, invocation.source())));
}
if (args.length == 0)
if (args.length == 0) {
return suggest;
else
} else {
return finalizeSuggest(suggest, args[args.length - 1]);
}
}
public List<String> finalizeSuggest(List<String> possibleValues, String remaining) {
@@ -101,19 +104,21 @@ public class PartyCommand implements SimpleCommand {
return finalValues;
}
public Component getHelpMessage(CommandSource source) {
public ComponentLike getHelpMessage(CommandSource source) {
StringBuilder stringBuilder = new StringBuilder();
subCommands.stream()
.filter(subCommand -> source.hasPermission(subCommand.getPermission()))
.forEach(subCommand -> stringBuilder.append(subCommand.getHelpMessage()).append("\n"));
if (source.hasPermission("command.chat.p"))
if (source.hasPermission("command.chat.p")) {
stringBuilder.append(Config.PARTY_HELP_CHAT).append("\n");
if (stringBuilder.length() != 0)
}
if (!stringBuilder.isEmpty()) {
stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), "");
}
return Utility.parseMiniMessage(Config.PARTY_HELP_WRAPPER,
Placeholder.component("commands", Utility.parseMiniMessage(stringBuilder.toString()))
);
Placeholder.component("commands", Utility.parseMiniMessage(stringBuilder.toString()))
);
}
}
}
@@ -15,7 +15,7 @@ public class Reload {
.<CommandSource>literal("reloadchat")
.requires(ctx -> ctx.hasPermission("command.chat.reloadchat"))
.executes(context -> {
VelocityChat.getPlugin().ReloadConfig();
VelocityChat.getPlugin().reloadConfig();
return 1;
})
.build();
@@ -16,8 +16,8 @@ import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import jdk.jshell.execution.Util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.time.Duration;
@@ -30,7 +30,7 @@ import java.util.stream.IntStream;
public class VoteToMuteHelper {
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>");
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>").asComponent();
public VoteToMuteHelper(ProxyServer proxyServer) {
RequiredArgumentBuilder<CommandSource, String> playerNode = RequiredArgumentBuilder
@@ -51,8 +51,9 @@ public class VoteToMuteHelper {
.map(Player::getUsername)
.toList();
if (possibleValues.isEmpty())
if (possibleValues.isEmpty()) {
return Suggestions.empty();
}
String remaining = builder.getRemaining().toLowerCase();
possibleValues.stream()
@@ -82,39 +83,39 @@ public class VoteToMuteHelper {
.<CommandSource>literal("page")
.requires(commandSource -> commandSource.hasPermission("chat.vote-to-mute"))
.then(RequiredArgumentBuilder.<CommandSource, Integer>argument("page number", IntegerArgumentType.integer(1))
.suggests(((commandContext, suggestionsBuilder) -> {
if (!(commandContext.getSource() instanceof Player player)) {
return suggestionsBuilder.buildFuture();
}
Optional<VoteToMuteStarter> instance = VoteToMuteStarter.getInstance(player.getUniqueId());
if (instance.isEmpty()) {
return suggestionsBuilder.buildFuture();
}
VoteToMuteStarter voteToMuteStarter = instance.get();
String remaining = suggestionsBuilder.getRemaining().toLowerCase();
int totalPages = voteToMuteStarter.getTotalPages();
IntStream.range(1, totalPages + 1)
.mapToObj(String::valueOf)
.filter((String str) -> str.toLowerCase().startsWith(remaining))
.map(StringArgumentType::escapeIfRequired)
.forEach(suggestionsBuilder::suggest);
return suggestionsBuilder.buildFuture();
}))
.executes(commandContext -> {
if (!(commandContext.getSource() instanceof Player player)) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Only players can use this command.</red>"));
return 1;
}
Optional<VoteToMuteStarter> instance = VoteToMuteStarter.getInstance(player.getUniqueId());
if (instance.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>You don't have an active vote to mute.</red>"));
return 1;
}
int pageNumber = commandContext.getArgument("page number", Integer.class);
instance.get().showPage(pageNumber);
return 1;
})
).executes(commandContext -> {
.suggests(((commandContext, suggestionsBuilder) -> {
if (!(commandContext.getSource() instanceof Player player)) {
return suggestionsBuilder.buildFuture();
}
Optional<VoteToMuteStarter> instance = VoteToMuteStarter.getInstance(player.getUniqueId());
if (instance.isEmpty()) {
return suggestionsBuilder.buildFuture();
}
VoteToMuteStarter voteToMuteStarter = instance.get();
String remaining = suggestionsBuilder.getRemaining().toLowerCase();
int totalPages = voteToMuteStarter.getTotalPages();
IntStream.range(1, totalPages + 1)
.mapToObj(String::valueOf)
.filter((String str) -> str.toLowerCase().startsWith(remaining))
.map(StringArgumentType::escapeIfRequired)
.forEach(suggestionsBuilder::suggest);
return suggestionsBuilder.buildFuture();
}))
.executes(commandContext -> {
if (!(commandContext.getSource() instanceof Player player)) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Only players can use this command.</red>"));
return 1;
}
Optional<VoteToMuteStarter> instance = VoteToMuteStarter.getInstance(player.getUniqueId());
if (instance.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>You don't have an active vote to mute.</red>"));
return 1;
}
int pageNumber = commandContext.getArgument("page number", Integer.class);
instance.get().showPage(pageNumber);
return 1;
})
).executes(commandContext -> {
sendHelpMessage(commandContext.getSource());
return 1;
});
@@ -123,53 +124,53 @@ public class VoteToMuteHelper {
.<CommandSource>literal("messages")
.requires(commandSource -> commandSource.hasPermission("chat.vote-to-mute"))
.then(RequiredArgumentBuilder.<CommandSource, String>argument("list of messages", StringArgumentType.greedyString())
.executes(commandContext -> {
if (!(commandContext.getSource() instanceof Player player)) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Only players can use this command.</red>"));
return 1;
}
Optional<VoteToMuteStarter> instance = VoteToMuteStarter.getInstance(player.getUniqueId());
if (instance.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>You don't have an active vote to mute.</red>"));
return 1;
}
String listOfPages = commandContext.getArgument("list of messages", String.class);
if (!listOfPages.matches("([1-9][0-9]*, )*[1-9][0-9]*")) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Please make sure to format the command correctly.</red>"));
return 1;
}
VoteToMuteStarter voteToMuteStarter = instance.get();
.executes(commandContext -> {
if (!(commandContext.getSource() instanceof Player player)) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Only players can use this command.</red>"));
return 1;
}
Optional<VoteToMuteStarter> instance = VoteToMuteStarter.getInstance(player.getUniqueId());
if (instance.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>You don't have an active vote to mute.</red>"));
return 1;
}
String listOfPages = commandContext.getArgument("list of messages", String.class);
if (!listOfPages.matches("([1-9][0-9]*, )*[1-9][0-9]*")) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Please make sure to format the command correctly.</red>"));
return 1;
}
VoteToMuteStarter voteToMuteStarter = instance.get();
List<Integer> collect = Arrays.stream(listOfPages.split(", "))
.map(Integer::parseInt)
.collect(Collectors.toList());
Optional<Integer> max = collect.stream().max(Integer::compare);
if (max.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Some of your selected messages do not exist.</red>"));
return 1;
}
int highestLogEntry = max.get();
List<Integer> collect = Arrays.stream(listOfPages.split(", "))
.map(Integer::parseInt)
.collect(Collectors.toList());
Optional<Integer> max = collect.stream().max(Integer::compare);
if (max.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Some of your selected messages do not exist.</red>"));
return 1;
}
int highestLogEntry = max.get();
if (voteToMuteStarter.getTotalLogEntries() < highestLogEntry) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Some of your selected messages do not exist.</red>"));
return 1;
}
if (voteToMuteStarter.getTotalLogEntries() < highestLogEntry) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Some of your selected messages do not exist.</red>"));
return 1;
}
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty()) {
sendHelpMessage(commandContext.getSource());
return 1;
}
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty()) {
sendHelpMessage(commandContext.getSource());
return 1;
}
Component chatLogs = voteToMuteStarter.getChatLogsAndClose(collect);
RegisteredServer server = currentServer.get().getServer();
long count = getTotalEligiblePlayers(server, voteToMuteStarter.countLowerRanks());
new ActiveVoteToMute(voteToMuteStarter.getVotedPlayer(), server, proxyServer, Duration.ofMinutes(5),
(int) count, voteToMuteStarter.countLowerRanks(), chatLogs, player)
.start();
return 1;
})
).executes(commandContext -> {
Component chatLogs = voteToMuteStarter.getChatLogsAndClose(collect);
RegisteredServer server = currentServer.get().getServer();
long count = getTotalEligiblePlayers(server, voteToMuteStarter.countLowerRanks());
new ActiveVoteToMute(voteToMuteStarter.getVotedPlayer(), server, proxyServer, Duration.ofMinutes(5),
(int) count, voteToMuteStarter.countLowerRanks(), chatLogs, player)
.start();
return 1;
})
).executes(commandContext -> {
sendHelpMessage(commandContext.getSource());
return 1;
});
@@ -177,47 +178,48 @@ public class VoteToMuteHelper {
LiteralArgumentBuilder<CommandSource> voteNode = LiteralArgumentBuilder
.<CommandSource>literal("vote")
.then(playerNode
.then(yesNoNode
.executes(commandContext -> {
if (!(commandContext.getSource() instanceof Player player)) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<red>Only players are allowed to vote</red>"));
return 1;
}
String playerName = commandContext.getArgument("player", String.class);
Optional<ActiveVoteToMute> optionalActiveVoteToMute = ActiveVoteToMute.getInstance(playerName);
if (optionalActiveVoteToMute.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<red>This player does not have an active vote to mute them.</red>"));
return 1;
}
ActiveVoteToMute activeVoteToMute = optionalActiveVoteToMute.get();
.then(yesNoNode
.executes(commandContext -> {
if (!(commandContext.getSource() instanceof Player player)) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<red>Only players are allowed to vote</red>"));
return 1;
}
String playerName = commandContext.getArgument("player", String.class);
Optional<ActiveVoteToMute> optionalActiveVoteToMute = ActiveVoteToMute.getInstance(playerName);
if (optionalActiveVoteToMute.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<red>This player does not have an active vote to mute them.</red>"));
return 1;
}
ActiveVoteToMute activeVoteToMute = optionalActiveVoteToMute.get();
if (!activeVoteToMute.countLowerRanks()) {
if (!player.hasPermission("chat.vote-to-mute")) {
player.sendMessage(Utility.parseMiniMessage("<red>You are not eligible to vote.</red>"));
return 1;
}
}
if (!activeVoteToMute.countLowerRanks()) {
if (!player.hasPermission("chat.vote-to-mute")) {
player.sendMessage(Utility.parseMiniMessage("<red>You are not eligible to vote.</red>"));
return 1;
}
}
String vote = commandContext.getArgument("yesNo", String.class);
switch (vote.toLowerCase()) {
case "yes" -> {
activeVoteToMute.vote(player.getUniqueId(), true);
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<green>You voted to mute. Thanks for voting, staff will be online soon to review!</green>"));
player.getCurrentServer().ifPresent(serverConnection -> notifyEligiblePlayers(serverConnection.getServer(), activeVoteToMute));
}
case "no" -> {
activeVoteToMute.vote(player.getUniqueId(), false);
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<green>You voted <red>not</red> to mute. Thanks for voting, staff will be online soon to review!</green>"));
}
default -> commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<red><vote> is not a valid vote option</red>", Placeholder.parsed("vote", vote)));
}
return 1;
})).executes(context -> {
String vote = commandContext.getArgument("yesNo", String.class);
switch (vote.toLowerCase()) {
case "yes" -> {
activeVoteToMute.vote(player.getUniqueId(), true);
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<green>You voted to mute. Thanks for voting, staff will be online soon to review!</green>"));
player.getCurrentServer().ifPresent(serverConnection -> notifyEligiblePlayers(serverConnection.getServer(), activeVoteToMute));
}
case "no" -> {
activeVoteToMute.vote(player.getUniqueId(), false);
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<green>You voted <red>not</red> to mute. Thanks for voting, staff will be online soon to review!</green>"));
}
default ->
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<red><vote> is not a valid vote option</red>", Placeholder.parsed("vote", vote)));
}
return 1;
})).executes(context -> {
sendHelpMessage(context.getSource());
return 1;
})).executes(context -> {
@@ -254,14 +256,14 @@ public class VoteToMuteHelper {
}
private void notifyEligiblePlayers(RegisteredServer server, ActiveVoteToMute activeVoteToMute) {
Component message = Utility.parseMiniMessage("<prefix><green><voted_for> out of <total_votes> players have voted to mute <player></green>",
Placeholder.component("prefix", prefix),
Placeholder.parsed("voted_for", String.valueOf(activeVoteToMute.getVotedFor())),
Placeholder.parsed("total_votes", String.valueOf(activeVoteToMute.getTotalEligibleVoters())),
Placeholder.parsed("player", activeVoteToMute.getVotedPlayer().getUsername()));
ComponentLike message = Utility.parseMiniMessage("<prefix><green><voted_for> out of <total_votes> players have voted to mute <player></green>",
Placeholder.component("prefix", prefix),
Placeholder.parsed("voted_for", String.valueOf(activeVoteToMute.getVotedFor())),
Placeholder.parsed("total_votes", String.valueOf(activeVoteToMute.getTotalEligibleVoters())),
Placeholder.parsed("player", activeVoteToMute.getVotedPlayer().getUsername()));
boolean countLowerRanks = activeVoteToMute.countLowerRanks();
server.getPlayersConnected().stream()
.filter(player -> countLowerRanks ? player.hasPermission("chat.backup-vote-to-mute") : player.hasPermission("chat.vote-to-mute"))
.filter(player -> countLowerRanks ? player.hasPermission("chat.backup-vote-to-mute") : player.hasPermission("chat.vote-to-mute"))
.forEach(player -> player.sendMessage(message));
}
@@ -46,11 +46,12 @@ public class Disband implements SubCommand {
source.sendMessage(Utility.parseMiniMessage(getHelpMessage()));
return;
}
VelocityChat.getPlugin().getChatHandler().sendPartyMessage(party,
Utility.parseMiniMessage(Config.DISBANDED_PARTY,
Placeholder.unparsed("owner", player.getUsername()),
Placeholder.unparsed("party", party.getPartyName())
), null);
VelocityChat.getPlugin().getChatHandler()
.sendPartyMessage(party,
Utility.parseMiniMessage(Config.DISBANDED_PARTY,
Placeholder.unparsed("owner", player.getUsername()),
Placeholder.unparsed("party", party.getPartyName())
).asComponent(), null);
party.delete();
}
@@ -40,19 +40,20 @@ public class Info implements SubCommand {
List<Component> displayNames = new ArrayList<>();
for (PartyUser partyUser : party.getPartyUsers()) {
Optional<Player> optionalPlayer = VelocityChat.getPlugin().getProxy().getPlayer(partyUser.getUuid());
if (optionalPlayer.isPresent() && optionalPlayer.get().isActive())
displayNames.add(Config.ONLINE_PREFIX.append(partyUser.getDisplayName()));
else
displayNames.add(Config.OFFLINE_PREFIX.append(partyUser.getDisplayName()));
if (optionalPlayer.isPresent() && optionalPlayer.get().isActive()) {
displayNames.add(Config.ONLINE_PREFIX.asComponent().append(partyUser.getDisplayName()));
} else {
displayNames.add(Config.OFFLINE_PREFIX.asComponent().append(partyUser.getDisplayName()));
}
}
PartyUser owner = party.getPartyUser(party.getOwnerUuid());
source.sendMessage(Utility.parseMiniMessage(Config.PARTY_INFO,
Placeholder.unparsed("party", party.getPartyName()),
Placeholder.unparsed("password", party.getPartyPassword()),
Placeholder.component("owner", owner == null ? MiniMessage.miniMessage().deserialize("Unknown Owner") : owner.getDisplayName()),
Placeholder.component("members", Component.join(JoinConfiguration.separator(Component.text(", ")), displayNames))
));
Placeholder.unparsed("party", party.getPartyName()),
Placeholder.unparsed("password", party.getPartyPassword()),
Placeholder.component("owner", owner == null ? MiniMessage.miniMessage().deserialize("Unknown Owner") : owner.getDisplayName()),
Placeholder.component("members", Component.join(JoinConfiguration.separator(Component.text(", ")), displayNames))
));
}
@Override
@@ -43,7 +43,7 @@ public class Join implements SubCommand {
return;
}
// party.addUser(ChatUserManager.getChatUser(player.getUniqueId())); //Removed until we can get nicknames to translate to colors correctly
// party.addUser(ChatUserManager.getChatUser(player.getUniqueId())); //Removed until we can get nicknames to translate to colors correctly
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
if (chatUser.getPartyId() == party.getPartyId()) {
@@ -52,11 +52,12 @@ public class Join implements SubCommand {
}
party.addUser(chatUser, player.getUsername());
source.sendMessage(Utility.parseMiniMessage(Config.JOINED_PARTY, Placeholder.parsed("party_name", party.getPartyName())));
VelocityChat.getPlugin().getChatHandler().sendPartyMessage(party,
Utility.parseMiniMessage(Config.PLAYER_JOINED_PARTY,
Placeholder.component("player_name", chatUser.getDisplayName()),
Placeholder.parsed("party_name", party.getPartyName())
), null);
VelocityChat.getPlugin().getChatHandler()
.sendPartyMessage(party,
Utility.parseMiniMessage(Config.PLAYER_JOINED_PARTY,
Placeholder.component("player_name", chatUser.getDisplayName()),
Placeholder.parsed("party_name", party.getPartyName())
).asComponent(), null);
}
@Override
@@ -35,28 +35,31 @@ public class Leave implements SubCommand {
return;
}
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty())
if (currentServer.isEmpty()) {
return;
}
party.removeUser(player.getUniqueId());
if (party.getOwnerUuid().equals(player.getUniqueId())) {
if (party.getPartyUsers().size() > 0) {
if (!party.getPartyUsers().isEmpty()) {
UUID uuid = party.setNewOwner();
source.sendMessage(Utility.parseMiniMessage(Config.NOTIFY_FINDING_NEW_OWNER));
VelocityChat.getPlugin().getChatHandler().sendPartyMessage(party,
Utility.parseMiniMessage(Config.OWNER_LEFT_PARTY,
Placeholder.unparsed("old_owner", player.getUsername()),
Placeholder.unparsed("new_owner", party.getPartyUser(uuid).getPlayerName())
), null);
VelocityChat.getPlugin().getChatHandler()
.sendPartyMessage(party,
Utility.parseMiniMessage(Config.OWNER_LEFT_PARTY,
Placeholder.unparsed("old_owner", player.getUsername()),
Placeholder.unparsed("new_owner", party.getPartyUser(uuid).getPlayerName())
).asComponent(), null);
} else {
party.delete();
}
} else {
source.sendMessage(Utility.parseMiniMessage(Config.LEFT_PARTY));
VelocityChat.getPlugin().getChatHandler().sendPartyMessage(party,
Utility.parseMiniMessage(Config.PLAYER_LEFT_PARTY,
Placeholder.unparsed("player_name", player.getUsername())
), null);
VelocityChat.getPlugin().getChatHandler()
.sendPartyMessage(party,
Utility.parseMiniMessage(Config.PLAYER_LEFT_PARTY,
Placeholder.unparsed("player_name", player.getUsername())
).asComponent(), null);
}
}
@@ -45,18 +45,19 @@ public class Name implements SubCommand {
}
if (PartyManager.getParty(args[1]) != null) {
source.sendMessage(Utility.parseMiniMessage(Config.PARTY_EXISTS,
Placeholder.unparsed("party", args[1])
));
Placeholder.unparsed("party", args[1])
));
return;
}
String oldName = party.getPartyName();
party.setPartyName(args[1]);
VelocityChat.getPlugin().getChatHandler().sendPartyMessage(party, Utility.parseMiniMessage(Config.RENAMED_PARTY,
Placeholder.component("owner", ChatUserManager.getChatUser(player.getUniqueId()).getDisplayName()),
Placeholder.unparsed("old_name", oldName),
Placeholder.unparsed("new_name", args[1])
), null);
VelocityChat.getPlugin().getChatHandler()
.sendPartyMessage(party, Utility.parseMiniMessage(Config.RENAMED_PARTY,
Placeholder.component("owner", ChatUserManager.getChatUser(player.getUniqueId()).getDisplayName()),
Placeholder.unparsed("old_name", oldName),
Placeholder.unparsed("new_name", args[1])
).asComponent(), null);
}
@Override
@@ -14,7 +14,6 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class Owner implements SubCommand {
@Override
@@ -44,31 +43,36 @@ public class Owner implements SubCommand {
PartyUser partyUser = party.getPartyUser(args[1]);
if (partyUser == null) {
source.sendMessage(Utility.parseMiniMessage(Config.NOT_A_PARTY_MEMBER,
Placeholder.unparsed("player", args[1])
));
Placeholder.unparsed("player", args[1])
));
return;
}
party.setNewOwner(partyUser.getUuid());
VelocityChat.getPlugin().getChatHandler().sendPartyMessage(party,
Utility.parseMiniMessage(Config.NEW_PARTY_OWNER,
Placeholder.unparsed("old_owner", player.getUsername()),
Placeholder.unparsed("new_owner", partyUser.getPlayerName())
), null);
VelocityChat.getPlugin().getChatHandler()
.sendPartyMessage(party,
Utility.parseMiniMessage(Config.NEW_PARTY_OWNER,
Placeholder.unparsed("old_owner", player.getUsername()),
Placeholder.unparsed("new_owner", partyUser.getPlayerName())
).asComponent(), null);
}
@Override
public List<String> suggest(String[] args, CommandSource source) {
ArrayList<String> suggest = new ArrayList<>();
if (!(source instanceof Player player))
if (!(source instanceof Player player)) {
return suggest;
}
UUID uuid = player.getUniqueId();
Party party = PartyManager.getParty(uuid);
if (party == null)
if (party == null) {
return suggest;
if (args.length == 1 || args.length == 2)
}
if (args.length == 1 || args.length == 2) {
suggest.addAll(party.getPartyUsers().stream()
.filter(partyUser -> !partyUser.getUuid().equals(uuid))
.map(PartyUser::getPlayerName).collect(Collectors.toList()));
.filter(partyUser -> !partyUser.getUuid().equals(uuid))
.map(PartyUser::getPlayerName)
.toList());
}
return suggest;
}
@@ -10,6 +10,7 @@ import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.NotNull;
@@ -25,12 +26,12 @@ import java.util.stream.Collectors;
public class ActiveVoteToMute {
private static final HashMap<String, ActiveVoteToMute> instances = new HashMap<>();
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>");
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>").asComponent();
private final Player votedPlayer;
private final Player startedByPlayer;
private HashSet<UUID> votedFor = new HashSet<>();
private HashSet<UUID> votedAgainst = new HashSet<>();
private final HashSet<UUID> votedFor = new HashSet<>();
private final HashSet<UUID> votedAgainst = new HashSet<>();
private int totalEligibleVoters;
private final boolean countLowerRanks;
private final RegisteredServer server;
@@ -39,14 +40,16 @@ public class ActiveVoteToMute {
private boolean endedVote = false;
public static Optional<ActiveVoteToMute> getInstance(String username) {
if (!instances.containsKey(username))
if (!instances.containsKey(username)) {
return Optional.empty();
}
return Optional.of(instances.get(username));
}
public static void removePotentialVoter(Player player, RegisteredServer previousServer) {
if (!player.hasPermission("chat.backup-vote-to-mute"))
if (!player.hasPermission("chat.backup-vote-to-mute")) {
return;
}
if (player.hasPermission("chat.vote-to-mute")) {
instances.values().stream()
.filter(activeVoteToMute -> previousServer == null || activeVoteToMute.getServer().getServerInfo().hashCode() == previousServer.getServerInfo().hashCode())
@@ -60,8 +63,9 @@ public class ActiveVoteToMute {
}
public static void addPotentialVoter(Player player, ServerConnection server) {
if (!player.hasPermission("chat.backup-vote-to-mute"))
if (!player.hasPermission("chat.backup-vote-to-mute")) {
return;
}
if (player.hasPermission("chat.vote-to-mute")) {
instances.values().stream()
.filter(activeVoteToMute -> activeVoteToMute.getServer().getServerInfo().hashCode() == server.getServerInfo().hashCode())
@@ -85,7 +89,7 @@ public class ActiveVoteToMute {
instances.put(votedPlayer.getUsername(), this);
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
executorService.schedule(this::endVote,
duration.toMinutes(), TimeUnit.MINUTES);
duration.toMinutes(), TimeUnit.MINUTES);
this.startedByPlayer = startedByPlayer;
}
@@ -94,22 +98,23 @@ public class ActiveVoteToMute {
}
private void endVote() {
if (endedVote)
if (endedVote) {
return;
}
instances.remove(votedPlayer.getUsername());
if (votePassed()) {
mutePlayer();
return;
}
Component message = Utility.parseMiniMessage("<prefix> <red>The vote to mute <player> has failed, they will not be muted.</red>",
Placeholder.component("prefix", prefix), Placeholder.parsed("player", votedPlayer.getUsername()));
ComponentLike message = Utility.parseMiniMessage("<prefix> <red>The vote to mute <player> has failed, they will not be muted.</red>",
Placeholder.component("prefix", prefix), Placeholder.parsed("player", votedPlayer.getUsername()));
server.getPlayersConnected().stream()
.filter(player -> countLowerRanks ? player.hasPermission("chat.backup-vote-to-mute") : player.hasPermission("chat.vote-to-mute"))
.forEach(player -> player.sendMessage(message));
}
public void start() {
Component message = getVoteStartMessage();
ComponentLike message = getVoteStartMessage();
server.getPlayersConnected().stream()
.filter(player -> countLowerRanks ? player.hasPermission("chat.backup-vote-to-mute") : player.hasPermission("chat.vote-to-mute"))
.forEach(player -> player.sendMessage(message));
@@ -147,24 +152,23 @@ public class ActiveVoteToMute {
}
private void mutePlayer() {
Component message = Utility.parseMiniMessage("<prefix> <green>The vote to mute <player> has passed, they will be muted.</green>",
Placeholder.component("prefix", prefix), Placeholder.parsed("player", votedPlayer.getUsername()));
ComponentLike message = Utility.parseMiniMessage("<prefix> <green>The vote to mute <player> has passed, they will be muted.</green>",
Placeholder.component("prefix", prefix), Placeholder.parsed("player", votedPlayer.getUsername()));
server.getPlayersConnected().stream()
.filter(player -> countLowerRanks ? player.hasPermission("chat.backup-vote-to-mute") : player.hasPermission("chat.vote-to-mute"))
.forEach(player -> player.sendMessage(message));
proxyServer.getCommandManager().executeAsync(proxyServer.getConsoleCommandSource(),
String.format("tempmute %s 1h Muted by the community - under review. -p", votedPlayer.getUsername()));
String.format("tempmute %s 1h Muted by the community - under review. -p", votedPlayer.getUsername()));
String chatLogsString = PlainTextComponentSerializer.plainText().serialize(chatLogs);
EmbedBuilder embedBuilder = buildMutedEmbed(chatLogsString);
ALogger.info(String.format("Player %s muted by vote\nLogs:\n%s\n\nVotes for:\n%s\nVotes against:\n%s\n",
votedPlayer.getUsername(),
chatLogsString,
parseUUIDsToPlayerOrString(votedFor),
parseUUIDsToPlayerOrString(votedAgainst)
));
votedPlayer.getUsername(),
chatLogsString,
parseUUIDsToPlayerOrString(votedFor),
parseUUIDsToPlayerOrString(votedAgainst)
));
long id = Config.serverChannelId.get("general");
DiscordLink.getPlugin().getBot().sendEmbedToDiscord(id, embedBuilder, -1);
@@ -177,14 +181,14 @@ public class ActiveVoteToMute {
embedBuilder.setTitle("Player muted by vote");
embedBuilder.setColor(Color.CYAN);
embedBuilder.addField("Logs",
chatLogsString.substring(0, Math.min(chatLogsString.length(), 1024)),
false);
chatLogsString.substring(0, Math.min(chatLogsString.length(), 1024)),
false);
embedBuilder.addField("Server",
server.getServerInfo().getName().substring(0, 1).toUpperCase() + server.getServerInfo().getName().substring(1),
true);
server.getServerInfo().getName().substring(0, 1).toUpperCase() + server.getServerInfo().getName().substring(1),
true);
embedBuilder.addField("Started by",
String.format("Username: %s\nUUID: %s", startedByPlayer.getUsername(), startedByPlayer.getUniqueId().toString()),
true);
String.format("Username: %s\nUUID: %s", startedByPlayer.getUsername(), startedByPlayer.getUniqueId().toString()),
true);
return embedBuilder;
}
@@ -200,25 +204,27 @@ public class ActiveVoteToMute {
public void addEligibleVoter(Player player) {
UUID uuid = player.getUniqueId();
if (votedAgainst.contains(uuid) || votedFor.contains(uuid))
if (votedAgainst.contains(uuid) || votedFor.contains(uuid)) {
return;
}
totalEligibleVoters++;
player.sendMessage(getVoteStartMessage());
}
public void removeEligibleVoter(UUID uuid) {
if (votedFor.contains(uuid) || votedAgainst.contains(uuid))
if (votedFor.contains(uuid) || votedAgainst.contains(uuid)) {
return;
}
totalEligibleVoters--;
}
private Component getVoteStartMessage() {
private ComponentLike getVoteStartMessage() {
return Utility.parseMiniMessage(
String.format("""
<prefix> <green>A vote to mute <player> for one hour has been started, please read the logs below before voting.</green>
<logs>
<prefix> Click: <click:run_command:'/votetomutehelper vote %s yes'><red>Mute</red></click> --- <click:run_command:'/votetomutehelper vote %s no'><yellow>Don't mute</yellow></click>""",
votedPlayer.getUsername(), votedPlayer.getUsername()),
<prefix> <green>A vote to mute <player> for one hour has been started, please read the logs below before voting.</green>
<logs>
<prefix> Click: <click:run_command:'/votetomutehelper vote %s yes'><red>Mute</red></click> --- <click:run_command:'/votetomutehelper vote %s no'><yellow>Don't mute</yellow></click>""",
votedPlayer.getUsername(), votedPlayer.getUsername()),
Placeholder.component("prefix", prefix),
Placeholder.parsed("player", votedPlayer.getUsername()),
Placeholder.component("logs", chatLogs));
@@ -18,7 +18,7 @@ public class VoteToMuteStarter {
private static final HashMap<UUID, VoteToMuteStarter> instanceMap = new HashMap<>();
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>");
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>").asComponent();
private final ChatLogHandler chatLogHandler;
private final Player votedPlayer;
private final Player commandSource;
@@ -27,8 +27,9 @@ public class VoteToMuteStarter {
private final boolean countLowerRanks;
public static Optional<VoteToMuteStarter> getInstance(UUID uuid) {
if (!instanceMap.containsKey(uuid))
if (!instanceMap.containsKey(uuid)) {
return Optional.empty();
}
return Optional.of(instanceMap.get(uuid));
}
@@ -45,14 +46,14 @@ public class VoteToMuteStarter {
chatLogHandler.retrieveChatLogs(votedPlayer.getUniqueId(), Duration.ofMinutes(10), serverName).whenCompleteAsync((chatLogs, throwable) -> {
if (throwable != null) {
commandSource.sendMessage(Utility.parseMiniMessage("<prefix> <red>Unable to retrieve messages</red> for player <player>",
Placeholder.component("prefix", prefix),
Placeholder.parsed("player", votedPlayer.getUsername())));
Placeholder.component("prefix", prefix),
Placeholder.parsed("player", votedPlayer.getUsername())));
return;
}
parseChatLogs(chatLogs);
commandSource.sendMessage(Utility.parseMiniMessage(
"<prefix> <green>Please select up to 10 messages other players should see to decide their vote, seperated by comma's. " +
"Example: <gold>/votetomutehelper messages 1, 2, 5, 8</gold></green>", Placeholder.component("prefix", prefix)));
"Example: <gold>/votetomutehelper messages 1, 2, 5, 8</gold></green>", Placeholder.component("prefix", prefix)));
showPage(1);
});
}
@@ -63,13 +64,14 @@ public class VoteToMuteStarter {
chatLogs.sort(Comparator.comparing(ChatLog::getTimestamp).reversed());
parsedChatLogs = IntStream.range(0, chatLogs.size())
.mapToObj(i -> Utility.parseMiniMessage(
"<number>. <prefix> <player>: <message>",
TagResolver.resolver(
Placeholder.unparsed("message", chatLogs.get(i).getMessage()),
Placeholder.parsed("number", String.valueOf(i + 1)),
playerTag, prefixTag
))
)
"<number>. <prefix> <player>: <message>",
TagResolver.resolver(
Placeholder.unparsed("message", chatLogs.get(i).getMessage()),
Placeholder.parsed("number", String.valueOf(i + 1)),
playerTag,
prefixTag
)).asComponent()
)
.toList();
}
@@ -89,16 +91,17 @@ public class VoteToMuteStarter {
.append("><hover:show_text:'<gold>Click to go to next page'><gold><next page></gold></hover></click> ");
}
commandSource.sendMessage(Utility.parseMiniMessage(stringBuilder.toString(),
Placeholder.parsed("player", votedPlayer.getUsername()),
Placeholder.component("prefix", prefix),
Placeholder.component("logs", chatLogsComponent)));
Placeholder.parsed("player", votedPlayer.getUsername()),
Placeholder.component("prefix", prefix),
Placeholder.component("logs", chatLogsComponent)));
}
/**
* Retrieves the chat logs for the given list of IDs. It removes 1 from the IDs before using them
* It removes the instance from the hashmap after this function call
* Retrieves the chat logs for the given list of IDs. It removes 1 from the IDs before using them It removes the
* instance from the hashmap after this function call
*
* @param ids A list of integers representing the IDs of the chat logs to retrieve.
*
* @return A Component object containing the selected chat logs joined by newlines.
*/
public Component getChatLogsAndClose(List<Integer> ids) {
@@ -3,7 +3,7 @@ package com.alttd.velocitychat.data;
import com.alttd.chat.config.ServerConfig;
import com.alttd.chat.managers.ChatUserManager;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import java.util.UUID;
@@ -31,8 +31,7 @@ public class ServerWrapper {
return serverName;
}
public boolean globalChat()
{
public boolean globalChat() {
return globalChat;
}
@@ -40,10 +39,11 @@ public class ServerWrapper {
return joinMessages;
}
public void sendJoinLeaveMessage(UUID uuid, Component component) {
if(joinMessages())
public void sendJoinLeaveMessage(UUID uuid, ComponentLike component) {
if (joinMessages()) {
getRegisteredServer().getPlayersConnected().stream()
.filter(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers().contains(uuid))
.forEach(p -> p.sendMessage(component));
}
}
}
@@ -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,29 +16,50 @@ 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;
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.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.*;
import java.util.Date;
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);
Optional<Player> optionalPlayer = VelocityChat.getPlugin().getProxy().getPlayer(uuid);
if(optionalPlayer.isEmpty()) return;
if (optionalPlayer.isEmpty()) {
return;
}
Player player = optionalPlayer.get();
Optional<Player> optionalPlayer2 = VelocityChat.getPlugin().getProxy().getPlayer(target);
if(optionalPlayer2.isEmpty()) return;
if (optionalPlayer2.isEmpty()) {
return;
}
Player player2 = optionalPlayer2.get();
ChatUser targetUser = ChatUserManager.getChatUser(player2.getUniqueId());
@@ -46,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;
if(player.getCurrentServer().isPresent() && player2.getCurrentServer().isPresent()) {
//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);
.replaceAll("<receivername>", player2.getUsername()), Placeholders
).asComponent();
ByteArrayDataOutput buf = ByteStreams.newDataOutput();
buf.writeUTF("privatemessageout");
buf.writeUTF(player.getUniqueId().toString());
@@ -67,7 +98,8 @@ public class ChatHandler {
serverConnection = player2.getCurrentServer().get();
component = Utility.parseMiniMessage(Config.MESSAGERECIEVER
.replaceAll("<sendername>", player.getUsername())
.replaceAll("<receivername>", player2.getUsername()), Placeholders);
.replaceAll("<receivername>", player2.getUsername()), Placeholders
).asComponent();
buf = ByteStreams.newDataOutput();
buf.writeUTF("privatemessagein");
buf.writeUTF(player2.getUniqueId().toString());
@@ -86,24 +118,24 @@ public class ChatHandler {
Placeholder.unparsed("target", (target.isEmpty() ? " tried to say: " : " -> " + target + ": ")),
Placeholder.unparsed("input", input)
);
Component blockedNotification = Utility.parseMiniMessage(Config.NOTIFICATIONFORMAT, Placeholders);
ComponentLike blockedNotification = Utility.parseMiniMessage(Config.NOTIFICATIONFORMAT, Placeholders);
serverConnection.getServer().getPlayersConnected().forEach(pl ->{
serverConnection.getServer().getPlayersConnected().forEach(pl -> {
if (pl.hasPermission("chat.alert-blocked")) {
pl.sendMessage(blockedNotification);
}
});
player.sendMessage(Utility.parseMiniMessage("<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>"));
"this constitutes as your only warning. Any further attempts at bypassing the filter will result in staff intervention.</red>"));
}
public void sendPartyMessage(Party party, Component message, @Nullable List<UUID> ignoredPlayers)
{
public void sendPartyMessage(Party party, Component message, @Nullable List<UUID> ignoredPlayers) {
VelocityChat.getPlugin().getProxy().getAllPlayers().stream()
.filter(pl -> {
UUID uuid = pl.getUniqueId();
if (ignoredPlayers != null && ignoredPlayers.contains(uuid))
if (ignoredPlayers != null && ignoredPlayers.contains(uuid)) {
return false;
}
return party.getPartyUsers().stream().anyMatch(pu -> pu.getUuid().equals(uuid));
}).forEach(pl -> {
pl.sendMessage(message);
@@ -112,81 +144,178 @@ 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;
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;
}
Component 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)
.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());
Component spyMessage = Utility.parseMiniMessage(Config.PARTY_SPY, Placeholders);
for(Player pl : serverConnection.getServer().getPlayersConnected()) {
if(pl.hasPermission(Config.SPYPERMISSION) && !party.getPartyUsersUuid().contains(pl.getUniqueId())) {
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) {
Component component = GsonComponentSerializer.gson().deserialize(message);
VelocityChat.getPlugin().getProxy().getAllPlayers().stream().filter(target -> target.hasPermission("command.chat.globaladminchat")/*TODO permission*/).forEach(target -> {
target.sendMessage(component);
});
VelocityChat.getPlugin().getProxy().getAllPlayers()
.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) {
Component senderName = Component.text(Config.CONSOLENAME);
ComponentLike senderName = Component.text(Config.CONSOLENAME);
String serverName = "Altitude";
if (commandSource instanceof Player) {
Player sender = (Player) commandSource;
ChatUser user = ChatUserManager.getChatUser(sender.getUniqueId());
if(user == null) return;
senderName = user.getDisplayName();
serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer().get().getServerInfo().getName() : "Altitude";
if (commandSource instanceof Player sender) {
ChatUser chatUser = ChatUserManager.getChatUser(sender.getUniqueId());
if (chatUser == null) {
return;
}
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)
);
Component component = Utility.parseMiniMessage(Config.GACFORMAT, Placeholders);
ComponentLike component = Utility.parseMiniMessage(Config.GACFORMAT, Placeholders);
VelocityChat.getPlugin().getProxy().getAllPlayers().stream().filter(target -> target.hasPermission("command.chat.globaladminchat")/*TODO permission*/).forEach(target -> {
target.sendMessage(component);
});
VelocityChat.getPlugin().getProxy().getAllPlayers()
.stream()
.filter(target -> target.hasPermission("command.chat.globaladminchat"))
.forEach(target -> target.sendMessage(component));
}
public void sendMail(CommandSource commandSource, String recipient, String message) {
UUID uuid = Config.CONSOLEUUID;;
UUID uuid = Config.CONSOLEUUID;
String senderName = Config.CONSOLENAME;
UUID targetUUID;
if (commandSource instanceof Player player) {
@@ -197,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 {
@@ -205,6 +335,10 @@ public class ChatHandler {
}
Mail mail = new Mail(targetUUID, uuid, message);
ChatUser chatUser = ChatUserManager.getChatUser(targetUUID);
if (chatUser.getIgnoredPlayers().contains(uuid)) {
commandSource.sendMessage(Utility.parseMiniMessage("<red>You cannot mail this player</red>"));
return;
}
chatUser.addMail(mail);
// TODO load from config
String finalSenderName = senderName;
@@ -244,7 +378,7 @@ public class ChatHandler {
}
private Component parseMails(List<Mail> mails, boolean mark) {
Component component = Utility.parseMiniMessage(Config.mailHeader);
Component component = Utility.parseMiniMessage(Config.mailHeader).asComponent();
for (Mail mail : mails) {
if (mail.isUnRead() && mark) {
mail.setReadTime(System.currentTimeMillis());
@@ -257,29 +391,17 @@ 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()))
)
);
Component mailMessage = Utility.parseMiniMessage(Config.mailBody, Placeholders);
ComponentLike mailMessage = Utility.parseMiniMessage(Config.mailBody, Placeholders);
component = component.append(Component.newline()).append(mailMessage);
}
component = component.append(Component.newline()).append(Utility.parseMiniMessage(Config.mailFooter));
return component;
}
public void partyChat(String partyId, UUID uuid, Component message) {
Party party = PartyManager.getParty(Integer.parseInt(partyId));
if (party == null) {
ALogger.warn("Received a non existent party");
return;
}
List<UUID> ignoredPlayers = ChatUserManager.getChatUser(uuid).getIgnoredPlayers();
List<UUID> partyUsersUuid = party.getPartyUsersUuid();
VelocityChat.getPlugin().getProxy().getAllPlayers().stream()
.filter(p -> partyUsersUuid.contains(p.getUniqueId()))
.filter(p -> !ignoredPlayers.contains(p.getUniqueId()))
.forEach(p -> p.sendMessage(message));
}
public void mutePlayer(String uuid, boolean muted) {
ByteArrayDataOutput buf = ByteStreams.newDataOutput();
buf.writeUTF("chatpunishments");
@@ -289,26 +411,44 @@ public class ChatHandler {
private String getTimeAgo(Duration duration) {
StringBuilder stringBuilder = new StringBuilder();
if (duration.toDays() != 0)
if (duration.toDays() != 0) {
stringBuilder.append(duration.toDays()).append("d ");
if (duration.toHoursPart() != 0 || !stringBuilder.isEmpty())
}
if (duration.toHoursPart() != 0 || !stringBuilder.isEmpty()) {
stringBuilder.append(duration.toHoursPart()).append("h ");
}
stringBuilder.append(duration.toMinutesPart()).append("m ago");
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()) {
for (ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
component = component.replaceText(
TextReplacementConfig.builder()
.times(Config.EMOTELIMIT)
@@ -317,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,24 +5,20 @@ 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.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.util.ArrayList;
import java.util.List;
// TODO code CLEANUP
public class ChatListener {
private VelocityChat plugin;
private final VelocityChat plugin;
public ChatListener() {
plugin = VelocityChat.getPlugin();
}
@Subscribe(order = PostOrder.FIRST)
@Subscribe(priority = 0)
public void onGlobalStaffChat(GlobalAdminChatEvent event) {
String senderName = Config.CONSOLENAME;
String serverName = "Altitude";
@@ -30,18 +26,20 @@ public class ChatListener {
if (commandSource instanceof Player) {
Player sender = (Player) event.getSender();
senderName = sender.getUsername();
serverName = sender.getCurrentServer().isPresent() ? sender.getCurrentServer().get().getServerInfo().getName() : "Altitude";
serverName = sender.getCurrentServer().isPresent()
? sender.getCurrentServer().get().getServerInfo().getName() : "Altitude";
}
Component message = Utility.parseMiniMessage(Config.GACFORMAT,
Placeholder.parsed("sender", senderName),
Placeholder.component("message", Utility.parseMiniMessage(event.getMessage())),
Placeholder.parsed("server", serverName)
);
ComponentLike message = Utility.parseMiniMessage(Config.GACFORMAT,
Placeholder.parsed("sender", senderName),
Placeholder.component("message", Utility.parseMiniMessage(event.getMessage())),
Placeholder.parsed("server", serverName)
);
plugin.getProxy().getAllPlayers().stream().filter(target -> target.hasPermission("command.chat.globaladminchat")).forEach(target -> {
target.sendMessage(message);
});
plugin.getProxy().getAllPlayers()
.stream()
.filter(target -> target.hasPermission("command.chat.globaladminchat"))
.forEach(target -> target.sendMessage(message));
}
}
@@ -1,17 +1,16 @@
package com.alttd.velocitychat.listeners;
import com.alttd.chat.config.Config;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.PartyManager;
import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.Mail;
import com.alttd.chat.objects.Party;
import com.alttd.chat.util.Utility;
import com.alttd.velocitychat.VelocityChat;
import com.alttd.chat.config.Config;
import com.alttd.velocitychat.commands.vote_to_mute.ActiveVoteToMute;
import com.alttd.velocitychat.data.ServerWrapper;
import com.alttd.velocitychat.handlers.ServerHandler;
import com.alttd.chat.managers.PartyManager;
import com.alttd.chat.objects.Party;
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;
@@ -21,64 +20,75 @@ import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.adventure.text.Component;
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 net.kyori.adventure.title.Title;
import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
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();
Party party = PartyManager.getParty(event.getPlayer().getUniqueId());
if (party == null) return;
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
if (chatUser == null)
if (party == null) {
return;
}
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
if (chatUser == null) {
return;
}
VelocityChat.getPlugin().getChatHandler().sendPartyMessage(party,
Utility.parseMiniMessage(Config.PARTY_MEMBER_LOGGED_ON,
Placeholder.component("player", chatUser.getDisplayName())
),
chatUser.getIgnoredPlayers());
Utility.parseMiniMessage(Config.PARTY_MEMBER_LOGGED_ON,
Placeholder.component("player", chatUser.getDisplayName())
).asComponent(),
chatUser.getIgnoredPlayers());
// TODO setup ChatUser on Proxy
//VelocityChat.getPlugin().getChatHandler().addPlayer(new ChatPlayer(event.getPlayer().getUniqueId()));
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();
if (previousServer != null) {
ActiveVoteToMute.removePotentialVoter(player, previousServer);
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty())
if (currentServer.isEmpty()) {
return;
}
ActiveVoteToMute.addPotentialVoter(player, currentServer.get());
return;
}
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty())
if (currentServer.isEmpty()) {
return;
}
ActiveVoteToMute.addPotentialVoter(player, currentServer.get());
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
List<Mail> unReadMail = chatUser.getUnReadMail();
if (unReadMail.isEmpty())
if (unReadMail.isEmpty()) {
return;
}
VelocityChat plugin = VelocityChat.getPlugin();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
if (!player.isActive())
if (!player.isActive()) {
return;
}
Component message = Utility.parseMiniMessage(Config.mailUnread,
Placeholder.unparsed("amount", String.valueOf(unReadMail.size())));
ComponentLike message = Utility.parseMiniMessage(Config.mailUnread,
Placeholder.unparsed("amount", String.valueOf(unReadMail.size())));
player.sendMessage(message);
player.showTitle(Title.title(message, Component.empty()));
player.showTitle(Title.title(message.asComponent(), Component.empty()));
}).delay(Config.mailDisplayDelay * 50L, TimeUnit.MILLISECONDS).schedule();
}
@@ -88,24 +98,25 @@ public class ProxyPlayerListener {
ActiveVoteToMute.removePotentialVoter(event.getPlayer(), null);
UUID uuid = event.getPlayer().getUniqueId();
Party party = PartyManager.getParty(event.getPlayer().getUniqueId());
if (party == null) return;
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
if (chatUser == null)
if (party == null) {
return;
}
ChatUser chatUser = ChatUserManager.getChatUser(uuid);
if (chatUser == null) {
return;
}
VelocityChat.getPlugin().getChatHandler().sendPartyMessage(party,
Utility.parseMiniMessage(Config.PARTY_MEMBER_LOGGED_OFF,
Placeholder.component("player", chatUser.getDisplayName())
),
chatUser.getIgnoredPlayers());
Utility.parseMiniMessage(Config.PARTY_MEMBER_LOGGED_OFF,
Placeholder.component("player", chatUser.getDisplayName())
).asComponent(),
chatUser.getIgnoredPlayers());
// TODO setup ChatUser on Proxy
//VelocityChat.getPlugin().getChatHandler().removePlayer(event.getPlayer().getUniqueId());
}
private static final HashSet<UUID> silentJoin = new HashSet<>();
public static void addSilentJoin(UUID uuid)
{
public static void addSilentJoin(UUID uuid) {
silentJoin.add(uuid);
}
@@ -122,11 +133,11 @@ public class ProxyPlayerListener {
Placeholder.parsed("player", player.getUsername()),
Placeholder.parsed("from_server", previousServer.getServerInfo().getName()),
Placeholder.parsed("to_server", event.getServer().getServerInfo().getName())
);
);
if (silentJoin.remove(uuid)) {
Component message = Utility.parseMiniMessage(Config.SILENT_JOIN_JOINED_FROM,
placeholders);
ComponentLike message = Utility.parseMiniMessage(Config.SILENT_JOIN_JOINED_FROM,
placeholders);
event.getServer().getPlayersConnected().stream()
.filter(player1 -> player1.hasPermission("command.chat.silent-join-notify"))
.forEach(player1 -> player1.sendMessage(message));
@@ -134,17 +145,17 @@ public class ProxyPlayerListener {
}
ServerWrapper wrapper = serverHandler.getWrapper(previousServer.getServerInfo().getName());
if(wrapper != null) {
if (wrapper != null) {
wrapper.sendJoinLeaveMessage(uuid, Utility.parseMiniMessage(Config.SERVERSWTICHMESSAGETO, placeholders));
}
wrapper = serverHandler.getWrapper(event.getServer().getServerInfo().getName());
if(wrapper != null) {
if (wrapper != null) {
wrapper.sendJoinLeaveMessage(uuid, Utility.parseMiniMessage(Config.SERVERSWTICHMESSAGEFROM, placeholders));
}
} else {
if (silentJoin.remove(uuid)) {
Component message = Utility.parseMiniMessage(Config.SILENT_JOIN_JOINED,
Placeholder.unparsed("player", player.getUsername()));
ComponentLike message = Utility.parseMiniMessage(Config.SILENT_JOIN_JOINED,
Placeholder.unparsed("player", player.getUsername()));
event.getServer().getPlayersConnected().stream()
.filter(player1 -> player1.hasPermission("command.chat.silent-join-notify"))
.forEach(player1 -> player1.sendMessage(message));
@@ -152,7 +163,7 @@ public class ProxyPlayerListener {
}
ServerWrapper wrapper = serverHandler.getWrapper(event.getServer().getServerInfo().getName());
if(wrapper != null) {
if (wrapper != null) {
wrapper.sendJoinLeaveMessage(uuid, Utility.parseMiniMessage(Config.SERVERJOINMESSAGE, Placeholder.unparsed("player", player.getUsername())));
}
}
@@ -165,11 +176,11 @@ public class ProxyPlayerListener {
RegisteredServer registeredServer = event.getPlayer().getCurrentServer().get().getServer();
ServerWrapper wrapper = serverHandler.getWrapper(registeredServer.getServerInfo().getName());
if(wrapper != null) {
if (wrapper != null) {
wrapper.sendJoinLeaveMessage(event.getPlayer().getUniqueId(), Utility.parseMiniMessage(Config.SERVERLEAVEMESSAGE,
Placeholder.unparsed("player", event.getPlayer().getUsername()),
Placeholder.unparsed("from_server", registeredServer.getServerInfo().getName())
));
Placeholder.unparsed("player", event.getPlayer().getUsername()),
Placeholder.unparsed("from_server", registeredServer.getServerInfo().getName())
));
}
}
}
@@ -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"));
}
}
+66
View File
@@ -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")
}
+144
View File
@@ -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
}