From 1eac257e6a1457365101a6d8c7974df6048d7565 Mon Sep 17 00:00:00 2001 From: sleepyyapril <123355664+sleepyyapril@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:17:53 -0400 Subject: [PATCH] AHelps: The Configuration Update (#1022) Co-authored-by: Aiden --- Content.Server/Administration/ServerApi.cs | 2 + .../Administration/Systems/BwoinkSystem.cs | 185 ++++++++++++------ Content.Shared/CCVar/CCVars.Admin.Ahelp.cs | 48 +++++ 3 files changed, 173 insertions(+), 62 deletions(-) diff --git a/Content.Server/Administration/ServerApi.cs b/Content.Server/Administration/ServerApi.cs index b592b9322d..1a73a52434 100644 --- a/Content.Server/Administration/ServerApi.cs +++ b/Content.Server/Administration/ServerApi.cs @@ -676,6 +676,8 @@ public sealed class BwoinkActionBody 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; } } #endregion diff --git a/Content.Server/Administration/Systems/BwoinkSystem.cs b/Content.Server/Administration/Systems/BwoinkSystem.cs index 11e1fd8e74..be58a94288 100644 --- a/Content.Server/Administration/Systems/BwoinkSystem.cs +++ b/Content.Server/Administration/Systems/BwoinkSystem.cs @@ -11,12 +11,14 @@ using Content.Server.Discord; using Content.Server.GameTicking; using Content.Server.Players.RateLimiting; +using Content.Server.Preferences.Managers; using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Players.RateLimiting; using JetBrains.Annotations; +using Pidgin.Configuration; using Robust.Server.Player; using Robust.Shared; using Robust.Shared.Configuration; @@ -43,6 +45,7 @@ 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!; [GeneratedRegex(@"^https://(?:(?:canary|ptb)\.)?discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")] private static partial Regex DiscordRegex(); @@ -635,8 +638,18 @@ public void OnWebhookBwoinkTextMessage(BwoinkTextMessage message, ServerApi.Bwoi // Note for forks: AdminData webhookAdminData = new(); - // TODO: fix args - OnBwoinkInternal(message, SystemUserId, webhookAdminData, body.Username, null, body.UserOnly, body.WebhookUpdate, true); + 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) @@ -660,64 +673,79 @@ protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySes if (_rateLimit.CountAction(eventArgs.SenderSession, RateLimitKey) != RateLimitStatus.Allowed) return; - OnBwoinkInternal(message, eventArgs.SenderSession.UserId, senderAdmin, eventArgs.SenderSession.Name, eventArgs.SenderSession.Channel, false, true, false); + var bwoinkParams = new BwoinkParams(message, + eventArgs.SenderSession.UserId, + senderAdmin, + eventArgs.SenderSession.Name, + eventArgs.SenderSession.Channel, + false, + true, + false); + OnBwoinkInternal(bwoinkParams); } /// /// 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 message being sent. - /// The network GUID of the person sending the message. - /// The admin privileges of the person sending the message. - /// The name of the person sending the message. - /// The channel to send a message to, e.g. in case of failure to send - /// If true, message should be sent off through the webhook if possible - /// Message originated from a webhook (e.g. Discord) - private void OnBwoinkInternal(BwoinkTextMessage message, NetUserId senderId, AdminData? senderAdmin, string senderName, INetChannel? senderChannel, bool userOnly, bool sendWebhook, bool fromWebhook) + /// The parameters of the message being sent. + private void OnBwoinkInternal(BwoinkParams bwoinkParams) { - _activeConversations[message.UserId] = DateTime.Now; + _activeConversations[bwoinkParams.Message.UserId] = DateTime.Now; - var escapedText = FormattedMessage.EscapeText(message.Text); - - string bwoinkText; - string adminPrefix = ""; + var escapedText = FormattedMessage.EscapeText(bwoinkParams.Message.Text); + var adminColor = _config.GetCVar(CCVars.AdminBwoinkColor); + var adminPrefix = ""; + var bwoinkText = $"{bwoinkParams.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 (bwoinkParams.SenderAdmin is not null && bwoinkParams.SenderAdmin.Title is not null) + adminPrefix = $"[bold]\\[{bwoinkParams.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}{senderName}[/color]"; + if (_config.GetCVar(CCVars.UseDiscordRoleName) && bwoinkParams.RoleName is not null) + adminPrefix = $"[bold]\\[{bwoinkParams.RoleName}\\][/bold] "; } - else if (fromWebhook || senderAdmin is not null && senderAdmin.HasFlag(AdminFlags.Adminhelp)) // Frontier: anything sent via webhooks are from an admin. + + // If role color is enabled and exists, use it, otherwise use the discord reply color + if (_config.GetCVar(CCVars.DiscordReplyColor) != string.Empty && bwoinkParams.FromWebhook) + adminColor = _config.GetCVar(CCVars.DiscordReplyColor); + + if (_config.GetCVar(CCVars.UseDiscordRoleColor) && bwoinkParams.RoleColor is not null) + adminColor = bwoinkParams.RoleColor; + + if (!bwoinkParams.FromWebhook + && _config.GetCVar(CCVars.UseAdminOOCColorInBwoinks) + && bwoinkParams.SenderAdmin is not null) { - bwoinkText = $"[color=red]{adminPrefix}{senderName}[/color]"; + var prefs = _preferencesManager.GetPreferences(bwoinkParams.SenderId); + adminColor = prefs.AdminOOCColor.ToHex(); } - else + + if (bwoinkParams.SenderAdmin is not null) { - bwoinkText = $"{senderName}"; + if (bwoinkParams.SenderAdmin.Flags == + AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently. + bwoinkText = $"[color=purple]{adminPrefix}{bwoinkParams.SenderName}[/color]"; + else if (bwoinkParams.FromWebhook || bwoinkParams.SenderAdmin.HasFlag(AdminFlags.Adminhelp)) // Frontier: anything sent via webhooks are from an admin. + bwoinkText = $"[color={adminColor}]{adminPrefix}{bwoinkParams.SenderName}[/color]"; } - if (fromWebhook) - bwoinkText = $"(DISCORD) {bwoinkText}"; + if (bwoinkParams.FromWebhook) + bwoinkText = $"{_config.GetCVar(CCVars.DiscordReplyPrefix)}{bwoinkText}"; - bwoinkText = $"{(message.PlaySound ? "" : "(S) ")}{bwoinkText}: {escapedText}"; + bwoinkText = $"{(bwoinkParams.Message.PlaySound ? "" : "(S) ")}{bwoinkText}: {escapedText}"; // If it's not an admin / admin chooses to keep the sound then play it. - var playSound = senderAdmin == null || message.PlaySound; - var msg = new BwoinkTextMessage(message.UserId, senderId, bwoinkText, playSound: playSound); + var playSound = bwoinkParams.SenderAdmin == null || bwoinkParams.Message.PlaySound; + var msg = new BwoinkTextMessage(bwoinkParams.Message.UserId, bwoinkParams.SenderId, bwoinkText, playSound: playSound); LogBwoink(msg); var admins = GetTargetAdmins(); // Notify all admins - if (!userOnly) + if (!bwoinkParams.UserOnly) { foreach (var channel in admins) { @@ -727,13 +755,13 @@ private void OnBwoinkInternal(BwoinkTextMessage message, NetUserId senderId, Adm string adminPrefixWebhook = ""; - if (_config.GetCVar(CCVars.AhelpAdminPrefixWebhook) && senderAdmin is not null && senderAdmin.Title is not null) + if (_config.GetCVar(CCVars.AhelpAdminPrefixWebhook) && bwoinkParams.SenderAdmin is not null && bwoinkParams.SenderAdmin.Title is not null) { - adminPrefixWebhook = $"[bold]\\[{senderAdmin.Title}\\][/bold] "; + adminPrefixWebhook = $"[bold]\\[{bwoinkParams.SenderAdmin.Title}\\][/bold] "; } // Notify player - if (_playerManager.TryGetSessionById(message.UserId, out var session)) + if (_playerManager.TryGetSessionById(bwoinkParams.Message.UserId, out var session)) { if (!admins.Contains(session.Channel)) { @@ -742,28 +770,22 @@ private void OnBwoinkInternal(BwoinkTextMessage message, NetUserId senderId, Adm { string overrideMsgText; // Doing the same thing as above, but with the override name. Theres probably a better way to do this. - if (senderAdmin is not null && - senderAdmin.Flags == + if (bwoinkParams.SenderAdmin is not null && + bwoinkParams.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)) - { + else if (bwoinkParams.SenderAdmin is not null && bwoinkParams.SenderAdmin.HasFlag(AdminFlags.Adminhelp)) overrideMsgText = $"[color=red]{adminPrefixWebhook}{_overrideClientName}[/color]"; - } else - { - overrideMsgText = $"{senderName}"; // Not an admin, name is not overridden. - } + overrideMsgText = $"{bwoinkParams.SenderName}"; // Not an admin, name is not overridden. - if (fromWebhook) - overrideMsgText = $"(DC) {overrideMsgText}"; + if (bwoinkParams.FromWebhook) + overrideMsgText = $"{_config.GetCVar(CCVars.DiscordReplyPrefix)}{overrideMsgText}"; - overrideMsgText = $"{(message.PlaySound ? "" : "(S) ")}{overrideMsgText}: {escapedText}"; + overrideMsgText = $"{(bwoinkParams.Message.PlaySound ? "" : "(S) ")}{overrideMsgText}: {escapedText}"; - RaiseNetworkEvent(new BwoinkTextMessage(message.UserId, - senderId, + RaiseNetworkEvent(new BwoinkTextMessage(bwoinkParams.Message.UserId, + bwoinkParams.SenderId, overrideMsgText, playSound: playSound), session.Channel); @@ -774,13 +796,13 @@ private void OnBwoinkInternal(BwoinkTextMessage message, NetUserId senderId, Adm } var sendsWebhook = _webhookUrl != string.Empty; - if (sendsWebhook && sendWebhook) + if (sendsWebhook && bwoinkParams.SendWebhook) { if (!_messageQueues.ContainsKey(msg.UserId)) _messageQueues[msg.UserId] = new Queue(); - var str = message.Text; - var unameLength = senderName.Length; + var str = bwoinkParams.Message.Text; + var unameLength = bwoinkParams.SenderName.Length; if (unameLength + str.Length + _maxAdditionalChars > DescriptionMax) { @@ -789,13 +811,13 @@ private void OnBwoinkInternal(BwoinkTextMessage message, NetUserId senderId, Adm var nonAfkAdmins = GetNonAfkAdmins(); var messageParams = new AHelpMessageParams( - senderName, + bwoinkParams.SenderName, str, - senderId != message.UserId, + bwoinkParams.SenderId != bwoinkParams.Message.UserId, _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"), _gameTicker.RunLevel, playedSound: playSound, - isDiscord: fromWebhook, + isDiscord: bwoinkParams.FromWebhook, noReceivers: nonAfkAdmins.Count == 0 ); _messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(messageParams)); @@ -805,11 +827,11 @@ private void OnBwoinkInternal(BwoinkTextMessage message, NetUserId senderId, Adm return; // No admin online, let the player know - if (senderChannel != null) + if (bwoinkParams.SenderChannel != null) { var systemText = Loc.GetString("bwoink-system-starmute-message-no-other-users"); - var starMuteMsg = new BwoinkTextMessage(message.UserId, SystemUserId, systemText); - RaiseNetworkEvent(starMuteMsg, senderChannel); + var starMuteMsg = new BwoinkTextMessage(bwoinkParams.Message.UserId, SystemUserId, systemText); + RaiseNetworkEvent(starMuteMsg, bwoinkParams.SenderChannel); } } // End Frontier: @@ -833,6 +855,7 @@ private IList GetTargetAdmins() private static DiscordRelayedData GenerateAHelpMessage(AHelpMessageParams parameters) { + var config = IoCManager.Resolve(); var stringbuilder = new StringBuilder(); if (parameters.Icon != null) @@ -850,7 +873,7 @@ private static DiscordRelayedData GenerateAHelpMessage(AHelpMessageParams parame stringbuilder.Append(" **(S)**"); if (parameters.IsDiscord) // Frontier - Discord Indicator - stringbuilder.Append(" **(DC)**"); + stringbuilder.Append($" **{config.GetCVar(CCVars.DiscordReplyPrefix)}**"); if (parameters.Icon == null) stringbuilder.Append($" **{parameters.Username}:** "); @@ -941,6 +964,44 @@ public AHelpMessageParams( } } + 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; + } + } + public enum PlayerStatusType { Connected, diff --git a/Content.Shared/CCVar/CCVars.Admin.Ahelp.cs b/Content.Shared/CCVar/CCVars.Admin.Ahelp.cs index 48f3965bb5..d9d22aed63 100644 --- a/Content.Shared/CCVar/CCVars.Admin.Ahelp.cs +++ b/Content.Shared/CCVar/CCVars.Admin.Ahelp.cs @@ -36,4 +36,52 @@ public sealed partial class CCVars /// public static readonly CVarDef AhelpAdminPrefixWebhook = CVarDef.Create("ahelp.admin_prefix_webhook", 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("ahelp.use_discord_role_color", true, 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("ahelp.use_discord_role_name", true, 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("ahelp.discord_reply_prefix", "(DISCORD) ", CVar.SERVERONLY); + + /// + /// The color of the names of admins. This is the fallback color for admins. + /// + /// + /// + /// + public static readonly CVarDef AdminBwoinkColor = + CVarDef.Create("ahelp.admin_bwoink_color", "red", CVar.SERVERONLY); + + /// + /// The color of the names of admins who reply from discord. Leave empty to disable. + /// Unused if UseDiscordRoleColor is true. + /// Overrides AdminBwoinkColor. + /// + /// + /// + public static readonly CVarDef DiscordReplyColor = + CVarDef.Create("ahelp.discord_reply_color", string.Empty, CVar.SERVERONLY); + + /// + /// 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("ahelp.bwoink_use_admin_ooc_color", true, CVar.SERVERONLY); }