59 Commits
Author SHA1 Message Date
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
auto f1b8af6136 Merge branch 'ignore_fix' 2025-03-21 20:35:06 +01:00
auto 9e437079e4 Remove Galaxy dependency
Switched back to paper since we don't use any features from Galaxy that aren't already in paper
2025-03-21 20:34:45 +01:00
Len bfc9af69d0 Fix URL clicking in chat. 2025-02-09 18:04:55 +01:00
Len ea705c879f Allow nick request from nick preview message 2025-02-07 20:45:21 +01:00
Len f5c0a0676c Delay unread mail message and also send it as a title message 2025-02-07 20:44:59 +01:00
stijn 9bd9e21321 Merge pull request 'vote_mute' (#2) from vote_mute into main
Reviewed-on: #2
2025-01-22 20:45:21 +00:00
stijn 1c570fd524 Merge branch 'main' into vote_mute 2025-01-22 20:43:24 +00:00
stijn 131ea6da9d Merge pull request 'ignore_fix' (#1) from ignore_fix into main
Reviewed-on: #1
2025-01-22 20:43:13 +00:00
auto 45eceaf4a6 Merge branch 'main' into ignore_fix 2025-01-02 00:37:11 +01:00
auto 3275db13d0 Merge branch 'main' into vote_mute 2025-01-02 00:36:36 +01:00
auto c5eebb7e88 Merge remote-tracking branch 'origin/main' 2025-01-02 00:36:11 +01:00
auto 5e34e6e7fa Don't ignore users with bypass permission
Refactored chat logic to respect 'chat.ignorebypass' permission for both senders and receivers. This only applies to cases where a user is sending text to another user, checks for login/out messages were not fixed
2025-01-02 00:30:55 +01:00
auto c5c60d25b1 Cleanup 2025-01-02 00:22:03 +01:00
Len 804e619e8a Add workaround to display nickname when tagging a player in chat. 2024-10-06 19:29:31 +02:00
auto b3e545a0c8 Log custom messages sent in custom channels
Added a log statement to capture and log information whenever a message is sent in a custom channel. This includes the player's name, the channel name, and the message content for better monitoring and debugging.
2024-09-25 20:03:12 +02:00
Len 773638c222 Add Jenkinsfile 2024-08-04 22:28:28 +02:00
StijnandGitHub 73e67eed6c Vote mute (#3)
* Implement chat log handler with database support

The code changes introduce the ability to log chat messages. A new ChatLogHandler class has been added that manages the queue of chat log messages, both storing them in memory and writing them to a database. New columns have been added to the database and the interactivity with the database is handled using prepared statements to improve security and performance. The chat messages are deleted from the database after a certain period, which can be configured.

* Add start of VoteToMute functionality in chat system

Implemented VoteToMute system enabling initiated voting for muting a player in chat. This includes creating new classes "ActiveVoteToMute", "VoteToMute", and "VoteToMuteStarter". The "VoteToMute" class handles the voting command logic, it allows players to vote on whether to mute other players. The code also adds a call to register this new command in the main VelocityChat class.

* Replace Object2ObjectOpenHashMap with HashMap

The usage of Object2ObjectOpenHashMap in storing chat logs and the chat log handler was switched to HashMap. This was done to ensure the plugin can be run on proxy as velocity does not include this library

* Add VoteToMuteHelper and enhance ActiveVoteToMute

Implemented a new module titled VoteToMuteHelper to enhance the voting system and augment the user experience. This module enhances the system by providing relevant player suggestions and setting up the mute player. Made updates to the ActiveVoteToMute module to handle potential voters and mute the player if the vote is passed. VoteToMute module is also updated to include the total eligible players. The code is made robust by adding appropriate error checks.

* Enable optional logging in ChatLogHandler

Modified the ChatLogHandler to support optional logging by introducing a new argument in the getInstance() method. This argument sets the logging state during instantiation. This facilitates better flexibility when using the ChatLogHandler across different sections of the code base as logging requirements may differ.

* Add mute vote results sent to Discord and staff presence check

Enhanced the vote-to-mute feature by adding a function that sends the mute vote results to a general channel on Discord. A 'staff presence' check has also been added which prevents the mute vote from being initiated if a staff member is online, instead, it prompts users to directly contact a staff member for help.

* Fix chat log message deletion query

Corrected the SQL query in the `deleteOldMessages` method within `ChatLogQueries.java`. Originally, it was incorrectly deleting newer messages rather than older ones due to an incorrect comparison symbol. It has now been adjusted to properly delete older messages based on the provided duration.

* Add player muted logging and abstract embed building

The update introduces a log entry indicating when a player has been muted due to voting. The embed creation for this process has been isolated and extracted into a separate function. This contributes to better code modularity and organization.

* Implement logging for ChatLogHandler

The ChatLogHandler now includes logging for better troubleshooting and understanding of the server state. Logging triggers when chat logging is disabled, when it starts, and also if there's a failure in saving chat messages to the database.

* Refactor death message display in PlayerListener

The commit refactors the method for displaying player death messages in the PlayerListener class. Specifically, it adds functionality to replace usernames with display names in death notifications. More descriptive death messages with themed colors and icons are now shown.

* Refine ChatPlugin and improve "vote to mute" logic

Modified ChatPlugin to include "thisPlugin" within the ShutdownListener initialization. Additionally, adjusted the mute vote failure message color from green to red in ActiveVoteToMute file and added a condition to return false if there are no votes. Also, made improvements to the pagination logic in the VoteToMuteStarter file. Lastly, improved the chat logging mechanics in ChatLogHandler by adding and refining various log information for capturing action details.

* Update vote validation and argument naming

Updated the method "votePassed" in "ActiveVoteToMute" to consider a scenario where no votes have been made for muting. Also, corrected the argument name from "yesno" to "yesNo" in "VoteToMuteHelper" to match with its name in the command constructor

* Update vote to mute functionality in chat system

Several changes were made to update the vote to mute functionality in the chat system. The threshold for eligible players online for vote has been changed from 10 to 6. In addition, improvements have been made to prevent the vote from ending prematurely. Lastly, feedback messages to users when they cast their vote and an update to the vote start message format have been implemented.

* Update vote-to-mute feature

The vote-to-mute feature is updated to include information about the vote initiating player. Also, the duration to retrieve chat logs increased from 5 minutes to 10 minutes. Lastly, eligible players are now notified live about the voting progress.

* Fix message validation and chat log order

Corrected conditional logic in VoteToMuteHelper to validate messages properly and adjusted sorting of chat logs in VoteToMuteStarter to display in reverse chronological order. The update now accurately verifies the existence of selected messages and presents recent logs first for more user-friendly navigation.
2024-07-27 23:16:18 +02:00
Len c9e819645a Update build.gradle.kts 2024-07-27 22:33:39 +02:00
Len 33dc113b36 Revert "Remove chat decorators"
This reverts commit bd48c3f0ca.
2024-07-27 16:05:15 +02:00
Len ba8ba373fd Remove CMI 2024-07-27 16:05:11 +02:00
Len c3e27edb7e Move down receivers in ChatListener.java 2024-07-27 11:17:34 +02:00
Len bd48c3f0ca Remove chat decorators 2024-07-27 11:03:55 +02:00
Len 71a8c652dc Update to 1.21 2024-07-21 18:57:58 +02:00
auto 0877d05296 Merge remote-tracking branch 'origin/main' 2024-07-19 22:03:03 +02:00
Len e1d6da56d0 Revert "Update gradle-wrapper.properties to 8.8"
This reverts commit 2c3feac367.
2024-07-19 21:09:26 +02:00
Len 13e4966a12 Revert "Update to 1.21?"
This reverts commit 3ea078237d.
2024-07-19 21:09:26 +02:00
auto 340975e99e Change default config file path
The path to the configuration file that was initially set to the user's home directory has been modified. It has been shifted to "/mnt/configs/ChatPlugin".
2024-07-19 20:46:39 +02:00
Len 3ea078237d Update to 1.21? 2024-07-06 11:00:58 +02:00
Len 2c3feac367 Update gradle-wrapper.properties to 8.8 2024-07-06 11:00:58 +02:00
Len c97f5a54d7 invert boolean check 2024-07-06 11:00:58 +02:00
auto a908e32d0b Fix message validation and chat log order
Corrected conditional logic in VoteToMuteHelper to validate messages properly and adjusted sorting of chat logs in VoteToMuteStarter to display in reverse chronological order. The update now accurately verifies the existence of selected messages and presents recent logs first for more user-friendly navigation.
2024-06-04 21:18:57 +02:00
auto 8393d12c6d Update vote-to-mute feature
The vote-to-mute feature is updated to include information about the vote initiating player. Also, the duration to retrieve chat logs increased from 5 minutes to 10 minutes. Lastly, eligible players are now notified live about the voting progress.
2024-05-05 16:14:48 +02:00
auto 782c7df4ef Update vote to mute functionality in chat system
Several changes were made to update the vote to mute functionality in the chat system. The threshold for eligible players online for vote has been changed from 10 to 6. In addition, improvements have been made to prevent the vote from ending prematurely. Lastly, feedback messages to users when they cast their vote and an update to the vote start message format have been implemented.
2024-05-05 15:42:59 +02:00
auto a876a9f77b Update vote validation and argument naming
Updated the method "votePassed" in "ActiveVoteToMute" to consider a scenario where no votes have been made for muting. Also, corrected the argument name from "yesno" to "yesNo" in "VoteToMuteHelper" to match with its name in the command constructor
2024-05-05 00:11:56 +02:00
auto f3147b3256 Refine ChatPlugin and improve "vote to mute" logic
Modified ChatPlugin to include "thisPlugin" within the ShutdownListener initialization. Additionally, adjusted the mute vote failure message color from green to red in ActiveVoteToMute file and added a condition to return false if there are no votes. Also, made improvements to the pagination logic in the VoteToMuteStarter file. Lastly, improved the chat logging mechanics in ChatLogHandler by adding and refining various log information for capturing action details.
2024-05-04 23:41:27 +02:00
auto 65503a02f3 Refactor death message display in PlayerListener
The commit refactors the method for displaying player death messages in the PlayerListener class. Specifically, it adds functionality to replace usernames with display names in death notifications. More descriptive death messages with themed colors and icons are now shown.
2024-04-30 22:15:46 +02:00
auto 7140a0cd78 Merge branch 'main' into vote_mute 2024-04-28 21:38:26 +02:00
auto 99f66c3b4b Prevent empty stack exception in PlayerListener
An additional condition is added to prevent an exception from being thrown when attempting to peek an empty stack in the PlayerListener.java file. This should resolve issues where checking if the playerDeathsStack is before the cut off time caused an empty stack exception error.
2024-04-28 21:38:12 +02:00
auto 187f71d6c3 Implement logging for ChatLogHandler
The ChatLogHandler now includes logging for better troubleshooting and understanding of the server state. Logging triggers when chat logging is disabled, when it starts, and also if there's a failure in saving chat messages to the database.
2024-04-28 21:36:45 +02:00
auto 3b2aa84164 Add player muted logging and abstract embed building
The update introduces a log entry indicating when a player has been muted due to voting. The embed creation for this process has been isolated and extracted into a separate function. This contributes to better code modularity and organization.
2024-04-13 16:52:56 +02:00
auto 0fb497c2f1 Merge branch 'main' into vote_mute 2024-04-13 16:15:56 +02:00
auto de24ca4437 Refactor PlayerListener for mute handling and text prioritization
Improved the PlayerListener class by adjusting event handling priority and adding nullability constraints for PlayerDeathEvent for better program robustness. Also removed a now unused import of the TextComponent class for cleaner codebase management.
2024-04-13 16:15:35 +02:00
auto e351e86bea Add mute handling and text styling in PlayerListener
Enhanced PlayerListener feature by incorporating checks for server mute status before sending death messages. Additionally, styled the death messages by adding text color and italics when mute state is false to improve readability.
2024-04-10 22:02:01 +02:00
auto 192fca3a89 Fix chat log message deletion query
Corrected the SQL query in the `deleteOldMessages` method within `ChatLogQueries.java`. Originally, it was incorrectly deleting newer messages rather than older ones due to an incorrect comparison symbol. It has now been adjusted to properly delete older messages based on the provided duration.
2024-04-07 19:16:32 +02:00
auto d6135f2456 Add mute vote results sent to Discord and staff presence check
Enhanced the vote-to-mute feature by adding a function that sends the mute vote results to a general channel on Discord. A 'staff presence' check has also been added which prevents the mute vote from being initiated if a staff member is online, instead, it prompts users to directly contact a staff member for help.
2024-04-07 18:29:46 +02:00
auto c057c69653 Enable optional logging in ChatLogHandler
Modified the ChatLogHandler to support optional logging by introducing a new argument in the getInstance() method. This argument sets the logging state during instantiation. This facilitates better flexibility when using the ChatLogHandler across different sections of the code base as logging requirements may differ.
2024-04-07 18:11:19 +02:00
auto c25767e473 Add VoteToMuteHelper and enhance ActiveVoteToMute
Implemented a new module titled VoteToMuteHelper to enhance the voting system and augment the user experience. This module enhances the system by providing relevant player suggestions and setting up the mute player. Made updates to the ActiveVoteToMute module to handle potential voters and mute the player if the vote is passed. VoteToMute module is also updated to include the total eligible players. The code is made robust by adding appropriate error checks.
2024-04-07 18:07:20 +02:00
auto 37aa9fdf4c Replace Object2ObjectOpenHashMap with HashMap
The usage of Object2ObjectOpenHashMap in storing chat logs and the chat log handler was switched to HashMap. This was done to ensure the plugin can be run on proxy as velocity does not include this library
2024-04-07 16:32:33 +02:00
auto ed2ba74772 Add start of VoteToMute functionality in chat system
Implemented VoteToMute system enabling initiated voting for muting a player in chat. This includes creating new classes "ActiveVoteToMute", "VoteToMute", and "VoteToMuteStarter". The "VoteToMute" class handles the voting command logic, it allows players to vote on whether to mute other players. The code also adds a call to register this new command in the main VelocityChat class.
2024-04-06 19:39:36 +02:00
auto 44d6e994cc Implement chat log handler with database support
The code changes introduce the ability to log chat messages. A new ChatLogHandler class has been added that manages the queue of chat log messages, both storing them in memory and writing them to a database. New columns have been added to the database and the interactivity with the database is handled using prepared statements to improve security and performance. The chat messages are deleted from the database after a certain period, which can be configured.
2024-04-06 17:25:15 +02:00
auto 4e92285261 Add throwable to warn and error methods in ALogger
This update introduces modifications to the 'ALogger.java' file to handle possible exceptions more efficiently. It extends the warn and error functions to include a Throwable parameter, making error logs more informative for easier debugging.
2024-04-06 15:35:08 +02:00
auto a377bdfe48 Implement player death message limits
The code now includes player death message limits, with a configured maximum number of messages per defined period. This was added in the PlayerListener class and configuration options were placed in Config.java. This change will restrict spamming of death messages.
2024-04-06 13:11:05 +02:00
auto 4af85d0e79 Add option to disable chat filters in private channels
A constructor parameter and corresponding field 'disableInPrivate' have been added to the ChatFilter class. This handles disabling specific chat filters in private channels. The necessary checks have been implemented in RegexManager. The configuration for each filter is also updated in RegexConfig to incorporate this new functionality.
2024-03-31 13:37:05 +02:00
auto 034de90062 Implement evidence for automatic ban for 'punish' filter violations
When a user who violate the 'punish' filter rules is automatically banned, there will now be a post about it in the #evidence channel.
2024-03-02 20:41:59 +01:00
auto bd8fa02f1e Add 'punish' filter and automatic banning functionality
Extended the RegexManager filterText method to include a 'punish' case that triggers an automatic ban for users who violate the filter. This commit also updates the PluginMessageListener to handle 'punish' commands, thus completing the execution of an auto-ban function.
2024-03-02 19:18:13 +01:00
47 changed files with 1637 additions and 195 deletions
Vendored
+20
View File
@@ -0,0 +1,20 @@
pipeline {
agent any
stages {
stage('Gradle') {
steps {
sh './gradlew build'
}
}
stage('Archive') {
steps {
archiveArtifacts artifacts: 'build/libs/', followSymlinks: false
}
}
stage('discord') {
steps {
discordSend description: "Build: ${BUILD_NUMBER}", showChangeset: true, result: currentBuild.currentResult, title: currentBuild.fullProjectName, webhookURL: env.discordwebhook
}
}
}
}
+4 -3
View File
@@ -3,9 +3,10 @@ plugins {
} }
dependencies { dependencies {
compileOnly("com.alttd:Galaxy-API:1.19.2-R0.1-SNAPSHOT") { // compileOnly("com.alttd:Galaxy-API:1.21-R0.1-SNAPSHOT") {
// exclude("net.kyori") // exclude("net.kyori")
} // }
compileOnly("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT")
compileOnly("org.spongepowered:configurate-yaml:4.1.2") // Configurate compileOnly("org.spongepowered:configurate-yaml:4.1.2") // Configurate
compileOnly("net.luckperms:api:5.3") // Luckperms compileOnly("net.luckperms:api:5.3") // Luckperms
} }
@@ -24,4 +25,4 @@ publishing {
credentials(PasswordCredentials::class) credentials(PasswordCredentials::class)
} }
} }
} }
@@ -15,9 +15,9 @@ public abstract interface ChatAPI {
DatabaseConnection getDataBase(); DatabaseConnection getDataBase();
void ReloadConfig(); void reloadConfig();
void ReloadChatFilters(); void reloadChatFilters();
HashMap<String, String> getPrefixes(); 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.DatabaseConnection;
import com.alttd.chat.database.Queries; import com.alttd.chat.database.Queries;
import com.alttd.chat.managers.ChatUserManager; import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.PartyManager;
import com.alttd.chat.managers.RegexManager; import com.alttd.chat.managers.RegexManager;
import com.alttd.chat.util.ALogger;
import net.luckperms.api.LuckPerms; import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider; import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.group.Group; import net.luckperms.api.model.group.Group;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
public class ChatImplementation implements ChatAPI{ public class ChatImplementation implements ChatAPI{
@@ -26,7 +23,7 @@ public class ChatImplementation implements ChatAPI{
public ChatImplementation() { public ChatImplementation() {
instance = this; instance = this;
ReloadConfig(); reloadConfig();
luckPerms = getLuckPerms(); luckPerms = getLuckPerms();
databaseConnection = getDataBase(); databaseConnection = getDataBase();
@@ -57,13 +54,13 @@ public class ChatImplementation implements ChatAPI{
} }
@Override @Override
public void ReloadConfig() { public void reloadConfig() {
Config.init(); Config.init();
loadPrefixes(); loadPrefixes();
} }
@Override @Override
public void ReloadChatFilters() { public void reloadChatFilters() {
RegexManager.initialize(); RegexManager.initialize();
} }
@@ -32,7 +32,7 @@ public final class Config {
public static File CONFIGPATH; public static File CONFIGPATH;
public static void init() { public static void init() {
CONFIGPATH = new File(System.getProperty("user.home") + File.separator + "share" + File.separator + "configs" + File.separator + "ChatPlugin"); CONFIGPATH = new File(File.separator + "mnt" + File.separator + "configs" + File.separator + "ChatPlugin");
CONFIG_FILE = new File(CONFIGPATH, "config.yml"); CONFIG_FILE = new File(CONFIGPATH, "config.yml");
configLoader = YamlConfigurationLoader.builder() configLoader = YamlConfigurationLoader.builder()
.file(CONFIG_FILE) .file(CONFIG_FILE)
@@ -368,6 +368,8 @@ public final class Config {
public static String CUSTOM_CHANNEL_TOGGLED = "<yellow>Toggled <channel> <status>.</yellow>"; public static String CUSTOM_CHANNEL_TOGGLED = "<yellow>Toggled <channel> <status>.</yellow>";
public static Component TOGGLED_ON = null; public static Component TOGGLED_ON = null;
public static Component TOGGLED_OFF = null; public static Component 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() { private static void chatChannels() {
ConfigurationNode node = getNode("chat-channels"); ConfigurationNode node = getNode("chat-channels");
if (node.empty()) { if (node.empty()) {
@@ -383,12 +385,16 @@ public final class Config {
new CustomChannel(channelName, new CustomChannel(channelName,
getString(key + "format", ""), getString(key + "format", ""),
getList(key + "servers", Collections.EMPTY_LIST), getList(key + "servers", Collections.EMPTY_LIST),
getBoolean(key + "proxy", false)); getBoolean(key + "proxy", false),
getBoolean(key + "local", false)
);
} }
CUSTOM_CHANNEL_TOGGLED = getString("chat-channels-messages.channel-toggled", CUSTOM_CHANNEL_TOGGLED); 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>")); 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 SERVERMUTEPERMISSION = "chat.command.mute-server";
@@ -424,6 +430,7 @@ public final class Config {
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 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 mailSent = "<green>Successfully send mail to <player_name></green>: <#2e8b57><message></#2e8b57>";
public static List<String> mailCommandAlias = new ArrayList<>(); public static List<String> mailCommandAlias = new ArrayList<>();
public static int mailDisplayDelay = 5;
private static void mailSettings() { private static void mailSettings() {
mailHeader = getString("settings.mail.header", mailHeader); mailHeader = getString("settings.mail.header", mailHeader);
mailBody = getString("settings.mail.message", mailBody); mailBody = getString("settings.mail.message", mailBody);
@@ -432,6 +439,7 @@ public final class Config {
mailReceived = getString("settings.mail.mail-received", mailReceived); mailReceived = getString("settings.mail.mail-received", mailReceived);
mailUnread = getString("settings.mail.mail-unread", mailUnread); mailUnread = getString("settings.mail.mail-unread", mailUnread);
mailSent = getString("settings.mail.mail-sent", mailSent); mailSent = getString("settings.mail.mail-sent", mailSent);
mailDisplayDelay = getInt("settings.mail.delay", mailDisplayDelay);
} }
public static HashMap<String, Long> serverChannelId = new HashMap<>(); public static HashMap<String, Long> serverChannelId = new HashMap<>();
@@ -503,7 +511,7 @@ public final class Config {
public static String NICK_TOO_SOON = "<red>Please wait <time><red> until requesting a new nickname"; 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_REQUEST_NEW = "<green>New nickname request by <player><green>!";
public static String NICK_TRYOUT = "<white><prefix><white> <nick><gray>: <white>Hi, this is what my new nickname could look like!"; 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_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_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.";
@@ -543,4 +551,20 @@ public final class Config {
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); NICK_CURRENT = getString("nicknames.messages.nick-current", NICK_CURRENT);
} }
public static int DEATH_MESSAGES_MAX_PER_PERIOD = 5;
public static int DEATH_MESSAGES_LIMIT_PERIOD_MINUTES = 15;
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);
}
public static long CHAT_LOG_DELETE_OLDER_THAN_DAYS = 31;
public static long CHAT_LOG_SAVE_DELAY_MINUTES = 5;
private static void chatLogSettings() {
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);
}
} }
@@ -178,13 +178,18 @@ public final class RegexConfig {
String regex = entry.getValue().node("regex").getString(); String regex = entry.getValue().node("regex").getString();
String replacement = entry.getValue().node("replacement").getString(); String replacement = entry.getValue().node("replacement").getString();
List<String> exclusions = entry.getValue().node("exclusions").getList(io.leangen.geantyref.TypeToken.get(String.class), new ArrayList<>()); List<String> exclusions = entry.getValue().node("exclusions").getList(io.leangen.geantyref.TypeToken.get(String.class), new ArrayList<>());
boolean disableInPrivate = false;
ConfigurationNode node = entry.getValue().node("disable-in-private");
if (node != null) {
disableInPrivate = node.getBoolean();
}
if (type == null || type.isEmpty() || regex == null || regex.isEmpty()) { if (type == null || type.isEmpty() || regex == null || regex.isEmpty()) {
ALogger.warn("Filter: " + name + " was set up incorrectly"); ALogger.warn("Filter: " + name + " was set up incorrectly");
} else { } else {
if (replacement == null || replacement.isEmpty()) { if (replacement == null || replacement.isEmpty()) {
replacement = name; replacement = name;
} }
ChatFilter chatFilter = new ChatFilter(name, type, regex, replacement, exclusions); ChatFilter chatFilter = new ChatFilter(name, type, regex, replacement, exclusions, disableInPrivate);
RegexManager.addFilter(chatFilter); RegexManager.addFilter(chatFilter);
} }
} catch(SerializationException ex) { } catch(SerializationException ex) {
@@ -0,0 +1,98 @@
package com.alttd.chat.database;
import com.alttd.chat.objects.chat_log.ChatLog;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.util.ALogger;
import org.jetbrains.annotations.NotNull;
import java.sql.*;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
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"
+ ")";
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);
if (isSuccess) {
connection.commit();
return true;
} else {
connection.rollback();
ALogger.warn("Failed to store messages");
return false;
}
} catch (SQLException sqlException) {
ALogger.error("Failed to store chat messages", sqlException);
throw new CompletionException("Failed to store chat messages", sqlException);
}
});
}
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 = ?";
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, uuid.toString());
preparedStatement.setTimestamp(2, Timestamp.from(Instant.now().minus(duration)));
preparedStatement.setString(3, server);
ResultSet resultSet = preparedStatement.executeQuery();
List<ChatLog> chatLogs = new ArrayList<>();
while (resultSet.next()) {
ChatLog chatLog = chatLogHandler.loadFromResultSet(resultSet);
chatLogs.add(chatLog);
}
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);
}
});
}
public static CompletableFuture<Boolean> deleteOldMessages(Duration duration) {
String query = "DELETE FROM chat_log WHERE time_stamp < ?";
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(query);
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);
}
});
}
}
@@ -2,7 +2,6 @@ package com.alttd.chat.database;
import com.alttd.chat.config.Config; import com.alttd.chat.config.Config;
import com.alttd.chat.util.ALogger;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
@@ -66,6 +65,21 @@ public class DatabaseConnection {
return connection; return connection;
} }
/**
* 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);
connection.setAutoCommit(false);
return connection;
}
/** /**
* Sets the connection for this instance * Sets the connection for this instance
*/ */
@@ -21,6 +21,7 @@ public class Queries {
tables.add("CREATE TABLE IF NOT EXISTS mails (`id` INT NOT NULL AUTO_INCREMENT, `uuid` VARCHAR(36) NOT NULL, `sender` VARCHAR(36) NOT NULL, `message` VARCHAR(256) NOT NULL, `sendtime` BIGINT default 0, `readtime` BIGINT default 0, PRIMARY KEY (`id`))"); tables.add("CREATE TABLE IF NOT EXISTS mails (`id` INT NOT NULL AUTO_INCREMENT, `uuid` VARCHAR(36) NOT NULL, `sender` VARCHAR(36) NOT NULL, `message` VARCHAR(256) NOT NULL, `sendtime` BIGINT default 0, `readtime` BIGINT default 0, PRIMARY KEY (`id`))");
createNicknamesTable(); createNicknamesTable();
createRequestedNicknamesTable(); createRequestedNicknamesTable();
ChatLogQueries.createChatLogTable();
try { try {
Connection connection = DatabaseConnection.getConnection(); Connection connection = DatabaseConnection.getConnection();
@@ -8,10 +8,13 @@ import com.alttd.chat.objects.ModifiableString;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import net.luckperms.api.cacheddata.CachedPermissionData; import net.luckperms.api.cacheddata.CachedPermissionData;
import net.luckperms.api.model.user.User; import net.luckperms.api.model.user.User;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class RegexManager { public class RegexManager {
@@ -43,17 +46,24 @@ public class RegexManager {
} }
public static boolean filterText(String playerName, UUID uuid, ModifiableString modifiableString, String channel) { // TODO loop all objects in the list and check if they violate based on the MATCHER public static boolean filterText(String playerName, UUID uuid, ModifiableString modifiableString, String channel) { // TODO loop all objects in the list and check if they violate based on the MATCHER
return filterText(playerName, uuid, modifiableString, true, channel); return filterText(playerName, uuid, modifiableString, true, channel, null);
} }
public static boolean filterText(String playerName, UUID uuid, ModifiableString modifiableString, boolean matcher, String channel) { public static boolean filterText(String playerName, UUID uuid, ModifiableString modifiableString, boolean matcher, String channel) {
return filterText(playerName, uuid, modifiableString, matcher, channel, null);
}
public static boolean filterText(String playerName, UUID uuid, ModifiableString modifiableString, boolean matcher, String channel, Consumer<FilterType> filterAction) {
User user = ChatAPI.get().getLuckPerms().getUserManager().getUser(uuid); User user = ChatAPI.get().getLuckPerms().getUserManager().getUser(uuid);
if (user == null) { if (user == null) {
ALogger.warn("Tried to check chat filters for a user who doesn't exist in LuckPerms"); ALogger.warn("Tried to check chat filters for a user who doesn't exist in LuckPerms");
return false; return false;
} }
CachedPermissionData permissionData = user.getCachedData().getPermissionData(); CachedPermissionData permissionData = user.getCachedData().getPermissionData();
boolean isPrivate = channel.equals("party");
for(ChatFilter chatFilter : chatFilters) { for(ChatFilter chatFilter : chatFilters) {
if (isPrivate && chatFilter.isDisabledInPrivate())
continue;
switch (chatFilter.getType()) { switch (chatFilter.getType()) {
case CHAT: case CHAT:
break; break;
@@ -74,6 +84,25 @@ public class RegexManager {
chatFilter.replaceMatcher(modifiableString); chatFilter.replaceMatcher(modifiableString);
} }
break; break;
case PUNISH:
if (permissionData.checkPermission("chat.bypass-punish").asBoolean())
break;
if (chatFilter.matches(modifiableString)) {
ALogger.info(playerName + " triggered the punish filter for " + chatFilter.getName()
+ " with: " + modifiableString.string() + ".");
if (filterAction == null){
ALogger.info("No filterAction was provided, not doing anything");
return false;
}
Player player = Bukkit.getPlayer(uuid);
if (player == null) {
ALogger.warn("Tried to punish a player who triggered the filter, but the player is offline.");
return false;
}
filterAction.accept(FilterType.PUNISH);
return false;
}
} }
} }
return true; return true;
@@ -0,0 +1,10 @@
package com.alttd.chat.objects;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public interface BatchInsertable {
void prepareStatement(PreparedStatement preparedStatement) throws SQLException;
}
@@ -17,14 +17,16 @@ public class ChatFilter {
private final Pattern pattern; private final Pattern pattern;
private final String replacement; private final String replacement;
private final List<String> exclusions; private final List<String> exclusions;
private final boolean disableInPrivate;
public ChatFilter(String name, String type, String regex, String replacement, List<String> exclusions) { public ChatFilter(String name, String type, String regex, String replacement, List<String> exclusions, boolean disableInPrivate) {
this.name = name; this.name = name;
this.filterType = FilterType.getType(type); this.filterType = FilterType.getType(type);
this.regex = regex; this.regex = regex;
this.pattern = Pattern.compile(getRegex(), Pattern.CASE_INSENSITIVE); this.pattern = Pattern.compile(getRegex(), Pattern.CASE_INSENSITIVE);
this.replacement = replacement; this.replacement = replacement;
this.exclusions = exclusions; this.exclusions = exclusions;
this.disableInPrivate = disableInPrivate;
} }
public String getName() { public String getName() {
@@ -47,6 +49,10 @@ public class ChatFilter {
return this.exclusions; return this.exclusions;
} }
public boolean isDisabledInPrivate() {
return disableInPrivate;
}
public boolean matches(ModifiableString filterableString) { public boolean matches(ModifiableString filterableString) {
String input = filterableString.string(); String input = filterableString.string();
Matcher matcher = pattern.matcher(input); Matcher matcher = pattern.matcher(input);
@@ -5,7 +5,8 @@ public enum FilterType {
EMOTE("emote"), EMOTE("emote"),
CHAT("chat"), CHAT("chat"),
REPLACEMATCHER("replacematcher"), REPLACEMATCHER("replacematcher"),
BLOCK("block"); BLOCK("block"),
PUNISH("punish");
private final String name; private final String name;
@@ -10,12 +10,14 @@ public class Channel {
protected String channelName; protected String channelName;
protected String format; protected String format;
protected boolean proxy; protected boolean proxy;
protected boolean local;
public Channel(String channelName, String format, boolean proxy) { public Channel(String channelName, String format, boolean proxy, boolean local) {
this.permission = "chat.channel." + channelName.toLowerCase(); this.permission = "chat.channel." + channelName.toLowerCase();
this.channelName = channelName; this.channelName = channelName;
this.format = format; this.format = format;
this.proxy = proxy; this.proxy = proxy;
this.local = local;
channels.put(channelName.toLowerCase(), this); channels.put(channelName.toLowerCase(), this);
} }
@@ -39,6 +41,10 @@ public class Channel {
return proxy; return proxy;
} }
public boolean isLocal() {
return local;
}
public static Channel getChatChannel(String channelName) { public static Channel getChatChannel(String channelName) {
return channels.get(channelName.toLowerCase()); return channels.get(channelName.toLowerCase());
} }
@@ -5,14 +5,13 @@ import java.util.*;
public class CustomChannel extends Channel { public class CustomChannel extends Channel {
private final List<String> servers; private final List<String> servers;
public CustomChannel(String channelName, String format, List<String> servers, boolean proxy) { public CustomChannel(String channelName, String format, List<String> servers, boolean proxy, boolean local) {
super(channelName, format, proxy); super(channelName, format, proxy, local);
this.permission = "chat.channel." + channelName.toLowerCase(); this.permission = "chat.channel." + channelName.toLowerCase();
this.channelName = channelName; this.channelName = channelName;
this.format = format; this.format = format;
this.servers = servers; this.servers = servers;
this.proxy = proxy; this.proxy = proxy;
channels.put(channelName.toLowerCase(), this);
} }
public List<String> getServers() { public List<String> getServers() {
return servers; return servers;
@@ -2,6 +2,6 @@ package com.alttd.chat.objects.channels;
public abstract class DefaultChannel extends Channel{ public abstract class DefaultChannel extends Channel{
public DefaultChannel(String channelName, String format, boolean proxy) { public DefaultChannel(String channelName, String format, boolean proxy) {
super(channelName, format, proxy); super(channelName, format, proxy, false);
} }
} }
@@ -0,0 +1,52 @@
package com.alttd.chat.objects.chat_log;
import com.alttd.chat.objects.BatchInsertable;
import org.jetbrains.annotations.NotNull;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.UUID;
public class ChatLog implements BatchInsertable {
private final UUID uuid;
private final Instant timestamp;
private final String server;
private final String message;
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);
}
public UUID getUuid() {
return uuid;
}
public Instant getTimestamp() {
return timestamp;
}
public String getMessage() {
return message;
}
public boolean isBlocked() {
return blocked;
}
}
@@ -0,0 +1,128 @@
package com.alttd.chat.objects.chat_log;
import com.alttd.chat.config.Config;
import com.alttd.chat.database.ChatLogQueries;
import com.alttd.chat.util.ALogger;
import org.jetbrains.annotations.NotNull;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
public class ChatLogHandler {
private static ChatLogHandler instance = null;
private ScheduledExecutorService executorService = null;
public static ChatLogHandler getInstance(boolean enableLogging) {
if (instance == null)
instance = new ChatLogHandler(enableLogging);
return instance;
}
private boolean isSaving;
private final Queue<ChatLog> chatLogQueue = new ConcurrentLinkedQueue<>();
private final HashMap<UUID, List<ChatLog>> chatLogs = new HashMap<>();
public ChatLogHandler(boolean enableLogging) {
if (!enableLogging) {
ALogger.info("Logging is not enabled on this server.");
return;
}
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()));
} else {
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));
},
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
*/
public void shutDown() {
executorService.shutdown();
saveToDatabase(true);
}
private synchronized void savingToDatabase(boolean saving) {
isSaving = saving;
}
private synchronized boolean isBlocked() {
return isSaving;
}
public synchronized void addLog(ChatLog chatLog) {
if (isBlocked()) {
chatLogQueue.add(chatLog);
} else {
chatLogs.computeIfAbsent(chatLog.getUuid(), k -> new ArrayList<>()).add(chatLog);
}
}
private void saveToDatabase(boolean onMainThread) {
savingToDatabase(true);
ALogger.info(String.format("Saving %d messages to database", chatLogs.size()));
CompletableFuture<Boolean> booleanCompletableFuture = ChatLogQueries.storeMessages(chatLogs);
if (onMainThread) {
booleanCompletableFuture.join();
ALogger.info("Finished saving messages on main thread");
return;
}
booleanCompletableFuture.whenComplete((result, throwable) -> {
if (throwable == null && result) {
chatLogs.clear();
} else {
ALogger.error("Failed to save chat messages.");
}
savingToDatabase(false);
if (!chatLogQueue.isEmpty()) {
ALogger.info("Adding back messages from queue to chatLogs map");
}
while (!chatLogQueue.isEmpty()) {
addLog(chatLogQueue.remove());
}
ALogger.info("Finished saving messages");
});
}
public 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 chatMessage = resultSet.getString("chat_message");
boolean chatMessageBlocked = resultSet.getInt("blocked") == 1;
return new ChatLog(chatLogUUID, chatTimestamp, server, chatMessage, chatMessageBlocked);
}
public void addChatLog(UUID uuid, String server, String message, boolean blocked) {
addLog(new ChatLog(uuid, Instant.now(), server, message, blocked));
}
public CompletableFuture<List<ChatLog>> retrieveChatLogs(UUID uuid, Duration duration, String server) {
List<ChatLog> chatLogList = chatLogs.getOrDefault(uuid, new ArrayList<>());
return ChatLogQueries.retrieveMessages(this, uuid, duration, server)
.thenCompose(chatLogs -> CompletableFuture.supplyAsync(() -> {
chatLogList.addAll(chatLogs);
return chatLogList;
}))
.exceptionally(ex -> {
throw new CompletionException(ex);
});
}
}
@@ -16,6 +16,10 @@ public class ALogger {
logger.warn(message); logger.warn(message);
} }
public static void warn(String message, Throwable throwable) {
logger.warn(message, throwable);
}
public static void info(String message) { public static void info(String message) {
logger.info(message); logger.info(message);
} }
@@ -23,4 +27,8 @@ public class ALogger {
public static void error(String message) { public static void error(String message) {
logger.error(message); logger.error(message);
} }
public static void error(String message, Throwable throwable) {
logger.error(message, throwable);
}
} }
@@ -23,6 +23,7 @@ public class Utility {
private static final List<String> EMPTY_LIST = new ArrayList<>(); 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 DEFAULT_URL_PATTERN = Pattern.compile("(?:(https?)://)?([-\\w_.]+\\.\\w{2,})(/\\S*)?");
static final Pattern URL_SCHEME_PATTERN = Pattern.compile("^[a-z][a-z0-9+\\-.]*:");
private static MiniMessage miniMessage = null; private static MiniMessage miniMessage = null;
@@ -307,6 +308,9 @@ public class Utility {
String url = matcher.group(); String url = matcher.group();
String clickUrl = url; String clickUrl = url;
String urlFormat = Config.URLFORMAT; 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>", url).replaceAll("<clickurl>", clickUrl));
} }
return message; return message;
+2 -15
View File
@@ -1,25 +1,12 @@
plugins { plugins {
`java-library` `java-library`
id("com.github.johnrengelman.shadow") version "7.1.0" id("io.github.goooler.shadow") version "8.1.8"
} }
allprojects { allprojects {
group = "com.alttd.chat" group = "com.alttd.chat"
version = "2.0.0-SNAPSHOT" version = "2.0.0-SNAPSHOT"
description = "All in one minecraft chat plugin" description = "All in one minecraft chat plugin"
// repositories {
// mavenCentral()
// maven("https://repo.destro.xyz/snapshots") // Altitude - Galaxy
// maven("https://oss.sonatype.org/content/groups/public/") // Adventure
// maven("https://oss.sonatype.org/content/repositories/snapshots/") // Minimessage
// maven("https://oss.sonatype.org/content/repositories/") // Minimessage
// maven("https://nexus.velocitypowered.com/repository/") // Velocity
// maven("https://nexus.velocitypowered.com/repository/maven-public/") // Velocity
// maven("https://repo.spongepowered.org/maven") // Configurate
// maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // Papi
// maven("https://jitpack.io")
// }
} }
subprojects { subprojects {
@@ -27,7 +14,7 @@ subprojects {
java { java {
toolchain { toolchain {
languageVersion.set(JavaLanguageVersion.of(17)) languageVersion.set(JavaLanguageVersion.of(21))
} }
} }
+3 -36
View File
@@ -1,58 +1,25 @@
import java.io.FileOutputStream
import java.net.URL
plugins { plugins {
`maven-publish` `maven-publish`
id("com.github.johnrengelman.shadow") id("io.github.goooler.shadow")
id("xyz.jpenilla.run-paper") version "1.0.6"
} }
dependencies { dependencies {
implementation(project(":api")) // API implementation(project(":api")) // API
compileOnly("com.alttd:Galaxy-API:1.20.4-R0.1-SNAPSHOT") // Galaxy // 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("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("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("net.luckperms:api:5.3") // Luckperms
compileOnly(files("../libs/CMI.jar"))
} }
tasks { tasks {
shadowJar { shadowJar {
archiveFileName.set("${rootProject.name}-${project.name}-${project.version}.jar") archiveFileName.set("${rootProject.name}-${project.name}-${project.version}.jar")
// minimize()
} }
build { build {
// setBuildDir("${rootProject.buildDir}")
dependsOn(shadowJar) dependsOn(shadowJar)
} }
runServer {
val dir = File(System.getProperty("user.home") + "/share/devserver/");
if (!dir.parentFile.exists()) {
dir.parentFile.mkdirs()
}
runDirectory.set(dir)
val fileName = "/galaxy.jar"
var file = File(dir.path + fileName)
if (!file.parentFile.exists()) {
file.parentFile.mkdirs()
}
if (!file.exists()) {
download("https://repo.destro.xyz/snapshots/com/alttd/Galaxy-Server/Galaxy-paperclip-1.19.2-R0.1-SNAPSHOT-reobf.jar", file)
}
serverJar(file)
minecraftVersion("1.19.2")
}
} }
fun download(link: String, path: File) {
URL(link).openStream().use { input ->
FileOutputStream(path).use { output ->
input.copyTo(output)
}
}
}
@@ -5,22 +5,23 @@ import com.alttd.chat.config.Config;
import com.alttd.chat.config.ServerConfig; import com.alttd.chat.config.ServerConfig;
import com.alttd.chat.database.DatabaseConnection; import com.alttd.chat.database.DatabaseConnection;
import com.alttd.chat.handler.ChatHandler; import com.alttd.chat.handler.ChatHandler;
import com.alttd.chat.listeners.BookListener; import com.alttd.chat.listeners.*;
import com.alttd.chat.listeners.ChatListener;
import com.alttd.chat.listeners.PlayerListener;
import com.alttd.chat.listeners.PluginMessage;
import com.alttd.chat.nicknames.Nicknames; import com.alttd.chat.nicknames.Nicknames;
import com.alttd.chat.nicknames.NicknamesEvents; import com.alttd.chat.nicknames.NicknamesEvents;
import com.alttd.chat.objects.channels.Channel; import com.alttd.chat.objects.channels.Channel;
import com.alttd.chat.objects.channels.CustomChannel; import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.ServerName;
import com.alttd.chat.util.Utility; import com.alttd.chat.util.Utility;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.util.List; import java.util.List;
public class ChatPlugin extends JavaPlugin { public class ChatPlugin extends JavaPlugin {
@@ -40,8 +41,9 @@ public class ChatPlugin extends JavaPlugin {
chatAPI = new ChatImplementation(); chatAPI = new ChatImplementation();
chatHandler = new ChatHandler(); chatHandler = new ChatHandler();
DatabaseConnection.initialize(); DatabaseConnection.initialize();
serverConfig = new ServerConfig(Bukkit.getServerName()); serverConfig = new ServerConfig(ServerName.getServerName());
registerListener(new PlayerListener(serverConfig), new ChatListener(), new BookListener()); ChatLogHandler chatLogHandler = ChatLogHandler.getInstance(true);
registerListener(new PlayerListener(serverConfig), new ChatListener(chatLogHandler), new BookListener(), new ShutdownListener(chatLogHandler, this));
if(serverConfig.GLOBALCHAT) { if(serverConfig.GLOBALCHAT) {
registerCommand("globalchat", new GlobalChat()); registerCommand("globalchat", new GlobalChat());
registerCommand("toggleglobalchat", new ToggleGlobalChat()); registerCommand("toggleglobalchat", new ToggleGlobalChat());
@@ -58,8 +60,10 @@ public class ChatPlugin extends JavaPlugin {
registerCommand("p", new PartyChat()); registerCommand("p", new PartyChat());
registerCommand("emotes", new Emotes()); registerCommand("emotes", new Emotes());
for (Channel channel : Channel.getChannels()) { for (Channel channel : Channel.getChannels()) {
if (!(channel instanceof CustomChannel customChannel)) continue; if (!(channel instanceof CustomChannel customChannel)) {
this.getServer().getCommandMap().register(channel.getChannelName().toLowerCase(), new ChatChannel(customChannel)); continue;
}
this.getServer().getCommandMap().register(channel.getChannelName().toLowerCase(), new ChatChannel(customChannel));
} }
messageChannel = Config.MESSAGECHANNEL; messageChannel = Config.MESSAGECHANNEL;
@@ -117,10 +121,10 @@ public class ChatPlugin extends JavaPlugin {
serverConfig.MUTED = !serverConfig.MUTED; serverConfig.MUTED = !serverConfig.MUTED;
} }
public void ReloadConfig() { public void reloadConfig() {
chatAPI.ReloadConfig(); chatAPI.reloadConfig();
chatAPI.ReloadChatFilters(); chatAPI.reloadChatFilters();
serverConfig = new ServerConfig(Bukkit.getServerName()); serverConfig = new ServerConfig(ServerName.getServerName());
Bukkit.broadcast(Utility.parseMiniMessage("Reloaded ChatPlugin Config."), "command.chat.reloadchat"); Bukkit.broadcast(Utility.parseMiniMessage("Reloaded ChatPlugin Config."), "command.chat.reloadchat");
ALogger.info("Reloaded ChatPlugin config."); ALogger.info("Reloaded ChatPlugin config.");
} }
@@ -38,7 +38,7 @@ public class ChatChannel extends BukkitCommand {
} }
if(args.length == 0 && player.hasPermission(channel.getPermission())) { if(args.length == 0 && player.hasPermission(channel.getPermission())) {
player.sendMiniMessage(Config.CUSTOM_CHANNEL_TOGGLED, TagResolver.resolver( player.sendRichMessage(Config.CUSTOM_CHANNEL_TOGGLED, TagResolver.resolver(
Placeholder.unparsed("channel", channel.getChannelName()), Placeholder.unparsed("channel", channel.getChannelName()),
Placeholder.component("status", toggleableForCustomChannel.toggle(player.getUniqueId()) Placeholder.component("status", toggleableForCustomChannel.toggle(player.getUniqueId())
? Config.TOGGLED_ON : Config.TOGGLED_OFF))); ? Config.TOGGLED_ON : Config.TOGGLED_OFF)));
@@ -27,7 +27,7 @@ public class PartyChat extends Toggleable implements CommandExecutor {
} }
if(args.length == 0) { if(args.length == 0) {
player.sendMiniMessage(Config.PARTY_TOGGLED, Placeholder.component("status", player.sendRichMessage(Config.PARTY_TOGGLED, Placeholder.component("status",
toggle(player.getUniqueId()) ? Config.TOGGLED_ON : Config.TOGGLED_OFF)); toggle(player.getUniqueId()) ? Config.TOGGLED_ON : Config.TOGGLED_OFF));
return true; return true;
} }
@@ -8,7 +8,9 @@ import com.alttd.chat.objects.ChatFilter;
import com.alttd.chat.objects.ChatUser; import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.ModifiableString; import com.alttd.chat.objects.ModifiableString;
import com.alttd.chat.objects.channels.CustomChannel; import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.GalaxyUtility; import com.alttd.chat.util.GalaxyUtility;
import com.alttd.chat.util.ServerName;
import com.alttd.chat.util.Utility; import com.alttd.chat.util.Utility;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
@@ -20,12 +22,16 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ChatHandler { public class ChatHandler {
@@ -125,12 +131,11 @@ public class ChatHandler {
Component senderName = user.getDisplayName(); Component senderName = user.getDisplayName();
Component prefix = user.getPrefix(); Component prefix = user.getPrefix();
TagResolver placeholders = TagResolver.resolver( TagResolver placeholders = TagResolver.resolver(
Placeholder.component("sender", senderName), Placeholder.component("sender", senderName),
Placeholder.component("prefix", prefix), Placeholder.component("prefix", prefix),
Placeholder.component("message", parseMessageContent(player, message)), Placeholder.component("message", parseMessageContent(player, message)),
Placeholder.parsed("server", Bukkit.getServerName()) Placeholder.parsed("server", ServerName.getServerName())
); );
Component component = Utility.parseMiniMessage(Config.GCFORMAT, placeholders); Component component = Utility.parseMiniMessage(Config.GCFORMAT, placeholders);
@@ -156,7 +161,10 @@ public class ChatHandler {
return; return;
} }
if (isMuted(player, message, "[" + channel.getChannelName() + " Muted] ")) return; if (isMuted(player, message, "[" + channel.getChannelName() + " Muted] ")) {
ALogger.info("Refusing to send message by muted user");
return;
}
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId()); ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
Component senderName = user.getDisplayName(); Component senderName = user.getDisplayName();
@@ -164,7 +172,7 @@ public class ChatHandler {
TagResolver placeholders = TagResolver.resolver( TagResolver placeholders = TagResolver.resolver(
Placeholder.component("sender", senderName), Placeholder.component("sender", senderName),
Placeholder.component("message", parseMessageContent(player, message)), Placeholder.component("message", parseMessageContent(player, message)),
Placeholder.parsed("server", Bukkit.getServerName()), Placeholder.parsed("server", ServerName.getServerName()),
Placeholder.parsed("channel", channel.getChannelName()) Placeholder.parsed("channel", channel.getChannelName())
); );
Component component = Utility.parseMiniMessage(channel.getFormat(), placeholders); Component component = Utility.parseMiniMessage(channel.getFormat(), placeholders);
@@ -175,15 +183,16 @@ public class ChatHandler {
player, player,
Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())), Utility.parseMiniMessage(Utility.parseColors(modifiableString.string())),
""); "");
return; // the message was blocked ALogger.info("Refusing to send blocked chat message");
return;
} }
component = modifiableString.component(); component = modifiableString.component();
if (channel.isProxy()) { if (channel.isProxy()) {
sendChatChannelMessage(player, channel.getChannelName(), "chatchannel", component); sendChatChannelMessage(player, channel.getChannelName(), "chatchannel", component, message);
} else { } else {
sendChatChannelMessage(channel, player.getUniqueId(), component); sendChatChannelMessage(channel, player.getUniqueId(), component, message);
} }
} }
@@ -236,13 +245,49 @@ public class ChatHandler {
// } // }
} }
private void sendChatChannelMessage(CustomChannel chatChannel, UUID uuid, Component component) { private void sendChatChannelMessage(CustomChannel chatChannel, UUID uuid, Component component, String message) {
if (!chatChannel.getServers().contains(Bukkit.getServerName())) return; Player player = Bukkit.getPlayer(uuid);
if (player == null) {
ALogger.warn("Failed to send chat message from non existent player");
return;
}
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(receiver -> !ChatUserManager.getChatUser(receiver.getUniqueId()).getIgnoredPlayers().contains(uuid)
|| receiver.hasPermission("chat.ignorebypass"));
}
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));
List<UUID> recipientUUIDs = recipientPlayers.stream().map(Entity::getUniqueId).toList();
Bukkit.getServer().getOnlinePlayers().stream() Bukkit.getServer().getOnlinePlayers().stream()
.filter(p -> p.hasPermission(chatChannel.getPermission())) .filter(onlinePlayer -> onlinePlayer.hasPermission(Config.SPYPERMISSION))
.filter(p -> !ChatUserManager.getChatUser(p.getUniqueId()).getIgnoredPlayers().contains(uuid)) .filter(onlinePlayer -> !recipientUUIDs.contains(onlinePlayer.getUniqueId()))
.forEach(p -> p.sendMessage(component)); .forEach(onlinePlayer -> onlinePlayer.sendRichMessage(Config.CHANNEL_SPY,
Placeholder.component("sender", player.name()),
Placeholder.parsed("channel", chatChannel.getChannelName()),
Placeholder.parsed("message", message)));
} }
private void sendPluginMessage(Player player, String channel, Component component) { private void sendPluginMessage(Player player, String channel, Component component) {
@@ -262,7 +307,7 @@ public class ChatHandler {
player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray()); player.sendPluginMessage(plugin, Config.MESSAGECHANNEL, out.toByteArray());
} }
public void sendChatChannelMessage(Player player, String chatChannelName, String channel, Component component) { public void sendChatChannelMessage(Player player, String chatChannelName, String channel, Component component, String ignored) {
ByteArrayDataOutput out = ByteStreams.newDataOutput(); ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF(channel); out.writeUTF(channel);
out.writeUTF(chatChannelName); out.writeUTF(chatChannelName);
@@ -5,13 +5,14 @@ import com.alttd.chat.config.Config;
import com.alttd.chat.handler.ChatHandler; import com.alttd.chat.handler.ChatHandler;
import com.alttd.chat.managers.ChatUserManager; import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.RegexManager; import com.alttd.chat.managers.RegexManager;
import com.alttd.chat.objects.ChatFilter; import com.alttd.chat.objects.*;
import com.alttd.chat.objects.ChatUser; import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.objects.ModifiableString;
import com.alttd.chat.objects.Toggleable;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.GalaxyUtility; import com.alttd.chat.util.GalaxyUtility;
import com.alttd.chat.util.ServerName;
import com.alttd.chat.util.Utility; 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.AsyncChatCommandDecorateEvent;
import io.papermc.paper.event.player.AsyncChatDecorateEvent; import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import io.papermc.paper.event.player.AsyncChatEvent; import io.papermc.paper.event.player.AsyncChatEvent;
@@ -32,12 +33,19 @@ import org.jetbrains.annotations.NotNull;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ChatListener implements Listener { public class ChatListener implements Listener {
private final PlainTextComponentSerializer plainTextComponentSerializer = PlainTextComponentSerializer.plainText(); private final PlainTextComponentSerializer plainTextComponentSerializer = PlainTextComponentSerializer.plainText();
private final ChatLogHandler chatLogHandler;
public ChatListener(ChatLogHandler chatLogHandler) {
this.chatLogHandler = chatLogHandler;
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
@@ -76,25 +84,42 @@ public class ChatListener implements Listener {
} }
Player player = event.getPlayer(); Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
Set<Player> receivers = event.viewers().stream().filter(audience -> audience instanceof Player)
.map(audience -> (Player) audience)
.filter(receiver -> !ChatUserManager.getChatUser(receiver.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId()))
.collect(Collectors.toSet());
Component input = event.message().colorIfAbsent(NamedTextColor.WHITE); Component input = event.message().colorIfAbsent(NamedTextColor.WHITE);
ModifiableString modifiableString = new ModifiableString(input); ModifiableString modifiableString = new ModifiableString(input);
// todo a better way for this // todo a better way for this
if(!RegexManager.filterText(player.getName(), player.getUniqueId(), modifiableString, "chat")) { 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); event.setCancelled(true);
GalaxyUtility.sendBlockedNotification("Language", player, GalaxyUtility.sendBlockedNotification("Language", player,
modifiableString.component(), modifiableString.component(),
""); "");
chatLogHandler.addChatLog(uuid, ServerName.getServerName(), PlainTextComponentSerializer.plainText().serialize(input), true);
return; // the message was blocked 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<>(); Set<Player> playersToPing = new HashSet<>();
pingPlayers(playersToPing, modifiableString, player); pingPlayers(playersToPing, modifiableString, player);
@@ -105,6 +130,7 @@ public class ChatListener implements Listener {
for (Player pingPlayer : playersToPing) { for (Player pingPlayer : playersToPing) {
pingPlayer.playSound(pingPlayer.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1); pingPlayer.playSound(pingPlayer.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1);
} }
chatLogHandler.addChatLog(uuid, ServerName.getServerName(), modifiableString.string(), false);
ALogger.info(PlainTextComponentSerializer.plainText().serialize(input)); ALogger.info(PlainTextComponentSerializer.plainText().serialize(input));
} }
@@ -114,34 +140,40 @@ public class ChatListener implements Listener {
String nickName = PlainTextComponentSerializer.plainText().serialize(onlinePlayer.displayName()); String nickName = PlainTextComponentSerializer.plainText().serialize(onlinePlayer.displayName());
Pattern namePattern = Pattern.compile("\\b(?<!\\\\)" + name + "\\b", Pattern.CASE_INSENSITIVE); Pattern namePattern = Pattern.compile("\\b(?<!\\\\)" + name + "\\b", Pattern.CASE_INSENSITIVE);
// Pattern escapedNamePattern = 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 nickPattern = Pattern.compile("\\b(?<!\\\\)" + nickName + "\\b", Pattern.CASE_INSENSITIVE);
// Pattern escapedNickPattern = 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()) { if (namePattern.matcher(modifiableString.string()).find()) {
modifiableString.replace(TextReplacementConfig.builder() modifiableString.replace(TextReplacementConfig.builder()
.once() .once()
.match(namePattern) .match(namePattern)
.replacement(mention.append(onlinePlayer.displayName())) .replacement(mention.append(onlinePlayerUser.getDisplayName()))
.build()); .build());
//TODO replace all instances of \name with just name but using the match result so the capitalization doesn't change //TODO replace all instances of \name with just name but using the match result so the capitalization doesn't change
// modifiableString.replace(TextReplacementConfig.builder() // modifiableString.replace(TextReplacementConfig.builder()
// .once() // .once()
// .match(escapedNamePattern) // .match(escapedNamePattern)
// .replacement((a, b) -> { // .replacement((a, b) -> {
// String substring = a.group().substring(1); // String substring = a.group().substring(1);
// return ; // return ;
// }); // });
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId()))
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId())
|| player.hasPermission("chat.ignorebypass")) {
playersToPing.add(onlinePlayer); playersToPing.add(onlinePlayer);
}
} else if (nickPattern.matcher(modifiableString.string()).find()) { } else if (nickPattern.matcher(modifiableString.string()).find()) {
modifiableString.replace(TextReplacementConfig.builder() modifiableString.replace(TextReplacementConfig.builder()
.once() .once()
.match(nickPattern) .match(nickPattern)
.replacement(mention.append(onlinePlayer.displayName())) .replacement(mention.append(onlinePlayerUser.getDisplayName()))
.build()); .build());
if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId())) if (!ChatUserManager.getChatUser(onlinePlayer.getUniqueId()).getIgnoredPlayers().contains(player.getUniqueId())
|| player.hasPermission("chat.ignorebypass")) {
playersToPing.add(onlinePlayer); playersToPing.add(onlinePlayer);
}
} }
} }
} }
@@ -170,7 +202,7 @@ public class ChatListener implements Listener {
}); });
MiniMessage miniMessage = MiniMessage.builder().tags(tagResolver.build()).build(); MiniMessage miniMessage = MiniMessage.builder().tags(tagResolver.build()).build();
Component component = miniMessage.deserialize(rawMessage); Component component = miniMessage.deserialize(Utility.formatText(rawMessage));
for(ChatFilter chatFilter : RegexManager.getEmoteFilters()) { for(ChatFilter chatFilter : RegexManager.getEmoteFilters()) {
component = component.replaceText( component = component.replaceText(
TextReplacementConfig.builder() TextReplacementConfig.builder()
@@ -191,4 +223,4 @@ public class ChatListener implements Listener {
} }
} }
@@ -11,15 +11,27 @@ import com.alttd.chat.objects.Toggleable;
import com.alttd.chat.util.GalaxyUtility; import com.alttd.chat.util.GalaxyUtility;
import com.alttd.chat.util.Utility; import com.alttd.chat.util.Utility;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Stack;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -38,8 +50,8 @@ public class PlayerListener implements Listener {
UUID uuid = player.getUniqueId(); UUID uuid = player.getUniqueId();
Toggleable.disableToggles(uuid); Toggleable.disableToggles(uuid);
if (serverConfig.FIRST_JOIN_MESSAGES && System.currentTimeMillis() - player.getFirstPlayed() < TimeUnit.SECONDS.toMillis(10)) { if (serverConfig.FIRST_JOIN_MESSAGES && (!player.hasPlayedBefore() || System.currentTimeMillis() - player.getFirstPlayed() < TimeUnit.SECONDS.toMillis(10))) {
player.getServer().sendMessage(MiniMessage.miniMessage().deserialize(Config.FIRST_JOIN, Placeholder.parsed("player", player.getName()))); Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Config.FIRST_JOIN, Placeholder.parsed("player", player.getName())));
} }
ChatUser user = ChatUserManager.getChatUser(uuid); ChatUser user = ChatUserManager.getChatUser(uuid);
@@ -86,4 +98,43 @@ 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();
Stack<Instant> playerDeathsStack = sendPlayerDeaths.computeIfAbsent(uuid, key -> new Stack<>());
Instant cutOff = Instant.now().minus(Config.DEATH_MESSAGES_LIMIT_PERIOD_MINUTES, ChronoUnit.MINUTES);
while (!playerDeathsStack.isEmpty() && playerDeathsStack.peek().isBefore(cutOff)) {
playerDeathsStack.pop();
}
if (playerDeathsStack.size() > Config.DEATH_MESSAGES_MAX_PER_PERIOD || serverConfig.MUTED) {
event.deathMessage(Component.empty());
return;
}
Component component = event.deathMessage();
playerDeathsStack.push(Instant.now());
if (component == null) {
return;
}
TextReplacementConfig playerReplacement = TextReplacementConfig.builder()
.match(event.getPlayer().getName())
.replacement(event.getPlayer().displayName())
.build();
component = component.replaceText(playerReplacement);
Player killer = event.getPlayer().getKiller();
if (killer != null) {
TextReplacementConfig killerReplacement = TextReplacementConfig.builder()
.match(killer.getName())
.replacement(killer.displayName())
.build();
component = component.replaceText(killerReplacement);
}
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);
}
} }
@@ -11,24 +11,25 @@ import com.alttd.chat.objects.channels.Channel;
import com.alttd.chat.objects.channels.CustomChannel; import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.objects.ChatUser; import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.ServerName;
import com.alttd.chat.util.Utility; import com.alttd.chat.util.Utility;
import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener; import org.bukkit.plugin.messaging.PluginMessageListener;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
public class PluginMessage implements PluginMessageListener { public class PluginMessage implements PluginMessageListener {
@Override @Override
public void onPluginMessageReceived(String channel, Player player, byte[] bytes) { public void onPluginMessageReceived(String channel, @NotNull Player ignored, byte[] bytes) {
if (!channel.equals(Config.MESSAGECHANNEL)) { if (!channel.equals(Config.MESSAGECHANNEL)) {
return; return;
} }
@@ -38,35 +39,36 @@ public class PluginMessage implements PluginMessageListener {
case "privatemessagein": { case "privatemessagein": {
UUID uuid = UUID.fromString(in.readUTF()); UUID uuid = UUID.fromString(in.readUTF());
String target = in.readUTF(); String target = in.readUTF();
Player p = Bukkit.getPlayer(uuid); Player player = Bukkit.getPlayer(uuid);
String message = in.readUTF(); String message = in.readUTF();
UUID targetuuid = UUID.fromString(in.readUTF()); UUID targetuuid = UUID.fromString(in.readUTF());
if (p != null) { if (player == null) {
ChatUser chatUser = ChatUserManager.getChatUser(uuid); break;
if (!chatUser.getIgnoredPlayers().contains(targetuuid)) { }
p.sendMessage(GsonComponentSerializer.gson().deserialize(message)); ChatUser chatUser = ChatUserManager.getChatUser(uuid);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1); // todo load this from config if (isTargetNotIgnored(chatUser, targetuuid)) {
ChatUser user = ChatUserManager.getChatUser(uuid); player.sendMessage(GsonComponentSerializer.gson().deserialize(message));
if (!user.getReplyContinueTarget().equalsIgnoreCase(target)) player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1); // todo load this from config
user.setReplyTarget(target); ChatUser user = ChatUserManager.getChatUser(uuid);
} if (!user.getReplyContinueTarget().equalsIgnoreCase(target))
user.setReplyTarget(target);
} }
break;
} }
case "privatemessageout": { case "privatemessageout": {
UUID uuid = UUID.fromString(in.readUTF()); UUID uuid = UUID.fromString(in.readUTF());
String target = in.readUTF(); String target = in.readUTF();
Player p = Bukkit.getPlayer(uuid); Player player = Bukkit.getPlayer(uuid);
String message = in.readUTF(); String message = in.readUTF();
UUID targetuuid = UUID.fromString(in.readUTF()); UUID targetuuid = UUID.fromString(in.readUTF());
if (p != null) { if (player == null) {
ChatUser chatUser = ChatUserManager.getChatUser(uuid); break;
if (!chatUser.getIgnoredPlayers().contains(targetuuid)) { }
chatUser.setReplyTarget(target); ChatUser chatUser = ChatUserManager.getChatUser(uuid);
p.sendMessage(GsonComponentSerializer.gson().deserialize(message)); if (isTargetNotIgnored(chatUser, targetuuid)) {
chatUser.setReplyTarget(target);
player.sendMessage(GsonComponentSerializer.gson().deserialize(message));
// ChatUser user = ChatUserManager.getChatUser(uuid); // ChatUser user = ChatUserManager.getChatUser(uuid);
// user.setReplyTarget(target); // user.setReplyTarget(target);
}
} }
break; break;
} }
@@ -78,7 +80,7 @@ public class PluginMessage implements PluginMessageListener {
Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission(Config.GCPERMISSION)).forEach(p -> { Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission(Config.GCPERMISSION)).forEach(p -> {
ChatUser chatUser = ChatUserManager.getChatUser(p.getUniqueId()); ChatUser chatUser = ChatUserManager.getChatUser(p.getUniqueId());
if (!chatUser.getIgnoredPlayers().contains(uuid)) { if (isTargetNotIgnored(chatUser, uuid)) {
p.sendMessage(GsonComponentSerializer.gson().deserialize(message)); p.sendMessage(GsonComponentSerializer.gson().deserialize(message));
} }
}); });
@@ -163,7 +165,7 @@ public class PluginMessage implements PluginMessageListener {
break; break;
} }
case "reloadconfig": case "reloadconfig":
ChatPlugin.getInstance().ReloadConfig(); ChatPlugin.getInstance().reloadConfig();
break; break;
case "chatpunishments": case "chatpunishments":
UUID uuid = UUID.fromString(in.readUTF()); UUID uuid = UUID.fromString(in.readUTF());
@@ -193,7 +195,7 @@ public class PluginMessage implements PluginMessageListener {
ALogger.warn("Received ChatChannel message for non existent channel."); ALogger.warn("Received ChatChannel message for non existent channel.");
return; return;
} }
if (!chatChannel.getServers().contains(Bukkit.getServerName())) { if (!chatChannel.getServers().contains(ServerName.getServerName())) {
ALogger.warn("Received ChatChannel message for the wrong server."); ALogger.warn("Received ChatChannel message for the wrong server.");
return; return;
} }
@@ -216,4 +218,15 @@ public class PluginMessage implements PluginMessageListener {
}.runTaskAsynchronously(ChatPlugin.getInstance()); }.runTaskAsynchronously(ChatPlugin.getInstance());
} }
} private boolean isTargetNotIgnored(ChatUser chatUser, UUID targetUUID) {
if (!chatUser.getIgnoredPlayers().contains(targetUUID)) {
return true;
}
Player target = Bukkit.getPlayer(targetUUID);
if (target == null) {
return true;
}
return target.hasPermission("chat.ignorebypass");
}
}
@@ -0,0 +1,27 @@
package com.alttd.chat.listeners;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
public class ShutdownListener implements Listener {
private final ChatLogHandler chatLogHandler;
private final Plugin thisPlugin;
public ShutdownListener(ChatLogHandler chatLogHandler, Plugin thisPlugin) {
this.chatLogHandler = chatLogHandler;
this.thisPlugin = thisPlugin;
}
@EventHandler
public void onShutdown(PluginDisableEvent event) {
if (!event.getPlugin().getName().equals(thisPlugin.getName())){
return;
}
chatLogHandler.shutDown();
}
}
@@ -149,24 +149,24 @@ public class NickUtilities
public static boolean validNick(Player sender, OfflinePlayer target, String nickName) { public static boolean validNick(Player sender, OfflinePlayer target, String nickName) {
if (!noBlockedCodes(nickName)) { if (!noBlockedCodes(nickName)) {
sender.sendMiniMessage(Config.NICK_BLOCKED_COLOR_CODES, null); sender.sendRichMessage(Config.NICK_BLOCKED_COLOR_CODES);
return false; return false;
} }
if (!Utility.checkNickBrightEnough(nickName)) { if (!Utility.checkNickBrightEnough(nickName)) {
sender.sendMiniMessage("<red>At least one color must be brighter than 30 for each color</red>", null); sender.sendRichMessage("<red>At least one color must be brighter than 30 for each color</red>");
return false; return false;
} }
String cleanNick = NickUtilities.removeAllColors(nickName); String cleanNick = NickUtilities.removeAllColors(nickName);
if (cleanNick.length() < 3 || cleanNick.length() > 16) { if (cleanNick.length() < 3 || cleanNick.length() > 16) {
sender.sendMiniMessage(Config.NICK_INVALID_LENGTH, null); sender.sendRichMessage(Config.NICK_INVALID_LENGTH);
return false; return false;
} }
if (!cleanNick.matches("[a-zA-Z0-9_]*") || nickName.length() > 192) { //192 is if someone puts {#xxxxxx<>} in front of every letter if (!cleanNick.matches("[a-zA-Z0-9_]*") || nickName.length() > 192) { //192 is if someone puts {#xxxxxx<>} in front of every letter
sender.sendMiniMessage(Config.NICK_INVALID_CHARACTERS, null); sender.sendRichMessage(Config.NICK_INVALID_CHARACTERS);
return false; return false;
} }
@@ -183,7 +183,7 @@ public class NickUtilities
if (uniqueId.equals(uuid)){ if (uniqueId.equals(uuid)){
ChatPlugin.getInstance().getLogger().info(uuid + " " + uniqueId); ChatPlugin.getInstance().getLogger().info(uuid + " " + uniqueId);
} }
sender.sendMiniMessage(Config.NICK_TAKEN, null); sender.sendRichMessage(Config.NICK_TAKEN);
return false; return false;
} }
} }
@@ -1,7 +1,5 @@
package com.alttd.chat.nicknames; package com.alttd.chat.nicknames;
import com.Zrips.CMI.CMI;
import com.Zrips.CMI.Containers.CMIUser;
import com.alttd.chat.ChatAPI; import com.alttd.chat.ChatAPI;
import com.alttd.chat.ChatPlugin; import com.alttd.chat.ChatPlugin;
import com.alttd.chat.config.Config; import com.alttd.chat.config.Config;
@@ -13,7 +11,6 @@ import com.alttd.chat.objects.Nick;
import com.alttd.chat.util.Utility; import com.alttd.chat.util.Utility;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.luckperms.api.LuckPerms; import net.luckperms.api.LuckPerms;
@@ -98,7 +95,8 @@ public class Nicknames implements CommandExecutor, TabCompleter {
sender.sendMessage(Utility.parseMiniMessage(Config.NICK_TRYOUT, sender.sendMessage(Utility.parseMiniMessage(Config.NICK_TRYOUT,
Placeholder.component("prefix", Utility.applyColor(api.getUserManager().getUser(player.getUniqueId()) Placeholder.component("prefix", Utility.applyColor(api.getUserManager().getUser(player.getUniqueId())
.getCachedData().getMetaData().getPrefix())), // TODO pull this from chatuser? .getCachedData().getMetaData().getPrefix())), // TODO pull this from chatuser?
Placeholder.component("nick", Utility.applyColor(args[1])))); Placeholder.component("nick", Utility.applyColor(args[1])),
Placeholder.unparsed("nickrequest", args[1])));
} }
} else { } else {
sender.sendMessage(Utility.parseMiniMessage(Config.NICK_NO_LUCKPERMS)); sender.sendMessage(Utility.parseMiniMessage(Config.NICK_NO_LUCKPERMS));
@@ -114,7 +112,7 @@ public class Nicknames implements CommandExecutor, TabCompleter {
Placeholder.component("nickname", chatUser.getDisplayName()), Placeholder.component("nickname", chatUser.getDisplayName()),
Placeholder.parsed("currentnickname", chatUser.getNickNameString()) Placeholder.parsed("currentnickname", chatUser.getNickNameString())
); );
player.sendMiniMessage(Config.NICK_CURRENT, placeholders); player.sendRichMessage(Config.NICK_CURRENT, placeholders);
} }
break; break;
case "help": case "help":
@@ -384,7 +382,7 @@ public class Nicknames implements CommandExecutor, TabCompleter {
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId()); ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
user.setDisplayName(player.getName()); user.setDisplayName(player.getName());
player.displayName(user.getDisplayName()); player.displayName(user.getDisplayName());
updateCMIUser(player, null); // updateCMIUser(player, null);
} }
public String getNick(final Player player) { public String getNick(final Player player) {
@@ -399,25 +397,25 @@ public class Nicknames implements CommandExecutor, TabCompleter {
ChatUser user = ChatUserManager.getChatUser(player.getUniqueId()); ChatUser user = ChatUserManager.getChatUser(player.getUniqueId());
user.setDisplayName(nickName); user.setDisplayName(nickName);
player.displayName(user.getDisplayName()); player.displayName(user.getDisplayName());
updateCMIUser(player, nickName); // updateCMIUser(player, nickName);
} }
// public static String format(final String m) { // public static String format(final String m) {
// return NickUtilities.applyColor(m); // return NickUtilities.applyColor(m);
// } // }
public void updateCMIUser(Player player, String nickName) { // public void updateCMIUser(Player player, String nickName) {
if (!isCMIEnabled()) // if (!isCMIEnabled())
return; // return;
//
CMIUser cmiUser = CMI.getInstance().getPlayerManager().getUser(player); // CMIUser cmiUser = CMI.getInstance().getPlayerManager().getUser(player);
if (nickName == null){ // if (nickName == null){
cmiUser.setNickName(null, true); // cmiUser.setNickName(null, true);
} else { // } else {
cmiUser.setNickName(NickUtilities.applyColor(nickName), true); // cmiUser.setNickName(NickUtilities.applyColor(nickName), true);
} // }
cmiUser.updateDisplayName(); // cmiUser.updateDisplayName();
} // }
private Boolean isCMIEnabled = null; private Boolean isCMIEnabled = null;
private Boolean isCMIEnabled() { private Boolean isCMIEnabled() {
@@ -1,12 +1,8 @@
package com.alttd.chat.nicknames; package com.alttd.chat.nicknames;
import com.Zrips.CMI.commands.list.colorlimits;
import com.Zrips.CMI.utils.Util;
import com.alttd.chat.ChatPlugin; import com.alttd.chat.ChatPlugin;
import com.alttd.chat.config.Config; import com.alttd.chat.config.Config;
import com.alttd.chat.database.Queries; import com.alttd.chat.database.Queries;
import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.Nick; import com.alttd.chat.objects.Nick;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataInput;
@@ -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;
}
}
@@ -41,6 +41,7 @@ public class ToggleableForCustomChannel extends Toggleable {
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
ALogger.info(String.format("%s sent %s message: %s", player.getName(), customChannel.getChannelName(), message));
ChatPlugin.getInstance().getChatHandler().chatChannel(player, customChannel, message); ChatPlugin.getInstance().getChatHandler().chatChannel(player, customChannel, message);
} }
}.runTaskAsynchronously(ChatPlugin.getInstance()); }.runTaskAsynchronously(ChatPlugin.getInstance());
BIN
View File
Binary file not shown.
+12 -1
View File
@@ -4,11 +4,14 @@ include(":api")
include(":galaxy") include(":galaxy")
include(":velocity") include(":velocity")
val nexusUser = providers.gradleProperty("alttdSnapshotUsername").get()
val nexusPass = providers.gradleProperty("alttdSnapshotPassword").get()
dependencyResolutionManagement { dependencyResolutionManagement {
repositories { repositories {
// mavenLocal() // mavenLocal()
mavenCentral() mavenCentral()
maven("https://repo.destro.xyz/snapshots") // Altitude - Galaxy // maven("https://repo.destro.xyz/snapshots") // Altitude - Galaxy
maven("https://oss.sonatype.org/content/groups/public/") // Adventure maven("https://oss.sonatype.org/content/groups/public/") // Adventure
maven("https://oss.sonatype.org/content/repositories/snapshots/") // Minimessage maven("https://oss.sonatype.org/content/repositories/snapshots/") // Minimessage
maven("https://nexus.velocitypowered.com/repository/") // Velocity maven("https://nexus.velocitypowered.com/repository/") // Velocity
@@ -16,6 +19,14 @@ dependencyResolutionManagement {
maven("https://repo.spongepowered.org/maven") // Configurate maven("https://repo.spongepowered.org/maven") // Configurate
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // Papi maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // Papi
maven("https://jitpack.io") maven("https://jitpack.io")
maven {
name = "nexus"
url = uri("https://repo.alttd.com/repository/alttd-snapshot/")
credentials {
username = nexusUser
password = nexusPass
}
}
} }
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
} }
+3 -3
View File
@@ -1,6 +1,6 @@
plugins { plugins {
`maven-publish` `maven-publish`
id("com.github.johnrengelman.shadow") id("io.github.goooler.shadow")
} }
dependencies { dependencies {
@@ -11,7 +11,7 @@ dependencies {
implementation("org.spongepowered", "configurate-yaml", "4.1.2") implementation("org.spongepowered", "configurate-yaml", "4.1.2")
compileOnly("net.kyori:adventure-text-minimessage:4.10.1") compileOnly("net.kyori:adventure-text-minimessage:4.10.1")
compileOnly("com.gitlab.ruany:LiteBansAPI:0.3.5") compileOnly("com.gitlab.ruany:LiteBansAPI:0.3.5")
compileOnly("com.alttd.proxydiscordlink:ProxyDiscordLink:1.0.0-BETA-SNAPSHOT") compileOnly("com.alttd.proxydiscordlink:ProxyDiscordLink:1.0.1-SNAPSHOT")
} }
tasks { tasks {
@@ -30,4 +30,4 @@ tasks {
dependsOn(shadowJar) dependsOn(shadowJar)
} }
} }
@@ -5,6 +5,7 @@ import com.alttd.chat.ChatImplementation;
import com.alttd.chat.managers.ChatUserManager; import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.managers.PartyManager; import com.alttd.chat.managers.PartyManager;
import com.alttd.chat.objects.ChatUser; import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.velocitychat.commands.*; import com.alttd.velocitychat.commands.*;
import com.alttd.chat.config.Config; import com.alttd.chat.config.Config;
import com.alttd.chat.database.DatabaseConnection; import com.alttd.chat.database.DatabaseConnection;
@@ -81,9 +82,9 @@ public class VelocityChat {
ChatUserManager.addUser(console); ChatUserManager.addUser(console);
} }
public void ReloadConfig() { public void reloadConfig() {
chatAPI.ReloadConfig(); chatAPI.reloadConfig();
chatAPI.ReloadChatFilters(); chatAPI.reloadChatFilters();
serverHandler.cleanup(); serverHandler.cleanup();
ByteArrayDataOutput buf = ByteStreams.newDataOutput(); ByteArrayDataOutput buf = ByteStreams.newDataOutput();
buf.writeUTF("reloadconfig"); buf.writeUTF("reloadconfig");
@@ -108,11 +109,14 @@ public class VelocityChat {
} }
public void loadCommands() { public void loadCommands() {
ChatLogHandler instance = ChatLogHandler.getInstance(false);
new SilentJoinCommand(server); new SilentJoinCommand(server);
new GlobalAdminChat(server); new GlobalAdminChat(server);
new Reload(server); new Reload(server);
new MailCommand(server); new MailCommand(server);
new Report(server); new Report(server);
new VoteToMute(server, instance);
new VoteToMuteHelper(server);
server.getCommandManager().register("party", new PartyCommand()); server.getCommandManager().register("party", new PartyCommand());
// all (proxy)commands go here // all (proxy)commands go here
} }
@@ -15,7 +15,7 @@ public class Reload {
.<CommandSource>literal("reloadchat") .<CommandSource>literal("reloadchat")
.requires(ctx -> ctx.hasPermission("command.chat.reloadchat")) .requires(ctx -> ctx.hasPermission("command.chat.reloadchat"))
.executes(context -> { .executes(context -> {
VelocityChat.getPlugin().ReloadConfig(); VelocityChat.getPlugin().reloadConfig();
return 1; return 1;
}) })
.build(); .build();
@@ -0,0 +1,163 @@
package com.alttd.velocitychat.commands;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.util.Utility;
import com.alttd.velocitychat.commands.vote_to_mute.VoteToMuteStarter;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource;
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 net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class VoteToMute {
public VoteToMute(ProxyServer proxyServer, ChatLogHandler chatLogHandler) {
RequiredArgumentBuilder<CommandSource, String> playerNode = RequiredArgumentBuilder
.<CommandSource, String>argument("player", StringArgumentType.string())
.suggests((context, builder) -> {
List<Player> possiblePlayers;
if (context.getSource() instanceof Player player) {
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isPresent()) {
possiblePlayers = getEligiblePlayers(currentServer.get().getServer());
} else {
possiblePlayers = getEligiblePlayers(proxyServer);
}
} else {
possiblePlayers = getEligiblePlayers(proxyServer);
}
Collection<String> possibleValues = possiblePlayers.stream()
.map(Player::getUsername)
.toList();
if (possibleValues.isEmpty())
return Suggestions.empty();
String remaining = builder.getRemaining().toLowerCase();
possibleValues.stream()
.filter(str -> str.toLowerCase().startsWith(remaining))
.map(StringArgumentType::escapeIfRequired)
.forEach(builder::suggest);
return builder.buildFuture();
})
.executes(context -> {
sendHelpMessage(context.getSource());
return 1;
});
LiteralCommandNode<CommandSource> command = LiteralArgumentBuilder
.<CommandSource>literal("votetomute")
.requires(commandSource -> commandSource.hasPermission("chat.vote-to-mute"))
.requires(commandSource -> commandSource instanceof Player)
.then(playerNode
.suggests(((commandContext, suggestionsBuilder) -> {
if (!(commandContext.getSource() instanceof Player player)) {
return suggestionsBuilder.buildFuture();
}
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty()) {
sendHelpMessage(commandContext.getSource());
return suggestionsBuilder.buildFuture();
}
String remaining = suggestionsBuilder.getRemaining().toLowerCase();
currentServer.get().getServer().getPlayersConnected().stream()
.filter(connectedPlayer -> connectedPlayer.hasPermission("chat.affected-by-vote-to-mute"))
.map(Player::getUsername)
.filter((String str) -> str.toLowerCase().startsWith(remaining))
.map(StringArgumentType::escapeIfRequired)
.forEach(suggestionsBuilder::suggest);
return suggestionsBuilder.buildFuture();
}))
.executes(commandContext -> {
String playerName = commandContext.getArgument("player", String.class);
Optional<Player> optionalPlayer = proxyServer.getPlayer(playerName);
if (optionalPlayer.isEmpty()) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<red>Player <player> is not online.</red>",
Placeholder.parsed("player", playerName)));
return 1;
}
Player voteTarget = optionalPlayer.get();
if (!voteTarget.hasPermission("chat.affected-by-vote-to-mute")) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage(
"<red>Player <player> can not be muted by a vote.</red>",
Placeholder.parsed("player", playerName)));
return 1;
}
Player player = (Player) commandContext.getSource();
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty()) {
sendHelpMessage(commandContext.getSource());
return 1;
}
RegisteredServer server = currentServer.get().getServer();
if (currentServer.get().getServer().getPlayersConnected().stream().anyMatch(onlinePlayer -> onlinePlayer.hasPermission("chat.staff"))) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>There is a staff member online, so vote to mute can not be used. Please contact a staff member for help instead.</red>"));
return 1;
}
boolean countLowerRanks = false;
long count = getTotalEligiblePlayers(server, false);
if (count < 6) {
countLowerRanks = true;
count = getTotalEligiblePlayers(server, true);
if (count < 6) {
commandContext.getSource().sendMessage(Utility.parseMiniMessage("<red>Not enough eligible players online to vote.</red>"));
return 1;
}
}
new VoteToMuteStarter(chatLogHandler, voteTarget, player, server.getServerInfo().getName(), countLowerRanks)
.start();
return 1;
}))
.executes(context -> {
sendHelpMessage(context.getSource());
return 1;
})
.build();
BrigadierCommand brigadierCommand = new BrigadierCommand(command);
CommandMeta.Builder metaBuilder = proxyServer.getCommandManager().metaBuilder(brigadierCommand);
CommandMeta meta = metaBuilder.build();
proxyServer.getCommandManager().register(meta, brigadierCommand);
}
private int getTotalEligiblePlayers(RegisteredServer server, boolean countLowerRanks) {
return (int) server.getPlayersConnected().stream()
.filter(player -> countLowerRanks ? player.hasPermission("chat.backup-vote-to-mute") : player.hasPermission("chat.vote-to-mute"))
.count();
}
private void sendHelpMessage(CommandSource commandSource) {
commandSource.sendMessage(Utility.parseMiniMessage("<red>Use: <gold>/votetomute <player></gold>.</red>"));
}
private List<Player> getEligiblePlayers(ProxyServer proxyServer) {
return proxyServer.getAllPlayers().stream()
.filter(player -> player.hasPermission("chat.affected-by-vote-to-mute"))
.collect(Collectors.toList());
}
private List<Player> getEligiblePlayers(RegisteredServer registeredServer) {
return registeredServer.getPlayersConnected().stream()
.filter(player -> player.hasPermission("chat.affected-by-vote-to-mute"))
.collect(Collectors.toList());
}
}
@@ -0,0 +1,285 @@
package com.alttd.velocitychat.commands;
import com.alttd.chat.util.Utility;
import com.alttd.velocitychat.commands.vote_to_mute.ActiveVoteToMute;
import com.alttd.velocitychat.commands.vote_to_mute.VoteToMuteStarter;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource;
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.minimessage.tag.resolver.Placeholder;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class VoteToMuteHelper {
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>");
public VoteToMuteHelper(ProxyServer proxyServer) {
RequiredArgumentBuilder<CommandSource, String> playerNode = RequiredArgumentBuilder
.<CommandSource, String>argument("player", StringArgumentType.string())
.suggests((context, builder) -> {
List<Player> possiblePlayers;
if (context.getSource() instanceof Player player) {
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isPresent()) {
possiblePlayers = getEligiblePlayers(currentServer.get().getServer());
} else {
possiblePlayers = getEligiblePlayers(proxyServer);
}
} else {
possiblePlayers = getEligiblePlayers(proxyServer);
}
Collection<String> possibleValues = possiblePlayers.stream()
.map(Player::getUsername)
.toList();
if (possibleValues.isEmpty())
return Suggestions.empty();
String remaining = builder.getRemaining().toLowerCase();
possibleValues.stream()
.filter(str -> str.toLowerCase().startsWith(remaining))
.map(StringArgumentType::escapeIfRequired)
.forEach(builder::suggest);
return builder.buildFuture();
})
.executes(context -> {
sendHelpMessage(context.getSource());
return 1;
});
RequiredArgumentBuilder<CommandSource, String> yesNoNode = RequiredArgumentBuilder.
<CommandSource, String>argument("yesNo", StringArgumentType.string())
.suggests(((commandContext, suggestionsBuilder) -> {
List<String> yesNoValues = Arrays.asList("yes", "no");
String remaining = suggestionsBuilder.getRemaining().toLowerCase();
yesNoValues.stream()
.filter((String str) -> str.toLowerCase().startsWith(remaining))
.map(StringArgumentType::escapeIfRequired)
.forEach(suggestionsBuilder::suggest);
return suggestionsBuilder.buildFuture();
}));
LiteralArgumentBuilder<CommandSource> pageNode = LiteralArgumentBuilder
.<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 -> {
sendHelpMessage(commandContext.getSource());
return 1;
});
LiteralArgumentBuilder<CommandSource> enterMessagesNode = LiteralArgumentBuilder
.<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();
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;
}
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 -> {
sendHelpMessage(commandContext.getSource());
return 1;
});
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();
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 -> {
sendHelpMessage(context.getSource());
return 1;
})).executes(context -> {
sendHelpMessage(context.getSource());
return 1;
});
LiteralCommandNode<CommandSource> command = LiteralArgumentBuilder
.<CommandSource>literal("votetomutehelper")
.requires(commandSource -> commandSource.hasPermission("chat.backup-vote-to-mute"))
.requires(commandSource -> commandSource instanceof Player)
.then(voteNode)
.then(pageNode)
.then(enterMessagesNode)
.executes(context -> {
sendHelpMessage(context.getSource());
return 1;
})
.build();
BrigadierCommand brigadierCommand = new BrigadierCommand(command);
CommandMeta.Builder metaBuilder = proxyServer.getCommandManager().metaBuilder(brigadierCommand);
CommandMeta meta = metaBuilder.build();
proxyServer.getCommandManager().register(meta, brigadierCommand);
}
private int getTotalEligiblePlayers(RegisteredServer server, boolean countLowerRanks) {
return (int) server.getPlayersConnected().stream()
.filter(player -> countLowerRanks ? player.hasPermission("chat.backup-vote-to-mute") : player.hasPermission("chat.vote-to-mute"))
.count();
}
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()));
boolean countLowerRanks = activeVoteToMute.countLowerRanks();
server.getPlayersConnected().stream()
.filter(player -> countLowerRanks ? player.hasPermission("chat.backup-vote-to-mute") : player.hasPermission("chat.vote-to-mute"))
.forEach(player -> player.sendMessage(message));
}
private void sendHelpMessage(CommandSource commandSource) {
commandSource.sendMessage(Utility.parseMiniMessage("<red>Use: <gold>/votetomutehelper <player></gold>.</red>"));
}
private List<Player> getEligiblePlayers(ProxyServer proxyServer) {
return proxyServer.getAllPlayers().stream()
.filter(player -> player.hasPermission("chat.affected-by-vote-to-mute"))
.collect(Collectors.toList());
}
private List<Player> getEligiblePlayers(RegisteredServer registeredServer) {
return registeredServer.getPlayersConnected().stream()
.filter(player -> player.hasPermission("chat.affected-by-vote-to-mute"))
.collect(Collectors.toList());
}
}
@@ -0,0 +1,238 @@
package com.alttd.velocitychat.commands.vote_to_mute;
import com.alttd.chat.config.Config;
import com.alttd.chat.util.ALogger;
import com.alttd.chat.util.Utility;
import com.alttd.proxydiscordlink.DiscordLink;
import com.alttd.proxydiscordlink.lib.net.dv8tion.jda.api.EmbedBuilder;
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 net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
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 final Player votedPlayer;
private final Player startedByPlayer;
private HashSet<UUID> votedFor = new HashSet<>();
private HashSet<UUID> votedAgainst = new HashSet<>();
private int totalEligibleVoters;
private final boolean countLowerRanks;
private final RegisteredServer server;
private final ProxyServer proxyServer;
private final Component chatLogs;
private boolean endedVote = false;
public static Optional<ActiveVoteToMute> getInstance(String 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"))
return;
if (player.hasPermission("chat.vote-to-mute")) {
instances.values().stream()
.filter(activeVoteToMute -> previousServer == null || activeVoteToMute.getServer().getServerInfo().hashCode() == previousServer.getServerInfo().hashCode())
.forEach(inst -> inst.removeEligibleVoter(player.getUniqueId()));
} else {
instances.values().stream()
.filter(ActiveVoteToMute::countLowerRanks)
.filter(activeVoteToMute -> previousServer == null || activeVoteToMute.getServer().getServerInfo().hashCode() == previousServer.getServerInfo().hashCode())
.forEach(inst -> inst.removeEligibleVoter(player.getUniqueId()));
}
}
public static void addPotentialVoter(Player player, ServerConnection server) {
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())
.forEach(activeVoteToMute -> activeVoteToMute.addEligibleVoter(player));
} else {
instances.values().stream()
.filter(ActiveVoteToMute::countLowerRanks)
.filter(activeVoteToMute -> activeVoteToMute.getServer().getServerInfo().hashCode() == server.getServerInfo().hashCode())
.forEach(activeVoteToMute -> activeVoteToMute.addEligibleVoter(player));
}
}
public ActiveVoteToMute(@NotNull Player votedPlayer, @NotNull RegisteredServer server, ProxyServer proxyServer, Duration duration,
int totalEligibleVoters, boolean countLowerRanks, Component chatLogs, @NotNull Player startedByPlayer) {
this.chatLogs = chatLogs;
this.votedPlayer = votedPlayer;
this.totalEligibleVoters = totalEligibleVoters;
this.countLowerRanks = countLowerRanks;
this.server = server;
this.proxyServer = proxyServer;
instances.put(votedPlayer.getUsername(), this);
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
executorService.schedule(this::endVote,
duration.toMinutes(), TimeUnit.MINUTES);
this.startedByPlayer = startedByPlayer;
}
private RegisteredServer getServer() {
return server;
}
private void endVote() {
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()));
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();
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 vote(UUID uuid, boolean votedToMute) {
if (votedToMute) {
votedFor.add(uuid);
votedAgainst.remove(uuid);
if (!votePassed()) {
return;
}
endedVote = true;
instances.remove(votedPlayer.getUsername());
mutePlayer();
} else {
votedAgainst.add(uuid);
votedFor.remove(uuid);
}
}
public boolean votePassed() {
double totalVotes = (votedFor.size() + votedAgainst.size());
if (totalVotes == 0 || votedFor.isEmpty()) {
return false;
}
if (totalVotes / totalEligibleVoters < 0.6) {
return false;
}
return votedFor.size() / totalVotes > 0.6;
}
public boolean countLowerRanks() {
return countLowerRanks;
}
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()));
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 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)
));
long id = Config.serverChannelId.get("general");
DiscordLink.getPlugin().getBot().sendEmbedToDiscord(id, embedBuilder, -1);
}
@NotNull
private EmbedBuilder buildMutedEmbed(String chatLogsString) {
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setAuthor(votedPlayer.getUsername(), null, "https://crafatar.com/avatars/" + votedPlayer.getUniqueId() + "?overlay");
embedBuilder.setTitle("Player muted by vote");
embedBuilder.setColor(Color.CYAN);
embedBuilder.addField("Logs",
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);
embedBuilder.addField("Started by",
String.format("Username: %s\nUUID: %s", startedByPlayer.getUsername(), startedByPlayer.getUniqueId().toString()),
true);
return embedBuilder;
}
private String parseUUIDsToPlayerOrString(Collection<UUID> uuids) {
return uuids.stream().map(uuid -> {
Optional<Player> player = proxyServer.getPlayer(uuid);
if (player.isPresent()) {
return player.get().getUsername();
}
return uuid.toString();
}).collect(Collectors.joining("\n"));
}
public void addEligibleVoter(Player player) {
UUID uuid = player.getUniqueId();
if (votedAgainst.contains(uuid) || votedFor.contains(uuid))
return;
totalEligibleVoters++;
player.sendMessage(getVoteStartMessage());
}
public void removeEligibleVoter(UUID uuid) {
if (votedFor.contains(uuid) || votedAgainst.contains(uuid))
return;
totalEligibleVoters--;
}
private Component 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()),
Placeholder.component("prefix", prefix),
Placeholder.parsed("player", votedPlayer.getUsername()),
Placeholder.component("logs", chatLogs));
}
public Player getVotedPlayer() {
return votedPlayer;
}
public int getVotedFor() {
return votedFor.size();
}
public int getTotalEligibleVoters() {
return totalEligibleVoters;
}
}
@@ -0,0 +1,129 @@
package com.alttd.velocitychat.commands.vote_to_mute;
import com.alttd.chat.objects.chat_log.ChatLog;
import com.alttd.chat.objects.chat_log.ChatLogHandler;
import com.alttd.chat.util.Utility;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class VoteToMuteStarter {
private static final HashMap<UUID, VoteToMuteStarter> instanceMap = new HashMap<>();
private static final Component prefix = Utility.parseMiniMessage("<gold>[VoteMute]</gold>");
private final ChatLogHandler chatLogHandler;
private final Player votedPlayer;
private final Player commandSource;
private final String serverName;
private List<Component> parsedChatLogs;
private final boolean countLowerRanks;
public static Optional<VoteToMuteStarter> getInstance(UUID uuid) {
if (!instanceMap.containsKey(uuid))
return Optional.empty();
return Optional.of(instanceMap.get(uuid));
}
public VoteToMuteStarter(ChatLogHandler chatLogHandler, Player votedPlayer, Player commandSource, String serverName, boolean countLowerRanks) {
this.chatLogHandler = chatLogHandler;
this.votedPlayer = votedPlayer;
this.commandSource = commandSource;
this.serverName = serverName;
this.countLowerRanks = countLowerRanks;
instanceMap.put(commandSource.getUniqueId(), this);
}
public void start() {
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())));
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)));
showPage(1);
});
}
private void parseChatLogs(List<ChatLog> chatLogs) {
TagResolver.Single playerTag = Placeholder.parsed("player", votedPlayer.getUsername());
TagResolver.Single prefixTag = Placeholder.component("prefix", prefix);
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
))
)
.toList();
}
public void showPage(int page) {
List<Component> collect = parsedChatLogs.stream().skip((page - 1) * 10L).limit(10L).toList();
Component chatLogsComponent = Component.join(JoinConfiguration.newlines(), collect);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<prefix> ChatLogs for <player>\n<logs>\n");
if (page > 1) {
stringBuilder.append("<click:run_command:/votetomutehelper page ")
.append(page - 1)
.append("><hover:show_text:'<gold>Click to go to previous page'><gold><previous page></gold></hover></click> ");
}
if (parsedChatLogs.size() > page * 10) {
stringBuilder.append("<click:run_command:/votetomutehelper page ")
.append(page + 1)
.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)));
}
/**
* 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) {
List<Component> selectedChatLogs = ids.stream()
.filter(id -> id >= 1 && id <= parsedChatLogs.size())
.map(id -> parsedChatLogs.get(id - 1))
.collect(Collectors.toList());
instanceMap.remove(commandSource.getUniqueId());
return Component.join(JoinConfiguration.newlines(), selectedChatLogs);
}
public int getTotalPages() {
return (int) Math.ceil((double) parsedChatLogs.size() / 10);
}
public Player getVotedPlayer() {
return votedPlayer;
}
public int getTotalLogEntries() {
return parsedChatLogs.size();
}
public boolean countLowerRanks() {
return countLowerRanks;
}
}
@@ -205,6 +205,10 @@ public class ChatHandler {
} }
Mail mail = new Mail(targetUUID, uuid, message); Mail mail = new Mail(targetUUID, uuid, message);
ChatUser chatUser = ChatUserManager.getChatUser(targetUUID); 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); chatUser.addMail(mail);
// TODO load from config // TODO load from config
String finalSenderName = senderName; String finalSenderName = senderName;
@@ -319,4 +323,4 @@ public class ChatHandler {
return component; return component;
} }
} }
@@ -4,11 +4,14 @@ import com.alttd.chat.managers.ChatUserManager;
import com.alttd.chat.objects.ChatUser; import com.alttd.chat.objects.ChatUser;
import com.alttd.chat.objects.channels.CustomChannel; import com.alttd.chat.objects.channels.CustomChannel;
import com.alttd.chat.util.ALogger; import com.alttd.chat.util.ALogger;
import com.alttd.proxydiscordlink.DiscordLink;
import com.alttd.proxydiscordlink.lib.net.dv8tion.jda.api.EmbedBuilder;
import com.alttd.velocitychat.VelocityChat; import com.alttd.velocitychat.VelocityChat;
import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
@@ -16,8 +19,10 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import java.awt.*;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public class PluginMessageListener { public class PluginMessageListener {
@@ -93,6 +98,20 @@ public class PluginMessageListener {
proxy.getAllServers().forEach(registeredServer -> proxy.getAllServers().forEach(registeredServer ->
registeredServer.sendPluginMessage(VelocityChat.getPlugin().getChannelIdentifier(), event.getData())); registeredServer.sendPluginMessage(VelocityChat.getPlugin().getChannelIdentifier(), event.getData()));
} }
case "punish" -> {
String playerName = in.readUTF();
ProxyServer proxy = VelocityChat.getPlugin().getProxy();
ConsoleCommandSource consoleCommandSource = proxy.getConsoleCommandSource();
proxy.getCommandManager().executeAsync(consoleCommandSource, String.format("ban %s Automatic ban, please appeal if you feel review is needed.", playerName));
ALogger.info(String.format("Auto banned %s due to violating the `punish` filter.", playerName));
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setTitle("Automatic ban through the chat filter");
embedBuilder.setAuthor(playerName, null, "https://crafatar.com/avatars/" + in.readUTF() + "?overlay");
embedBuilder.setDescription(String.format("`%s`\n\n Auto permanent ban\n\n Auto banned for violating the `punish` chat filter. This could be a false positive! Their message was\n||%s||", playerName, in.readUTF()));
embedBuilder.setColor(Color.RED);
DiscordLink.getPlugin().getBot().sendEmbedToDiscord(514922317923614728L, embedBuilder, -1);
}
default -> { default -> {
VelocityChat.getPlugin().getLogger().info("server " + event.getSource()); VelocityChat.getPlugin().getLogger().info("server " + event.getSource());
ProxyServer proxy = VelocityChat.getPlugin().getProxy(); ProxyServer proxy = VelocityChat.getPlugin().getProxy();
@@ -6,6 +6,7 @@ import com.alttd.chat.objects.Mail;
import com.alttd.chat.util.Utility; import com.alttd.chat.util.Utility;
import com.alttd.velocitychat.VelocityChat; import com.alttd.velocitychat.VelocityChat;
import com.alttd.chat.config.Config; 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.data.ServerWrapper;
import com.alttd.velocitychat.handlers.ServerHandler; import com.alttd.velocitychat.handlers.ServerHandler;
import com.alttd.chat.managers.PartyManager; import com.alttd.chat.managers.PartyManager;
@@ -17,12 +18,15 @@ import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent; import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.title.Title;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
public class ProxyPlayerListener { public class ProxyPlayerListener {
@@ -48,20 +52,40 @@ public class ProxyPlayerListener {
@Subscribe(order = PostOrder.LAST) @Subscribe(order = PostOrder.LAST)
public void afterPlayerLogin(ServerPostConnectEvent event) { public void afterPlayerLogin(ServerPostConnectEvent event) {
if (event.getPreviousServer() != null)
return;
Player player = event.getPlayer(); Player player = event.getPlayer();
RegisteredServer previousServer = event.getPreviousServer();
if (previousServer != null) {
ActiveVoteToMute.removePotentialVoter(player, previousServer);
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty())
return;
ActiveVoteToMute.addPotentialVoter(player, currentServer.get());
return;
}
Optional<ServerConnection> currentServer = player.getCurrentServer();
if (currentServer.isEmpty())
return;
ActiveVoteToMute.addPotentialVoter(player, currentServer.get());
ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId()); ChatUser chatUser = ChatUserManager.getChatUser(player.getUniqueId());
List<Mail> unReadMail = chatUser.getUnReadMail(); List<Mail> unReadMail = chatUser.getUnReadMail();
if (unReadMail.isEmpty()) if (unReadMail.isEmpty())
return; return;
player.sendMessage(Utility.parseMiniMessage(Config.mailUnread, VelocityChat plugin = VelocityChat.getPlugin();
Placeholder.unparsed("amount", String.valueOf(unReadMail.size())) plugin.getProxy().getScheduler().buildTask(plugin, () -> {
)); if (!player.isActive())
return;
Component message = Utility.parseMiniMessage(Config.mailUnread,
Placeholder.unparsed("amount", String.valueOf(unReadMail.size())));
player.sendMessage(message);
player.showTitle(Title.title(message, Component.empty()));
}).delay(Config.mailDisplayDelay * 50L, TimeUnit.MILLISECONDS).schedule();
} }
@Subscribe @Subscribe
public void quitEvent(DisconnectEvent event) { public void quitEvent(DisconnectEvent event) {
ActiveVoteToMute.removePotentialVoter(event.getPlayer(), null);
UUID uuid = event.getPlayer().getUniqueId(); UUID uuid = event.getPlayer().getUniqueId();
Party party = PartyManager.getParty(event.getPlayer().getUniqueId()); Party party = PartyManager.getParty(event.getPlayer().getUniqueId());
if (party == null) return; if (party == null) return;