VoteWebhooks.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using Content.Server.GameTicking;
  2. using Content.Server.Voting;
  3. using Robust.Server;
  4. using Robust.Shared.Utility;
  5. using System.Text.Json;
  6. using System.Text.Json.Nodes;
  7. namespace Content.Server.Discord.WebhookMessages;
  8. public sealed class VoteWebhooks : IPostInjectInit
  9. {
  10. [Dependency] private readonly IEntitySystemManager _entSys = default!;
  11. [Dependency] private readonly DiscordWebhook _discord = default!;
  12. [Dependency] private readonly IBaseServer _baseServer = default!;
  13. private ISawmill _sawmill = default!;
  14. public WebhookState? CreateWebhookIfConfigured(VoteOptions voteOptions, string? webhookUrl = null, string? customVoteName = null, string? customVoteMessage = null)
  15. {
  16. // All this webhook code is complete garbage.
  17. // I tried to clean it up somewhat, at least to fix the glaring bugs in it.
  18. // Jesus christ man what is with our code review process.
  19. if (string.IsNullOrEmpty(webhookUrl))
  20. return null;
  21. // Set up the webhook payload
  22. var serverName = _baseServer.ServerName;
  23. var fields = new List<WebhookEmbedField>();
  24. foreach (var voteOption in voteOptions.Options)
  25. {
  26. var newVote = new WebhookEmbedField
  27. {
  28. Name = voteOption.text,
  29. Value = Loc.GetString("custom-vote-webhook-option-pending")
  30. };
  31. fields.Add(newVote);
  32. }
  33. var gameTicker = _entSys.GetEntitySystemOrNull<GameTicker>();
  34. _sawmill = Logger.GetSawmill("discord");
  35. var runLevel = gameTicker != null ? Loc.GetString($"game-run-level-{gameTicker.RunLevel}") : "";
  36. var runId = gameTicker != null ? gameTicker.RoundId : 0;
  37. var voteName = customVoteName ?? Loc.GetString("custom-vote-webhook-name");
  38. var description = customVoteMessage ?? voteOptions.Title;
  39. var payload = new WebhookPayload()
  40. {
  41. Username = voteName,
  42. Embeds = new List<WebhookEmbed>
  43. {
  44. new()
  45. {
  46. Title = voteOptions.InitiatorText,
  47. Color = 13438992, // #CD1010
  48. Description = description,
  49. Footer = new WebhookEmbedFooter
  50. {
  51. Text = Loc.GetString(
  52. "custom-vote-webhook-footer",
  53. ("serverName", serverName),
  54. ("roundId", runId),
  55. ("runLevel", runLevel)),
  56. },
  57. Fields = fields,
  58. },
  59. },
  60. };
  61. var state = new WebhookState
  62. {
  63. WebhookUrl = webhookUrl,
  64. Payload = payload,
  65. };
  66. CreateWebhookMessage(state, payload);
  67. return state;
  68. }
  69. public void UpdateWebhookIfConfigured(WebhookState? state, VoteFinishedEventArgs finished)
  70. {
  71. if (state == null)
  72. return;
  73. var embed = state.Payload.Embeds![0];
  74. embed.Color = 2353993; // #23EB49
  75. for (var i = 0; i < finished.Votes.Count; i++)
  76. {
  77. var oldName = embed.Fields[i].Name;
  78. var newValue = finished.Votes[i].ToString();
  79. embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = newValue, Inline = true };
  80. }
  81. state.Payload.Embeds[0] = embed;
  82. UpdateWebhookMessage(state, state.Payload, state.MessageId);
  83. }
  84. public void UpdateCancelledWebhookIfConfigured(WebhookState? state, string? customCancelReason = null)
  85. {
  86. if (state == null)
  87. return;
  88. var embed = state.Payload.Embeds![0];
  89. embed.Color = 13356304; // #CBCD10
  90. if (customCancelReason == null)
  91. embed.Description += "\n\n" + Loc.GetString("custom-vote-webhook-cancelled");
  92. else
  93. embed.Description += "\n\n" + customCancelReason;
  94. for (var i = 0; i < embed.Fields.Count; i++)
  95. {
  96. var oldName = embed.Fields[i].Name;
  97. embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = Loc.GetString("custom-vote-webhook-option-cancelled"), Inline = true };
  98. }
  99. state.Payload.Embeds[0] = embed;
  100. UpdateWebhookMessage(state, state.Payload, state.MessageId);
  101. }
  102. // Sends the payload's message.
  103. public async void CreateWebhookMessage(WebhookState state, WebhookPayload payload)
  104. {
  105. try
  106. {
  107. if (await _discord.GetWebhook(state.WebhookUrl) is not { } identifier)
  108. return;
  109. state.Identifier = identifier.ToIdentifier();
  110. _sawmill.Debug(JsonSerializer.Serialize(payload));
  111. var request = await _discord.CreateMessage(identifier.ToIdentifier(), payload);
  112. var content = await request.Content.ReadAsStringAsync();
  113. state.MessageId = ulong.Parse(JsonNode.Parse(content)?["id"]!.GetValue<string>()!);
  114. }
  115. catch (Exception e)
  116. {
  117. _sawmill.Error($"Error while sending vote webhook to Discord: {e}");
  118. }
  119. }
  120. // Edits a pre-existing payload message, given an ID
  121. public async void UpdateWebhookMessage(WebhookState state, WebhookPayload payload, ulong id)
  122. {
  123. if (state.MessageId == 0)
  124. {
  125. _sawmill.Warning("Failed to deliver update to custom vote webhook: message ID was zero. This likely indicates a previous connection error sending the original message.");
  126. return;
  127. }
  128. DebugTools.Assert(state.Identifier != default);
  129. try
  130. {
  131. await _discord.EditMessage(state.Identifier, id, payload);
  132. }
  133. catch (Exception e)
  134. {
  135. _sawmill.Error($"Error while updating vote webhook on Discord: {e}");
  136. }
  137. }
  138. public sealed class WebhookState
  139. {
  140. public required string WebhookUrl;
  141. public required WebhookPayload Payload;
  142. public WebhookIdentifier Identifier;
  143. public ulong MessageId;
  144. }
  145. void IPostInjectInit.PostInject() { }
  146. }