-
Notifications
You must be signed in to change notification settings - Fork 475
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fd398c7
commit 37292a8
Showing
7 changed files
with
300 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace Content.Shared.GameTicking; | ||
|
||
public sealed class RoundEndedEvent : EntityEventArgs | ||
{ | ||
public int RoundId { get; } | ||
public TimeSpan RoundDuration { get; } | ||
|
||
public RoundEndedEvent(int roundId, TimeSpan roundDuration) | ||
{ | ||
RoundId = roundId; | ||
RoundDuration = roundDuration; | ||
} | ||
} |
179 changes: 179 additions & 0 deletions
179
Content.Server/Nyanotrasen/RoundNotifications/RoundNotificationsSystem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using Content.Shared.CCVar; | ||
using Content.Server.Maps; | ||
using Content.Shared.GameTicking; | ||
using Robust.Shared; | ||
using Robust.Shared.Configuration; | ||
|
||
namespace Content.Server.Nyanotrasen.RoundNotifications; | ||
|
||
/// <summary> | ||
/// Listen game events and send notifications to Discord | ||
/// </summary> | ||
public sealed class RoundNotificationsSystem : EntitySystem | ||
{ | ||
[Dependency] private readonly IConfigurationManager _config = default!; | ||
[Dependency] private readonly IGameMapManager _gameMapManager = default!; | ||
|
||
private ISawmill _sawmill = default!; | ||
private readonly HttpClient _httpClient = new(); | ||
|
||
private string _webhookUrl = String.Empty; | ||
private string _roleId = String.Empty; | ||
private bool _roundStartOnly; | ||
private string _serverName = string.Empty; | ||
|
||
/// <inheritdoc/> | ||
public override void Initialize() | ||
{ | ||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart); | ||
SubscribeLocalEvent<RoundStartedEvent>(OnRoundStarted); | ||
SubscribeLocalEvent<RoundEndedEvent>(OnRoundEnded); | ||
|
||
_config.OnValueChanged(CCVars.DiscordRoundWebhook, value => _webhookUrl = value, true); | ||
_config.OnValueChanged(CCVars.DiscordRoundRoleId, value => _roleId = value, true); | ||
_config.OnValueChanged(CCVars.DiscordRoundStartOnly, value => _roundStartOnly = value, true); | ||
_config.OnValueChanged(CVars.GameHostName, OnServerNameChanged, true); | ||
|
||
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("notifications"); | ||
} | ||
|
||
private void OnServerNameChanged(string obj) | ||
{ | ||
_serverName = obj; | ||
} | ||
|
||
private void OnRoundRestart(RoundRestartCleanupEvent e) | ||
{ | ||
if (String.IsNullOrEmpty(_webhookUrl)) | ||
return; | ||
|
||
var text = Loc.GetString("discord-round-new"); | ||
|
||
SendDiscordMessage(text, true, 0x91B2C7); | ||
} | ||
|
||
private void OnRoundStarted(RoundStartedEvent e) | ||
{ | ||
if (String.IsNullOrEmpty(_webhookUrl)) | ||
return; | ||
|
||
var map = _gameMapManager.GetSelectedMap(); | ||
var mapName = map?.MapName ?? Loc.GetString("discord-round-unknown-map"); | ||
var text = Loc.GetString("discord-round-start", | ||
("id", e.RoundId), | ||
("map", mapName)); | ||
|
||
SendDiscordMessage(text, false); | ||
} | ||
|
||
private void OnRoundEnded(RoundEndedEvent e) | ||
{ | ||
if (String.IsNullOrEmpty(_webhookUrl) || _roundStartOnly) | ||
return; | ||
|
||
var text = Loc.GetString("discord-round-end", | ||
("id", e.RoundId), | ||
("hours", Math.Truncate(e.RoundDuration.TotalHours)), | ||
("minutes", e.RoundDuration.Minutes), | ||
("seconds", e.RoundDuration.Seconds)); | ||
|
||
SendDiscordMessage(text, false, 0xB22B27); | ||
} | ||
|
||
private async void SendDiscordMessage(string text, bool ping = false, int color = 0x41F097) | ||
{ | ||
|
||
// Limit server name to 1500 characters, in case someone tries to be a little funny | ||
var serverName = _serverName[..Math.Min(_serverName.Length, 1500)]; | ||
var message = ""; | ||
if (!String.IsNullOrEmpty(_roleId) && ping) | ||
message = $"<@&{_roleId}>"; | ||
|
||
// Build the embed | ||
var payload = new WebhookPayload | ||
{ | ||
Message = message, | ||
Embeds = new List<Embed> | ||
{ | ||
new() | ||
{ | ||
Title = Loc.GetString("discord-round-title"), | ||
Description = text, | ||
Color = color, | ||
Footer = new EmbedFooter | ||
{ | ||
Text = $"{serverName}" | ||
}, | ||
}, | ||
}, | ||
}; | ||
if (!String.IsNullOrEmpty(_roleId) && ping) | ||
payload.AllowedMentions = new Dictionary<string, string[]> {{ "roles", new []{ _roleId } }}; | ||
|
||
var request = await _httpClient.PostAsync($"{_webhookUrl}?wait=true", | ||
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); | ||
|
||
var content = await request.Content.ReadAsStringAsync(); | ||
if (!request.IsSuccessStatusCode) | ||
{ | ||
_sawmill.Log(LogLevel.Error, | ||
$"Discord returned bad status code when posting message: {request.StatusCode}\nResponse: {content}"); | ||
return; | ||
} | ||
} | ||
|
||
// https://discord.com/developers/docs/resources/channel#message-object-message-structure | ||
private struct WebhookPayload | ||
{ | ||
[JsonPropertyName("username")] public string? Username { get; set; } = null; | ||
|
||
[JsonPropertyName("avatar_url")] public string? AvatarUrl { get; set; } = null; | ||
|
||
[JsonPropertyName("content")] public string Message { get; set; } = ""; | ||
|
||
[JsonPropertyName("embeds")] public List<Embed>? Embeds { get; set; } = null; | ||
|
||
[JsonPropertyName("allowed_mentions")] | ||
public Dictionary<string, string[]> AllowedMentions { get; set; } = | ||
new() | ||
{ | ||
{ "parse", Array.Empty<string>() }, | ||
}; | ||
|
||
public WebhookPayload() | ||
{ | ||
} | ||
} | ||
|
||
// https://discord.com/developers/docs/resources/channel#embed-object-embed-structure | ||
private struct Embed | ||
{ | ||
[JsonPropertyName("title")] public string Title { get; set; } = ""; | ||
|
||
[JsonPropertyName("description")] public string Description { get; set; } = ""; | ||
|
||
[JsonPropertyName("color")] public int Color { get; set; } = 0; | ||
|
||
[JsonPropertyName("footer")] public EmbedFooter? Footer { get; set; } = null; | ||
|
||
public Embed() | ||
{ | ||
} | ||
} | ||
|
||
// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure | ||
private struct EmbedFooter | ||
{ | ||
[JsonPropertyName("text")] public string Text { get; set; } = ""; | ||
|
||
[JsonPropertyName("icon_url")] public string? IconUrl { get; set; } | ||
|
||
public EmbedFooter() | ||
{ | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using Robust.Shared.Serialization; | ||
|
||
namespace Content.Shared.GameTicking | ||
{ | ||
[Serializable, NetSerializable] | ||
public sealed class RoundStartedEvent : EntityEventArgs | ||
{ | ||
public int RoundId { get; } | ||
|
||
public RoundStartedEvent(int roundId) | ||
{ | ||
RoundId = roundId; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
discord-round-new = A new round is starting! | ||
discord-round-start = Round #{ $id } on map "{ $map }" has started. | ||
discord-round-end = Round #{ $id } has ended. It lasted for {$hours} hours, {$minutes} minutes, and {$seconds} seconds. | ||
discord-round-title = Round Notification |