diff --git a/build.gradle.kts b/build.gradle.kts index 3c73dbc..86a19f0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation("com.velocitypowered:velocity-api:3.1.2-SNAPSHOT") implementation("cloud.commandframework:cloud-velocity:1.8.2") implementation("cloud.commandframework:cloud-minecraft-extras:1.8.2") + implementation("org.apache.logging.log4j:log4j-core:2.23.1") compileOnly("net.luckperms:api:5.4") compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.0.0") } @@ -40,7 +41,7 @@ fun runCommand(command: String): String { val release = System.getenv("GRADLE_RELEASE").equals("true", ignoreCase = true) val gitHash = runCommand("git rev-parse --short HEAD") group = "com.oskarsmc" -version = "1.4.0" +version = "1.5.0" if (!release) { version = "$version-$gitHash-SNAPSHOT" diff --git a/src/main/java/com/oskarsmc/message/Message.java b/src/main/java/com/oskarsmc/message/Message.java index a0cb216..5c06b72 100644 --- a/src/main/java/com/oskarsmc/message/Message.java +++ b/src/main/java/com/oskarsmc/message/Message.java @@ -8,9 +8,11 @@ import com.google.inject.Key; import com.google.inject.TypeLiteral; import com.oskarsmc.message.command.MessageCommand; +import com.oskarsmc.message.command.MessageToggleCommand; import com.oskarsmc.message.command.ReplyCommand; import com.oskarsmc.message.command.SocialSpyCommand; import com.oskarsmc.message.configuration.MessageSettings; +import com.oskarsmc.message.configuration.UserData; import com.oskarsmc.message.locale.CommandExceptionHandler; import com.oskarsmc.message.locale.TranslationManager; import com.oskarsmc.message.logic.MessageMetrics; @@ -50,6 +52,7 @@ public final class Message { @Subscribe public void onProxyInitialization(ProxyInitializeEvent event) { MessageSettings messageSettings = new MessageSettings(dataFolder, logger); + UserData userData = new UserData(dataFolder, logger); injector = injector.createChildInjector( new CloudInjectionModule<>( @@ -81,7 +84,9 @@ public void onProxyInitialization(ProxyInitializeEvent event) { messageSettings.miniPlaceholdersIntegration(false); } } - + if (messageSettings.isLoggingEnabled()){ + com.oskarsmc.message.util.Logger.setLogContext(dataFolder, logger); + } // Register custom exception handlers injector.getInstance(CommandExceptionHandler.class); @@ -91,6 +96,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { // Commands injector.getInstance(MessageCommand.class); + injector.getInstance(MessageToggleCommand.class); injector.getInstance(SocialSpyCommand.class); injector.getInstance(ReplyCommand.class); diff --git a/src/main/java/com/oskarsmc/message/command/MessageCommand.java b/src/main/java/com/oskarsmc/message/command/MessageCommand.java index e08902e..3c666cc 100644 --- a/src/main/java/com/oskarsmc/message/command/MessageCommand.java +++ b/src/main/java/com/oskarsmc/message/command/MessageCommand.java @@ -7,12 +7,14 @@ import cloud.commandframework.velocity.arguments.PlayerArgument; import com.google.inject.Inject; import com.oskarsmc.message.configuration.MessageSettings; +import com.oskarsmc.message.configuration.UserData; import com.oskarsmc.message.event.MessageEvent; import com.oskarsmc.message.logic.MessageHandler; import com.oskarsmc.message.util.DefaultPermission; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; +import net.kyori.adventure.text.Component; import org.jetbrains.annotations.NotNull; /** @@ -25,9 +27,10 @@ public final class MessageCommand { * @param commandManager Command Manager * @param proxyServer Proxy Server * @param messageHandler Message Handler + * @param userData User Data */ @Inject - public MessageCommand(@NotNull MessageSettings messageSettings, @NotNull VelocityCommandManager commandManager, ProxyServer proxyServer, MessageHandler messageHandler) { + public MessageCommand(@NotNull MessageSettings messageSettings, @NotNull VelocityCommandManager commandManager, ProxyServer proxyServer, MessageHandler messageHandler, UserData userData) { Command.Builder builder = commandManager.commandBuilder("message", messageSettings.messageAliases().toArray(new String[0])); commandManager.command(builder @@ -36,12 +39,15 @@ public MessageCommand(@NotNull MessageSettings messageSettings, @NotNull Velocit .permission(new DefaultPermission("osmc.message.send")) .handler(context -> { Player receiver = context.get("player"); - - proxyServer.getEventManager().fire(new MessageEvent( - context.getSender(), - receiver, - context.get("message") - )).thenAccept(messageHandler::handleMessageEvent); + if (!userData.getUserMessageState(receiver.getUniqueId()) && !context.getSender().hasPermission("osmc.message.bypass")) { + context.getSender().sendMessage(Component.translatable("oskarsmc.messages.disabled")); + } else { + proxyServer.getEventManager().fire(new MessageEvent( + context.getSender(), + receiver, + context.get("message") + )).thenAccept(messageHandler::handleMessageEvent); + } }) ); } diff --git a/src/main/java/com/oskarsmc/message/command/MessageToggleCommand.java b/src/main/java/com/oskarsmc/message/command/MessageToggleCommand.java new file mode 100644 index 0000000..3eb4b41 --- /dev/null +++ b/src/main/java/com/oskarsmc/message/command/MessageToggleCommand.java @@ -0,0 +1,65 @@ +package com.oskarsmc.message.command; + +import cloud.commandframework.Command; +import cloud.commandframework.velocity.VelocityCommandManager; +import com.google.inject.Inject; +import com.oskarsmc.message.configuration.MessageSettings; +import com.oskarsmc.message.configuration.UserData; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +/** + * Message Toggle command + */ +public final class MessageToggleCommand { + + /** + * Construct the message toggle command. + * + * @param messageSettings Message Settings + * @param commandManager Command Manager + * @param userData User Data + */ + @Inject + public MessageToggleCommand(@NotNull MessageSettings messageSettings, @NotNull VelocityCommandManager commandManager, UserData userData) { + Command.Builder builder = commandManager.commandBuilder("msgtoggle",messageSettings.msgToggleAliases().toArray(new String[0])).permission("osmc.message.toggle"); + + commandManager.command(builder + .senderType(Player.class) + .literal("on") + .handler(context -> { + Player player = (Player) context.getSender(); + UUID playerUUID = player.getUniqueId(); + userData.saveUserMessageState(playerUUID, true); + player.sendMessage(Component.translatable("oskarsmc.message.command.msgtoggle.on")); + }) + ); + + commandManager.command(builder + .senderType(Player.class) + .literal("off") + .handler(context -> { + Player player = (Player) context.getSender(); + UUID playerUUID = player.getUniqueId(); + userData.saveUserMessageState(playerUUID, false); + player.sendMessage(Component.translatable("oskarsmc.message.command.msgtoggle.off")); + }) + ); + + commandManager.command(builder + .senderType(Player.class) + .handler(context -> { + Player player = (Player) context.getSender(); + UUID playerId = player.getUniqueId(); + boolean canBeMessaged = userData.getUserMessageState(playerId); + userData.saveUserMessageState(playerId, !canBeMessaged); + player.sendMessage(Component.translatable("oskarsmc.message.command.msgtoggle." + (canBeMessaged ? "off" : "on"))); + }) + ); + + } +} diff --git a/src/main/java/com/oskarsmc/message/configuration/MessageSettings.java b/src/main/java/com/oskarsmc/message/configuration/MessageSettings.java index bfa5e8e..92b1f84 100644 --- a/src/main/java/com/oskarsmc/message/configuration/MessageSettings.java +++ b/src/main/java/com/oskarsmc/message/configuration/MessageSettings.java @@ -29,16 +29,19 @@ public final class MessageSettings { private String messageSentMiniMessage; private String messageReceivedMiniMessage; private String messageSocialSpyMiniMessage; + private String messageLogFormat; private HashMap, String> customErrorHandlers; private List messageAlias; private List replyAlias; private List socialSpyAlias; + private List msgToggleAlias; private boolean luckpermsIntegration; private boolean miniPlaceholdersIntegration; private boolean selfMessageSending; + private boolean loggingEnabled; private final double configVersion; private boolean enabled; @@ -74,16 +77,18 @@ public MessageSettings(@DataDirectory @NotNull Path dataFolder, Logger logger) { this.luckpermsIntegration = toml.getBoolean("plugin.luckperms-integration"); this.selfMessageSending = toml.getBoolean("plugin.allow-self-message-sending"); this.miniPlaceholdersIntegration = toml.getBoolean("plugin.miniplaceholders-integration"); + this.loggingEnabled = toml.getBoolean("plugin.enable-logging"); // Messages - Message this.messageSentMiniMessage = toml.getString("messages.message-sent"); this.messageReceivedMiniMessage = toml.getString("messages.message-received"); this.messageSocialSpyMiniMessage = toml.getString("messages.message-socialspy"); - + this.messageLogFormat = toml.getString("messages.message-log-format"); // Aliases this.messageAlias = toml.getList("aliases.message"); this.replyAlias = toml.getList("aliases.reply"); this.socialSpyAlias = toml.getList("aliases.socialspy"); + this.msgToggleAlias = toml.getList("aliases.toggle"); // Exceptions this.customErrorHandlers = new HashMap<>(); @@ -227,6 +232,16 @@ public boolean selfMessageSending() { return selfMessageSending; } + /** + * Get if the config enables the logging of messages. + * + * @return If the config enables the logging of messages. + */ + @Pure + public boolean isLoggingEnabled() { + return loggingEnabled; + } + /** * Get the configuration version. * @@ -247,6 +262,16 @@ public String messageSocialSpyMiniMessage() { return messageSocialSpyMiniMessage; } + /** + * Get the MiniMessage markup of the log entry. + * + * @return The MiniMessage markup of the log entry. + */ + @Pure + public String messageLogFormat() { + return messageLogFormat; + } + /** * Get the aliases of the message command. * @@ -267,6 +292,16 @@ public List socialSpyAliases() { return socialSpyAlias; } + /** + * Get the aliases of the msgtoggle command. + * + * @return The aliases of the msgtoggle command. + */ + @Pure + public List msgToggleAliases() { + return msgToggleAlias; + } + /** * Get the aliases of the reply command. * diff --git a/src/main/java/com/oskarsmc/message/configuration/UserData.java b/src/main/java/com/oskarsmc/message/configuration/UserData.java new file mode 100644 index 0000000..dae245f --- /dev/null +++ b/src/main/java/com/oskarsmc/message/configuration/UserData.java @@ -0,0 +1,98 @@ +package com.oskarsmc.message.configuration; + +import com.google.inject.Inject; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +/** + * User Data class + */ +public final class UserData { + private final Path dataFolder; + private final Path file; + + /** + * Construct userdata. + * + * @param dataFolder Data Folder + * @param logger Logger + */ + @Inject + public UserData(@DataDirectory @NotNull Path dataFolder, Logger logger) { + this.dataFolder = dataFolder; + this.file = this.dataFolder.resolve("userdata.yml"); + + createUserDataFile(); + } + + /** + * Save the user message state. + * + * @param playerUUID Player UUID + * @param canBeMessaged Can be messaged + */ + public void saveUserMessageState(UUID playerUUID, boolean canBeMessaged) { + final ConfigurationNode yaml = loadConfig(); + yaml.getNode("users", playerUUID.toString(), "canBeMessaged").setValue(canBeMessaged); + saveUserData(yaml); + } + + /** + * Get the user message state. + * + * @param playerUUID Player UUID + * @return Can be messaged + */ + public boolean getUserMessageState(UUID playerUUID) { + final ConfigurationNode yaml = loadConfig(); + return yaml.getNode("users", playerUUID.toString(), "canBeMessaged").getBoolean(true); + } + + private void createUserDataFile() { + if (!Files.exists(dataFolder)) { + try { + Files.createDirectory(dataFolder); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + if (!Files.exists(file)) { + try (InputStream in = MessageSettings.class.getResourceAsStream("/userdata.yml")) { + assert in != null; + Files.copy(in, file); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private @NotNull Path userDataFile() { + return this.file; + } + + private ConfigurationNode loadConfig() { + try { + return YAMLConfigurationLoader.builder().setPath(this.file).build().load(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void saveUserData(ConfigurationNode yaml) { + try { + YAMLConfigurationLoader.builder().setPath(this.file).build().save(yaml); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/oskarsmc/message/logic/MessageHandler.java b/src/main/java/com/oskarsmc/message/logic/MessageHandler.java index fb1ba59..b98a272 100644 --- a/src/main/java/com/oskarsmc/message/logic/MessageHandler.java +++ b/src/main/java/com/oskarsmc/message/logic/MessageHandler.java @@ -2,6 +2,7 @@ import com.oskarsmc.message.configuration.MessageSettings; import com.oskarsmc.message.event.MessageEvent; +import com.oskarsmc.message.util.Logger; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import io.github.miniplaceholders.api.MiniPlaceholders; @@ -28,6 +29,7 @@ public final class MessageHandler { private final MessageSettings messageSettings; private final MiniMessage miniMessage = MiniMessage.miniMessage(); private final ConcurrentHashMap conversations = new ConcurrentHashMap<>(); + /** * Conversation Watchers */ @@ -113,6 +115,10 @@ public void handleMessageEvent(@NotNull MessageEvent event) { for (CommandSource watcher : conversationWatchers) { watcher.sendMessage(socialSpyComponent); } + + if (messageSettings.isLoggingEnabled()){ + Logger.addLog(miniMessage.deserialize(messageSettings.messageLogFormat(), placeholders)); + } } private @NotNull TagResolver craftLuckpermsPlaceholders(String role, CachedMetaData cachedMetaData) { diff --git a/src/main/java/com/oskarsmc/message/util/Logger.java b/src/main/java/com/oskarsmc/message/util/Logger.java new file mode 100644 index 0000000..f9fc564 --- /dev/null +++ b/src/main/java/com/oskarsmc/message/util/Logger.java @@ -0,0 +1,95 @@ +package com.oskarsmc.message.util; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.action.*; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; + +import java.io.Serializable; +import java.nio.file.Path; + +/** + * The logger class. This class is responsible for logging messages to the console and to a file. + */ +public class Logger { + + private static org.slf4j.Logger logger = null; + private static org.apache.logging.log4j.Logger fileLogger = LogManager.getLogger(Logger.class); + private static Path dataFolder = null; + + /** + * Set the log context. + * + * @param dataFolder Data Folder + * @param logger Logger + */ + public static void setLogContext(Path dataFolder, org.slf4j.Logger logger) { + Logger.logger = logger; + Logger.dataFolder = dataFolder; + intializeLogger(); + } + + private static void intializeLogger(){ + if (!dataFolder.resolve("logs").toFile().exists()) { + dataFolder.resolve("logs").toFile().mkdir(); + } + + Layout layout = PatternLayout.newBuilder() + .withPattern("%d{MM-dd-yy HH:mm:ss.SSS} %m%n") + .build(); + ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.newRootLogger(org.apache.logging.log4j.Level.INFO); + builder.setConfigurationName("ConversationsLogger"); + Configuration configuration = builder.build(); + IfLastModified ifLastModified = IfLastModified.createAgeCondition(Duration.parse("P14D")); + IfFileName ifFileName = IfFileName.createNameCondition("*/conversations-*.log.gz", null); + DeleteAction deleteAction = DeleteAction.createDeleteAction( + dataFolder.toString() + "/logs/", false, 1, false, null, + new PathCondition[]{ifLastModified,ifFileName}, null, configuration); + Action[] actions = new Action[]{deleteAction}; + RollingFileAppender rollingAppender = RollingFileAppender.newBuilder() + .setName("ConversationFile") + .withFileName(dataFolder.toString() + "/logs/conversations.log") + .withFilePattern(dataFolder.toString() + "/logs/conversations-%d{MM-dd-yyyy}.log.gz") + .withAppend(true) + .setLayout(layout) + .withStrategy(DefaultRolloverStrategy.newBuilder() + .withMax("20") + .withMin("1") + .withCompressionLevelStr("1") + .withFileIndex("min") + .withCustomActions(actions) + .withStopCustomActionsOnError(true) + .build()) + .withPolicy(TimeBasedTriggeringPolicy.newBuilder().withInterval(1).build()) + .setConfiguration(configuration) + .build(); + rollingAppender.start(); + org.apache.logging.log4j.core.Logger coreLogger = (org.apache.logging.log4j.core.Logger) fileLogger; + coreLogger.addAppender(rollingAppender); + coreLogger.info("Conversation Logger initialized"); + } + + /** + * Add a log to the console and file. + * + * @param log Component to log + */ + public static synchronized void addLog(Component log) { + if (logger == null || dataFolder == null) { + return; + } + String plain = PlainTextComponentSerializer.plainText().serialize(log); + logger.info(plain); + fileLogger.info(plain); + } +} diff --git a/src/main/java/com/oskarsmc/message/util/MessageModule.java b/src/main/java/com/oskarsmc/message/util/MessageModule.java index abbe251..a5b6d0d 100644 --- a/src/main/java/com/oskarsmc/message/util/MessageModule.java +++ b/src/main/java/com/oskarsmc/message/util/MessageModule.java @@ -4,6 +4,7 @@ import com.google.inject.Provides; import com.google.inject.Singleton; import com.oskarsmc.message.command.MessageCommand; +import com.oskarsmc.message.command.MessageToggleCommand; import com.oskarsmc.message.command.ReplyCommand; import com.oskarsmc.message.command.SocialSpyCommand; import com.oskarsmc.message.configuration.MessageSettings; @@ -31,6 +32,7 @@ public MessageModule(MessageSettings messageSettings) { protected void configure() { bind(MessageSettings.class).toInstance(messageSettings); bind(MessageCommand.class).in(Singleton.class); + bind(MessageToggleCommand.class).in(Singleton.class); bind(ReplyCommand.class).in(Singleton.class); bind(SocialSpyCommand.class).in(Singleton.class); bind(MessageMetrics.class).in(Singleton.class); diff --git a/src/main/java/com/oskarsmc/message/util/VersionUtils.java b/src/main/java/com/oskarsmc/message/util/VersionUtils.java index a27798b..8ab921a 100644 --- a/src/main/java/com/oskarsmc/message/util/VersionUtils.java +++ b/src/main/java/com/oskarsmc/message/util/VersionUtils.java @@ -11,7 +11,7 @@ public final class VersionUtils { /** * The latest config version. */ - public static final double CONFIG_VERSION = 1.2; + public static final double CONFIG_VERSION = 1.3; /** * Check if the Message Settings object is up-to-date. diff --git a/src/main/resources/config.toml b/src/main/resources/config.toml index f872f40..9f13bca 100644 --- a/src/main/resources/config.toml +++ b/src/main/resources/config.toml @@ -4,6 +4,7 @@ enabled = true luckperms-integration = true # If luckperms is found, use luckperms prefixes and suffixes. miniplaceholders-integration = true # If miniplaceholders is present, you can use the MiniPlaceholders placeholders in the messages. allow-self-message-sending = true # Allow a player to send messages to themselves. +enable-logging = true # Enable message logging to file and console. # Customise messages using MiniMessage # Documentation: https://docs.adventure.kyori.net/minimessage.html#format or https://webui.adventure.kyori.net/ @@ -14,7 +15,7 @@ allow-self-message-sending = true # Allow a player to send messages to themselve message-sent = "[YOU → ] " message-received = "[YOU] " message-socialspy = "[SocialSpy] []: " - +message-log-format = ": " # Customise error handling - leave blank to disable # Documentation: https://docs.adventure.kyori.net/minimessage.html#format or https://webui.adventure.kyori.net/ @@ -28,7 +29,8 @@ message-socialspy = "[SocialSpy] [ message = ["msg", "tell"] reply = ["r"] socialspy = ["ss"] +toggle = ["msgtoggle"] # Please don't touch this [developer-info] -config-version = 1.2 \ No newline at end of file +config-version = 1.3 \ No newline at end of file diff --git a/src/main/resources/translations.json b/src/main/resources/translations.json index 197e9f6..d8e58cd 100644 --- a/src/main/resources/translations.json +++ b/src/main/resources/translations.json @@ -8,7 +8,10 @@ "oskarsmc.message.command.common.argument.message-description": "The message to send to the player.", "oskarsmc.message.command.socialspy.on": "SocialSpy enabled.", "oskarsmc.message.command.socialspy.off": "SocialSpy disabled.", - "oskarsmc.message.command.common.self-sending-error": "You cannot send messages to yourself." + "oskarsmc.message.command.msgtoggle.on": "Messages enabled.", + "oskarsmc.message.command.msgtoggle.off": "Messages disabled.", + "oskarsmc.message.command.common.self-sending-error": "You cannot send messages to yourself.", + "oskarsmc.messages.disabled": "That player has messages disabled." } }, { @@ -18,7 +21,10 @@ "oskarsmc.message.command.common.argument.message-description": "El mensaje que se enviara al jugador.", "oskarsmc.message.command.socialspy.on": "SocialSpy habilitado.", "oskarsmc.message.command.socialspy.off": "SocialSpy deshabilitado.", - "oskarsmc.message.command.common.self-sending-error": "No puedes enviarte mensajes a ti mismo." + "oskarsmc.message.command.msgtoggle.on": "Mensajes habilitado.", + "oskarsmc.message.command.msgtoggle.off": "Mensajes deshabilitado.", + "oskarsmc.message.command.common.self-sending-error": "No puedes enviarte mensajes a ti mismo.", + "oskarsmc.messages.disabled": "Ese jugador tiene los mensajes deshabilitados." } } ] diff --git a/src/main/resources/userdata.yml b/src/main/resources/userdata.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/velocity-plugin.json b/src/main/resources/velocity-plugin.json index d4e82de..e02a3af 100644 --- a/src/main/resources/velocity-plugin.json +++ b/src/main/resources/velocity-plugin.json @@ -6,7 +6,8 @@ "url": "https://software.oskarsmc.com/plugins/velocity/message/", "authors": [ "OskarsMC", - "OskarZyg" + "OskarZyg", + "SnowzNZ" ], "dependencies": [ {