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);
}