diff --git a/Content.Server/Administration/ServerApi.cs b/Content.Server/Administration/ServerApi.cs index f1f09d4b503..7a1c6a82060 100644 --- a/Content.Server/Administration/ServerApi.cs +++ b/Content.Server/Administration/ServerApi.cs @@ -6,13 +6,16 @@ using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; +using Content.Server._NF.Administration; using Content.Server.Administration.Systems; +using Content.Server.Administration.Managers; // Frontier using Content.Server.GameTicking; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; using Content.Server.Maps; using Content.Server.RoundEnd; using Content.Shared.Administration.Managers; +using Content.Shared.Administration; // Frontier using Content.Shared.CCVar; using Content.Shared.GameTicking.Components; using Content.Shared.Prototypes; @@ -48,7 +51,7 @@ public sealed partial class ServerApi : IPostInjectInit [Dependency] private readonly IStatusHost _statusHost = default!; [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly ISharedPlayerManager _playerManager = default!; - [Dependency] private readonly ISharedAdminManager _adminManager = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; // Frontier: ISharedAdminManager [Dependency] private readonly IGameMapManager _gameMapManager = default!; [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -81,6 +84,8 @@ void IPostInjectInit.PostInject() RegisterActorHandler(HttpMethod.Post, "/admin/actions/force_preset", ActionForcePreset); RegisterActorHandler(HttpMethod.Post, "/admin/actions/set_motd", ActionForceMotd); RegisterActorHandler(HttpMethod.Patch, "/admin/actions/panic_bunker", ActionPanicPunker); + + RegisterHandler(HttpMethod.Post, "/admin/actions/send_bwoink", ActionSendBwoink); // Frontier - Discord Ahelp Reply } public void Initialize() @@ -394,6 +399,38 @@ await RunOnMainThread(async () => await RespondOk(context); }); } + #endregion + + #region Frontier + // Creating a region here incase more actions are added in the future + + private async Task ActionSendBwoink(IStatusHandlerContext context) + { + var body = await ReadJson(context); + if (body == null) + return; + + await RunOnMainThread(async () => + { + // Player not online or wrong Guid + if (!_playerManager.TryGetSessionById(new NetUserId(body.Guid), out var player)) + { + await RespondError( + context, + ErrorCode.PlayerNotFound, + HttpStatusCode.UnprocessableContent, + "Player not found"); + return; + } + + var serverBwoinkSystem = _entitySystemManager.GetEntitySystem(); + var message = new SharedBwoinkSystem.BwoinkTextMessage(player.UserId, SharedBwoinkSystem.SystemUserId, body.Text); + serverBwoinkSystem.OnWebhookBwoinkTextMessage(message, body); + + // Respond with OK + await RespondOk(context); + }); + } #endregion diff --git a/Content.Server/Administration/Systems/BwoinkSystem.cs b/Content.Server/Administration/Systems/BwoinkSystem.cs index 4358b7e3876..6276304a75a 100644 --- a/Content.Server/Administration/Systems/BwoinkSystem.cs +++ b/Content.Server/Administration/Systems/BwoinkSystem.cs @@ -5,12 +5,15 @@ using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Content.Server._NF.Administration; // Frontier using Content.Server.Administration.Managers; using Content.Server.Afk; using Content.Server.Database; using Content.Server.Discord; using Content.Server.GameTicking; using Content.Server.Players.RateLimiting; +using Content.Server.Preferences.Managers; +using Content.Shared._DV.CCVars; // Frontier using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.GameTicking; @@ -43,8 +46,9 @@ public sealed partial class BwoinkSystem : SharedBwoinkSystem [Dependency] private readonly IAfkManager _afkManager = default!; [Dependency] private readonly IServerDbManager _dbManager = default!; [Dependency] private readonly PlayerRateLimitManager _rateLimit = default!; + [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; // Frontier - [GeneratedRegex(@"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")] + [GeneratedRegex(@"^https://(?:(?:canary|ptb)\.)?discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")] // Frontier: support alt discords private static partial Regex DiscordRegex(); private string _webhookUrl = string.Empty; @@ -82,17 +86,32 @@ public sealed partial class BwoinkSystem : SharedBwoinkSystem private int _maxAdditionalChars; private readonly Dictionary _activeConversations = new(); + // AHelp config settings + private bool _useAdminOOCColorInBwoinks = false; // Delta-v + private bool _useDiscordRoleColor = false; // Delta-v + private bool _useDiscordRoleName = false; // Delta-v + private string _discordReplyPrefix = "(DISCORD) "; // Delta-v + private string _adminBwoinkColor = "red"; // Delta-v + private string _discordReplyColor = string.Empty; // Delta-v + public override void Initialize() { base.Initialize(); Subs.CVar(_config, CCVars.DiscordOnCallWebhook, OnCallChanged, true); - Subs.CVar(_config, CCVars.DiscordAHelpWebhook, OnWebhookChanged, true); Subs.CVar(_config, CCVars.DiscordAHelpFooterIcon, OnFooterIconChanged, true); Subs.CVar(_config, CCVars.DiscordAHelpAvatar, OnAvatarChanged, true); Subs.CVar(_config, CVars.GameHostName, OnServerNameChanged, true); Subs.CVar(_config, CCVars.AdminAhelpOverrideClientName, OnOverrideChanged, true); + + Subs.CVar(_config, DCCVars.UseAdminOOCColorInBwoinks, OnUseAdminOOCColorInBwoinksChanged, true); + Subs.CVar(_config, DCCVars.UseDiscordRoleColor, OnUseDiscordRoleColorChanged, true); + Subs.CVar(_config, DCCVars.UseDiscordRoleName, OnUseDiscordRoleNameChanged, true); + Subs.CVar(_config, DCCVars.DiscordReplyPrefix, OnDiscordReplyPrefixChanged, true); + Subs.CVar(_config, DCCVars.AdminBwoinkColor, OnAdminBwoinkColorChanged, true); + Subs.CVar(_config, DCCVars.DiscordReplyColor, OnDiscordReplyColorChanged, true); + _sawmill = IoCManager.Resolve().GetSawmill("AHELP"); var defaultParams = new AHelpMessageParams( @@ -103,7 +122,7 @@ public override void Initialize() _gameTicker.RunLevel, playedSound: false ); - _maxAdditionalChars = GenerateAHelpMessage(defaultParams).Message.Length; + _maxAdditionalChars = GenerateAHelpMessage(defaultParams, _discordReplyPrefix).Message.Length; _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; SubscribeLocalEvent(OnGameRunLevelChanged); @@ -118,6 +137,46 @@ public override void Initialize() ); } + private void OnDiscordReplyColorChanged(string newValue) + { + _discordReplyColor = newValue; + } + + private void OnAdminBwoinkColorChanged(string newValue) + { + _adminBwoinkColor = newValue; + } + + private void OnDiscordReplyPrefixChanged(string newValue) + { + var defaultParams = new AHelpMessageParams( + string.Empty, + string.Empty, + true, + _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"), + _gameTicker.RunLevel, + playedSound: false + ); + + _discordReplyPrefix = newValue; + _maxAdditionalChars = GenerateAHelpMessage(defaultParams, _discordReplyPrefix).Message.Length; + } + + private void OnUseDiscordRoleNameChanged(bool newValue) + { + _useDiscordRoleName = newValue; + } + + private void OnUseDiscordRoleColorChanged(bool newValue) + { + _useDiscordRoleColor = newValue; + } + + private void OnUseAdminOOCColorInBwoinksChanged(bool newValue) + { + _useAdminOOCColorInBwoinks = newValue; + } + private async void OnCallChanged(string url) { _onCallUrl = url; @@ -142,7 +201,7 @@ private async void OnCallChanged(string url) var webhookId = match.Groups[1].Value; var webhookToken = match.Groups[2].Value; - _onCallData = await GetWebhookData(webhookId, webhookToken); + _onCallData = await GetWebhookData(url); // Frontier: support other urls } private void PlayerRateLimitedAction(ICommonSession obj) @@ -275,7 +334,7 @@ private void NotifyAdmins(ICommonSession session, string message, PlayerStatusTy var queue = _messageQueues.GetOrNew(session.UserId); var escapedText = FormattedMessage.EscapeText(message); messageParams.Message = escapedText; - var discordMessage = GenerateAHelpMessage(messageParams); + var discordMessage = GenerateAHelpMessage(messageParams, _discordReplyPrefix); queue.Enqueue(discordMessage); } } @@ -351,6 +410,7 @@ private async void OnWebhookChanged(string url) { // TODO: Ideally, CVar validation during setting should be better integrated Log.Warning("Webhook URL does not appear to be valid. Using anyways..."); + await GetWebhookData(url); // Frontier - Support for Custom URLS, we still want to see if theres Webhook data available return; } @@ -360,16 +420,13 @@ private async void OnWebhookChanged(string url) return; } - var webhookId = match.Groups[1].Value; - var webhookToken = match.Groups[2].Value; - // Fire and forget - _webhookData = await GetWebhookData(webhookId, webhookToken); + await GetWebhookData(url); // Frontier - Support for Custom URLS } - private async Task GetWebhookData(string id, string token) + private async Task GetWebhookData(string url) // Frontier - Support for Custom URLS { - var response = await _httpClient.GetAsync($"https://discord.com/api/v10/webhooks/{id}/{token}"); + var response = await _httpClient.GetAsync(url); // Frontier var content = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) @@ -480,6 +537,7 @@ private async void ProcessQueue(NetUserId userId, Queue mess var payload = GeneratePayload(existingEmbed.Description, existingEmbed.Username, + userId.UserId, // Frontier, this is used to identify the players in the webhook existingEmbed.CharacterName); // If there is no existing embed, create a new one @@ -546,7 +604,7 @@ private async void ProcessQueue(NetUserId userId, Queue mess $"**[Go to ahelp](https://discord.com/channels/{guildId}/{channelId}/{existingEmbed.Id})**"); } - payload = GeneratePayload(message.ToString(), existingEmbed.Username, existingEmbed.CharacterName); + payload = GeneratePayload(message.ToString(), existingEmbed.Username, userId, existingEmbed.CharacterName); // Frontier var request = await _httpClient.PostAsync($"{_onCallUrl}?wait=true", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); @@ -566,7 +624,7 @@ private async void ProcessQueue(NetUserId userId, Queue mess _processingChannels.Remove(userId); } - private WebhookPayload GeneratePayload(string messages, string username, string? characterName = null) + private WebhookPayload GeneratePayload(string messages, string username, Guid userId, string? characterName = null) // Frontier: added Guid { // Add character name if (characterName != null) @@ -592,6 +650,7 @@ private WebhookPayload GeneratePayload(string messages, string username, string? return new WebhookPayload { Username = username, + UserID = userId, // Frontier, this is used to identify the players in the webhook AvatarUrl = string.IsNullOrWhiteSpace(_avatarUrl) ? null : _avatarUrl, Embeds = new List { @@ -629,10 +688,30 @@ public override void Update(float frameTime) } } + // Frontier: webhook text messages + public void OnWebhookBwoinkTextMessage(BwoinkTextMessage message, BwoinkActionBody body) + { + // Note for forks: + AdminData webhookAdminData = new(); + + var bwoinkParams = new BwoinkParams( + message, + SystemUserId, + webhookAdminData, + body.Username, + null, + body.UserOnly, + body.WebhookUpdate, + true, + body.RoleName, + body.RoleColor); + OnBwoinkInternal(bwoinkParams); + } + protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs) { base.OnBwoinkTextMessage(message, eventArgs); - _activeConversations[message.UserId] = DateTime.Now; + var senderSession = eventArgs.SenderSession; // TODO: Sanitize text? @@ -650,46 +729,95 @@ protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySes if (_rateLimit.CountAction(eventArgs.SenderSession, RateLimitKey) != RateLimitStatus.Allowed) return; - var escapedText = FormattedMessage.EscapeText(message.Text); + var bwoinkParams = new BwoinkParams(message, + eventArgs.SenderSession.UserId, + senderAdmin, + eventArgs.SenderSession.Name, + eventArgs.SenderSession.Channel, + false, + true, + false); + OnBwoinkInternal(bwoinkParams); + } - string bwoinkText; - string adminPrefix = ""; + /// + /// Sends a bwoink. Common to both internal messages (sent via the ahelp or admin interface) and webhook messages (sent through the webhook, e.g. via Discord) + /// + /// The parameters of the message being sent. + private void OnBwoinkInternal(BwoinkParams bwoinkParams) + { + var fromWebhook = bwoinkParams.FromWebhook; + var message = bwoinkParams.Message; + var roleColor = bwoinkParams.RoleColor; + var roleName = bwoinkParams.RoleName; + var senderAdmin = bwoinkParams.SenderAdmin; + var senderChannel = bwoinkParams.SenderChannel; + var senderId = bwoinkParams.SenderId; + var senderName = bwoinkParams.SenderName; + var userOnly = bwoinkParams.UserOnly; + var sendWebhook = bwoinkParams.SendWebhook; + + _activeConversations[message.UserId] = DateTime.Now; + + var escapedText = FormattedMessage.EscapeText(message.Text); + var adminColor = _adminBwoinkColor; + var adminPrefix = ""; + var bwoinkText = $"{senderName}"; //Getting an administrator position - if (_config.GetCVar(CCVars.AhelpAdminPrefix) && senderAdmin is not null && senderAdmin.Title is not null) + if (_config.GetCVar(CCVars.AhelpAdminPrefix)) { - adminPrefix = $"[bold]\\[{senderAdmin.Title}\\][/bold] "; - } + if (senderAdmin is not null && senderAdmin.Title is not null) + adminPrefix = $"[bold]\\[{senderAdmin.Title}\\][/bold] "; - if (senderAdmin is not null && - senderAdmin.Flags == - AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently. - { - bwoinkText = $"[color=purple]{adminPrefix}{senderSession.Name}[/color]"; + if (_useDiscordRoleName && roleName is not null) + adminPrefix = $"[bold]\\[{roleName}\\][/bold] "; } - else if (senderAdmin is not null && senderAdmin.HasFlag(AdminFlags.Adminhelp)) + + if (!fromWebhook + && _useAdminOOCColorInBwoinks + && senderAdmin is not null) { - bwoinkText = $"[color=red]{adminPrefix}{senderSession.Name}[/color]"; + var prefs = _preferencesManager.GetPreferences(senderId); + adminColor = prefs.AdminOOCColor.ToHex(); } - else + + // If role color is enabled and exists, use it, otherwise use the discord reply color + if (_discordReplyColor != string.Empty && fromWebhook) + adminColor = _discordReplyColor; + + if (_useDiscordRoleColor && roleColor is not null) + adminColor = roleColor; + + if (senderAdmin is not null) { - bwoinkText = $"{senderSession.Name}"; + if (senderAdmin.Flags == + AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently. + bwoinkText = $"[color=purple]{adminPrefix}{senderName}[/color]"; + else if (fromWebhook || senderAdmin.HasFlag(AdminFlags.Adminhelp)) // Frontier: anything sent via webhooks are from an admin. + bwoinkText = $"[color={adminColor}]{adminPrefix}{senderName}[/color]"; } + if (fromWebhook) + bwoinkText = $"{_discordReplyPrefix}{bwoinkText}"; + bwoinkText = $"{(message.PlaySound ? "" : "(S) ")}{bwoinkText}: {escapedText}"; // If it's not an admin / admin chooses to keep the sound then play it. - var playSound = !senderAHelpAdmin || message.PlaySound; - var msg = new BwoinkTextMessage(message.UserId, senderSession.UserId, bwoinkText, playSound: playSound); + var playSound = senderAdmin == null || message.PlaySound; + var msg = new BwoinkTextMessage(message.UserId, senderId, bwoinkText, playSound: playSound); LogBwoink(msg); var admins = GetTargetAdmins(); // Notify all admins - foreach (var channel in admins) + if (!userOnly) { - RaiseNetworkEvent(msg, channel); + foreach (var channel in admins) + { + RaiseNetworkEvent(msg, channel); + } } string adminPrefixWebhook = ""; @@ -712,22 +840,19 @@ protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySes if (senderAdmin is not null && senderAdmin.Flags == AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently. - { overrideMsgText = $"[color=purple]{adminPrefixWebhook}{_overrideClientName}[/color]"; - } else if (senderAdmin is not null && senderAdmin.HasFlag(AdminFlags.Adminhelp)) - { overrideMsgText = $"[color=red]{adminPrefixWebhook}{_overrideClientName}[/color]"; - } else - { - overrideMsgText = $"{senderSession.Name}"; // Not an admin, name is not overridden. - } + overrideMsgText = $"{senderName}"; // Not an admin, name is not overridden. + + if (fromWebhook) + overrideMsgText = $"{_discordReplyPrefix}{overrideMsgText}"; overrideMsgText = $"{(message.PlaySound ? "" : "(S) ")}{overrideMsgText}: {escapedText}"; RaiseNetworkEvent(new BwoinkTextMessage(message.UserId, - senderSession.UserId, + senderId, overrideMsgText, playSound: playSound), session.Channel); @@ -738,13 +863,13 @@ protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySes } var sendsWebhook = _webhookUrl != string.Empty; - if (sendsWebhook) + if (sendsWebhook && sendWebhook) { if (!_messageQueues.ContainsKey(msg.UserId)) _messageQueues[msg.UserId] = new Queue(); var str = message.Text; - var unameLength = senderSession.Name.Length; + var unameLength = senderName.Length; if (unameLength + str.Length + _maxAdditionalChars > DescriptionMax) { @@ -753,25 +878,30 @@ protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySes var nonAfkAdmins = GetNonAfkAdmins(); var messageParams = new AHelpMessageParams( - senderSession.Name, + senderName, str, - !personalChannel, + senderId != message.UserId, _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"), _gameTicker.RunLevel, playedSound: playSound, + isDiscord: fromWebhook, noReceivers: nonAfkAdmins.Count == 0 ); - _messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(messageParams)); + _messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(messageParams, _discordReplyPrefix)); } if (admins.Count != 0 || sendsWebhook) return; // No admin online, let the player know - var systemText = Loc.GetString("bwoink-system-starmute-message-no-other-users"); - var starMuteMsg = new BwoinkTextMessage(message.UserId, SystemUserId, systemText); - RaiseNetworkEvent(starMuteMsg, senderSession.Channel); + if (senderChannel != null) + { + var systemText = Loc.GetString("bwoink-system-starmute-message-no-other-users"); + var starMuteMsg = new BwoinkTextMessage(message.UserId, SystemUserId, systemText); + RaiseNetworkEvent(starMuteMsg, senderChannel); + } } + // End Frontier: private IList GetNonAfkAdmins() { @@ -790,7 +920,7 @@ private IList GetTargetAdmins() .ToList(); } - private static DiscordRelayedData GenerateAHelpMessage(AHelpMessageParams parameters) + private static DiscordRelayedData GenerateAHelpMessage(AHelpMessageParams parameters, string? discordReplyPrefix = "(DISCORD)") // Delta-v { var stringbuilder = new StringBuilder(); @@ -807,6 +937,10 @@ private static DiscordRelayedData GenerateAHelpMessage(AHelpMessageParams parame stringbuilder.Append($" **{parameters.RoundTime}**"); if (!parameters.PlayedSound) stringbuilder.Append(" **(S)**"); + + if (parameters.IsDiscord) // Frontier - Discord Indicator + stringbuilder.Append($" **{discordReplyPrefix}**"); + if (parameters.Icon == null) stringbuilder.Append($" **{parameters.Username}:** "); else @@ -870,6 +1004,7 @@ public sealed class AHelpMessageParams public GameRunLevel RoundState { get; set; } public bool PlayedSound { get; set; } public bool NoReceivers { get; set; } + public bool IsDiscord { get; set; } // Frontier public string? Icon { get; set; } public AHelpMessageParams( @@ -879,6 +1014,7 @@ public AHelpMessageParams( string roundTime, GameRunLevel roundState, bool playedSound, + bool isDiscord = false, // Frontier bool noReceivers = false, string? icon = null) { @@ -887,6 +1023,7 @@ public AHelpMessageParams( IsAdmin = isAdmin; RoundTime = roundTime; RoundState = roundState; + IsDiscord = isDiscord; // Frontier PlayedSound = playedSound; NoReceivers = noReceivers; Icon = icon; diff --git a/Content.Server/Discord/WebhookPayload.cs b/Content.Server/Discord/WebhookPayload.cs index fdf5f48444a..8d587e0bd14 100644 --- a/Content.Server/Discord/WebhookPayload.cs +++ b/Content.Server/Discord/WebhookPayload.cs @@ -5,6 +5,8 @@ namespace Content.Server.Discord; // https://discord.com/developers/docs/resources/channel#message-object-message-structure public struct WebhookPayload { + [JsonPropertyName("UserID")] // Frontier, this is used to identify the players in the webhook + public Guid? UserID { get; set; } /// /// The message to send in the webhook. Maximum of 2000 characters. /// diff --git a/Content.Server/_NF/Administration/BwoinkData.cs b/Content.Server/_NF/Administration/BwoinkData.cs new file mode 100644 index 00000000000..329d1f16d1d --- /dev/null +++ b/Content.Server/_NF/Administration/BwoinkData.cs @@ -0,0 +1,53 @@ +using Content.Shared.Administration; +using Robust.Shared.Network; + +namespace Content.Server._NF.Administration; + +public sealed class BwoinkActionBody +{ + public required string Text { get; init; } + public required string Username { get; init; } + public required Guid Guid { get; init; } + public bool UserOnly { get; init; } + public required bool WebhookUpdate { get; init; } + public required string RoleName { get; init; } + public required string RoleColor { get; init; } +} + +public sealed class BwoinkParams +{ + public SharedBwoinkSystem.BwoinkTextMessage Message { get; set; } + public NetUserId SenderId { get; set; } + public AdminData? SenderAdmin { get; set; } + public string SenderName { get; set; } + public INetChannel? SenderChannel { get; set; } + public bool UserOnly { get; set; } + public bool SendWebhook { get; set; } + public bool FromWebhook { get; set; } + public string? RoleName { get; set; } + public string? RoleColor { get; set; } + + public BwoinkParams( + SharedBwoinkSystem.BwoinkTextMessage message, + NetUserId senderId, + AdminData? senderAdmin, + string senderName, + INetChannel? senderChannel, + bool userOnly, + bool sendWebhook, + bool fromWebhook, + string? roleName = null, + string? roleColor = null) + { + Message = message; + SenderId = senderId; + SenderAdmin = senderAdmin; + SenderName = senderName; + SenderChannel = senderChannel; + UserOnly = userOnly; + SendWebhook = sendWebhook; + FromWebhook = fromWebhook; + RoleName = roleName; + RoleColor = roleColor; + } +} diff --git a/Content.Shared/_DV/CCVars/DCCVars.cs b/Content.Shared/_DV/CCVars/DCCVars.cs index 178a84caeb3..d28faf854d2 100644 --- a/Content.Shared/_DV/CCVars/DCCVars.cs +++ b/Content.Shared/_DV/CCVars/DCCVars.cs @@ -116,4 +116,43 @@ public sealed class DCCVars /// public static readonly CVarDef DiscordPlayerFeedbackWebhook = CVarDef.Create("discord.player_feedback_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL); + + /// + /// Use the admin's Admin OOC color in bwoinks. + /// If either the ooc color or this is not set, uses the admin.admin_bwoink_color value. + /// + public static readonly CVarDef UseAdminOOCColorInBwoinks = + CVarDef.Create("admin.bwoink_use_admin_ooc_color", false, CVar.SERVERONLY); + + /// + /// If an admin replies to users from discord, should it use their discord role color? (if applicable) + /// Overrides DiscordReplyColor and AdminBwoinkColor. + /// + public static readonly CVarDef UseDiscordRoleColor = + CVarDef.Create("admin.use_discord_role_color", false, CVar.SERVERONLY); + + /// + /// If an admin replies to users from discord, should it use their discord role name? (if applicable) + /// + public static readonly CVarDef UseDiscordRoleName = + CVarDef.Create("admin.use_discord_role_name", false, CVar.SERVERONLY); + + /// + /// The text before an admin's name when replying from discord to indicate they're speaking from discord. + /// + public static readonly CVarDef DiscordReplyPrefix = + CVarDef.Create("admin.discord_reply_prefix", "(DC) ", CVar.SERVERONLY); + + /// + /// The color of the names of admins. This is the fallback color for admins. + /// + public static readonly CVarDef AdminBwoinkColor = + CVarDef.Create("admin.admin_bwoink_color", "red", CVar.SERVERONLY); + + /// + /// The color of the names of admins who reply from discord. Leave empty to disable. + /// Overrides AdminBwoinkColor. + /// + public static readonly CVarDef DiscordReplyColor = + CVarDef.Create("admin.discord_reply_color", string.Empty, CVar.SERVERONLY); }