From c9a06a9253bb87a7c4dd7910ef5b9d486d072e9e Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:53:21 -0400 Subject: [PATCH 01/17] chore: revert game path --- DiscordConnector.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordConnector.csproj b/DiscordConnector.csproj index 494ddbc..8e3b4df 100644 --- a/DiscordConnector.csproj +++ b/DiscordConnector.csproj @@ -13,7 +13,7 @@ - /Users/nwesterhausen/Downloads/valheim_0.218.15 + G:\Valheim\0.218.15 $(ProjectDir)bin/ $(BinDir)DiscordConnector/ From 18311e2b1e27d52946d355dc72ce6cff0fc1d127 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Tue, 11 Jun 2024 17:26:36 -0400 Subject: [PATCH 02/17] wip: begin refactoring DiscordApi classes --- src/DiscordApi.cs | 174 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/src/DiscordApi.cs b/src/DiscordApi.cs index e41034d..8a3be72 100644 --- a/src/DiscordApi.cs +++ b/src/DiscordApi.cs @@ -246,6 +246,180 @@ private static void SendSerializedJson(string serializedJson) } } +internal class DiscordExecuteWebhook +{ +#nullable enable + /// + /// The message contents (up to 2000 characters). Required if `embeds` is not provided. + /// + public string? content { get; set; } + /// + /// Override the default username of the webhook. + /// + public string? username { get; set; } + /// + /// Override the default avatar of the webhook. + /// + public string? avatar_url { get; set; } + /// + /// An array of up to 10 embed objects. Required if `content` is not provided. + /// + public List? embeds { get; set; } + /// + /// Allowed mentions for the message. + /// + public AllowedMentions? allowed_mentions { get; set; } +#nullable restore + // ! Any additional fields are also left out, as they are not used in this plugin. + + /// + /// Create an empty DiscordExecuteWebhook object. + /// + public DiscordExecuteWebhook() + { + content = null; + username = null; + avatar_url = null; + embeds = null; + allowed_mentions = null; + } + + /// + /// Create a DiscordExecuteWebhook object with a message content. This is the most common use case: a simple message. + /// + public DiscordExecuteWebhook(string content) + { + this.content = content; + username = null; + avatar_url = null; + embeds = null; + allowed_mentions = null; + } + + /// + /// Set the +} + +internal class AllowedMentions +{ + /// + /// An array of allowed mention types to parse from the content. + /// + /// Allowed mention types: + /// + /// `roles` - Role mentions + /// `users` - User mentions + /// `everyone` - @everyone/@here mentions + /// + /// empty (none allowed) + public List parse { get; set; } + /// + /// Array of role_ids to mention (Max size of 100) + /// + /// empty + public List roles { get; set; } + /// + /// Array of user_ids to mention (Max size of 100) + /// + /// empty + public List users { get; set; } + /// + /// For replies, whether to mention the user being replied to. + /// + /// false + public bool replied_user { get; set; } + + public AllowedMentions() + { + parse = []; + roles = []; + users = []; + replied_user = false; + } + + /// + /// Enable `@everyone` and `@here` mentions. + /// + public void AllowEveryone() + { + if (!parse.Contains("everyone")) + parse.Add("everyone"); + } + + /// + /// Enable `@everyone` and `@here` mentions. + /// + public void AllowHere() + { + if (!parse.Contains("everyone")) + parse.Add("everyone"); + } + + /// + /// Add a role_id to the allowed mentions. + /// + /// The role_id to allow mentions for + public void AllowRole(string role_id) + { + if (!roles.Contains(role_id)) + roles.Add(role_id); + } + + /// + /// Add a list of role_ids to the allowed mentions. + /// + /// The role_ids to allow mentions for + public void AllowRoles(List role_ids) + { + foreach (string role_id in role_ids) + { + AllowRole(role_id); + } + } + + /// + /// Remove a role_id from the allowed mentions. + /// + /// The role_id to disallow mentions for + public void DisallowRole(string role_id) + { + if (roles.Contains(role_id)) + roles.Remove(role_id); + } + + /// + /// Add a user_id to the allowed mentions. + /// + /// The user_id to allow mentions for + public void AllowUser(string user_id) + { + if (!users.Contains(user_id)) + users.Add(user_id); + } + + /// + /// Add a list of user_ids to the allowed mentions. + /// + /// The user_ids to allow mentions for + public void AllowUsers(List user_ids) + { + foreach (string user_id in user_ids) + { + AllowUser(user_id); + } + } + + /// + /// Remove a user_id from the allowed mentions. + /// + /// The user_id to disallow mentions for + public void DisallowUser(string user_id) + { + if (users.Contains(user_id)) + users.Remove(user_id); + } +} + /// /// Simple webhook object is used for messages that contain only a simple string. /// From 99ee14b8f6e487f8bc512ce588105602fd7cde65 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:14:20 -0400 Subject: [PATCH 03/17] feat: working extra webhook and webhook username overrides Fulfills basics of #273 and all of #253 --- src/Config/ExtraWebhookConfig.cs | 157 ++++++++++++++++++++ src/Config/MainConfig.cs | 32 ++++- src/Config/PluginConfig.cs | 19 ++- src/DiscordApi.cs | 238 ++++++++++++++++++++----------- src/Webhook.cs | 13 +- 5 files changed, 365 insertions(+), 94 deletions(-) create mode 100644 src/Config/ExtraWebhookConfig.cs diff --git a/src/Config/ExtraWebhookConfig.cs b/src/Config/ExtraWebhookConfig.cs new file mode 100644 index 0000000..cc8fdac --- /dev/null +++ b/src/Config/ExtraWebhookConfig.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using BepInEx.Configuration; + +namespace DiscordConnector.Config; + +internal class ExtraWebhookConfig +{ + /// + /// The config file extension for this config file. + /// + public static string ConfigExtension = "extraWebhooks"; + /// + /// The maximum number of webhooks that can be defined in the config file. + /// + private const int MAX_WEBHOOKS = 16; + + /// + /// The config entries for the webhook urls. + /// + private List> webhookUrlList { get; set; } + /// + /// The config entries for the webhook events. + /// + private List> webhookEventsList { get; set; } + /// + /// The config entries for the webhook username overrides. + /// + private List> webhookUsernameOverrideList { get; set; } + /// + /// The webhook entries defined in the config file. + /// + private List webhookEntries { get; set; } + /// + /// A reference to the config file that this config is using. + /// + private readonly ConfigFile config; + /// + /// Title of the section in the config file + /// + private const string EXTRA_WEBHOOKS = "Extra Webhooks"; + + /// + /// Creates a new ExtraWebhookConfig object with the given config file. + /// + /// The config file to use for this config + public ExtraWebhookConfig(ConfigFile configFile) + { + config = configFile; + + webhookUrlList = []; + webhookEventsList = []; + webhookUsernameOverrideList = []; + + LoadConfig(); + + webhookEntries = LoadWebhookEntries(); + } + + /// + /// Reloads the config file and updates the webhook entries. + /// + public void ReloadConfig() + { + config.Reload(); + config.Save(); + + webhookEntries = LoadWebhookEntries(); + } + + + /// + /// Initializes the config file with the default values and the config entries available. + /// + private void LoadConfig() + { + for (int i = 0; i < MAX_WEBHOOKS; i++) + { + webhookUrlList.Add(config.Bind( + EXTRA_WEBHOOKS, + $"Webhook URL {i + 1}", + "", + $"Discord channel webhook URL. For instructions, reference the 'MAKING A WEBHOOK' section of " + Environment.NewLine + + "Discord's documentation: https://support.Discord.com/hc/en-us/articles/228383668-Intro-to-Webhook") + ); + + webhookEventsList.Add(config.Bind( + EXTRA_WEBHOOKS, + $"Webhook Events {i + 1}", + "ALL", + $"Specify a subset of possible events to send to this webhook ({i + 1}). Previously all events would go the webhook." + Environment.NewLine + + "Format should be the keyword 'ALL' or a semi-colon separated list, e.g. 'serverLaunch;serverStart;serverSave;'" + Environment.NewLine + + "Full list of valid options here: https://discord-connector.valheim.games.nwest.one/config/main.html#webhook-events") + ); + + webhookUsernameOverrideList.Add(config.Bind( + EXTRA_WEBHOOKS, + $"Webhook Username Override {i + 1}", + "", + $"Optional: Override the username of the webhook for this entry ({i + 1})." + Environment.NewLine + + "If left blank, the webhook will use the default username set in the main config.") + ); + } + + config.Save(); + } + + /// + /// Converts the config entries into a list of WebhookEntry objects + /// + /// A list of webhooks ready for use. Only returns webhooks that are set up correctly. + private List LoadWebhookEntries() + { + List entries = []; + + for (int i = 0; i < webhookUrlList.Count; i++) + { + // If either the URL or the events are empty, skip this entry + if (string.IsNullOrEmpty(webhookUrlList[i].Value) || string.IsNullOrEmpty(webhookEventsList[i].Value)) + { + continue; + } + entries.Add(new WebhookEntry(webhookUrlList[i].Value, Webhook.StringToEventList(webhookEventsList[i].Value), webhookUsernameOverrideList[i].Value)); + } + + return entries; + } + + /// + /// Returns this config as a JSON string (used mostly for debugging) + /// + /// JSON String of this config file + public string ConfigAsJson() + { + string jsonString = "{"; + + for (int i = 0; i < MAX_WEBHOOKS; i++) + { + jsonString += $"\"webhookURL{i + 1}\": \"{webhookUrlList[i].Value}\","; + jsonString += $"\"webhookEvents{i + 1}\": \"{webhookEventsList[i].Value}\","; + jsonString += $"\"webhookUsernameOverride{i + 1}\": \"{webhookUsernameOverrideList[i].Value}\","; + } + + jsonString += "}"; + + return jsonString; + } + + /// + /// Returns the webhook entries defined in the config file. + /// + /// A list of webhook entries + public List GetWebhookEntries() + { + return webhookEntries; + } +} diff --git a/src/Config/MainConfig.cs b/src/Config/MainConfig.cs index e674cb9..c6d2293 100644 --- a/src/Config/MainConfig.cs +++ b/src/Config/MainConfig.cs @@ -22,16 +22,19 @@ public enum RetrievalDiscernmentMethods public const string RetrieveBySteamID = "PlayerId: Treat each PlayerId as a separate player"; public const string RetrieveByNameAndSteamID = "NameAndPlayerId: Treat each [PlayerId:CharacterName] combo as a separate player"; public const string RetrieveByName = "Name: Treat each CharacterName as a separate player"; - private ConfigFile config; - private static List mutedPlayers; + private readonly ConfigFile config; + private static List mutedPlayers; private static Regex mutedPlayersRegex; private const string MAIN_SETTINGS = "Main Settings"; // Main Settings + private ConfigEntry defaultWebhookUsernameOverride; private ConfigEntry webhookUrl; private ConfigEntry webhookUrl2; private ConfigEntry webhookEvents; private ConfigEntry webhook2Events; + private ConfigEntry webhookUsernameOverride; + private ConfigEntry webhook2UsernameOverride; private ConfigEntry discordEmbedMessagesToggle; private ConfigEntry mutedDiscordUserList; private ConfigEntry mutedDiscordUserListRegex; @@ -89,6 +92,11 @@ public void ReloadConfig() private void LoadConfig() { + defaultWebhookUsernameOverride = config.Bind(MAIN_SETTINGS, + "Default Webhook Username Override", + "", + "Override the username of all webhooks for this instance of Discord Connector. If left blank, the webhook will use the default name (assigned by Discord)." + Environment.NewLine + + "This setting will be used for all webhooks unless overridden by a specific webhook username override setting."); webhookUrl = config.Bind(MAIN_SETTINGS, "Webhook URL", @@ -101,7 +109,12 @@ private void LoadConfig() "ALL", "Specify a subset of possible events to send to the primary webhook. Previously all events would go to the primary webhook." + Environment.NewLine + "Format should be the keyword 'ALL' or a semi-colon separated list, e.g. 'serverLifecycle;playerAll;playerFirstAll;leaderboardsAll;'" + Environment.NewLine + - "Full list of valid options here: https://discordconnector.valheim.nwest.games/config/main.html#webhook-events"); + "Full list of valid options here: https://discord-connector.valheim.games.nwest.one/config/main.html#webhook-events"); + + webhookUsernameOverride = config.Bind(MAIN_SETTINGS, + "Webhook Username Override", + "", + "Override the username of the webhook. If left blank, the webhook will use the default name."); webhookUrl2 = config.Bind(MAIN_SETTINGS, "Secondary Webhook URL", @@ -112,9 +125,15 @@ private void LoadConfig() webhook2Events = config.Bind(MAIN_SETTINGS, "Secondary Webhook Events", "ALL", - "Specify a subset of possible events to send to the primary webhook. Previously all events would go to the primary webhook." + Environment.NewLine + + "Specify a subset of possible events to send to the secondary webhook." + Environment.NewLine + "Format should be the keyword 'ALL' or a semi-colon separated list, e.g. 'serverLaunch;serverStart;serverSave;'" + Environment.NewLine + - "Full list of valid options here: https://discordconnector.valheim.nwest.games/config/main.html#webhook-events"); + "Full list of valid options here: https://discord-connector.valheim.games.nwest.one/config/main.html#webhook-events"); + + webhook2UsernameOverride = config.Bind(MAIN_SETTINGS, + "Secondary Webhook Username Override", + "", + "Optional: Override the username of the secondary webhook." + Environment.NewLine + + "If left blank, the webhook will use the default username set in the main config."); logDebugMessages = config.Bind(MAIN_SETTINGS, "Log Debug Messages", @@ -176,10 +195,13 @@ public string ConfigAsJson() { string jsonString = "{"; jsonString += "\"discord\":{"; + jsonString += $"\"defaultWebhookUsernameOverride\":\"{defaultWebhookUsernameOverride.Value}\","; jsonString += $"\"webhook\":\"{(string.IsNullOrEmpty(webhookUrl.Value) ? "unset" : "REDACTED")}\","; jsonString += $"\"webhookEvents\":\"{webhookEvents.Value}\","; + jsonString += $"\"webhookUsernameOverride\":\"{webhookUsernameOverride.Value}\","; jsonString += $"\"webhook2\":\"{(string.IsNullOrEmpty(webhookUrl2.Value) ? "unset" : "REDACTED")}\","; jsonString += $"\"webhook2Events\":\"{webhook2Events.Value}\","; + jsonString += $"\"webhook2UsernameOverride\":\"{webhook2UsernameOverride.Value}\","; jsonString += $"\"logDebugMessages\":\"{logDebugMessages.Value}\","; jsonString += $"\"fancierMessages\":\"{DiscordEmbedsEnabled}\","; jsonString += $"\"ignoredPlayers\":\"{mutedDiscordUserList.Value}\","; diff --git a/src/Config/PluginConfig.cs b/src/Config/PluginConfig.cs index 99b2c00..0cebaab 100644 --- a/src/Config/PluginConfig.cs +++ b/src/Config/PluginConfig.cs @@ -12,6 +12,7 @@ internal class PluginConfig private TogglesConfig togglesConfig; private VariableConfig variableConfig; private LeaderBoardConfig leaderBoardConfig; + private ExtraWebhookConfig extraWebhookConfig; public readonly string configPath; const string ConfigJsonFilename = "config-dump.json"; @@ -23,6 +24,7 @@ internal class PluginConfig "variables", "leaderBoard", "toggles", + "extraWebhooks", "main" }; @@ -81,24 +83,28 @@ public PluginConfig(ConfigFile config) string togglesConfigFilename = $"{PluginInfo.SHORT_PLUGIN_ID}-{TogglesConfig.ConfigExtension}.cfg"; string variableConfigFilename = $"{PluginInfo.SHORT_PLUGIN_ID}-{VariableConfig.ConfigExtension}.cfg"; string leaderBoardConfigFilename = $"{PluginInfo.SHORT_PLUGIN_ID}-{LeaderBoardConfig.ConfigExtension}.cfg"; + string extraWebhooksConfigFilename = $"{PluginInfo.SHORT_PLUGIN_ID}-{ExtraWebhookConfig.ConfigExtension}.cfg"; string mainConfigPath = Path.Combine(configPath, mainConfigFilename); string messagesConfigPath = Path.Combine(configPath, messageConfigFilename); string togglesConfigPath = Path.Combine(configPath, togglesConfigFilename); string variableConfigPath = Path.Combine(configPath, variableConfigFilename); string leaderBoardConfigPath = Path.Combine(configPath, leaderBoardConfigFilename); + string extraWebhooksConfigPath = Path.Combine(configPath, extraWebhooksConfigFilename); Plugin.StaticLogger.LogDebug($"Main config: {mainConfigPath}"); Plugin.StaticLogger.LogDebug($"Messages config: {messagesConfigPath}"); Plugin.StaticLogger.LogDebug($"Toggles config: {togglesConfigPath}"); Plugin.StaticLogger.LogDebug($"Variable config: {variableConfigPath}"); - Plugin.StaticLogger.LogDebug($"Leader board config: {leaderBoardConfigFilename}"); + Plugin.StaticLogger.LogDebug($"Leader board config: {leaderBoardConfigPath}"); + Plugin.StaticLogger.LogDebug($"Extra Webhook config: {extraWebhooksConfigPath}"); mainConfig = new MainConfig(new BepInEx.Configuration.ConfigFile(mainConfigPath, true)); messagesConfig = new MessagesConfig(new BepInEx.Configuration.ConfigFile(messagesConfigPath, true)); togglesConfig = new TogglesConfig(new BepInEx.Configuration.ConfigFile(togglesConfigPath, true)); variableConfig = new VariableConfig(new BepInEx.Configuration.ConfigFile(variableConfigPath, true)); leaderBoardConfig = new LeaderBoardConfig(new BepInEx.Configuration.ConfigFile(leaderBoardConfigPath, true)); + extraWebhookConfig = new ExtraWebhookConfig(new BepInEx.Configuration.ConfigFile(extraWebhooksConfigPath, true)); Plugin.StaticLogger.LogDebug("Configuration Loaded"); Plugin.StaticLogger.LogDebug($"Muted Players Regex pattern ('a^' is default for no matches): {mainConfig.MutedPlayersRegex.ToString()}"); @@ -112,6 +118,7 @@ public void ReloadConfig() togglesConfig.ReloadConfig(); variableConfig.ReloadConfig(); leaderBoardConfig.ReloadConfig(); + extraWebhookConfig.ReloadConfig(); } /// @@ -137,6 +144,9 @@ public void ReloadConfig(string configExt) case "leaderBoard": leaderBoardConfig.ReloadConfig(); return; + case "extraWebhooks": + extraWebhookConfig.ReloadConfig(); + return; default: return; } @@ -261,6 +271,12 @@ public void ReloadConfig(string configExt) public LeaderBoardConfigReference[] LeaderBoards => leaderBoardConfig.LeaderBoards; public ActivePlayersAnnouncementConfigValues ActivePlayersAnnouncement => leaderBoardConfig.ActivePlayersAnnouncement; + // Extra webhook config + public List ExtraWebhooks => extraWebhookConfig.GetWebhookEntries(); + + /// + /// Writes the loaded configuration to a JSON file in the config directory. + /// public void DumpConfigAsJson() { string jsonString = "{"; @@ -270,6 +286,7 @@ public void DumpConfigAsJson() jsonString += $"\"Config.Toggles\":{togglesConfig.ConfigAsJson()},"; jsonString += $"\"Config.Variables\":{variableConfig.ConfigAsJson()},"; jsonString += $"\"Config.LeaderBoard\":{leaderBoardConfig.ConfigAsJson()}"; + jsonString += $"\"Config.ExtraWebhooks\":{extraWebhookConfig.ConfigAsJson()}"; jsonString += "}"; diff --git a/src/DiscordApi.cs b/src/DiscordApi.cs index 8a3be72..8b53820 100644 --- a/src/DiscordApi.cs +++ b/src/DiscordApi.cs @@ -36,21 +36,12 @@ public static void SendMessage(Webhook.Event ev, string message, UnityEngine.Vec public static void SendMessage(Webhook.Event ev, string message) { // A simple string message - DiscordSimpleWebhook payload = new() + DiscordExecuteWebhook payload = new() { content = message }; - try - { - string payloadString = JsonConvert.SerializeObject(payload); - SendSerializedJson(ev, payloadString); - } - catch (Exception e) - { - Plugin.StaticLogger.LogWarning($"Error serializing payload: {e}"); - } - + payload.SendFor(ev); } /// @@ -67,57 +58,35 @@ public static void SendMessageWithFields(Webhook.Event ev, string content = null content = "Uh-oh! An unexpectedly empty message was sent!"; } - // Begin the payload object - string payloadString = "{"; + DiscordExecuteWebhook payload = new() + { + content = content + }; + // If we have fields at all, put them as embedded fields if (fields != null) { + payload.embeds = []; + List discordFields = []; // Convert the fields into JSON Strings List fieldStrings = []; foreach (Tuple t in fields) { - try + discordFields.Add(new DiscordField { - fieldStrings.Add(JsonConvert.SerializeObject(new DiscordField - { - name = t.Item1, - value = t.Item2 - })); - } - catch (Exception e) - { - Plugin.StaticLogger.LogWarning($"Error serializing field: {e}"); - } + name = t.Item1, + value = t.Item2 + }); } - if (fieldStrings.Count > 0) + // Add the fields to the payload + payload.embeds.Add(new DiscordEmbed { - // Put the field JSON strings into our payload object - // Fields go under embed as array - payloadString += "\"embeds\":[{\"fields\":["; - payloadString += string.Join(",", fieldStrings.ToArray()); - payloadString += "]}]"; - - // Cautiously put a comma if there is content to add to the payload as well - if (content != null) - { - payloadString += ","; - } - } + fields = discordFields + }); } - // If there is any content - if (content != null) - { - // Append the content to the payload - payloadString += $"\"content\":\"{content}\""; - } - - // Finish the payload JSON - payloadString += "}"; - - // Use our pre-existing method to send serialized JSON to discord - SendSerializedJson(ev, payloadString); + payload.SendFor(ev); } /// @@ -125,9 +94,9 @@ public static void SendMessageWithFields(Webhook.Event ev, string content = null /// /// The event which triggered this message /// Body data for the webhook as JSON serialized into a string - private static void SendSerializedJson(Webhook.Event ev, string serializedJson) + public static void SendSerializedJson(Webhook.Event ev, string serializedJson) { - Plugin.StaticLogger.LogDebug($"Trying webhook with payload: {serializedJson} (event: {ev})"); + Plugin.StaticLogger.LogDebug($"Finding webhooks for event: (event: {ev})"); if (ev == Webhook.Event.Other) { @@ -153,6 +122,37 @@ private static void SendSerializedJson(Webhook.Event ev, string serializedJson) Plugin.StaticLogger.LogDebug($"Sending {ev} message to Secondary Webhook"); DispatchRequest(Plugin.StaticConfig.SecondaryWebhook, byteArray); } + + // Check for any extra webhooks that should be sent to + foreach (WebhookEntry webhook in Plugin.StaticConfig.ExtraWebhooks) + { + if (webhook.HasEvent(ev)) + { + Plugin.StaticLogger.LogDebug($"Sending {ev} message to Extra Webhook: {webhook.Url}"); + DispatchRequest(webhook, byteArray); + } + } + } + + /// + /// Sends serialized JSON to the . + /// + /// Webhook definition to receive the JSON + /// Serialized JSON of the request to make + public static void SendSerializedJson(WebhookEntry webhook, string serializedJson) + { + Plugin.StaticLogger.LogDebug($"Trying webhook with payload: {serializedJson}"); + + // Guard against unset webhook or empty serialized json + if (string.IsNullOrEmpty(webhook.Url) || string.IsNullOrEmpty(serializedJson)) + { + return; + } + + // Responsible for sending a JSON string to the webhook. + byte[] byteArray = Encoding.UTF8.GetBytes(serializedJson); + + DispatchRequest(webhook, byteArray); } /// @@ -167,6 +167,7 @@ private static void DispatchRequest(WebhookEntry webhook, byte[] byteArray) Plugin.StaticLogger.LogDebug($"Dispatch attempted with empty webhook - ignoring"); return; } + Plugin.StaticLogger.LogDebug($"Dispatching request to {webhook.Url}"); // Create a web request to send the payload to discord WebRequest request = WebRequest.Create(webhook.Url); @@ -282,22 +283,106 @@ public DiscordExecuteWebhook() avatar_url = null; embeds = null; allowed_mentions = null; + + // Grab the allowed mentions from the configuration + // Grab the username override from the configuration + // Grab the avatar URL override from the configuration } /// - /// Create a DiscordExecuteWebhook object with a message content. This is the most common use case: a simple message. + /// Set the username for the webhook. /// - public DiscordExecuteWebhook(string content) + /// The username to set for the webhook + public void SetUsername(string username) { - this.content = content; - username = null; - avatar_url = null; - embeds = null; - allowed_mentions = null; + this.username = username; + } + + /// + /// Set the avatar URL for the webhook. + /// + /// The avatar URL to set for the webhook + public void SetAvatarUrl(string avatar_url) + { + this.avatar_url = avatar_url; + } + + /// + /// Validate that this object is ready to be serialized into JSON. + /// + /// This is a simple check to ensure that the object is not empty, and that it has at least one of the required fields. + /// + /// True if the object is valid, false otherwise + public bool IsValid() + { + return !string.IsNullOrEmpty(content) || embeds != null; } /// - /// Set the + /// Send the message to Discord. + /// + /// The event that triggered this message + public void SendFor(Webhook.Event ev) + { + // If the object is not valid, do not send it + if (!IsValid()) + { + Plugin.StaticLogger.LogWarning($"Attempted to send an invalid DiscordExecuteWebhook object:\n{this}"); + return; + } + + // Find any webhook events that match the event + try + { + if (Plugin.StaticConfig.PrimaryWebhook.HasEvent(ev)) + { + Plugin.StaticLogger.LogDebug($"Sending {ev} message to Primary Webhook"); + WebhookEntry primaryWebhook = Plugin.StaticConfig.PrimaryWebhook; + + if (primaryWebhook.HasUsernameOverride()) + { + SetUsername(primaryWebhook.UsernameOverride); + } + + DiscordApi.SendSerializedJson(primaryWebhook, JsonConvert.SerializeObject(this)); + + } + if (Plugin.StaticConfig.SecondaryWebhook.HasEvent(ev)) + { + Plugin.StaticLogger.LogDebug($"Sending {ev} message to Secondary Webhook"); + WebhookEntry secondaryWebhook = Plugin.StaticConfig.SecondaryWebhook; + + if (secondaryWebhook.HasUsernameOverride()) + { + SetUsername(secondaryWebhook.UsernameOverride); + } + + DiscordApi.SendSerializedJson(secondaryWebhook, JsonConvert.SerializeObject(this)); + + } + if (Plugin.StaticConfig.ExtraWebhooks != null) + { + foreach (WebhookEntry webhook in Plugin.StaticConfig.ExtraWebhooks) + { + if (webhook.HasEvent(ev)) + { + Plugin.StaticLogger.LogDebug($"Sending {ev} message to an Extra Webhook"); + if (webhook.HasUsernameOverride()) + { + SetUsername(webhook.UsernameOverride); + } + + DiscordApi.SendSerializedJson(webhook, JsonConvert.SerializeObject(this)); + + } + } + } + } + catch (Exception e) + { + Plugin.StaticLogger.LogWarning($"Error serializing payload: {e}"); + } + } } internal class AllowedMentions @@ -420,34 +505,6 @@ public void DisallowUser(string user_id) } } -/// -/// Simple webhook object is used for messages that contain only a simple string. -/// -internal class DiscordSimpleWebhook -{ - /// - /// The message content to send to Discord. This is considered simple because it is not a series of embeds/fields. - /// - /// This works best for simple messages, and is used for most messages sent by the plugin. - /// - /// E.g. "Player joined the game", "Player left the game", etc. - /// - public string content { get; set; } -} - -/// -/// Complex webhook object is used for messages that contain more than just a simple string. -/// -internal class DiscordComplexWebhook -{ - /// - /// Message content to send to Discord. This is considered complex because it contains multiple embeds/fields. - /// - /// This is used for POS embeds, and the leaderboards. - /// - public DiscordEmbed embeds { get; set; } -} - /// /// A complex Discord message, containing more than just a simple string. /// @@ -493,4 +550,11 @@ internal class DiscordField /// For example, the leaderboards are a list with `\n` as a separator, so they appear as an ordered list in Discord. /// public string value { get; set; } +#nullable enable + /// + /// Whether or not this field should display inline. + /// + /// false + public bool? inline { get; set; } +#nullable restore } diff --git a/src/Webhook.cs b/src/Webhook.cs index 63a8f6a..38d79db 100644 --- a/src/Webhook.cs +++ b/src/Webhook.cs @@ -165,8 +165,9 @@ class WebhookEntry { public string Url { get; set; } public List FireOnEvents { get; set; } + public string UsernameOverride { get; set; } = string.Empty; - public WebhookEntry(string url, List fireOnEvents) + public WebhookEntry(string url, List fireOnEvents, string usernameOverride = "") { if (string.IsNullOrEmpty(url)) { @@ -188,6 +189,16 @@ public WebhookEntry(string url, List fireOnEvents) { FireOnEvents = fireOnEvents; } + + if (!string.IsNullOrEmpty(usernameOverride)) + { + UsernameOverride = usernameOverride; + } + } + + public bool HasUsernameOverride() + { + return !string.IsNullOrEmpty(UsernameOverride); } internal bool HasEvent(Webhook.Event ev) From 057beafdf50ac40ded037e99c07517494db389f2 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:18:55 -0400 Subject: [PATCH 04/17] fix: add missing commas to json config dump and redact webhook URLs --- src/Config/ExtraWebhookConfig.cs | 2 +- src/Config/MessagesConfig.cs | 2 +- src/Config/PluginConfig.cs | 2 +- src/Config/TogglesConfig.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Config/ExtraWebhookConfig.cs b/src/Config/ExtraWebhookConfig.cs index cc8fdac..eaafcbe 100644 --- a/src/Config/ExtraWebhookConfig.cs +++ b/src/Config/ExtraWebhookConfig.cs @@ -136,7 +136,7 @@ public string ConfigAsJson() for (int i = 0; i < MAX_WEBHOOKS; i++) { - jsonString += $"\"webhookURL{i + 1}\": \"{webhookUrlList[i].Value}\","; + jsonString += $"\"webhookURL{i + 1}\": \"{(string.IsNullOrEmpty(webhookUrlList[i].Value) ? "unset" : "REDACTED")}\","; jsonString += $"\"webhookEvents{i + 1}\": \"{webhookEventsList[i].Value}\","; jsonString += $"\"webhookUsernameOverride{i + 1}\": \"{webhookUsernameOverrideList[i].Value}\","; } diff --git a/src/Config/MessagesConfig.cs b/src/Config/MessagesConfig.cs index 6ff6fbc..60ac562 100644 --- a/src/Config/MessagesConfig.cs +++ b/src/Config/MessagesConfig.cs @@ -221,7 +221,7 @@ public string ConfigAsJson() jsonString += $"\"startMessage\":\"{serverLoadedMessage.Value}\","; jsonString += $"\"stopMessage\":\"{serverStopMessage.Value}\","; jsonString += $"\"shutdownMessage\":\"{serverShutdownMessage.Value}\","; - jsonString += $"\"savedMessage\":\"{serverSavedMessage.Value}\""; + jsonString += $"\"savedMessage\":\"{serverSavedMessage.Value}\","; jsonString += $"\"serverNewDayMessage\":\"{serverNewDayMessage.Value}\""; jsonString += "},"; diff --git a/src/Config/PluginConfig.cs b/src/Config/PluginConfig.cs index 0cebaab..8f398ca 100644 --- a/src/Config/PluginConfig.cs +++ b/src/Config/PluginConfig.cs @@ -285,7 +285,7 @@ public void DumpConfigAsJson() jsonString += $"\"Config.Messages\":{messagesConfig.ConfigAsJson()},"; jsonString += $"\"Config.Toggles\":{togglesConfig.ConfigAsJson()},"; jsonString += $"\"Config.Variables\":{variableConfig.ConfigAsJson()},"; - jsonString += $"\"Config.LeaderBoard\":{leaderBoardConfig.ConfigAsJson()}"; + jsonString += $"\"Config.LeaderBoard\":{leaderBoardConfig.ConfigAsJson()},"; jsonString += $"\"Config.ExtraWebhooks\":{extraWebhookConfig.ConfigAsJson()}"; jsonString += "}"; diff --git a/src/Config/TogglesConfig.cs b/src/Config/TogglesConfig.cs index f2596a3..246637b 100644 --- a/src/Config/TogglesConfig.cs +++ b/src/Config/TogglesConfig.cs @@ -269,7 +269,7 @@ public string ConfigAsJson() jsonString += $"\"eventPausedEnabled\":\"{EventStopMessageEnabled}\","; jsonString += $"\"eventStoppedEnabled\":\"{EventPausedMessageEnabled}\","; jsonString += $"\"eventResumedEnabled\":\"{EventResumedMessageEnabled}\","; - jsonString += $"\"chatShoutAllCaps\":\"{ChatShoutAllCaps}\""; + jsonString += $"\"chatShoutAllCaps\":\"{ChatShoutAllCaps}\","; jsonString += $"\"newDayNumberToggle\":\"{NewDayNumberEnabled}\""; jsonString += "},"; From 4a01c9d6b4e0644072ef800ff00832771c3a22c6 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 07:07:39 -0400 Subject: [PATCH 05/17] fix: don't call ZNet's public IP method every time --- src/MessageTransformer.cs | 11 +---------- src/Plugin.cs | 2 +- src/Utility.cs | 13 +++++++++++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/MessageTransformer.cs b/src/MessageTransformer.cs index c949e1f..c22d94a 100644 --- a/src/MessageTransformer.cs +++ b/src/MessageTransformer.cs @@ -118,17 +118,8 @@ private static string ReplaceNumPlayers(string rawMessage) /// Raw message to format private static string ReplacePublicIp(string rawMessage) { - string publicIp = ""; - try - { - publicIp = ZNet.GetPublicIP(); - } - catch (System.Exception e) - { - Plugin.StaticLogger.LogError($"Unable to get Public IP from ZNet. {e.Message}"); - } return rawMessage - .Replace(PUBLIC_IP, publicIp); + .Replace(PUBLIC_IP, Plugin.PublicIpAddress); } /// diff --git a/src/Plugin.cs b/src/Plugin.cs index 0c361c9..36c2510 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -29,7 +29,7 @@ internal static string PublicIpAddress { return _publicIpAddress; } - _publicIpAddress = ZNet.GetPublicIP(); + _publicIpAddress = PublicIPChecker.GetPublicIP(); return _publicIpAddress; } } diff --git a/src/Utility.cs b/src/Utility.cs index 825833e..7c13b98 100644 --- a/src/Utility.cs +++ b/src/Utility.cs @@ -34,3 +34,16 @@ public static string HumanReadableMs(double ms) t.Milliseconds); } } + +internal static class PublicIPChecker +{ + /// + /// Get the public IP address of the server from https://ifconfig.me/ip + /// + /// The public IP address of the server + public static string GetPublicIP() + { + using System.Net.WebClient client = new(); + return client.DownloadString("https://ifconfig.me/ip"); + } +} From beb73da604d1f48e1b0706594ba196e1e1434013 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 07:15:52 -0400 Subject: [PATCH 06/17] fix: handle possible exceptions for IP lookup --- src/Utility.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Utility.cs b/src/Utility.cs index 7c13b98..1ef2166 100644 --- a/src/Utility.cs +++ b/src/Utility.cs @@ -43,7 +43,18 @@ internal static class PublicIPChecker /// The public IP address of the server public static string GetPublicIP() { - using System.Net.WebClient client = new(); - return client.DownloadString("https://ifconfig.me/ip"); + Plugin.StaticLogger.LogDebug("Getting public IP address."); + string address = string.Empty; + try + { + using System.Net.WebClient client = new(); + address = client.DownloadString("https://ifconfig.me/ip"); + } + catch (Exception e) + { + Plugin.StaticLogger.LogError($"Failed to get public IP address, an empty string will be used: {e.Message}"); + } + Plugin.StaticLogger.LogDebug($"Public IP address is '{address}'"); + return address; } } From 5a21e20f1665036553a09704ff43c3c2c12d00d9 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 08:02:58 -0400 Subject: [PATCH 07/17] feat: add avatar URL override for webhooks --- src/Config/ExtraWebhookConfig.cs | 63 +++++++++++++++++++++++++++- src/Config/MainConfig.cs | 70 ++++++++++++++++++++++++++------ src/DiscordApi.cs | 25 ++++++++++++ src/Webhook.cs | 66 +++++++++++++++++++++++++----- 4 files changed, 198 insertions(+), 26 deletions(-) diff --git a/src/Config/ExtraWebhookConfig.cs b/src/Config/ExtraWebhookConfig.cs index eaafcbe..3d6789e 100644 --- a/src/Config/ExtraWebhookConfig.cs +++ b/src/Config/ExtraWebhookConfig.cs @@ -28,6 +28,10 @@ internal class ExtraWebhookConfig /// private List> webhookUsernameOverrideList { get; set; } /// + /// The config entries for the webhook avatar overrides. + /// + private List> webhookAvatarOverrideList { get; set; } + /// /// The webhook entries defined in the config file. /// private List webhookEntries { get; set; } @@ -51,6 +55,7 @@ public ExtraWebhookConfig(ConfigFile configFile) webhookUrlList = []; webhookEventsList = []; webhookUsernameOverrideList = []; + webhookAvatarOverrideList = []; LoadConfig(); @@ -97,9 +102,17 @@ private void LoadConfig() EXTRA_WEBHOOKS, $"Webhook Username Override {i + 1}", "", - $"Optional: Override the username of the webhook for this entry ({i + 1})." + Environment.NewLine + + "Optional: Override the username of this webhook." + Environment.NewLine + "If left blank, the webhook will use the default username set in the main config.") ); + + webhookAvatarOverrideList.Add(config.Bind( + EXTRA_WEBHOOKS, + $"Webhook Avatar Override {i + 1}", + "", + "Optional: Override the avatar of this webhook with the image at the given URL." + Environment.NewLine + + "If left blank, the webhook will use the avatar defined on the Discord webhook in your server's settings.") + ); } config.Save(); @@ -111,6 +124,14 @@ private void LoadConfig() /// A list of webhooks ready for use. Only returns webhooks that are set up correctly. private List LoadWebhookEntries() { + // Check that all the lists are the same length + if (webhookUrlList.Count != webhookEventsList.Count || webhookUrlList.Count != webhookUsernameOverrideList.Count || webhookUrlList.Count != webhookAvatarOverrideList.Count) + { + Plugin.StaticLogger.LogError("Webhook lists are not the same length. This should not happen."); + Plugin.StaticLogger.LogError($"URLs: {webhookUrlList.Count}, Events: {webhookEventsList.Count}, Usernames: {webhookUsernameOverrideList.Count}, Avatars: {webhookAvatarOverrideList.Count}"); + return []; + } + List entries = []; for (int i = 0; i < webhookUrlList.Count; i++) @@ -118,9 +139,41 @@ private List LoadWebhookEntries() // If either the URL or the events are empty, skip this entry if (string.IsNullOrEmpty(webhookUrlList[i].Value) || string.IsNullOrEmpty(webhookEventsList[i].Value)) { + Plugin.StaticLogger.LogDebug($"ExtraWebhooks: Skipping Webhook {i + 1} because URL or Events are empty."); + continue; + } + + // Convert the events string into a list of events + List events = Webhook.StringToEventList(webhookEventsList[i].Value); + + // If the events list is empty, skip this entry + if (events.Count == 0) + { + Plugin.StaticLogger.LogDebug($"ExtraWebhooks: Skipping Webhook {i + 1} because events are empty."); continue; } - entries.Add(new WebhookEntry(webhookUrlList[i].Value, Webhook.StringToEventList(webhookEventsList[i].Value), webhookUsernameOverrideList[i].Value)); + + // Create a new WebhookEntry object + WebhookEntry entry = new( + webhookUrlList[i].Value, + events + ); + + if (!string.IsNullOrEmpty(webhookUsernameOverrideList[i].Value)) + { + entry.UsernameOverride = webhookUsernameOverrideList[i].Value; + } + if (!string.IsNullOrEmpty(webhookAvatarOverrideList[i].Value)) + { + entry.AvatarOverride = webhookAvatarOverrideList[i].Value; + } + + entries.Add(entry); + } + + if (entries.Count > 0) + { + Plugin.StaticLogger.LogDebug($"ExtraWebhooks: Loaded {entries.Count} webhooks from the config file."); } return entries; @@ -139,6 +192,12 @@ public string ConfigAsJson() jsonString += $"\"webhookURL{i + 1}\": \"{(string.IsNullOrEmpty(webhookUrlList[i].Value) ? "unset" : "REDACTED")}\","; jsonString += $"\"webhookEvents{i + 1}\": \"{webhookEventsList[i].Value}\","; jsonString += $"\"webhookUsernameOverride{i + 1}\": \"{webhookUsernameOverrideList[i].Value}\","; + jsonString += $"\"webhookAvatarOverride{i + 1}\": \"{webhookAvatarOverrideList[i].Value}\""; + + if (i < MAX_WEBHOOKS - 1) + { + jsonString += ","; + } } jsonString += "}"; diff --git a/src/Config/MainConfig.cs b/src/Config/MainConfig.cs index c6d2293..04fc973 100644 --- a/src/Config/MainConfig.cs +++ b/src/Config/MainConfig.cs @@ -35,6 +35,8 @@ public enum RetrievalDiscernmentMethods private ConfigEntry webhook2Events; private ConfigEntry webhookUsernameOverride; private ConfigEntry webhook2UsernameOverride; + private ConfigEntry webhookAvatarOverride; + private ConfigEntry webhook2AvatarOverride; private ConfigEntry discordEmbedMessagesToggle; private ConfigEntry mutedDiscordUserList; private ConfigEntry mutedDiscordUserListRegex; @@ -48,6 +50,9 @@ public enum RetrievalDiscernmentMethods private WebhookEntry primaryWebhook; private WebhookEntry secondaryWebhook; + /// + /// Creates a new MainConfig object with the given config file. + /// public MainConfig(ConfigFile configFile) { config = configFile; @@ -55,20 +60,13 @@ public MainConfig(ConfigFile configFile) Plugin.StaticLogger.SetLogLevel(logDebugMessages.Value); - mutedPlayers = new List(mutedDiscordUserList.Value.Split(';')); - if (String.IsNullOrEmpty(mutedDiscordUserListRegex.Value)) - { - mutedPlayersRegex = new Regex(@"a^"); - } - else - { - mutedPlayersRegex = new Regex(mutedDiscordUserListRegex.Value); - } - - primaryWebhook = new WebhookEntry(webhookUrl.Value, Webhook.StringToEventList(webhookEvents.Value)); - secondaryWebhook = new WebhookEntry(webhookUrl2.Value, Webhook.StringToEventList(webhook2Events.Value)); + UpdateMutedPlayers(); + UpdateWebhooks(); } + /// + /// Reloads the config file and updates the muted players and webhook entries. + /// public void ReloadConfig() { config.Reload(); @@ -76,8 +74,17 @@ public void ReloadConfig() Plugin.StaticLogger.SetLogLevel(logDebugMessages.Value); + UpdateMutedPlayers(); + UpdateWebhooks(); + } + + /// + /// Updates the muted players list with the values from the config file. + /// + private void UpdateMutedPlayers() + { mutedPlayers = new List(mutedDiscordUserList.Value.Split(';')); - if (String.IsNullOrEmpty(mutedDiscordUserListRegex.Value)) + if (string.IsNullOrEmpty(mutedDiscordUserListRegex.Value)) { mutedPlayersRegex = new Regex(@"a^"); } @@ -85,9 +92,32 @@ public void ReloadConfig() { mutedPlayersRegex = new Regex(mutedDiscordUserListRegex.Value); } + } + /// + /// Updates the webhook entries with the values from the config file. + /// + private void UpdateWebhooks() + { primaryWebhook = new WebhookEntry(webhookUrl.Value, Webhook.StringToEventList(webhookEvents.Value)); + if (!string.IsNullOrEmpty(webhookUsernameOverride.Value)) + { + primaryWebhook.UsernameOverride = webhookUsernameOverride.Value; + } + if (!string.IsNullOrEmpty(webhookAvatarOverride.Value)) + { + primaryWebhook.AvatarOverride = webhookAvatarOverride.Value; + } + secondaryWebhook = new WebhookEntry(webhookUrl2.Value, Webhook.StringToEventList(webhook2Events.Value)); + if (!string.IsNullOrEmpty(webhook2UsernameOverride.Value)) + { + secondaryWebhook.UsernameOverride = webhook2UsernameOverride.Value; + } + if (!string.IsNullOrEmpty(webhook2AvatarOverride.Value)) + { + secondaryWebhook.AvatarOverride = webhook2AvatarOverride.Value; + } } private void LoadConfig() @@ -116,6 +146,12 @@ private void LoadConfig() "", "Override the username of the webhook. If left blank, the webhook will use the default name."); + webhookAvatarOverride = config.Bind(MAIN_SETTINGS, + "Webhook Avatar Override", + "", + "Optional: Override the avatar of the primary webhook with the image at this URL." + Environment.NewLine + + "If left blank, the webhook will use the avatar set in your Discord server's settings."); + webhookUrl2 = config.Bind(MAIN_SETTINGS, "Secondary Webhook URL", "", @@ -135,6 +171,12 @@ private void LoadConfig() "Optional: Override the username of the secondary webhook." + Environment.NewLine + "If left blank, the webhook will use the default username set in the main config."); + webhook2AvatarOverride = config.Bind(MAIN_SETTINGS, + "Secondary Webhook Avatar Override", + "", + "Optional: Override the avatar of the secondary webhook with the image at this URL." + Environment.NewLine + + "If left blank, the webhook will use the avatar set in your Discord server's settings."); + logDebugMessages = config.Bind(MAIN_SETTINGS, "Log Debug Messages", false, @@ -199,9 +241,11 @@ public string ConfigAsJson() jsonString += $"\"webhook\":\"{(string.IsNullOrEmpty(webhookUrl.Value) ? "unset" : "REDACTED")}\","; jsonString += $"\"webhookEvents\":\"{webhookEvents.Value}\","; jsonString += $"\"webhookUsernameOverride\":\"{webhookUsernameOverride.Value}\","; + jsonString += $"\"webhookAvatarOverride\":\"{webhookAvatarOverride.Value}\","; jsonString += $"\"webhook2\":\"{(string.IsNullOrEmpty(webhookUrl2.Value) ? "unset" : "REDACTED")}\","; jsonString += $"\"webhook2Events\":\"{webhook2Events.Value}\","; jsonString += $"\"webhook2UsernameOverride\":\"{webhook2UsernameOverride.Value}\","; + jsonString += $"\"webhook2AvatarOverride\":\"{webhook2AvatarOverride.Value}\","; jsonString += $"\"logDebugMessages\":\"{logDebugMessages.Value}\","; jsonString += $"\"fancierMessages\":\"{DiscordEmbedsEnabled}\","; jsonString += $"\"ignoredPlayers\":\"{mutedDiscordUserList.Value}\","; diff --git a/src/DiscordApi.cs b/src/DiscordApi.cs index 8b53820..790d3d6 100644 --- a/src/DiscordApi.cs +++ b/src/DiscordApi.cs @@ -318,6 +318,15 @@ public bool IsValid() return !string.IsNullOrEmpty(content) || embeds != null; } + /// + /// Reset any overrides on the webhook. + /// + public void ResetOverrides() + { + username = null; + avatar_url = null; + } + /// /// Send the message to Discord. /// @@ -338,11 +347,16 @@ public void SendFor(Webhook.Event ev) { Plugin.StaticLogger.LogDebug($"Sending {ev} message to Primary Webhook"); WebhookEntry primaryWebhook = Plugin.StaticConfig.PrimaryWebhook; + ResetOverrides(); if (primaryWebhook.HasUsernameOverride()) { SetUsername(primaryWebhook.UsernameOverride); } + if (primaryWebhook.HasAvatarOverride()) + { + SetAvatarUrl(primaryWebhook.AvatarOverride); + } DiscordApi.SendSerializedJson(primaryWebhook, JsonConvert.SerializeObject(this)); @@ -351,11 +365,16 @@ public void SendFor(Webhook.Event ev) { Plugin.StaticLogger.LogDebug($"Sending {ev} message to Secondary Webhook"); WebhookEntry secondaryWebhook = Plugin.StaticConfig.SecondaryWebhook; + ResetOverrides(); if (secondaryWebhook.HasUsernameOverride()) { SetUsername(secondaryWebhook.UsernameOverride); } + if (secondaryWebhook.HasAvatarOverride()) + { + SetAvatarUrl(secondaryWebhook.AvatarOverride); + } DiscordApi.SendSerializedJson(secondaryWebhook, JsonConvert.SerializeObject(this)); @@ -367,10 +386,16 @@ public void SendFor(Webhook.Event ev) if (webhook.HasEvent(ev)) { Plugin.StaticLogger.LogDebug($"Sending {ev} message to an Extra Webhook"); + ResetOverrides(); + if (webhook.HasUsernameOverride()) { SetUsername(webhook.UsernameOverride); } + if (webhook.HasAvatarOverride()) + { + SetAvatarUrl(webhook.AvatarOverride); + } DiscordApi.SendSerializedJson(webhook, JsonConvert.SerializeObject(this)); diff --git a/src/Webhook.cs b/src/Webhook.cs index 38d79db..2068401 100644 --- a/src/Webhook.cs +++ b/src/Webhook.cs @@ -132,7 +132,7 @@ public static List StringToEventList(string configEntry) //Guard against empty string if (string.IsNullOrEmpty(configEntry)) { - return new List(); + return []; } //Clean string (remove all non-word non-semi-colon characters) @@ -142,20 +142,17 @@ public static List StringToEventList(string configEntry) // Check for ALL case if (cleaned.Equals("ALL")) { - return new List { Event.ALL }; + return [Event.ALL]; } - List events = new List(); + List events = []; foreach (string ev in cleaned.Split(';')) { events.Add(StringToEvent(ev)); } - foreach (Webhook.Event we in events) - { - Plugin.StaticLogger.LogDebug($"Added {we} to list"); - } + Plugin.StaticLogger.LogDebug($"Webhooks: parsed config entry '{configEntry}' => '{string.Join(", ", events)}'"); return events; } @@ -163,27 +160,56 @@ public static List StringToEventList(string configEntry) class WebhookEntry { + /// + /// The webhook endpoint URL + /// public string Url { get; set; } + /// + /// Which events should trigger this webhook + /// public List FireOnEvents { get; set; } + /// + /// The username to use for this webhook + /// public string UsernameOverride { get; set; } = string.Empty; + /// + /// The URL of the avatar to use for this webhook + /// + public string AvatarOverride { get; set; } = string.Empty; - public WebhookEntry(string url, List fireOnEvents, string usernameOverride = "") + /// + /// Create a new WebhookEntry, defaulting to all events + /// + /// webhook endpoint + public WebhookEntry(string url) + { + Url = url; + FireOnEvents = [Webhook.Event.ALL]; + } + + /// + /// Create a new WebhookEntry + /// + /// webhook endpoint + /// events to trigger this webhook + /// (Optional) username override + /// (Optional) avatar override + public WebhookEntry(string url, List fireOnEvents, string usernameOverride = "", string avatarOverride = "") { if (string.IsNullOrEmpty(url)) { Plugin.StaticLogger.LogDebug($"Coerced null or empty webhook url to empty string. Ignoring event list."); Url = ""; - FireOnEvents = new List(); + FireOnEvents = []; return; } Url = url; - if (fireOnEvents == null || fireOnEvents.Count == 0) { Plugin.StaticLogger.LogDebug($"Coerced null or empty webhook event list to empty list."); - FireOnEvents = new List(); + FireOnEvents = []; } else { @@ -194,13 +220,31 @@ public WebhookEntry(string url, List fireOnEvents, string usernam { UsernameOverride = usernameOverride; } + + if (!string.IsNullOrEmpty(avatarOverride)) + { + AvatarOverride = avatarOverride; + } } + /// + /// Check if the webhook has a username override + /// + /// True if a username override exists for this webhook public bool HasUsernameOverride() { return !string.IsNullOrEmpty(UsernameOverride); } + /// + /// Check if the webhook has an avatar override + /// + /// True if an avatar override exists for this webhook + public bool HasAvatarOverride() + { + return !string.IsNullOrEmpty(AvatarOverride); + } + internal bool HasEvent(Webhook.Event ev) { if (FireOnEvents.Count == 0) From edc8c69618bcc3cc40a66d710eeb3c0c7daa7957 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:50:20 -0400 Subject: [PATCH 08/17] docs: update documentation for new config settings --- Metadata/README.md | 2 +- Metadata/manifest.json | 2 +- Metadata/thunderstore.toml | 2 +- README.md | 2 +- docs/changelog.md | 2 +- docs/config/extra-webhooks.md | 42 +++++++++++++++++++ docs/config/main.md | 36 +++++++++++++++- .../default-config/discordconnector.cfg | 25 ++++++----- src/Config/ExtraWebhookConfig.cs | 4 +- src/Config/MainConfig.cs | 6 +-- 10 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 docs/config/extra-webhooks.md diff --git a/Metadata/README.md b/Metadata/README.md index e77579b..138e54a 100644 --- a/Metadata/README.md +++ b/Metadata/README.md @@ -1,7 +1,7 @@ # Discord Connector Connect your Valheim server (dedicated or served from the game itself) to a Discord Webhook. -(Find mod documentation on [the official website](https://discordconnector.valheim.nwest.games/).) +(Find mod documentation on [the official website](https://discord-connector.valheim.games.nwest.one/).) ## Features diff --git a/Metadata/manifest.json b/Metadata/manifest.json index fc2d7c7..528d876 100644 --- a/Metadata/manifest.json +++ b/Metadata/manifest.json @@ -1,7 +1,7 @@ { "name": "DiscordConnector", "version_number": "2.2.2", - "website_url": "https://discordconnector.valheim.nwest.games/", + "website_url": "https://discord-connector.valheim.games.nwest.one/", "description": "Connects your Valheim server to a Discord webhook. Works for both dedicated and client-hosted servers.", "dependencies": [ "denikson-BepInExPack_Valheim-5.4.2201" diff --git a/Metadata/thunderstore.toml b/Metadata/thunderstore.toml index 8b1aa83..7a0f3c9 100644 --- a/Metadata/thunderstore.toml +++ b/Metadata/thunderstore.toml @@ -6,7 +6,7 @@ namespace = "nwesterhausen" name = "DiscordConnector" versionNumber = "2.2.2" description = "Connects your Valheim server to a Discord webhook. Works for both dedicated and client-hosted servers." -websiteUrl = "https://discordconnector.valheim.nwest.games/" +websiteUrl = "https://discord-connector.valheim.games.nwest.one/" containsNsfwContent = false [package.dependencies] diff --git a/README.md b/README.md index c331de1..7268f16 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![NexusMods](https://img.shields.io/badge/NexusMods-2.1.14-%23D98F40?style=flat&labelColor=%2332393F)](https://www.nexusmods.com/valheim/mods/1551/) ![Built against Valheim version](https://img.shields.io/badge/Built_against_Valheim-0.218.15-purple?style=flat&labelColor=%2332393F) -Connect your Valheim server to Discord. ([See website for installation or configuration instructions](https://discordconnector.valheim.nwest.games/)). This plugin is largely based on [valheim-discord-notifier](https://github.com/aequasi/valheim-discord-notifier), but this plugin supports randomized messages, muting players, and Discord message embeds. +Connect your Valheim server to Discord. ([See website for installation or configuration instructions](https://discord-connector.valheim.games.nwest.one/)). This plugin is largely based on [valheim-discord-notifier](https://github.com/aequasi/valheim-discord-notifier), but this plugin supports randomized messages, muting players, and Discord message embeds. ## Plugin Details diff --git a/docs/changelog.md b/docs/changelog.md index 7efbf17..c2ed24d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -101,7 +101,7 @@ Adds a requested feature for a second webhook. Both webhooks can be configured t Connector sends, and by default (to be non-breaking) they will send all messages (which is the behavior of 2.1.8 and previous). Some plugins which may make use of Discord Connector's webhook to send messages can use the same method for sending and will be tagged as 'other' webhook events. For a full list of what webhook events can be configured, see the -[documentation](https://discordconnector.valheim.nwest.games/config/main.html#webhook-events). +[documentation](https://discord-connector.valheim.games.nwest.one/config/main.html#webhook-events). Features: diff --git a/docs/config/extra-webhooks.md b/docs/config/extra-webhooks.md new file mode 100644 index 0000000..a1a4b0a --- /dev/null +++ b/docs/config/extra-webhooks.md @@ -0,0 +1,42 @@ +# Extra Webhooks + +Filename `discordconnector.cfg` + +This file provides configuration for 16 additional webhooks. + +Each one supports the same settings as the primary and secondary that are in the [main config](./main). + +## Webhook 1 - 16 + +What can be configured for each webhook + +### Webhook URL + +Type: `String`, default value: `` + +Discord channel webhook URL. For instructions, reference the 'MAKING A WEBHOOK' section of [Discord's documentation](https://support.Discord.com/hc/en-us/articles/228383668-Intro-to-Webhook). + +### Webhook Events + +Type: `String`, default value: `ALL` + +Specify a subset of possible events to send to the primary webhook. Format should be the keyword 'ALL' or a semi-colon separated list, e.g. `serverLifecycle;playerAll;playerFirstAll;leaderboardsAll;` + +A full list of valid options for this are [here](https://discord-connector.valheim.games.nwest.one/config/webhook.events.html). + +### Webhook Username Override + +Type: `String`, default value: `` + +Override the username of this webhook. If left blank, the webhook will first try to use the default username from the main configuration, then will rely on the name provided by Discord. + +:::tip Default Username +A fallback default username to enforce can be set in the [main config](./main#default-webhook-username-override) + +::: + +### Webhook Avatar Override + +Type: `String`, default value: `` + +Override the avatar of this webhook. This should be a URL to an image. If left blank, the webhook will use the default avatar set in your Discord channel's "Integration" menu. diff --git a/docs/config/main.md b/docs/config/main.md index 68ea09e..5c7e201 100644 --- a/docs/config/main.md +++ b/docs/config/main.md @@ -2,6 +2,14 @@ Filename `discordconnector.cfg` +## Default Webhook Username Override + +Type: `String`, default value: `` + +Override the username of all webhooks for this instance of Discord Connector. If left blank, the webhook will use the default name (assigned by Discord in the Integration menu). + +This setting will be used for all webhooks unless overridden by a specific webhook username override setting. + ## Webhook URL Type: `String`, default value: `` @@ -14,7 +22,19 @@ Type: `String`, default value: `ALL` Specify a subset of possible events to send to the primary webhook. Format should be the keyword 'ALL' or a semi-colon separated list, e.g. `serverLifecycle;playerAll;playerFirstAll;leaderboardsAll;` -A full list of valid options for this are [here](https://discordconnector.valheim.nwest.games/config/webhook.events.html). +A full list of valid options for this are [here](https://discord-connector.valheim.games.nwest.one/config/webhook.events.html). + +## Webhook Username Override + +Type: `String`, default value: `` + +Override the username of this webhook. If left blank, the webhook will use the default username set in your Discord channel's "Integration" menu. + +## Webhook Avatar Override + +Type: `String`, default value: `` + +Override the avatar of this webhook. This should be a URL to an image. If left blank, the webhook will use the default avatar set in your Discord channel's "Integration" menu. ## Secondary Webhook URL @@ -28,7 +48,19 @@ Type: `String`, default value: `ALL` Specify a subset of possible events to send to the secondary webhook. Format should be the keyword 'ALL' or a semi-colon separated list, e.g. `serverLifecycle;playerAll;playerFirstAll;leaderboardsAll;` -A full list of valid options for this are [here](https://discordconnector.valheim.nwest.games/config/webhook.events.html). +A full list of valid options for this are [here](https://discord-connector.valheim.games.nwest.one/config/webhook.events.html). + +## Secondary Webhook Username Override + +Type: `String`, default value: `` + +Override the username of this webhook. If left blank, the webhook will use the default username set in your Discord channel's "Integration" menu. + +## Secondary Webhook Avatar Override + +Type: `String`, default value: `` + +Override the avatar of this webhook. This should be a URL to an image. If left blank, the webhook will use the default avatar set in your Discord channel's "Integration" menu. ## Log Debug Messages diff --git a/docs/public/default-config/discordconnector.cfg b/docs/public/default-config/discordconnector.cfg index fd00b5a..7f3741b 100644 --- a/docs/public/default-config/discordconnector.cfg +++ b/docs/public/default-config/discordconnector.cfg @@ -1,27 +1,27 @@ [Main Settings] -## Discord channel webhook URL. For instructions, reference the 'MAKING A WEBHOOK' section of +## Discord channel webhook URL. For instructions, reference the 'MAKING A WEBHOOK' section of ## Discord's documentation: https://support.Discord.com/hc/en-us/articles/228383668-Intro-to-Webhook # Setting type: String -# Default value: -Webhook URL = +# Default value: +Webhook URL = ## Specify a subset of possible events to send to the primary webhook. Previously all events would go to the primary webhook. ## Format should be the keyword 'ALL' or a semi-colon separated list, e.g. 'serverLifecycle;playerAll;playerFirstAll;leaderboardsAll;' -## Full list of valid options here: https://discordconnector.valheim.nwest.games/config/main.html#webhook-events +## Full list of valid options here: https://discord-connector.valheim.games.nwest.one/config/main.html#webhook-events # Setting type: String # Default value: ALL Webhook Events = ALL -## Discord channel webhook URL. For instructions, reference the 'MAKING A WEBHOOK' section of +## Discord channel webhook URL. For instructions, reference the 'MAKING A WEBHOOK' section of ## Discord's documentation: https://support.Discord.com/hc/en-us/articles/228383668-Intro-to-Webhook # Setting type: String -# Default value: -Secondary Webhook URL = +# Default value: +Secondary Webhook URL = ## Specify a subset of possible events to send to the primary webhook. Previously all events would go to the primary webhook. ## Format should be the keyword 'ALL' or a semi-colon separated list, e.g. 'serverLaunch;serverStart;serverSave;' -## Full list of valid options here: https://discordconnector.valheim.nwest.games/config/main.html#webhook-events +## Full list of valid options here: https://discord-connector.valheim.games.nwest.one/config/main.html#webhook-events # Setting type: String # Default value: ALL Secondary Webhook Events = ALL @@ -34,14 +34,14 @@ Use fancier discord messages = false ## It may be that you have some players that you never want to send Discord messages for. Adding a player name to this list will ignore them. ## Format should be a semicolon-separated list: Stuart;John McJohnny;Weird-name1 # Setting type: String -# Default value: -Ignored Players = +# Default value: +Ignored Players = ## It may be that you have some players that you never want to send Discord messages for. This option lets you provide a regular expression to filter out players if their name matches. ## Format should be a valid string for a .NET Regex (reference: https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference) # Setting type: String -# Default value: -Ignored Players (Regex) = +# Default value: +Ignored Players (Regex) = ## Disable this setting to disable any positions/coordinates being sent with messages (e.g. players deaths or players joining/leaving). (Overwrites any individual setting.) # Setting type: Boolean @@ -72,4 +72,3 @@ How to discern players in Record Retrieval = PlayerId # Setting type: Boolean # Default value: false Send Non-Player Shouts to Discord = false - diff --git a/src/Config/ExtraWebhookConfig.cs b/src/Config/ExtraWebhookConfig.cs index 3d6789e..40a086b 100644 --- a/src/Config/ExtraWebhookConfig.cs +++ b/src/Config/ExtraWebhookConfig.cs @@ -102,7 +102,7 @@ private void LoadConfig() EXTRA_WEBHOOKS, $"Webhook Username Override {i + 1}", "", - "Optional: Override the username of this webhook." + Environment.NewLine + + "Override the username of this webhook." + Environment.NewLine + "If left blank, the webhook will use the default username set in the main config.") ); @@ -110,7 +110,7 @@ private void LoadConfig() EXTRA_WEBHOOKS, $"Webhook Avatar Override {i + 1}", "", - "Optional: Override the avatar of this webhook with the image at the given URL." + Environment.NewLine + + "Override the avatar of this webhook with the image at the given URL." + Environment.NewLine + "If left blank, the webhook will use the avatar defined on the Discord webhook in your server's settings.") ); } diff --git a/src/Config/MainConfig.cs b/src/Config/MainConfig.cs index 04fc973..3decf89 100644 --- a/src/Config/MainConfig.cs +++ b/src/Config/MainConfig.cs @@ -149,7 +149,7 @@ private void LoadConfig() webhookAvatarOverride = config.Bind(MAIN_SETTINGS, "Webhook Avatar Override", "", - "Optional: Override the avatar of the primary webhook with the image at this URL." + Environment.NewLine + + "Override the avatar of the primary webhook with the image at this URL." + Environment.NewLine + "If left blank, the webhook will use the avatar set in your Discord server's settings."); webhookUrl2 = config.Bind(MAIN_SETTINGS, @@ -168,13 +168,13 @@ private void LoadConfig() webhook2UsernameOverride = config.Bind(MAIN_SETTINGS, "Secondary Webhook Username Override", "", - "Optional: Override the username of the secondary webhook." + Environment.NewLine + + "Override the username of the secondary webhook." + Environment.NewLine + "If left blank, the webhook will use the default username set in the main config."); webhook2AvatarOverride = config.Bind(MAIN_SETTINGS, "Secondary Webhook Avatar Override", "", - "Optional: Override the avatar of the secondary webhook with the image at this URL." + Environment.NewLine + + "Override the avatar of the secondary webhook with the image at this URL." + Environment.NewLine + "If left blank, the webhook will use the avatar set in your Discord server's settings."); logDebugMessages = config.Bind(MAIN_SETTINGS, From b0bc373d9cffed5caeabc78961c7bdc0a26686ee Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:52:14 -0400 Subject: [PATCH 09/17] docs: update sidebar --- docs/.vitepress/config.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index 03228bd..43343ce 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -73,6 +73,9 @@ export default { { text: 'Player Activity', link: '/config/leaderboards.playerstats' }, { text: 'Custom Leaderboards', link: '/config/leaderboards.custom' }, ] + }, + { + text: "Extra Webhooks", link: '/config/extra-webhooks', } ] }, @@ -88,4 +91,4 @@ export default { }, lastUpdatedText: 'Updated Date' } -} \ No newline at end of file +} From 9a60d9d14b3ed9bc8e0fccb492cd46bf78ead763 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:56:29 -0400 Subject: [PATCH 10/17] docs: fix typo --- docs/config/extra-webhooks.md | 2 +- docs/config/main.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/config/extra-webhooks.md b/docs/config/extra-webhooks.md index a1a4b0a..7c11962 100644 --- a/docs/config/extra-webhooks.md +++ b/docs/config/extra-webhooks.md @@ -20,7 +20,7 @@ Discord channel webhook URL. For instructions, reference the 'MAKING A WEBHOOK' Type: `String`, default value: `ALL` -Specify a subset of possible events to send to the primary webhook. Format should be the keyword 'ALL' or a semi-colon separated list, e.g. `serverLifecycle;playerAll;playerFirstAll;leaderboardsAll;` +Specify a subset of possible events to send to this webhook. Format should be the keyword 'ALL' or a semi-colon separated list, e.g. `serverLifecycle;playerAll;playerFirstAll;leaderboardsAll;` A full list of valid options for this are [here](https://discord-connector.valheim.games.nwest.one/config/webhook.events.html). diff --git a/docs/config/main.md b/docs/config/main.md index 5c7e201..55cd64c 100644 --- a/docs/config/main.md +++ b/docs/config/main.md @@ -132,6 +132,7 @@ Choose a method for how players will be separated from the results of a record q | Name | Treat each CharacterName as a separate player | | PlayerId | Treat each PlayerId as a separate player | | NameAndPlayerId | Treat each [PlayerId:CharacterName] combo as a separate player | + ::: :::warning What is the PlayerID? From ab4745a63bd8ca7907a035f9eb1742eaf3c6ea4a Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:01:27 -0400 Subject: [PATCH 11/17] fix: expose DefaultWebhookUsernameOverride --- src/Config/MainConfig.cs | 1 + src/Config/PluginConfig.cs | 1 + src/DiscordApi.cs | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/src/Config/MainConfig.cs b/src/Config/MainConfig.cs index 3decf89..21a6562 100644 --- a/src/Config/MainConfig.cs +++ b/src/Config/MainConfig.cs @@ -259,6 +259,7 @@ public string ConfigAsJson() return jsonString; } + public string DefaultWebhookUsernameOverride => defaultWebhookUsernameOverride.Value; public WebhookEntry PrimaryWebhook => primaryWebhook; public WebhookEntry SecondaryWebhook => secondaryWebhook; public bool CollectStatsEnabled => collectStatsToggle.Value; diff --git a/src/Config/PluginConfig.cs b/src/Config/PluginConfig.cs index 8f398ca..51ac215 100644 --- a/src/Config/PluginConfig.cs +++ b/src/Config/PluginConfig.cs @@ -192,6 +192,7 @@ public void ReloadConfig(string configExt) public bool EventResumedPosEnabled => mainConfig.SendPositionsEnabled && togglesConfig.EventResumedPosEnabled; // Main Config + public string DefaultWebhookUsernameOverride => mainConfig.DefaultWebhookUsernameOverride; public WebhookEntry PrimaryWebhook => mainConfig.PrimaryWebhook; public WebhookEntry SecondaryWebhook => mainConfig.SecondaryWebhook; public bool CollectStatsEnabled => mainConfig.CollectStatsEnabled; diff --git a/src/DiscordApi.cs b/src/DiscordApi.cs index 790d3d6..acca747 100644 --- a/src/DiscordApi.cs +++ b/src/DiscordApi.cs @@ -325,6 +325,11 @@ public void ResetOverrides() { username = null; avatar_url = null; + + if (!string.IsNullOrEmpty(Plugin.StaticConfig.DefaultWebhookUsernameOverride)) + { + SetUsername(Plugin.StaticConfig.DefaultWebhookUsernameOverride); + } } /// From 72884e71228955dee2bbee6977abbc47bcfaf210 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:33:08 -0400 Subject: [PATCH 12/17] docs: update variable description --- docs/config/messages.md | 42 +++++++++++++++++++--------------- docs/config/messages.player.md | 2 ++ docs/config/messages.server.md | 1 + 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/docs/config/messages.md b/docs/config/messages.md index 10ce181..a94fec3 100644 --- a/docs/config/messages.md +++ b/docs/config/messages.md @@ -11,22 +11,26 @@ If you wanted to have a couple different messages for when a player dies (always Player Death Message = %PLAYER_NAME% has died a beautiful death!;%PLAYER_NAME% went to their end with honor!;%PLAYER_NAME% died. ``` -::: - -::: tip Custom Variables -Any of the variables in [Variable Definitions](/config/variables) from the variables config file can be referenced in any message. - -::: details List of Variables - -- `%VAR1%` -- `%VAR2%` -- `%VAR3%` -- `%VAR4%` -- `%VAR5%` -- `%VAR6%` -- `%VAR7%` -- `%VAR8%` -- `%VAR9%` -- `%VAR10%` - -::: +## Variables + +Every message supports the following variables: + +| Variable | Replaced with | Notes | +| --------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `%PUBLICIP%` | Server's public IP (from a request to [ifconfig.me](https://ifconfig.me/)) | This is set up once at server launch. | +| `%DAY_NUMBER%` | Current day number on server | This will be 0 until the world actually gets loaded. | +| `%WORLD_NAME%` | World name of the world used on the server | This isn't available until the [serverLoaded](./messages.server.md#server-started-message) event (can't use it in [serverStart](./messages.server.md#server-launch-message) message) | +| `%NUM_PLAYERS%` | Number of currently online players | If used in a [playerLeave](./messages.player.md#player-leave-message) message, it will be auto decremented | +| `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | This won't be available until the [serverLoaded](./messages.server.md#server-started-message) eventmessages | +| `%VAR1%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR2%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR3%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR4%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR5%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR6%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR7%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR8%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR9%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR10%` | Value specified in the [custom variable config](./variables.custom.md) | | + +Some messages support additional variables, which are mentioned specifically on their config page; e.g., messages involving player actions support a position and other player information variables. diff --git a/docs/config/messages.player.md b/docs/config/messages.player.md index 8f348d7..22668df 100644 --- a/docs/config/messages.player.md +++ b/docs/config/messages.player.md @@ -9,6 +9,7 @@ | `%PLAYER_ID%` | Player's Platform ID | Player join/leave/shout/ping/death messages. | | `%SHOUT%` | Text of the player's shout | Player shout messages. | | `%POS%` | Player's coordinate position | Player join/leave/shout/ping/death messages. | + ::: ::: tip Available Dynamic Variables @@ -21,6 +22,7 @@ | `%WORLD_NAME%` | World name of the world used on the server | Any messages | | `%NUM_PLAYERS%` | Number of currently online players | Any messages | | `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | + ::: :::details Random Messages diff --git a/docs/config/messages.server.md b/docs/config/messages.server.md index f2ff673..1730f24 100644 --- a/docs/config/messages.server.md +++ b/docs/config/messages.server.md @@ -10,6 +10,7 @@ | `%WORLD_NAME%` | World name of the world used on the server | Any messages | | `%NUM_PLAYERS%` | Number of currently online players | Any messages | | `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | + ::: ## Server Launch Message From cbe304b0d97f7d7a5404cd98c489560048fde2a4 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:40:59 -0400 Subject: [PATCH 13/17] feat: replace variables inside custom variables --- docs/config/messages.events.md | 12 +++++------ docs/config/messages.leaderboards.md | 27 ++++++++++++++++++------ docs/config/messages.md | 23 +++++++++++---------- docs/config/messages.player.md | 18 ++++++++-------- docs/config/messages.playerfirsts.md | 26 ++++++++++------------- docs/config/messages.server.md | 31 +++++++++++++++++++--------- src/MessageTransformer.cs | 4 +++- 7 files changed, 83 insertions(+), 58 deletions(-) diff --git a/docs/config/messages.events.md b/docs/config/messages.events.md index bb2af14..7e321a8 100644 --- a/docs/config/messages.events.md +++ b/docs/config/messages.events.md @@ -12,11 +12,11 @@ In the event messages, anywhere in the message you can use the string vars `%EVE ::: tip Available Dynamic Variables -| Variable | Replaced with.. | Can be used in.. | -| ------------------- | ----------------------------------------------------------------------------------------- | ------------------- | -| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](/config/variables.html) config file) | Any messages | -| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | -| `%DAY_NUMBER%` | Current day number on server | Any messages | +| Variable | Replaced with.. | Can be used in.. | +| ------------------- | ---------------------------------------------------------------------------------------- | ------------------- | +| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](./variables.custom.md) config file) | Any messages | +| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | +| `%DAY_NUMBER%` | Current day number on server | Any messages | ::: :::details Random Messages @@ -31,7 +31,7 @@ Player Death Message = %PLAYER_NAME% has died a beautiful death!;%PLAYER_NAME% w ::: ::: details Available Custom Variables -These are defined in the [Custom Variables](/config/variables.html) config file. +These are defined in the [Custom Variables](./variables.custom.md) config file. `%VAR1%`, `%VAR2%`, `%VAR3%`, `%VAR4%`, `%VAR5%`, `%VAR6%`, `%VAR7%`, `%VAR8%`, `%VAR9%`, `%VAR10%` ::: diff --git a/docs/config/messages.leaderboards.md b/docs/config/messages.leaderboards.md index aad7163..1e82417 100644 --- a/docs/config/messages.leaderboards.md +++ b/docs/config/messages.leaderboards.md @@ -1,12 +1,27 @@ # Messages.LeaderBoards -::: tip Available Dynamic Variables +:::details Always Available Variables + +| Variable | Replaced with.. | Can be used in.. | +| ------------------- | ---------------------------------------------------------------------------------------- | ------------------- | +| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](./variables.custom.md) config file) | Any messages | +| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | +| `%DAY_NUMBER%` | Current day number on server | Any messages | +| `%WORLD_NAME%` | World name of the world used on the server | Any messages | +| `%NUM_PLAYERS%` | Number of currently online players | Any messages | +| `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | + +::: + +:::details Random Messages +All of the message options support having multiple messages defined in a semicolon (`;`) separated list. If you have multiple messages defined for these settings, one gets chosen at random when DiscordConnector decides to send the corresponding message. + +If you wanted to have a couple different messages for when a player dies (always chosen at random), you could simply set the config value like this: + +```toml +Player Death Message = %PLAYER_NAME% has died a beautiful death!;%PLAYER_NAME% went to their end with honor!;%PLAYER_NAME% died. +``` -| Variable | Replaced with.. | Can be used in.. | -| ------------------- | ----------------------------------------------------------------------------------------- | ------------------- | -| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](/config/variables.html) config file) | Any messages | -| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | -| `%DAY_NUMBER%` | Current day number on server | Any messages | ::: ## Leader Board Heading for Top N Players diff --git a/docs/config/messages.md b/docs/config/messages.md index a94fec3..b7936a6 100644 --- a/docs/config/messages.md +++ b/docs/config/messages.md @@ -2,7 +2,8 @@ Filename `discordconnector-messages.cfg` -:::tip Random Messages +## Random Messages + All of the message options support having multiple messages defined in a semicolon (`;`) separated list. If you have multiple messages defined for these settings, one gets chosen at random when DiscordConnector decides to send the corresponding message. If you wanted to have a couple different messages for when a player dies (always chosen at random), you could simply set the config value like this: @@ -22,15 +23,15 @@ Every message supports the following variables: | `%WORLD_NAME%` | World name of the world used on the server | This isn't available until the [serverLoaded](./messages.server.md#server-started-message) event (can't use it in [serverStart](./messages.server.md#server-launch-message) message) | | `%NUM_PLAYERS%` | Number of currently online players | If used in a [playerLeave](./messages.player.md#player-leave-message) message, it will be auto decremented | | `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | This won't be available until the [serverLoaded](./messages.server.md#server-started-message) eventmessages | -| `%VAR1%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR2%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR3%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR4%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR5%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR6%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR7%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR8%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR9%` | Value specified in the [custom variable config](./variables.custom.md) | | -| `%VAR10%` | Value specified in the [custom variable config](./variables.custom.md) | | +| `%VAR1%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR2%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR3%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR4%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR5%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR6%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR7%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR8%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR9%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | +| `%VAR10%` | Value specified in the [custom variable config](./variables.custom.md) | Custom variables can contain other variables, which will be replaced if the type of message supports it. | Some messages support additional variables, which are mentioned specifically on their config page; e.g., messages involving player actions support a position and other player information variables. diff --git a/docs/config/messages.player.md b/docs/config/messages.player.md index 22668df..5fe2ebd 100644 --- a/docs/config/messages.player.md +++ b/docs/config/messages.player.md @@ -14,14 +14,14 @@ ::: tip Available Dynamic Variables -| Variable | Replaced with.. | Can be used in.. | -| ------------------- | ----------------------------------------------------------------------------------------- | ------------------- | -| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](/config/variables.html) config file) | Any messages | -| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | -| `%DAY_NUMBER%` | Current day number on server | Any messages | -| `%WORLD_NAME%` | World name of the world used on the server | Any messages | -| `%NUM_PLAYERS%` | Number of currently online players | Any messages | -| `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | +| Variable | Replaced with.. | Can be used in.. | +| ------------------- | ---------------------------------------------------------------------------------------- | ------------------- | +| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](./variables.custom.md) config file) | Any messages | +| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | +| `%DAY_NUMBER%` | Current day number on server | Any messages | +| `%WORLD_NAME%` | World name of the world used on the server | Any messages | +| `%NUM_PLAYERS%` | Number of currently online players | Any messages | +| `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | ::: @@ -37,7 +37,7 @@ Player Death Message = %PLAYER_NAME% has died a beautiful death!;%PLAYER_NAME% w ::: ::: details Available Custom Variables -These are defined in the [Custom Variables](/config/variables.html) config file. +These are defined in the [Custom Variables](./variables.custom.md) config file. `%VAR1%`, `%VAR2%`, `%VAR3%`, `%VAR4%`, `%VAR5%`, `%VAR6%`, `%VAR7%`, `%VAR8%`, `%VAR9%`, `%VAR10%` ::: diff --git a/docs/config/messages.playerfirsts.md b/docs/config/messages.playerfirsts.md index 82f2e72..c82df50 100644 --- a/docs/config/messages.playerfirsts.md +++ b/docs/config/messages.playerfirsts.md @@ -8,18 +8,20 @@ | `%PLAYER_ID%` | Player's Platform ID | Player join/leave/shout/ping/death messages. | | `%SHOUT%` | Text of the player's shout | Player shout messages. | | `%POS%` | Player's coordinate position | Player join/leave/shout/ping/death messages. | + ::: -::: tip Available Dynamic Variables +:::details Always Available Variables + +| Variable | Replaced with.. | Can be used in.. | +| ------------------- | ---------------------------------------------------------------------------------------- | ------------------- | +| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](./variables.custom.md) config file) | Any messages | +| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | +| `%DAY_NUMBER%` | Current day number on server | Any messages | +| `%WORLD_NAME%` | World name of the world used on the server | Any messages | +| `%NUM_PLAYERS%` | Number of currently online players | Any messages | +| `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | -| Variable | Replaced with.. | Can be used in.. | -| ------------------- | ----------------------------------------------------------------------------------------- | ------------------- | -| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](/config/variables.html) config file) | Any messages | -| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | -| `%DAY_NUMBER%` | Current day number on server | Any messages | -| `%WORLD_NAME%` | World name of the world used on the server | Any messages | -| `%NUM_PLAYERS%` | Number of currently online players | Any messages | -| `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | ::: :::details Random Messages @@ -33,12 +35,6 @@ Player Death Message = %PLAYER_NAME% has died a beautiful death!;%PLAYER_NAME% w ::: -::: details Available Custom Variables -These are defined in the [Custom Variables](/config/variables.html) config file. - -`%VAR1%`, `%VAR2%`, `%VAR3%`, `%VAR4%`, `%VAR5%`, `%VAR6%`, `%VAR7%`, `%VAR8%`, `%VAR9%`, `%VAR10%` -::: - ## Player First Join Message Type: `String`, default value: `Welcome %PLAYER_NAME%, it\'s their first time on the server!` diff --git a/docs/config/messages.server.md b/docs/config/messages.server.md index 1730f24..58a4555 100644 --- a/docs/config/messages.server.md +++ b/docs/config/messages.server.md @@ -1,15 +1,26 @@ # Messages.Server -::: tip Available Dynamic Variables - -| Variable | Replaced with.. | Can be used in.. | -| ------------------- | ----------------------------------------------------------------------------------------- | ------------------- | -| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](/config/variables.html) config file) | Any messages | -| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | -| `%DAY_NUMBER%` | Current day number on server | Any messages | -| `%WORLD_NAME%` | World name of the world used on the server | Any messages | -| `%NUM_PLAYERS%` | Number of currently online players | Any messages | -| `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | +:::details Always Available Variables + +| Variable | Replaced with.. | Can be used in.. | +| ------------------- | ---------------------------------------------------------------------------------------- | ------------------- | +| `%VAR1%` - `VAR10%` | Custom variable value (defined in [Custom Variables](./variables.custom.md) config file) | Any messages | +| `%PUBLICIP%` | Server's public IP (according to the server) | Any server messages | +| `%DAY_NUMBER%` | Current day number on server | Any messages | +| `%WORLD_NAME%` | World name of the world used on the server | Any messages | +| `%NUM_PLAYERS%` | Number of currently online players | Any messages | +| `%JOIN_CODE%` | Server's join code (only if a join code exists, blank otherwise) | Any messages | + +::: + +:::details Random Messages +All of the message options support having multiple messages defined in a semicolon (`;`) separated list. If you have multiple messages defined for these settings, one gets chosen at random when DiscordConnector decides to send the corresponding message. + +If you wanted to have a couple different messages for when a player dies (always chosen at random), you could simply set the config value like this: + +```toml +Player Death Message = %PLAYER_NAME% has died a beautiful death!;%PLAYER_NAME% went to their end with honor!;%PLAYER_NAME% died. +``` ::: diff --git a/src/MessageTransformer.cs b/src/MessageTransformer.cs index c22d94a..1166445 100644 --- a/src/MessageTransformer.cs +++ b/src/MessageTransformer.cs @@ -40,7 +40,7 @@ internal static class MessageTransformer /// Raw message to format private static string ReplaceVariables(string rawMessage) { - return ReplaceDynamicVariables(rawMessage) + string customVariablesReplaced = rawMessage .Replace(VAR, Plugin.StaticConfig.UserVariable) .Replace(VAR_1, Plugin.StaticConfig.UserVariable1) .Replace(VAR_2, Plugin.StaticConfig.UserVariable2) @@ -51,6 +51,8 @@ private static string ReplaceVariables(string rawMessage) .Replace(VAR_7, Plugin.StaticConfig.UserVariable7) .Replace(VAR_8, Plugin.StaticConfig.UserVariable8) .Replace(VAR_9, Plugin.StaticConfig.UserVariable9); + + return ReplaceDynamicVariables(customVariablesReplaced) } /// From a0e986a93fad55ed836f0a4b3553b7cb5e3b9d48 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:42:30 -0400 Subject: [PATCH 14/17] fix: missing semicolon --- src/MessageTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessageTransformer.cs b/src/MessageTransformer.cs index 1166445..806f16d 100644 --- a/src/MessageTransformer.cs +++ b/src/MessageTransformer.cs @@ -52,7 +52,7 @@ private static string ReplaceVariables(string rawMessage) .Replace(VAR_8, Plugin.StaticConfig.UserVariable8) .Replace(VAR_9, Plugin.StaticConfig.UserVariable9); - return ReplaceDynamicVariables(customVariablesReplaced) + return ReplaceDynamicVariables(customVariablesReplaced); } /// From 32a09096063177573d969efdc9f774e013b952cb Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:33:49 -0400 Subject: [PATCH 15/17] feat: add config options to control allowed mentions --- docs/config/main.md | 60 ++++++++++++++++++ src/Config/MainConfig.cs | 114 ++++++++++++++++++++++++++++++++++- src/Config/MessagesConfig.cs | 48 +++++++-------- src/Config/PluginConfig.cs | 5 ++ src/Config/VariableConfig.cs | 24 ++++---- src/DiscordApi.cs | 69 ++++++++++++++++++--- 6 files changed, 274 insertions(+), 46 deletions(-) diff --git a/docs/config/main.md b/docs/config/main.md index 55cd64c..077fdbc 100644 --- a/docs/config/main.md +++ b/docs/config/main.md @@ -150,3 +150,63 @@ Enable this setting to have shouts which are performed by other mods/the server/ :::warning Muted Players These are still subject to censure by the muted player regex and list. ::: + +## Allow Mentions for @here and @everyone + +Type: `Boolean`, default value: `false` + +Enable this setting to allow the use of @here and @everyone in messages sent to Discord. + +:::warning +This setting is disabled by default to prevent accidental mentions of everyone in the Discord channel. There is no filtering to prevent players from using these mentions in their shouts or names. +::: + +## Allow Role Mentions + +Type: `Boolean`, default value: `true` + +Enable this setting to allow the use of role mentions in messages sent to Discord. Role mentions are in the format `<@&role_id>`, where `role_id` is the ID of the role you want to mention. + +:::warning +There is no filtering to prevent players from using these mentions in their shouts or names. +::: + +## Allow User Mentions + +Type: `Boolean`, default value: `true` + +Enable this setting to allow the use of user mentions in messages sent to Discord. User mentions are in the format `<@user_id>`, where `user_id` is the ID of the user you want to mention. + +:::warning +There is no filtering to prevent players from using these mentions in their shouts or names. +::: + +## Specifically Allowed Role Mentions + +Type: `String`, default value: `` + +Example value: `123452834;123452835` + +If you want to allow only certain roles to be mentioned, you can specify them here. This is a semicolon-separated list of role IDs where `role_id` is the ID of the role you want to allow mentions for. + +This setting will take precedence over the `Allow Role Mentions` setting (of course if all roles are allowed, this setting is redundant). + +:::tip How to get a Role ID +Send a message in Discord with a backslash before the role mention, e.g. `\@role_name`. This will display the role mention in the format `<@&role_id>`, where `role_id` is the ID of the role you want to mention. + +::: + +## Specifically Allowed User Mentions + +Type: `String`, default value: `` + +Example value: `123452834;123452835` + +If you want to allow only certain users to be mentioned, you can specify them here. This is a semicolon-separated list of user IDs where `user_id` is the ID of the user you want to allow mentions for. + +This setting will take precedence over the `Allow User Mentions` setting (of course if all users are allowed, this setting is redundant). + +:::tip How to get a User ID +Send a message in Discord with a backslash before the user mention, e.g. `\@user_name`. This will display the user mention in the format `<@user_id>`, where `user_id` is the ID of the user you want to mention. + +::: diff --git a/src/Config/MainConfig.cs b/src/Config/MainConfig.cs index 21a6562..3229750 100644 --- a/src/Config/MainConfig.cs +++ b/src/Config/MainConfig.cs @@ -46,6 +46,13 @@ public enum RetrievalDiscernmentMethods private ConfigEntry playerLookupPreference; private ConfigEntry allowNonPlayerShoutLogging; private ConfigEntry logDebugMessages; + private ConfigEntry allowMentionsHereEveryone; + private ConfigEntry allowMentionsAnyRole; + private ConfigEntry allowMentionsAnyUser; + private ConfigEntry allowedRoleMentions; + private ConfigEntry allowedUserMentions; + private List allowedRoleMentionsList; + private List allowedUserMentionsList; private WebhookEntry primaryWebhook; private WebhookEntry secondaryWebhook; @@ -62,6 +69,7 @@ public MainConfig(ConfigFile configFile) UpdateMutedPlayers(); UpdateWebhooks(); + UpdateAllowedMentions(); } /// @@ -76,6 +84,31 @@ public void ReloadConfig() UpdateMutedPlayers(); UpdateWebhooks(); + UpdateAllowedMentions(); + } + + /// + /// Updates the allowed mentions lists with the values from the config file. + /// + private void UpdateAllowedMentions() + { + if (string.IsNullOrEmpty(allowedRoleMentions.Value)) + { + allowedRoleMentionsList = []; + } + else + { + allowedRoleMentionsList = new List(allowedRoleMentions.Value.Split(';')); + } + + if (string.IsNullOrEmpty(allowedUserMentions.Value)) + { + allowedUserMentionsList = []; + } + else + { + allowedUserMentionsList = new List(allowedUserMentions.Value.Split(';')); + } } /// @@ -83,7 +116,15 @@ public void ReloadConfig() /// private void UpdateMutedPlayers() { - mutedPlayers = new List(mutedDiscordUserList.Value.Split(';')); + if (string.IsNullOrEmpty(mutedDiscordUserList.Value)) + { + mutedPlayers = []; + } + else + { + mutedPlayers = new List(mutedDiscordUserList.Value.Split(';')); + } + if (string.IsNullOrEmpty(mutedDiscordUserListRegex.Value)) { mutedPlayersRegex = new Regex(@"a^"); @@ -229,6 +270,35 @@ private void LoadConfig() "Enable this setting to have shouts which are performed by other mods/the server/non-players to be sent to Discord as well." + Environment.NewLine + "Note: These are still subject to censure by the muted player regex and list."); + allowMentionsHereEveryone = config.Bind(MAIN_SETTINGS, + "Allow @here and @everyone mentions", + false, + "Enable this setting to allow messages sent to Discord to mention @here and @everyone. Per the Discord API, these share the same setting." + Environment.NewLine + + "Note: There is no filtering in place to prevent abuse of these mentions (e.g. in a shout or player's name)."); + + allowMentionsAnyRole = config.Bind(MAIN_SETTINGS, + "Allow @role mentions", + true, + "Enable this setting to allow messages sent to Discord to mention roles. Roles mentioned this way use the format `<@&role_id>`" + Environment.NewLine + + "Note: There is no filtering in place to prevent abuse of these mentions (e.g. in a shout or player's name)."); + + allowMentionsAnyUser = config.Bind(MAIN_SETTINGS, + "Allow @user mentions", + true, + "Enable this setting to allow messages sent to Discord to mention users. Users mentioned this way use the format `<@user_id>`" + Environment.NewLine + + "Note: There is no filtering in place to prevent abuse of these mentions (e.g. in a shout or player's name)."); + + allowedRoleMentions = config.Bind(MAIN_SETTINGS, + "Allowed Role Mentions", + "", + "A semicolon-separated list of role IDs that are allowed to be mentioned in messages sent to Discord. These are just a number (no carets), e.g. `123;234`" + Environment.NewLine + + "Note: This setting is overshadowed if 'Allow @role mentions` is enabled, and only when that is disabled will these roles still be allowed to be mentioned."); + + allowedUserMentions = config.Bind(MAIN_SETTINGS, + "Allowed User Mentions", + "", + "A semicolon-separated list of user IDs that are allowed to be mentioned in messages sent to Discord. These are just a number (no carets), e.g. `123;234`" + Environment.NewLine + + "Note: This setting is overshadowed if 'Allow @user mentions` is enabled, and only when that is disabled will these users still be allowed to be mentioned."); config.Save(); } @@ -249,7 +319,42 @@ public string ConfigAsJson() jsonString += $"\"logDebugMessages\":\"{logDebugMessages.Value}\","; jsonString += $"\"fancierMessages\":\"{DiscordEmbedsEnabled}\","; jsonString += $"\"ignoredPlayers\":\"{mutedDiscordUserList.Value}\","; - jsonString += $"\"ignoredPlayersRegex\":\"{mutedDiscordUserListRegex.Value}\""; + jsonString += $"\"ignoredPlayersList\":["; + for (int i = 0; i < mutedPlayers.Count; i++) + { + jsonString += $"\"{mutedPlayers[i]}\""; + if (i < mutedPlayers.Count - 1) + { + jsonString += ","; + } + } + jsonString += "],"; + jsonString += $"\"ignoredPlayersRegex\":\"{mutedDiscordUserListRegex.Value}\","; + jsonString += $"\"allowMentionsHereEveryone\":\"{allowMentionsHereEveryone.Value}\","; + jsonString += $"\"allowMentionsAnyRole\":\"{allowMentionsAnyRole.Value}\","; + jsonString += $"\"allowMentionsAnyUser\":\"{allowMentionsAnyUser.Value}\","; + jsonString += $"\"allowedRoleMentions\":\"{allowedRoleMentions.Value}\","; + jsonString += $"\"allowedRoleMentionsList\":["; + for (int i = 0; i < allowedRoleMentionsList.Count; i++) + { + jsonString += $"\"{allowedRoleMentionsList[i]}\""; + if (i < allowedRoleMentionsList.Count - 1) + { + jsonString += ","; + } + } + jsonString += "],"; + jsonString += $"\"allowedUserMentions\":\"{allowedUserMentions.Value}\","; + jsonString += $"\"allowedUserMentionsList\":["; + for (int i = 0; i < allowedUserMentionsList.Count; i++) + { + jsonString += $"\"{allowedUserMentionsList[i]}\""; + if (i < allowedUserMentionsList.Count - 1) + { + jsonString += ","; + } + } + jsonString += "]"; jsonString += "},"; jsonString += $"\"collectStatsEnabled\":\"{CollectStatsEnabled}\","; jsonString += $"\"sendPositionsEnabled\":\"{SendPositionsEnabled}\","; @@ -270,5 +375,10 @@ public string ConfigAsJson() public bool AnnouncePlayerFirsts => announcePlayerFirsts.Value; public RetrievalDiscernmentMethods RecordRetrievalDiscernmentMethod => playerLookupPreference.Value; public bool AllowNonPlayerShoutLogging => allowNonPlayerShoutLogging.Value; + public bool AllowMentionsHereEveryone => allowMentionsHereEveryone.Value; + public bool AllowMentionsAnyRole => allowMentionsAnyRole.Value; + public bool AllowMentionsAnyUser => allowMentionsAnyUser.Value; + public List AllowedRoleMentions => allowedRoleMentionsList; + public List AllowedUserMentions => allowedUserMentionsList; } diff --git a/src/Config/MessagesConfig.cs b/src/Config/MessagesConfig.cs index 60ac562..78ddf9a 100644 --- a/src/Config/MessagesConfig.cs +++ b/src/Config/MessagesConfig.cs @@ -217,42 +217,42 @@ public string ConfigAsJson() string jsonString = "{"; jsonString += $"\"{SERVER_MESSAGES}\":{{"; - jsonString += $"\"launchMessage\":\"{serverLaunchMessage.Value}\","; - jsonString += $"\"startMessage\":\"{serverLoadedMessage.Value}\","; - jsonString += $"\"stopMessage\":\"{serverStopMessage.Value}\","; - jsonString += $"\"shutdownMessage\":\"{serverShutdownMessage.Value}\","; - jsonString += $"\"savedMessage\":\"{serverSavedMessage.Value}\","; - jsonString += $"\"serverNewDayMessage\":\"{serverNewDayMessage.Value}\""; + jsonString += $"\"launchMessage\":\"{serverLaunchMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"startMessage\":\"{serverLoadedMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"stopMessage\":\"{serverStopMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"shutdownMessage\":\"{serverShutdownMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"savedMessage\":\"{serverSavedMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"serverNewDayMessage\":\"{serverNewDayMessage.Value.Replace("\"", "\\\"")}\""; jsonString += "},"; jsonString += $"\"{PLAYER_MESSAGES}\":{{"; - jsonString += $"\"joinMessage\":\"{playerJoinMessage.Value}\","; - jsonString += $"\"deathMessage\":\"{playerDeathMessage.Value}\","; - jsonString += $"\"leaveMessage\":\"{playerLeaveMessage.Value}\","; - jsonString += $"\"pingMessage\":\"{playerPingMessage.Value}\","; - jsonString += $"\"shoutMessage\":\"{playerShoutMessage.Value}\""; + jsonString += $"\"joinMessage\":\"{playerJoinMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"deathMessage\":\"{playerDeathMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"leaveMessage\":\"{playerLeaveMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"pingMessage\":\"{playerPingMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"shoutMessage\":\"{playerShoutMessage.Value.Replace("\"", "\\\"")}\""; jsonString += "},"; jsonString += $"\"{PLAYER_FIRSTS_MESSAGES}\":{{"; - jsonString += $"\"joinMessage\":\"{playerJoinMessage.Value}\","; - jsonString += $"\"deathMessage\":\"{playerDeathMessage.Value}\","; - jsonString += $"\"leaveMessage\":\"{playerLeaveMessage.Value}\","; - jsonString += $"\"pingMessage\":\"{playerPingMessage.Value}\","; - jsonString += $"\"shoutMessage\":\"{playerShoutMessage.Value}\""; + jsonString += $"\"joinMessage\":\"{playerJoinMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"deathMessage\":\"{playerDeathMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"leaveMessage\":\"{playerLeaveMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"pingMessage\":\"{playerPingMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"shoutMessage\":\"{playerShoutMessage.Value.Replace("\"", "\\\"")}\""; jsonString += "},"; jsonString += $"\"{EVENT_MESSAGES}\":{{"; - jsonString += $"\"eventStartMessage\":\"{eventStartMessage.Value}\","; - jsonString += $"\"eventPausedMessage\":\"{eventPausedMessage.Value}\","; - jsonString += $"\"eventResumedMessage\":\"{eventResumedMessage.Value}\","; - jsonString += $"\"eventStopMessage\":\"{eventStopMessage.Value}\""; + jsonString += $"\"eventStartMessage\":\"{eventStartMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"eventPausedMessage\":\"{eventPausedMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"eventResumedMessage\":\"{eventResumedMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"eventStopMessage\":\"{eventStopMessage.Value.Replace("\"", "\\\"")}\""; jsonString += "},"; jsonString += $"\"{BOARD_MESSAGES}\":{{"; - jsonString += $"\"leaderBoardTopPlayersMessage\":\"{leaderBoardTopPlayersMessage.Value}\","; - jsonString += $"\"leaderBoardBottomPlayersMessage\":\"{leaderBoardBottomPlayersMessage.Value}\","; - jsonString += $"\"leaderBoardHighestPlayerMessage\":\"{leaderBoardHighestPlayerMessage.Value}\","; - jsonString += $"\"leaderBoardLowestPlayerMessage\":\"{leaderBoardLowestPlayerMessage.Value}\""; + jsonString += $"\"leaderBoardTopPlayersMessage\":\"{leaderBoardTopPlayersMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"leaderBoardBottomPlayersMessage\":\"{leaderBoardBottomPlayersMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"leaderBoardHighestPlayerMessage\":\"{leaderBoardHighestPlayerMessage.Value.Replace("\"", "\\\"")}\","; + jsonString += $"\"leaderBoardLowestPlayerMessage\":\"{leaderBoardLowestPlayerMessage.Value.Replace("\"", "\\\"")}\""; jsonString += "}"; jsonString += "}"; diff --git a/src/Config/PluginConfig.cs b/src/Config/PluginConfig.cs index 51ac215..ba8f9ef 100644 --- a/src/Config/PluginConfig.cs +++ b/src/Config/PluginConfig.cs @@ -203,6 +203,11 @@ public void ReloadConfig(string configExt) public List MutedPlayers => mainConfig.MutedPlayers; public Regex MutedPlayersRegex => mainConfig.MutedPlayersRegex; public bool AllowNonPlayerShoutLogging => mainConfig.AllowNonPlayerShoutLogging; + public bool AllowMentionsHereEveryone => mainConfig.AllowMentionsHereEveryone; + public bool AllowMentionsAnyRole => mainConfig.AllowMentionsAnyRole; + public bool AllowMentionsAnyUser => mainConfig.AllowMentionsAnyUser; + public List AllowedRoleMentions => mainConfig.AllowedRoleMentions; + public List AllowedUserMentions => mainConfig.AllowedUserMentions; // Messages.Server diff --git a/src/Config/VariableConfig.cs b/src/Config/VariableConfig.cs index 826eb82..23e8164 100644 --- a/src/Config/VariableConfig.cs +++ b/src/Config/VariableConfig.cs @@ -102,20 +102,20 @@ public string ConfigAsJson() { string jsonString = "{"; jsonString += "\"User-Defined\":{"; - jsonString += $"\"userVar\":\"{UserVariable}\","; - jsonString += $"\"userVar1\":\"{UserVariable1}\","; - jsonString += $"\"userVar2\":\"{UserVariable2}\","; - jsonString += $"\"userVar3\":\"{UserVariable3}\","; - jsonString += $"\"userVar4\":\"{UserVariable4}\","; - jsonString += $"\"userVar5\":\"{UserVariable5}\","; - jsonString += $"\"userVar6\":\"{UserVariable6}\","; - jsonString += $"\"userVar7\":\"{UserVariable7}\","; - jsonString += $"\"userVar8\":\"{UserVariable8}\","; - jsonString += $"\"userVar9\":\"{UserVariable9}\""; + jsonString += $"\"userVar\":\"{UserVariable.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar1\":\"{UserVariable1.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar2\":\"{UserVariable2.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar3\":\"{UserVariable3.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar4\":\"{UserVariable4.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar5\":\"{UserVariable5.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar6\":\"{UserVariable6.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar7\":\"{UserVariable7.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar8\":\"{UserVariable8.Replace("\"", "\\\"")}\","; + jsonString += $"\"userVar9\":\"{UserVariable9.Replace("\"", "\\\"")}\""; jsonString += "},"; jsonString += "\"Dynamic-Configured\":{"; - jsonString += $"\"posVarFormat\":\"{PosVarFormat}\","; - jsonString += $"\"appendedPosFormat\":\"{AppendedPosFormat}\""; + jsonString += $"\"posVarFormat\":\"{PosVarFormat.Replace("\"", "\\\"")}\","; + jsonString += $"\"appendedPosFormat\":\"{AppendedPosFormat.Replace("\"", "\\\"")}\""; jsonString += "}"; jsonString += "}"; return jsonString; diff --git a/src/DiscordApi.cs b/src/DiscordApi.cs index acca747..4a6be44 100644 --- a/src/DiscordApi.cs +++ b/src/DiscordApi.cs @@ -278,15 +278,10 @@ internal class DiscordExecuteWebhook /// public DiscordExecuteWebhook() { - content = null; - username = null; - avatar_url = null; - embeds = null; - allowed_mentions = null; + // allowed mentions are set for all webhooks right now + allowed_mentions = new(); - // Grab the allowed mentions from the configuration - // Grab the username override from the configuration - // Grab the avatar URL override from the configuration + ResetOverrides(); } /// @@ -450,6 +445,28 @@ public AllowedMentions() roles = []; users = []; replied_user = false; + + // Update from config + if (Plugin.StaticConfig.AllowMentionsHereEveryone) + { + AllowEveryone(); + } + if (Plugin.StaticConfig.AllowMentionsAnyRole) + { + AllowAnyRoles(); + } + if (Plugin.StaticConfig.AllowMentionsAnyUser) + { + AllowAnyUsers(); + } + if (Plugin.StaticConfig.AllowedRoleMentions.Count > 0) + { + AllowRoles(Plugin.StaticConfig.AllowedRoleMentions); + } + if (Plugin.StaticConfig.AllowedUserMentions.Count > 0) + { + AllowUsers(Plugin.StaticConfig.AllowedUserMentions); + } } /// @@ -533,6 +550,42 @@ public void DisallowUser(string user_id) if (users.Contains(user_id)) users.Remove(user_id); } + + /// + /// Allow any role mentions. + /// + public void AllowAnyRoles() + { + if (!parse.Contains("roles")) + parse.Add("roles"); + } + + /// + /// Allow any user mentions. + /// + public void AllowAnyUsers() + { + if (!parse.Contains("users")) + parse.Add("users"); + } + + /// + /// Disallow any role mentions. (Specific role_ids will still be allowed) + /// + public void DisallowAnyRoles() + { + if (parse.Contains("roles")) + parse.Remove("roles"); + } + + /// + /// Disallow any user mentions. (Specific user_ids will still be allowed) + /// + public void DisallowAnyUsers() + { + if (parse.Contains("users")) + parse.Remove("users"); + } } /// From 3fd9f313ad4f22b913d79427d162c29557b9ca8d Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:58:29 -0400 Subject: [PATCH 16/17] fix: when player's leave, proactively subtract from number of online players #275 --- src/Handlers.cs | 11 ++++++----- src/MessageTransformer.cs | 39 ++++++++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/Handlers.cs b/src/Handlers.cs index 3c4b723..3015281 100644 --- a/src/Handlers.cs +++ b/src/Handlers.cs @@ -330,6 +330,7 @@ private static void FinalizeFormattingAndSend(ZNetPeer peer, string playerHostNa { // Format the message accordingly, depending if it has the %POS% variable or not string finalMessage; + bool isPlayerLeaving = ev == Webhook.Event.PlayerLeave || ev == Webhook.Event.PlayerFirstLeave; if (preFormattedMessage.Contains("%POS%")) { if (!posEnabled) @@ -337,12 +338,12 @@ private static void FinalizeFormattingAndSend(ZNetPeer peer, string playerHostNa preFormattedMessage.Replace("%POS%", ""); } - finalMessage = MessageTransformer.FormatPlayerMessage(preFormattedMessage, peer.m_playerName, playerHostName, pos); + finalMessage = MessageTransformer.FormatPlayerMessage(preFormattedMessage, peer.m_playerName, playerHostName, pos, isPlayerLeaving); } else { - finalMessage = MessageTransformer.FormatPlayerMessage(preFormattedMessage, peer.m_playerName, playerHostName); + finalMessage = MessageTransformer.FormatPlayerMessage(preFormattedMessage, peer.m_playerName, playerHostName, isPlayerLeaving); } // If sending the position with the player join message is enabled @@ -365,7 +366,7 @@ private static void FinalizeFormattingAndSend(ZNetPeer peer, string playerHostNa /// /// Finish formatting the message based on the positional data (if allowed) and dispatch it to the Discord webhook. - /// + /// /// This method handles the text from shouts and other chat-adjacent things /// /// Player peer reference @@ -411,8 +412,8 @@ private static void FinalizeFormattingAndSend(ZNetPeer peer, string playerHostNa /// /// Handle a non-player chat message. Currently only works for shouts. - /// /// - /// If allowed in the configuration, this will send a message to discord as if a player shouted. + /// /// + /// If allowed in the configuration, this will send a message to discord as if a player shouted. /// /// Type of chat message /// Listed username of sender diff --git a/src/MessageTransformer.cs b/src/MessageTransformer.cs index 806f16d..0287889 100644 --- a/src/MessageTransformer.cs +++ b/src/MessageTransformer.cs @@ -38,7 +38,8 @@ internal static class MessageTransformer /// - `%VAR1%` through `%VAR10%` with the user variables /// /// Raw message to format - private static string ReplaceVariables(string rawMessage) + /// Subtract one from the number of online players + private static string ReplaceVariables(string rawMessage, bool subtractOne = false) { string customVariablesReplaced = rawMessage .Replace(VAR, Plugin.StaticConfig.UserVariable) @@ -52,7 +53,7 @@ private static string ReplaceVariables(string rawMessage) .Replace(VAR_8, Plugin.StaticConfig.UserVariable8) .Replace(VAR_9, Plugin.StaticConfig.UserVariable9); - return ReplaceDynamicVariables(customVariablesReplaced); + return ReplaceDynamicVariables(customVariablesReplaced, subtractOne); } /// @@ -65,13 +66,14 @@ private static string ReplaceVariables(string rawMessage) /// - `%JOIN_CODE%` with the join code of the server /// /// Raw message to format - private static string ReplaceDynamicVariables(string rawMessage) + /// Subtract one from the number of online players + private static string ReplaceDynamicVariables(string rawMessage, bool subtractOne = false) { // additionally add any other dynamic variables here.. string dynamicReplacedMessage = ReplacePublicIp(rawMessage); dynamicReplacedMessage = ReplaceWorldName(dynamicReplacedMessage); dynamicReplacedMessage = ReplaceDayNumber(dynamicReplacedMessage); - dynamicReplacedMessage = ReplaceNumPlayers(dynamicReplacedMessage); + dynamicReplacedMessage = ReplaceNumPlayers(dynamicReplacedMessage, subtractOne); dynamicReplacedMessage = ReplaceJoinCode(dynamicReplacedMessage); return dynamicReplacedMessage; @@ -104,8 +106,15 @@ private static string ReplaceDayNumber(string rawMessage) /// This will only replace `%NUM_PLAYERS%` with the number of players if the ZNet instance is available. /// /// Raw message to format - private static string ReplaceNumPlayers(string rawMessage) + /// Subtract one from the number of online players + private static string ReplaceNumPlayers(string rawMessage, bool subtractOne = false) { + if (subtractOne) + { + return rawMessage + .Replace(NUM_PLAYERS, ZNet.instance != null ? (ZNet.instance.GetNrOfPlayers() - 1).ToString() : NUM_PLAYERS); + } + return rawMessage .Replace(NUM_PLAYERS, ZNet.instance != null ? ZNet.instance.GetNrOfPlayers().ToString() : NUM_PLAYERS); } @@ -150,9 +159,10 @@ public static string FormatServerMessage(string rawMessage) /// Raw message to format /// Name of the player /// ID of the player - public static string FormatPlayerMessage(string rawMessage, string playerName, string playerId) + /// (Optional) Subtract one from the number of online players + public static string FormatPlayerMessage(string rawMessage, string playerName, string playerId, bool subtractOne = false) { - return MessageTransformer.ReplaceVariables(rawMessage) + return MessageTransformer.ReplaceVariables(rawMessage, subtractOne) .Replace(PLAYER_STEAMID, playerId) .Replace(PLAYER_ID, playerId) .Replace(PLAYER_NAME, playerName); @@ -166,9 +176,10 @@ public static string FormatPlayerMessage(string rawMessage, string playerName, s /// Name of the player /// ID of the player /// Position of the player - public static string FormatPlayerMessage(string rawMessage, string playerName, string playerId, UnityEngine.Vector3 pos) + /// (Optional) Subtract one from the number of online players + public static string FormatPlayerMessage(string rawMessage, string playerName, string playerId, UnityEngine.Vector3 pos, bool subtractOne = false) { - return MessageTransformer.FormatPlayerMessage(rawMessage, playerName, playerId) + return MessageTransformer.FormatPlayerMessage(rawMessage, playerName, playerId, subtractOne) .Replace(POS, $"{pos}"); } /// @@ -179,9 +190,10 @@ public static string FormatPlayerMessage(string rawMessage, string playerName, s /// Name of the player /// ID of the player /// Shout message - public static string FormatPlayerMessage(string rawMessage, string playerName, string playerId, string shout) + /// (Optional) Subtract one from the number of online players + public static string FormatPlayerMessage(string rawMessage, string playerName, string playerId, string shout, bool subtractOne = false) { - return MessageTransformer.FormatPlayerMessage(rawMessage, playerName, playerId) + return MessageTransformer.FormatPlayerMessage(rawMessage, playerName, playerId, subtractOne) .Replace(SHOUT, shout); } /// @@ -193,9 +205,10 @@ public static string FormatPlayerMessage(string rawMessage, string playerName, s /// Steam ID of the player /// Shout message /// Position of the player - public static string FormatPlayerMessage(string rawMessage, string playerName, string playerSteamId, string shout, UnityEngine.Vector3 pos) + /// (Optional) Subtract one from the number of online players + public static string FormatPlayerMessage(string rawMessage, string playerName, string playerSteamId, string shout, UnityEngine.Vector3 pos, bool subtractOne = false) { - return MessageTransformer.FormatPlayerMessage(rawMessage, playerName, playerSteamId, pos) + return MessageTransformer.FormatPlayerMessage(rawMessage, playerName, playerSteamId, pos, subtractOne) .Replace(SHOUT, shout); } /// From 178cbb2065cc4d1c15c38c749eec8c1c522492e1 Mon Sep 17 00:00:00 2001 From: Nick Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:09:10 -0400 Subject: [PATCH 17/17] chore: prepare release 2.3.0 --- Metadata/DiscordConnector-Nexus.readme | 2 +- Metadata/README.md | 19 ++++++++----------- Metadata/manifest.json | 2 +- Metadata/thunderstore.toml | 8 +------- README.md | 2 +- docs/changelog.md | 15 +++++++++++++++ src/PluginInfo.cs | 2 +- 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Metadata/DiscordConnector-Nexus.readme b/Metadata/DiscordConnector-Nexus.readme index abf5870..0048e5b 100644 --- a/Metadata/DiscordConnector-Nexus.readme +++ b/Metadata/DiscordConnector-Nexus.readme @@ -1,4 +1,4 @@ -DISCORD CONNECTOR - 2.2.2 +DISCORD CONNECTOR - 2.3.0 Connect your Valheim server to a Discord Webhook. Works for both dedicated and client-hosted servers. (Visit the github repo for a how-to guide.) :: REQUIREMENTS :: diff --git a/Metadata/README.md b/Metadata/README.md index 138e54a..1fb8b1e 100644 --- a/Metadata/README.md +++ b/Metadata/README.md @@ -34,20 +34,17 @@ See the [current roadmap](https://github.com/nwesterhausen/valheim-discordconnec ## Abridged Changelog -### Version 2.2.2 +### Version 2.3.0 Features -- add `%JOIN_CODE%` variable which will show the join code if crossplay is enabled -- add `%NUM_PLAYERS%` variable which will show the number of online players +- support up to 16 additional webhooks (adds a new config file, `discordconnector-extraWebhooks.cfg`) +- support restricting/allowing Discord mentions in messages (`@here` and `@everyone` are disabled by default -- possibly a breaking change) +- custom variables are now evaluated again for any embedded variables in the message +- added new configuration values to allow specifying a custom username and avatar for each webhook (to override the Discord webhook settings) +- added a configuration value that sets a default username for all webhooks (if not overridden) Fixes -- updated the documentation to reflect how `%WORLD_NAME%`, `%PUBLIC_IP%`, and `%DAY_NUMBER%` can be -in any messages and be replaced. - -Note: At server startup, some variables may not be available. They all should be available when server -is launched, but the join code may take a bit longer to display -- more testing is needed to know exactly -how much extra time it needs on average. If it is consistently unavailable, please file an issue and we -can come up with either a delayed startup message or another event that fires when the code becomes not -empty or changes. +- no longer relies on the `ZNet.GetPublicIP()` method and instead gets the public IP on its own at server start. +- `%NUM_PLAYERS%` proactively subtracts 1 if the event is a player leaving diff --git a/Metadata/manifest.json b/Metadata/manifest.json index 528d876..09e2951 100644 --- a/Metadata/manifest.json +++ b/Metadata/manifest.json @@ -1,6 +1,6 @@ { "name": "DiscordConnector", - "version_number": "2.2.2", + "version_number": "2.3.0", "website_url": "https://discord-connector.valheim.games.nwest.one/", "description": "Connects your Valheim server to a Discord webhook. Works for both dedicated and client-hosted servers.", "dependencies": [ diff --git a/Metadata/thunderstore.toml b/Metadata/thunderstore.toml index 7a0f3c9..fd43cc6 100644 --- a/Metadata/thunderstore.toml +++ b/Metadata/thunderstore.toml @@ -4,7 +4,7 @@ schemaVersion = "0.0.1" [package] namespace = "nwesterhausen" name = "DiscordConnector" -versionNumber = "2.2.2" +versionNumber = "2.3.0" description = "Connects your Valheim server to a Discord webhook. Works for both dedicated and client-hosted servers." websiteUrl = "https://discord-connector.valheim.games.nwest.one/" containsNsfwContent = false @@ -24,9 +24,3 @@ target = "./" [publish] repository = "https://thunderstore.io" communities = ["valheim"] -categories = [ - "hildirs-request-update", - "utility", - "server-side", - "ashlands-update", -] diff --git a/README.md b/README.md index 7268f16..f4c8993 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CodeQL](https://github.com/nwesterhausen/valheim-discordconnector/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/nwesterhausen/valheim-discordconnector/actions/workflows/codeql-analysis.yml) [![Build](https://github.com/nwesterhausen/valheim-discordconnector/actions/workflows/dotnet.yml/badge.svg)](https://github.com/nwesterhausen/valheim-discordconnector/actions/workflows/dotnet.yml) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/nwesterhausen/valheim-discordconnector?label=Github%20Release&style=flat&labelColor=%2332393F)](https://github.com/nwesterhausen/valheim-discordconnector/releases/latest) -[![Thunderstore.io](https://img.shields.io/badge/Thunderstore.io-2.2.2-%23375a7f?style=flat&labelColor=%2332393F)](https://valheim.thunderstore.io/package/nwesterhausen/DiscordConnector/) +[![Thunderstore.io](https://img.shields.io/badge/Thunderstore.io-2.3.0-%23375a7f?style=flat&labelColor=%2332393F)](https://valheim.thunderstore.io/package/nwesterhausen/DiscordConnector/) [![NexusMods](https://img.shields.io/badge/NexusMods-2.1.14-%23D98F40?style=flat&labelColor=%2332393F)](https://www.nexusmods.com/valheim/mods/1551/) ![Built against Valheim version](https://img.shields.io/badge/Built_against_Valheim-0.218.15-purple?style=flat&labelColor=%2332393F) diff --git a/docs/changelog.md b/docs/changelog.md index c2ed24d..49f59e6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,21 @@ A full changelog of changes, dating all the way back to the first release. +## Version 2.3.0 + +Features + +- support up to 16 additional webhooks (adds a new config file, `discordconnector-extraWebhooks.cfg`) +- support restricting/allowing Discord mentions in messages (`@here` and `@everyone` are disabled by default -- possibly a breaking change) +- custom variables are now evaluated again for any embedded variables in the message +- added new configuration values to allow specifying a custom username and avatar for each webhook (to override the Discord webhook settings) +- added a configuration value that sets a default username for all webhooks (if not overridden) + +Fixes + +- no longer relies on the `ZNet.GetPublicIP()` method and instead gets the public IP on its own at server start. +- `%NUM_PLAYERS%` proactively subtracts 1 if the event is a player leaving + ## Version 2.2.2 Features diff --git a/src/PluginInfo.cs b/src/PluginInfo.cs index cec8564..143a13c 100644 --- a/src/PluginInfo.cs +++ b/src/PluginInfo.cs @@ -16,7 +16,7 @@ internal static class PluginInfo { public const string PLUGIN_ID = "games.nwest.valheim.discordconnector"; public const string PLUGIN_NAME = "Valheim Discord Connector"; - public const string PLUGIN_VERSION = "2.2.2"; + public const string PLUGIN_VERSION = "2.3.0"; public const string PLUGIN_REPO_SHORT = "github: nwesterhausen/valheim-discordconnector"; public const string PLUGIN_AUTHOR = "Nicholas Westerhausen"; public const string SHORT_PLUGIN_ID = "discordconnector";