]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Relay custom votes to a webhook (#18561)
authorLankLTE <135308300+LankLTE@users.noreply.github.com>
Fri, 15 Dec 2023 04:03:32 +0000 (20:03 -0800)
committerGitHub <noreply@github.com>
Fri, 15 Dec 2023 04:03:32 +0000 (20:03 -0800)
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
Content.Server/Discord/DiscordWebhook.cs
Content.Server/Discord/WebhookEmbed.cs
Content.Server/Discord/WebhookEmbedField.cs [new file with mode: 0644]
Content.Server/Discord/WebhookMentions.cs
Content.Server/Discord/WebhookPayload.cs
Content.Server/Voting/Managers/VoteManager.cs
Content.Server/Voting/VoteCommands.cs
Content.Server/Voting/VoteFinishedEventArgs.cs
Content.Shared/CCVar/CCVars.cs
Resources/Locale/en-US/administration/commands/custom-vote-command.ftl [new file with mode: 0644]

index d8a931844457dc51a425fb6c04b749b2b79f2732..ace57667644c718b3d27e706a27c8453d480fa9a 100644 (file)
@@ -1,5 +1,7 @@
-using System.Net.Http;
+using System.Net.Http;
 using System.Net.Http.Json;
+using System.Text.Json;
+using System.Text.Json.Serialization;
 using System.Threading.Tasks;
 
 namespace Content.Server.Discord;
@@ -66,7 +68,7 @@ public sealed class DiscordWebhook : IPostInjectInit
     public async Task<HttpResponseMessage> CreateMessage(WebhookIdentifier identifier, WebhookPayload payload)
     {
         var url = $"{GetUrl(identifier)}?wait=true";
-        return await _http.PostAsJsonAsync(url, payload);
+        return await _http.PostAsJsonAsync(url, payload, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
     }
 
     /// <summary>
@@ -91,7 +93,7 @@ public sealed class DiscordWebhook : IPostInjectInit
     public async Task<HttpResponseMessage> EditMessage(WebhookIdentifier identifier, ulong messageId, WebhookPayload payload)
     {
         var url = $"{GetUrl(identifier)}/messages/{messageId}";
-        return await _http.PatchAsJsonAsync(url, payload);
+        return await _http.PatchAsJsonAsync(url, payload, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
     }
 
     void IPostInjectInit.PostInject()
index 8e0db0722c2114db3754ccb4d126c321294b24bd..25083e48956d89497e82bd31a98f422b23b79180 100644 (file)
@@ -1,10 +1,13 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
 namespace Content.Server.Discord;
 
 // https://discord.com/developers/docs/resources/channel#embed-object-embed-structure
 public struct WebhookEmbed
 {
+    [JsonPropertyName("title")]
+    public string Title { get; set; } = "";
+
     [JsonPropertyName("description")]
     public string Description { get; set; } = "";
 
@@ -14,6 +17,10 @@ public struct WebhookEmbed
     [JsonPropertyName("footer")]
     public WebhookEmbedFooter? Footer { get; set; } = null;
 
+
+    [JsonPropertyName("fields")]
+    public List<WebhookEmbedField> Fields { get; set; } = default!;
+
     public WebhookEmbed()
     {
     }
diff --git a/Content.Server/Discord/WebhookEmbedField.cs b/Content.Server/Discord/WebhookEmbedField.cs
new file mode 100644 (file)
index 0000000..5c03c84
--- /dev/null
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace Content.Server.Discord;
+
+// https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure
+public struct WebhookEmbedField
+{
+    [JsonPropertyName("name")]
+    public string Name { get; set; } = "";
+
+    [JsonPropertyName("value")]
+    public string Value { get; set; } = "";
+
+    [JsonPropertyName("inline")]
+    public bool Inline { get; set; } = true;
+
+    public WebhookEmbedField()
+    {
+    }
+}
index da945b363e097f648cfc75efe7a1eaef5804f4f4..e28726990f08a94298ca583a1ba5c71b7d242ece 100644 (file)
@@ -1,4 +1,4 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
 namespace Content.Server.Discord;
 
index 6300f86ad577dab0d5945a4f3210a4589c66a57f..fdf5f48444a9766db22d8e4ca3f4fc3709fe23ef 100644 (file)
@@ -1,4 +1,4 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
 namespace Content.Server.Discord;
 
@@ -9,13 +9,13 @@ public struct WebhookPayload
     ///     The message to send in the webhook. Maximum of 2000 characters.
     /// </summary>
     [JsonPropertyName("content")]
-    public string Content { get; set; } = "";
+    public string? Content { get; set; }
 
     [JsonPropertyName("username")]
     public string? Username { get; set; }
 
     [JsonPropertyName("avatar_url")]
-    public string? AvatarUrl { get; set; } = "";
+    public string? AvatarUrl { get; set; }
 
     [JsonPropertyName("embeds")]
     public List<WebhookEmbed>? Embeds { get; set; } = null;
index 90089afb544d4edbd724d068c07372b0b7e836ae..4fb022fad0ec8a746c6d0de17eeb0b6b8ae09d54 100644 (file)
@@ -373,10 +373,16 @@ namespace Content.Server.Voting.Managers
                 .First()
                 .Select(e => e.Data)
                 .ToImmutableArray();
+            // Store all votes in order for webhooks
+            var voteTally = new List<int>(); 
+            foreach(var entry in v.Entries)
+            {
+                voteTally.Add(entry.Votes);
+            }
 
             v.Finished = true;
             v.Dirty = true;
-            var args = new VoteFinishedEventArgs(winners.Length == 1 ? winners[0] : null, winners);
+            var args = new VoteFinishedEventArgs(winners.Length == 1 ? winners[0] : null, winners, voteTally);
             v.OnFinished?.Invoke(_voteHandles[v.Id], args);
             DirtyCanCallVoteAll();
         }
index 498c9d049417be534f2d3b2b8c843589ebd216b0..aad0ee43d742deb1a09eaabfaee144e75597b86a 100644 (file)
@@ -1,11 +1,20 @@
 using System.Linq;
+using System.Net.Http;
+using System.Text.Json;
+using System.Text.Json.Nodes;
 using Content.Server.Administration;
 using Content.Server.Administration.Logs;
 using Content.Server.Chat.Managers;
+using Content.Server.Discord;
+using Content.Server.GameTicking;
 using Content.Server.Voting.Managers;
 using Content.Shared.Administration;
+using Content.Shared.CCVar;
 using Content.Shared.Database;
 using Content.Shared.Voting;
+using Robust.Server.Player;
+using Robust.Shared;
+using Robust.Shared.Configuration;
 using Robust.Shared.Console;
 
 namespace Content.Server.Voting
@@ -61,6 +70,11 @@ namespace Content.Server.Voting
     public sealed class CreateCustomCommand : IConsoleCommand
     {
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
+        [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
+        [Dependency] private readonly IConfigurationManager _cfg = default!;
+        [Dependency] private readonly DiscordWebhook _discord = default!;
+
+        private ISawmill _sawmill = default!;
 
         private const int MaxArgCount = 10;
 
@@ -68,8 +82,15 @@ namespace Content.Server.Voting
         public string Description => Loc.GetString("cmd-customvote-desc");
         public string Help => Loc.GetString("cmd-customvote-help");
 
+        // Webhook stuff
+        private string _webhookUrl = string.Empty;
+        private ulong _webhookId;
+        private WebhookIdentifier? _webhookIdentifier;
+
         public void Execute(IConsoleShell shell, string argStr, string[] args)
         {
+            _sawmill = Logger.GetSawmill("vote");
+
             if (args.Length < 3 || args.Length > MaxArgCount)
             {
                 shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 3), ("upper", 10)));
@@ -91,6 +112,41 @@ namespace Content.Server.Voting
                 options.Options.Add((args[i], i));
             }
 
+            // Set up the webhook payload
+            string _serverName = _cfg.GetCVar(CVars.GameHostName);
+            _webhookUrl = _cfg.GetCVar(CCVars.DiscordVoteWebhook);
+
+
+            var _gameTicker = _entitySystem.GetEntitySystem<GameTicker>();
+
+            var payload = new WebhookPayload()
+            {
+                Username = Loc.GetString("custom-vote-webhook-name"),
+                Embeds = new List<WebhookEmbed>
+                {
+                    new()
+                    {
+                        Title = $"{shell.Player}",
+                        Color = 13438992,
+                        Description = options.Title,
+                        Footer = new WebhookEmbedFooter
+                        {
+                            Text = $"{_serverName} {_gameTicker.RoundId} {_gameTicker.RunLevel}",
+                        },
+
+                        Fields = new List<WebhookEmbedField> {},
+                    },
+                },
+            };
+
+            foreach (var voteOption in options.Options)
+            {
+                var NewVote = new WebhookEmbedField() { Name = voteOption.text,  Value = "0"};
+                payload.Embeds[0].Fields.Add(NewVote);
+            }
+
+            WebhookMessage(payload);
+
             options.SetInitiatorOrServer(shell.Player);
 
             if (shell.Player != null)
@@ -114,6 +170,18 @@ namespace Content.Server.Voting
                     _adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Custom vote {options.Title} finished: {args[(int) eventArgs.Winner]}");
                     chatMgr.DispatchServerAnnouncement(Loc.GetString("cmd-customvote-on-finished-win",("winner", args[(int) eventArgs.Winner])));
                 }
+
+                for (int i = 0; i < eventArgs.Votes.Count - 1; i++)
+                {
+                    var oldName = payload.Embeds[0].Fields[i].Name;
+                    var newValue = eventArgs.Votes[i].ToString();
+                    var newEmbed = payload.Embeds[0];
+                    newEmbed.Color = 2353993;
+                    payload.Embeds[0] = newEmbed;
+                    payload.Embeds[0].Fields[i] = new WebhookEmbedField() { Name = oldName, Value = newValue, Inline =  true};
+                }
+
+                WebhookMessage(payload, _webhookId);
             };
         }
 
