]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Station news Discord webhook (#36807)
authorssdaniel24 <107036969+ssdaniel24@users.noreply.github.com>
Sat, 10 May 2025 18:21:02 +0000 (21:21 +0300)
committerGitHub <noreply@github.com>
Sat, 10 May 2025 18:21:02 +0000 (11:21 -0700)
* Add news article Discord webhook

* Send all station articles on round end

* Changed event subscrice to RoundEndMessageEvent

* Review remarks fix

* Added new cvar discord.news_webhook_embed_color

Default color taken from news manager console sprite.

* Using EntityQueryEnumerator instead of GetStationInMap with TryComp

* Extra review remarks fixing

* Sorted imports

* Added article publication time in embed

* Removed markup from article content

* Added sorting for articles iteration

* Discord hook embed color cvar is string now

* Added comment about limits

* Added new cvar for posting articles during round

* Shitty discord rate limit handling

* Fixing copypaste accident

Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
* Null initialization of webhook id

* SendArticleToDiscordWebhook is non-void now

---------

Co-authored-by: Morb0 <14136326+Morb0@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Content.Server/MassMedia/Systems/NewsSystem.cs
Content.Shared/CCVar/CCVars.Discord.cs
Resources/Locale/en-US/mass-media/news-discord.ftl [new file with mode: 0644]

index 641f2074b805744af37b1b1a50a8b251c90258df..d3cd94f6566a86cb5cca5f9a5c60da4c253c88ed 100644 (file)
@@ -1,24 +1,33 @@
-using System.Diagnostics.CodeAnalysis;
 using Content.Server.Administration.Logs;
-using Content.Server.CartridgeLoader;
 using Content.Server.CartridgeLoader.Cartridges;
+using Content.Server.CartridgeLoader;
 using Content.Server.Chat.Managers;
+using Content.Server.Discord;
 using Content.Server.GameTicking;
 using Content.Server.MassMedia.Components;
 using Content.Server.Popups;
 using Content.Server.Station.Systems;
 using Content.Shared.Access.Components;
 using Content.Shared.Access.Systems;
-using Content.Shared.CartridgeLoader;
+using Content.Shared.CCVar;
 using Content.Shared.CartridgeLoader.Cartridges;
+using Content.Shared.CartridgeLoader;
 using Content.Shared.Database;
+using Content.Shared.GameTicking;
+using Content.Shared.IdentityManagement;
 using Content.Shared.MassMedia.Components;
 using Content.Shared.MassMedia.Systems;
 using Content.Shared.Popups;
 using Robust.Server.GameObjects;
+using Robust.Server;
 using Robust.Shared.Audio.Systems;
-using Content.Shared.IdentityManagement;
+using Robust.Shared.Configuration;
+using Robust.Shared.Maths;
 using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Threading.Tasks;
 
 namespace Content.Server.MassMedia.Systems;
 
@@ -34,11 +43,36 @@ public sealed class NewsSystem : SharedNewsSystem
     [Dependency] private readonly StationSystem _station = default!;
     [Dependency] private readonly GameTicker _ticker = default!;
     [Dependency] private readonly IChatManager _chatManager = default!;
+    [Dependency] private readonly DiscordWebhook _discord = default!;
+    [Dependency] private readonly IConfigurationManager _cfg = default!;
+    [Dependency] private readonly IBaseServer _baseServer = default!;
+
+    private WebhookIdentifier? _webhookId = null;
+    private Color _webhookEmbedColor;
+    private bool _webhookSendDuringRound;
 
     public override void Initialize()
     {
         base.Initialize();
 
+        // Discord hook
+        _cfg.OnValueChanged(CCVars.DiscordNewsWebhook,
+            value =>
+            {
+                if (!string.IsNullOrWhiteSpace(value))
+                    _discord.GetWebhook(value, data => _webhookId = data.ToIdentifier());
+            }, true);
+
+        _cfg.OnValueChanged(CCVars.DiscordNewsWebhookEmbedColor, value =>
+            {
+                _webhookEmbedColor = Color.LawnGreen;
+                if (Color.TryParse(value, out var color))
+                    _webhookEmbedColor = color;
+            }, true);
+
+        _cfg.OnValueChanged(CCVars.DiscordNewsWebhookSendDuringRound, value => _webhookSendDuringRound = value, true);
+        SubscribeLocalEvent<RoundEndMessageEvent>(OnRoundEndMessageEvent);
+
         // News writer
         SubscribeLocalEvent<NewsWriterComponent, MapInitEvent>(OnMapInit);
 
@@ -177,6 +211,9 @@ public sealed class NewsSystem : SharedNewsSystem
             RaiseLocalEvent(readerUid, ref args);
         }
 
+        if (_webhookSendDuringRound)
+            Task.Run(async () => await SendArticleToDiscordWebhook(article));
+
         UpdateWriterDevices();
     }
     #endregion
