From 988152869284706900e6aa5e7f1e5928e5142e16 Mon Sep 17 00:00:00 2001 From: ssdaniel24 <107036969+ssdaniel24@users.noreply.github.com> Date: Sat, 10 May 2025 21:21:02 +0300 Subject: [PATCH] Station news Discord webhook (#36807) * 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 * 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 --- .../MassMedia/Systems/NewsSystem.cs | 103 +++++++++++++++++- Content.Shared/CCVar/CCVars.Discord.cs | 21 ++++ .../Locale/en-US/mass-media/news-discord.ftl | 2 + 3 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 Resources/Locale/en-US/mass-media/news-discord.ftl diff --git a/Content.Server/MassMedia/Systems/NewsSystem.cs b/Content.Server/MassMedia/Systems/NewsSystem.cs index 641f2074b8..d3cd94f656 100644 --- a/Content.Server/MassMedia/Systems/NewsSystem.cs +++ b/Content.Server/MassMedia/Systems/NewsSystem.cs @@ -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(OnRoundEndMessageEvent); + // News writer SubscribeLocalEvent(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(); + + while (query.MoveNext(out _, out var comp)) + { + SendArticlesListToDiscordWebhook(comp.Articles.OrderBy(article => article.ShareTime)); + } + } + + private async void SendArticlesListToDiscordWebhook(IOrderedEnumerable 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 } diff --git a/Content.Shared/CCVar/CCVars.Discord.cs b/Content.Shared/CCVar/CCVars.Discord.cs index 6e4ef532cd..4be8680d2c 100644 --- a/Content.Shared/CCVar/CCVars.Discord.cs +++ b/Content.Shared/CCVar/CCVars.Discord.cs @@ -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 /// public static readonly CVarDef DiscordWatchlistConnectionBufferTime = CVarDef.Create("discord.watchlist_connection_buffer_time", 5f, CVar.SERVERONLY); + + /// + /// URL of the Discord webhook which will receive station news acticles at the round end. + /// If left empty, disables the webhook. + /// + public static readonly CVarDef DiscordNewsWebhook = + CVarDef.Create("discord.news_webhook", string.Empty, CVar.SERVERONLY); + + /// + /// HEX color of station news discord webhook's embed. + /// + public static readonly CVarDef DiscordNewsWebhookEmbedColor = + CVarDef.Create("discord.news_webhook_embed_color", Color.LawnGreen.ToHex(), CVar.SERVERONLY); + + /// + /// Whether or not articles should be sent mid-round instead of all at once at the round's end + /// + public static readonly CVarDef 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 index 0000000000..cf02742232 --- /dev/null +++ b/Resources/Locale/en-US/mass-media/news-discord.ftl @@ -0,0 +1,2 @@ +news-discord-footer = Server: {$server} | Round: #{$round} | Author: {$author} | Time: {$time} +news-discord-unknown-author = Unknown -- 2.51.2