From 84e0011ba74db83035a4268569aa338355235fde Mon Sep 17 00:00:00 2001 From: Paul Ritter Date: Wed, 29 Dec 2021 21:12:07 +0100 Subject: [PATCH] fixes up the ahelp relay a bit (#5866) * fixes up the ahelp relay a bit * Update Content.Server/Administration/BwoinkSystem.cs --- Content.Server/Administration/BwoinkSystem.cs | 156 ++++++++++++++---- 1 file changed, 126 insertions(+), 30 deletions(-) diff --git a/Content.Server/Administration/BwoinkSystem.cs b/Content.Server/Administration/BwoinkSystem.cs index 36094fb85d..b89729dc01 100644 --- a/Content.Server/Administration/BwoinkSystem.cs +++ b/Content.Server/Administration/BwoinkSystem.cs @@ -1,10 +1,14 @@ #nullable enable using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Http.Json; using System.Text; using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading; using Content.Server.Administration.Managers; using Content.Shared.Administration; using Content.Shared.CCVar; @@ -16,6 +20,7 @@ using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.IoC; using Robust.Shared.Log; +using Robust.Shared.Network; using Robust.Shared.Utility; namespace Content.Server.Administration @@ -28,16 +33,23 @@ namespace Content.Server.Administration [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly IPlayerLocator _playerLocator = default!; - private ISawmill? _sawmill; + private ISawmill _sawmill = default!; private readonly HttpClient _httpClient = new(); private string _webhookUrl = string.Empty; private string _serverName = string.Empty; + private readonly Dictionary _relayMessages = new(); + private readonly Dictionary> _messageQueues = new(); + private readonly HashSet _processingChannels = new(); + private const ushort MessageMax = 2000; + private int _maxAdditionalChars; public override void Initialize() { base.Initialize(); _config.OnValueChanged(CCVars.DiscordAHelpWebhook, OnWebhookChanged, true); _config.OnValueChanged(CVars.GameHostName, OnServerNameChanged, true); + _sawmill = IoCManager.Resolve().GetSawmill("AHELP"); + _maxAdditionalChars = GenerateAHelpMessage("", "", true, true).Length + Header("").Length; } private void OnServerNameChanged(string obj) @@ -57,6 +69,94 @@ namespace Content.Server.Administration _webhookUrl = obj; } + private string Header(string serverName) => $"Server: {serverName}"; + + private async void ProcessQueue(NetUserId channelId, Queue messages) + { + if (!_relayMessages.TryGetValue(channelId, out var oldMessage) || messages.Sum(x => x.Length+2) + oldMessage.content.Length > MessageMax) + { + var lookup = await _playerLocator.LookupIdAsync(channelId); + + if (lookup == null) + { + _sawmill.Log(LogLevel.Error, $"Unable to find player for netuserid {channelId} when sending discord webhook."); + _relayMessages.Remove(channelId); + return; + } + + oldMessage = (string.Empty, lookup.Username, Header(_serverName)); + } + + while (messages.TryDequeue(out var message)) + { + oldMessage.content += $"\n{message}"; + } + + var payload = new WebhookPayload() + { + username = oldMessage.username, + content = oldMessage.content + }; + + if (oldMessage.id == string.Empty) + { + var request = await _httpClient.PostAsync($"{_webhookUrl}?wait=true", + new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); + + var content = await request.Content.ReadAsStringAsync(); + if (!request.IsSuccessStatusCode) + { + _sawmill.Log(LogLevel.Error, $"Discord returned bad status code when posting message: {request.StatusCode}\nResponse: {content}"); + _relayMessages.Remove(channelId); + return; + } + + var id = JsonNode.Parse(content)?["id"]; + if (id == null) + { + _sawmill.Log(LogLevel.Error, $"Could not find id in json-content returned from discord webhook: {content}"); + _relayMessages.Remove(channelId); + return; + } + + oldMessage.id = id.ToString(); + } + else + { + var request = await _httpClient.PatchAsync($"{_webhookUrl}/messages/{oldMessage.id}", + new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); + + if (!request.IsSuccessStatusCode) + { + var content = await request.Content.ReadAsStringAsync(); + _sawmill.Log(LogLevel.Error, $"Discord returned bad status code when patching message: {request.StatusCode}\nResponse: {content}"); + _relayMessages.Remove(channelId); + return; + } + } + + _relayMessages[channelId] = oldMessage; + + _processingChannels.Remove(channelId); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var channelId in _messageQueues.Keys.ToArray()) + { + if(_processingChannels.Contains(channelId)) continue; + + var queue = _messageQueues[channelId]; + _messageQueues.Remove(channelId); + if (queue.Count == 0) continue; + _processingChannels.Add(channelId); + + ProcessQueue(channelId, queue); + } + } + protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs) { base.OnBwoinkTextMessage(message, eventArgs); @@ -75,7 +175,6 @@ namespace Content.Server.Administration var escapedText = FormattedMessage.EscapeText(message.Text); - var bwoinkText = senderAdmin switch { var x when x is not null && x.Flags == AdminFlags.Adminhelp => @@ -100,41 +199,25 @@ namespace Content.Server.Administration foreach (var channel in targets) RaiseNetworkEvent(msg, channel); + var noReceivers = targets.Count == 1; + var sendsWebhook = _webhookUrl != string.Empty; if (sendsWebhook) { - async void SendWebhook() + if (!_messageQueues.ContainsKey(msg.ChannelId)) + _messageQueues[msg.ChannelId] = new Queue(); + + var str = message.Text; + var unameLength = senderSession.Name.Length; + + if (unameLength+str.Length+_maxAdditionalChars > MessageMax) { - _sawmill ??= IoCManager.Resolve().GetSawmill("AHELP"); - - var lookup = await _playerLocator.LookupIdAsync(message.ChannelId); - - if (lookup == null) - { - _sawmill.Log(LogLevel.Error, $"Unable to find player for netuserid {msg.ChannelId} when sending discord webhook."); - return; - } - - var payload = new WebhookPayload() - { - username = _serverName, - content = $"`[{lookup.Username}]` {senderSession.Name}: \"{message.Text}\"" - }; - - var request = await _httpClient.PostAsync(_webhookUrl, - new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); - - if (!request.IsSuccessStatusCode) - { - var content = await request.Content.ReadAsStringAsync(); - _sawmill.Log(LogLevel.Error, $"Discord returned bad status code: {request.StatusCode}\nResponse: {content}"); - } + str = str[..(MessageMax - _maxAdditionalChars - unameLength)]; } - - SendWebhook(); + _messageQueues[msg.ChannelId].Enqueue(GenerateAHelpMessage(senderSession.Name, str, !personalChannel, noReceivers)); } - if (targets.Count == 1) + if (noReceivers) { var systemText = sendsWebhook ? Loc.GetString("bwoink-system-starmute-message-no-other-users-webhook") : @@ -144,6 +227,19 @@ namespace Content.Server.Administration } } + private string GenerateAHelpMessage(string username, string message, bool admin, bool noReceiver) + { + var stringbuilder = new StringBuilder(); + if (noReceiver) + stringbuilder.Append(":sos:"); + stringbuilder.Append(admin ? ":outbox_tray:" : ":inbox_tray:"); + stringbuilder.Append(' '); + stringbuilder.Append(username); + stringbuilder.Append(": "); + stringbuilder.Append(message); + return stringbuilder.ToString(); + } + private struct WebhookPayload { // ReSharper disable once InconsistentNaming