@@ -324,4 +361,62 @@ public sealed class NewsSystem : SharedNewsSystem
     {
         UpdateWriterUi(ent);
     }
+
+    #region Discord Hook
+
+    private void OnRoundEndMessageEvent(RoundEndMessageEvent ev)
+    {
+        if (_webhookSendDuringRound)
+            return;
+
+        var query = EntityManager.EntityQueryEnumerator<StationNewsComponent>();
+
+        while (query.MoveNext(out _, out var comp))
+        {
+            SendArticlesListToDiscordWebhook(comp.Articles.OrderBy(article => article.ShareTime));
+        }
+    }
+
+    private async void SendArticlesListToDiscordWebhook(IOrderedEnumerable<NewsArticle> articles)
+    {
+        foreach (var article in articles)
+        {
+            await Task.Delay(TimeSpan.FromSeconds(1)); // TODO: proper discord rate limit handling
+            await SendArticleToDiscordWebhook(article);
+        }
+    }
+
+    private async Task SendArticleToDiscordWebhook(NewsArticle article)
+    {
+        if (_webhookId is null)
+            return;
+
+        try
+        {
+            var embed = new WebhookEmbed
+            {
+                Title = article.Title,
+                // There is no need to cut article content. It's MaxContentLength smaller then discord's limit (4096):
+                Description = FormattedMessage.RemoveMarkupPermissive(article.Content),
+                Color = _webhookEmbedColor.ToArgb() & 0xFFFFFF, // HACK: way to get hex without A (transparency)
+                Footer = new WebhookEmbedFooter
+                {
+                    Text = Loc.GetString("news-discord-footer",
+                        ("server", _baseServer.ServerName),
+                        ("round", _ticker.RoundId),
+                        ("author", article.Author ?? Loc.GetString("news-discord-unknown-author")),
+                        ("time", article.ShareTime.ToString(@"hh\:mm\:ss")))
+                }
+            };
+            var payload = new WebhookPayload { Embeds = [embed] };
+            await _discord.CreateMessage(_webhookId.Value, payload);
+            Log.Info("Sent news article to Discord webhook");
+        }
+        catch (Exception e)
+        {
+            Log.Error($"Error while sending discord news article:\n{e}");
+        }
+    }
+
+    #endregion
 }
index 6e4ef532cdc7145851baf8ca23653242d277c218..4be8680d2c7a0af9fcfecb332518320a1e2ea431 100644 (file)
@@ -1,4 +1,5 @@
 using Robust.Shared.Configuration;
+using Robust.Shared.Maths;
 
 namespace Content.Shared.CCVar;
 
@@ -72,4 +73,24 @@ public sealed partial class CCVars
     /// </summary>
     public static readonly CVarDef<float> DiscordWatchlistConnectionBufferTime =
         CVarDef.Create("discord.watchlist_connection_buffer_time", 5f, CVar.SERVERONLY);
+
+    /// <summary>
+    ///     URL of the Discord webhook which will receive station news acticles at the round end.
+    ///     If left empty, disables the webhook.
+    /// </summary>
+    public static readonly CVarDef<string> DiscordNewsWebhook =
+        CVarDef.Create("discord.news_webhook", string.Empty, CVar.SERVERONLY);
+
+    /// <summary>
+    ///     HEX color of station news discord webhook's embed.
+    /// </summary>
+    public static readonly CVarDef<string> DiscordNewsWebhookEmbedColor =
+        CVarDef.Create("discord.news_webhook_embed_color", Color.LawnGreen.ToHex(), CVar.SERVERONLY);
+
+    /// <summary>
+    ///     Whether or not articles should be sent mid-round instead of all at once at the round's end
+    /// </summary>
+    public static readonly CVarDef<bool> DiscordNewsWebhookSendDuringRound =
+        CVarDef.Create("discord.news_webhook_send_during_round", false, CVar.SERVERONLY);
+
 }
diff --git a/Resources/Locale/en-US/mass-media/news-discord.ftl b/Resources/Locale/en-US/mass-media/news-discord.ftl
new file mode 100644 (file)
index 0000000..cf02742
--- /dev/null
@@ -0,0 +1,2 @@
+news-discord-footer = Server: {$server} | Round: #{$round} | Author: {$author} | Time: {$time}
+news-discord-unknown-author = Unknown