@@ -128,6 +196,38 @@ namespace Content.Server.Voting
             var n = args.Length - 1;
             return CompletionResult.FromHint(Loc.GetString("cmd-customvote-arg-option-n", ("n", n)));
         }
+
+        // Sends the payload's message.
+        private async void WebhookMessage(WebhookPayload payload)
+        {
+            if (string.IsNullOrEmpty(_webhookUrl))
+                return;
+
+            if (await _discord.GetWebhook(_webhookUrl) is not { } identifier)
+                return;
+
+            _webhookIdentifier = identifier.ToIdentifier();
+
+            _sawmill.Debug(JsonSerializer.Serialize(payload));
+
+            var request = await _discord.CreateMessage(_webhookIdentifier.Value, payload);
+            var content = await request.Content.ReadAsStringAsync();
+            _webhookId = ulong.Parse(JsonNode.Parse(content)?["id"]!.GetValue<string>()!);
+        }
+
+        // Edits a pre-existing payload message, given an ID
+        private async void WebhookMessage(WebhookPayload payload, ulong id)
+        {
+            if (string.IsNullOrEmpty(_webhookUrl))
+                return;
+
+            if (await _discord.GetWebhook(_webhookUrl) is not { } identifier)
+                return;
+
+            _webhookIdentifier = identifier.ToIdentifier();
+
+            var request = await _discord.EditMessage(_webhookIdentifier.Value, id, payload);
+        }
     }
 
 
