-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;
[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);
RaiseLocalEvent(readerUid, ref args);
}
+ if (_webhookSendDuringRound)
+ Task.Run(async () => await SendArticleToDiscordWebhook(article));
+
UpdateWriterDevices();
}
#endregion
{
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
}
using Robust.Shared.Configuration;
+using Robust.Shared.Maths;
namespace Content.Shared.CCVar;
/// </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);
+
}