1- using System . Diagnostics . CodeAnalysis ;
21using Content . Server . Administration . Logs ;
3- using Content . Server . CartridgeLoader ;
42using Content . Server . CartridgeLoader . Cartridges ;
3+ using Content . Server . CartridgeLoader ;
54using Content . Server . Chat . Managers ;
5+ using Content . Server . Discord ;
66using Content . Server . GameTicking ;
77using Content . Server . MassMedia . Components ;
88using Content . Server . Popups ;
99using Content . Server . Station . Systems ;
1010using Content . Shared . Access . Components ;
1111using Content . Shared . Access . Systems ;
12- using Content . Shared . CartridgeLoader ;
12+ using Content . Shared . CCVar ;
1313using Content . Shared . CartridgeLoader . Cartridges ;
14+ using Content . Shared . CartridgeLoader ;
1415using Content . Shared . Database ;
16+ using Content . Shared . GameTicking ;
17+ using Content . Shared . IdentityManagement ;
1518using Content . Shared . MassMedia . Components ;
1619using Content . Shared . MassMedia . Systems ;
1720using Content . Shared . Popups ;
1821using Robust . Server . GameObjects ;
22+ using Robust . Server ;
1923using Robust . Shared . Audio . Systems ;
20- using Content . Shared . IdentityManagement ;
24+ using Robust . Shared . Configuration ;
25+ using Robust . Shared . Maths ;
2126using Robust . Shared . Timing ;
27+ using Robust . Shared . Utility ;
28+ using System . Diagnostics . CodeAnalysis ;
29+ using System . Linq ;
30+ using System . Threading . Tasks ;
2231
2332namespace Content . Server . MassMedia . Systems ;
2433
@@ -34,11 +43,36 @@ public sealed class NewsSystem : SharedNewsSystem
3443 [ Dependency ] private readonly StationSystem _station = default ! ;
3544 [ Dependency ] private readonly GameTicker _ticker = default ! ;
3645 [ Dependency ] private readonly IChatManager _chatManager = default ! ;
46+ [ Dependency ] private readonly DiscordWebhook _discord = default ! ;
47+ [ Dependency ] private readonly IConfigurationManager _cfg = default ! ;
48+ [ Dependency ] private readonly IBaseServer _baseServer = default ! ;
49+
50+ private WebhookIdentifier ? _webhookId = null ;
51+ private Color _webhookEmbedColor ;
52+ private bool _webhookSendDuringRound ;
3753
3854 public override void Initialize ( )
3955 {
4056 base . Initialize ( ) ;
4157
58+ // Discord hook
59+ _cfg . OnValueChanged ( CCVars . DiscordNewsWebhook ,
60+ value =>
61+ {
62+ if ( ! string . IsNullOrWhiteSpace ( value ) )
63+ _discord . GetWebhook ( value , data => _webhookId = data . ToIdentifier ( ) ) ;
64+ } , true ) ;
65+
66+ _cfg . OnValueChanged ( CCVars . DiscordNewsWebhookEmbedColor , value =>
67+ {
68+ _webhookEmbedColor = Color . LawnGreen ;
69+ if ( Color . TryParse ( value , out var color ) )
70+ _webhookEmbedColor = color ;
71+ } , true ) ;
72+
73+ _cfg . OnValueChanged ( CCVars . DiscordNewsWebhookSendDuringRound , value => _webhookSendDuringRound = value , true ) ;
74+ SubscribeLocalEvent < RoundEndMessageEvent > ( OnRoundEndMessageEvent ) ;
75+
4276 // News writer
4377 SubscribeLocalEvent < NewsWriterComponent , MapInitEvent > ( OnMapInit ) ;
4478
@@ -177,6 +211,9 @@ private void OnWriteUiPublishMessage(Entity<NewsWriterComponent> ent, ref NewsWr
177211 RaiseLocalEvent ( readerUid , ref args ) ;
178212 }
179213
214+ if ( _webhookSendDuringRound )
215+ Task . Run ( async ( ) => await SendArticleToDiscordWebhook ( article ) ) ;
216+
180217 UpdateWriterDevices ( ) ;
181218 }
182219 #endregion
@@ -324,4 +361,62 @@ private void OnRequestArticleDraftMessage(Entity<NewsWriterComponent> ent, ref N
324361 {
325362 UpdateWriterUi ( ent ) ;
326363 }
364+
365+ #region Discord Hook
366+
367+ private void OnRoundEndMessageEvent ( RoundEndMessageEvent ev )
368+ {
369+ if ( _webhookSendDuringRound )
370+ return ;
371+
372+ var query = EntityManager . EntityQueryEnumerator < StationNewsComponent > ( ) ;
373+
374+ while ( query . MoveNext ( out _ , out var comp ) )
375+ {
376+ SendArticlesListToDiscordWebhook ( comp . Articles . OrderBy ( article => article . ShareTime ) ) ;
377+ }
378+ }
379+
380+ private async void SendArticlesListToDiscordWebhook ( IOrderedEnumerable < NewsArticle > articles )
381+ {
382+ foreach ( var article in articles )
383+ {
384+ await Task . Delay ( TimeSpan . FromSeconds ( 1 ) ) ; // TODO: proper discord rate limit handling
385+ await SendArticleToDiscordWebhook ( article ) ;
386+ }
387+ }
388+
389+ private async Task SendArticleToDiscordWebhook ( NewsArticle article )
390+ {
391+ if ( _webhookId is null )
392+ return ;
393+
394+ try
395+ {
396+ var embed = new WebhookEmbed
397+ {
398+ Title = article . Title ,
399+ // There is no need to cut article content. It's MaxContentLength smaller then discord's limit (4096):
400+ Description = FormattedMessage . RemoveMarkupPermissive ( article . Content ) ,
401+ Color = _webhookEmbedColor . ToArgb ( ) & 0xFFFFFF , // HACK: way to get hex without A (transparency)
402+ Footer = new WebhookEmbedFooter
403+ {
404+ Text = Loc . GetString ( "news-discord-footer" ,
405+ ( "server" , _baseServer . ServerName ) ,
406+ ( "round" , _ticker . RoundId ) ,
407+ ( "author" , article . Author ?? Loc . GetString ( "news-discord-unknown-author" ) ) ,
408+ ( "time" , article . ShareTime . ToString ( @"hh\:mm\:ss" ) ) )
409+ }
410+ } ;
411+ var payload = new WebhookPayload { Embeds = [ embed ] } ;
412+ await _discord . CreateMessage ( _webhookId . Value , payload ) ;
413+ Log . Info ( "Sent news article to Discord webhook" ) ;
414+ }
415+ catch ( Exception e )
416+ {
417+ Log . Error ( $ "Error while sending discord news article:\n { e } ") ;
418+ }
419+ }
420+
421+ #endregion
327422}
0 commit comments