index 344abd4db44bc2adf3599597074a59c65d954036..2a641272b0cdcd59dfaf9963f1c94e6cf4fb964b 100644 (file)
@@ -15,10 +15,16 @@ namespace Content.Server.Voting
         /// </summary>
         public readonly ImmutableArray<object> Winners;
 
-        public VoteFinishedEventArgs(object? winner, ImmutableArray<object> winners)
+        /// <summary>
+        ///     Stores all the votes in a string, for webhooks. 
+        /// </summary>
+        public readonly List<int> Votes;
+
+        public VoteFinishedEventArgs(object? winner, ImmutableArray<object> winners, List<int> votes)
         {
             Winner = winner;
             Winners = winners;
+            Votes = votes;
         }
     }
 }
index a8b2960728e0bb0eb81813e0838884facd7540d6..ad570ffd3f4880de49edb026dbf39fe2eef0d229 100644 (file)
@@ -349,6 +349,11 @@ namespace Content.Shared.CCVar
             CVarDef.Create("discord.ahelp_avatar", string.Empty, CVar.SERVERONLY);
 
         /// <summary>
+        /// URL of the Discord webhook which will relay all custom votes. If left empty, disables the webhook. 
+        /// </summary>
+        public static readonly CVarDef<string> DiscordVoteWebhook =
+            CVarDef.Create("discord.vote_webhook", string.Empty, CVar.SERVERONLY);
+
         /// URL of the Discord webhook which will relay round restart messages.
         /// </summary>
         public static readonly CVarDef<string> DiscordRoundUpdateWebhook =
@@ -360,6 +365,7 @@ namespace Content.Shared.CCVar
         public static readonly CVarDef<string> DiscordRoundEndRoleWebhook =
             CVarDef.Create("discord.round_end_role", string.Empty, CVar.SERVERONLY);
 
+
         /*
          * Suspicion
          */
diff --git a/Resources/Locale/en-US/administration/commands/custom-vote-command.ftl b/Resources/Locale/en-US/administration/commands/custom-vote-command.ftl
new file mode 100644 (file)
index 0000000..b8d64d2
--- /dev/null
@@ -0,0 +1 @@
+custom-vote-webhook-name = Custom Vote Held
\ No newline at end of file