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 <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>
This commit is contained in:
@@ -1,24 +1,33 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.CartridgeLoader;
|
|
||||||
using Content.Server.CartridgeLoader.Cartridges;
|
using Content.Server.CartridgeLoader.Cartridges;
|
||||||
|
using Content.Server.CartridgeLoader;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
|
using Content.Server.Discord;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.MassMedia.Components;
|
using Content.Server.MassMedia.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Shared.Access.Components;
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Access.Systems;
|
using Content.Shared.Access.Systems;
|
||||||
using Content.Shared.CartridgeLoader;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.CartridgeLoader.Cartridges;
|
using Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.GameTicking;
|
||||||
|
using Content.Shared.IdentityManagement;
|
||||||
using Content.Shared.MassMedia.Components;
|
using Content.Shared.MassMedia.Components;
|
||||||
using Content.Shared.MassMedia.Systems;
|
using Content.Shared.MassMedia.Systems;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Content.Shared.IdentityManagement;
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Content.Server.MassMedia.Systems;
|
namespace Content.Server.MassMedia.Systems;
|
||||||
|
|
||||||
@@ -34,11 +43,36 @@ public sealed class NewsSystem : SharedNewsSystem
|
|||||||
[Dependency] private readonly StationSystem _station = default!;
|
[Dependency] private readonly StationSystem _station = default!;
|
||||||
[Dependency] private readonly GameTicker _ticker = default!;
|
[Dependency] private readonly GameTicker _ticker = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = 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()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.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
|
// News writer
|
||||||
SubscribeLocalEvent<NewsWriterComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<NewsWriterComponent, MapInitEvent>(OnMapInit);
|
||||||
|
|
||||||
@@ -177,6 +211,9 @@ public sealed class NewsSystem : SharedNewsSystem
|
|||||||
RaiseLocalEvent(readerUid, ref args);
|
RaiseLocalEvent(readerUid, ref args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_webhookSendDuringRound)
|
||||||
|
Task.Run(async () => await SendArticleToDiscordWebhook(article));
|
||||||
|
|
||||||
UpdateWriterDevices();
|
UpdateWriterDevices();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -324,4 +361,62 @@ public sealed class NewsSystem : SharedNewsSystem
|
|||||||
{
|
{
|
||||||
UpdateWriterUi(ent);
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
namespace Content.Shared.CCVar;
|
namespace Content.Shared.CCVar;
|
||||||
|
|
||||||
@@ -72,4 +73,24 @@ public sealed partial class CCVars
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly CVarDef<float> DiscordWatchlistConnectionBufferTime =
|
public static readonly CVarDef<float> DiscordWatchlistConnectionBufferTime =
|
||||||
CVarDef.Create("discord.watchlist_connection_buffer_time", 5f, CVar.SERVERONLY);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
2
Resources/Locale/en-US/mass-media/news-discord.ftl
Normal file
2
Resources/Locale/en-US/mass-media/news-discord.ftl
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
news-discord-footer = Server: {$server} | Round: #{$round} | Author: {$author} | Time: {$time}
|
||||||
|
news-discord-unknown-author = Unknown
|
||||||
Reference in New Issue
Block a user