WatchlistWebhookManager.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. using Content.Server.Administration.Notes;
  2. using Content.Server.Database;
  3. using Content.Server.Discord;
  4. using Content.Shared.CCVar;
  5. using Robust.Server;
  6. using Robust.Server.Player;
  7. using Robust.Shared.Enums;
  8. using Robust.Shared.Configuration;
  9. using Robust.Shared.Network;
  10. using Robust.Shared.Player;
  11. using Robust.Shared.Timing;
  12. using System.Linq;
  13. using System.Text;
  14. namespace Content.Server.Administration.Managers;
  15. /// <summary>
  16. /// This manager sends a Discord webhook notification whenever a player with an active
  17. /// watchlist joins the server.
  18. /// </summary>
  19. public sealed class WatchlistWebhookManager : IWatchlistWebhookManager
  20. {
  21. [Dependency] private readonly IAdminNotesManager _adminNotes = default!;
  22. [Dependency] private readonly IBaseServer _baseServer = default!;
  23. [Dependency] private readonly IConfigurationManager _cfg = default!;
  24. [Dependency] private readonly DiscordWebhook _discord = default!;
  25. [Dependency] private readonly IGameTiming _gameTiming = default!;
  26. [Dependency] private readonly IPlayerManager _playerManager = default!;
  27. private ISawmill _sawmill = default!;
  28. private string _webhookUrl = default!;
  29. private TimeSpan _bufferTime;
  30. private List<WatchlistConnection> watchlistConnections = new();
  31. private TimeSpan? _bufferStartTime;
  32. public void Initialize()
  33. {
  34. _sawmill = Logger.GetSawmill("discord");
  35. _cfg.OnValueChanged(CCVars.DiscordWatchlistConnectionBufferTime, SetBufferTime, true);
  36. _cfg.OnValueChanged(CCVars.DiscordWatchlistConnectionWebhook, SetWebhookUrl, true);
  37. _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
  38. }
  39. private void SetBufferTime(float bufferTimeSeconds)
  40. {
  41. _bufferTime = TimeSpan.FromSeconds(bufferTimeSeconds);
  42. }
  43. private void SetWebhookUrl(string webhookUrl)
  44. {
  45. _webhookUrl = webhookUrl;
  46. }
  47. private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
  48. {
  49. if (e.NewStatus != SessionStatus.Connected)
  50. return;
  51. var watchlists = await _adminNotes.GetActiveWatchlists(e.Session.UserId);
  52. if (watchlists.Count == 0)
  53. return;
  54. watchlistConnections.Add(new WatchlistConnection(e.Session.Name, watchlists));
  55. if (_bufferTime > TimeSpan.Zero)
  56. {
  57. if (_bufferStartTime == null)
  58. _bufferStartTime = _gameTiming.RealTime;
  59. }
  60. else
  61. {
  62. SendDiscordMessage();
  63. }
  64. }
  65. public void Update()
  66. {
  67. if (_bufferStartTime != null && _gameTiming.RealTime > (_bufferStartTime + _bufferTime))
  68. {
  69. SendDiscordMessage();
  70. _bufferStartTime = null;
  71. }
  72. }
  73. private async void SendDiscordMessage()
  74. {
  75. try
  76. {
  77. if (string.IsNullOrWhiteSpace(_webhookUrl))
  78. return;
  79. var webhookData = await _discord.GetWebhook(_webhookUrl);
  80. if (webhookData == null)
  81. return;
  82. var webhookIdentifier = webhookData.Value.ToIdentifier();
  83. var messageBuilder = new StringBuilder(Loc.GetString("discord-watchlist-connection-header",
  84. ("players", watchlistConnections.Count),
  85. ("serverName", _baseServer.ServerName)));
  86. foreach (var connection in watchlistConnections)
  87. {
  88. messageBuilder.Append('\n');
  89. var watchlist = connection.Watchlists.First();
  90. var expiry = watchlist.ExpirationTime?.ToUnixTimeSeconds();
  91. messageBuilder.Append(Loc.GetString("discord-watchlist-connection-entry",
  92. ("playerName", connection.PlayerName),
  93. ("message", watchlist.Message),
  94. ("expiry", expiry ?? 0),
  95. ("otherWatchlists", connection.Watchlists.Count - 1)));
  96. }
  97. var payload = new WebhookPayload { Content = messageBuilder.ToString() };
  98. await _discord.CreateMessage(webhookIdentifier, payload);
  99. }
  100. catch (Exception e)
  101. {
  102. _sawmill.Error($"Error while sending discord watchlist connection message:\n{e}");
  103. }
  104. // Clear the buffered list regardless of whether the message is sent successfully
  105. // This prevents infinitely buffering connections if we fail to send a message
  106. watchlistConnections.Clear();
  107. }
  108. private sealed class WatchlistConnection
  109. {
  110. public string PlayerName;
  111. public List<AdminWatchlistRecord> Watchlists;
  112. public WatchlistConnection(string playerName, List<AdminWatchlistRecord> watchlists)
  113. {
  114. PlayerName = playerName;
  115. Watchlists = watchlists;
  116. }
  117. }
  118. }