Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions SS14.Watchdog/Components/Notifications/NotificationManager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

Expand All @@ -16,7 +19,7 @@ public sealed partial class NotificationManager(
ILogger<NotificationManager> logger,
IOptions<NotificationOptions> options)
{
public const string DiscordHttpClient = "discord_webhook";
public const string NotificationHttpClient = "watchdog_notification";

private readonly HttpClient _httpClient = http.CreateClient();

Expand All @@ -26,13 +29,26 @@ public void SendNotification(string message)
var optionsValue = options.Value;
if (optionsValue.DiscordWebhook == null)
{
logger.LogTrace("Not sending notification: no Discord webhook URL configured");
logger.LogTrace("Not sending discord notification: no Discord webhook URL configured");
return;
}

SendWebhook(optionsValue, message);
}

public async Task SendHttpNotification(string instanceId)
{
var optionsValue = options.Value;

if (optionsValue.NotifyUrls.Count == 0)
{
logger.LogTrace("Not sending HTTP notification: no HTTP URLs configured");
return;
}

await SendUpdatePosts(optionsValue, instanceId);
}

private async void SendWebhook(NotificationOptions optionsValue, string message)
{
logger.LogTrace("Sending notification \"{Message}\" to Discord webhook...", message);
Expand Down Expand Up @@ -60,6 +76,49 @@ private async void SendWebhook(NotificationOptions optionsValue, string message)
}
}

private async Task SendUpdatePosts(NotificationOptions optionsValue, string instanceId)
{
logger.LogTrace("Sending update notification post...");

try
{
var data = new UpdatePostExecute
{
InstanceId = instanceId
};

foreach (var urlString in optionsValue.NotifyUrls)
{
await SendSpecificUpdatePost(optionsValue, data, new Uri(urlString));
}
}
catch (Exception e)
{
logger.LogError(e, "Error while sending update notification post!");
}
}

private async Task SendSpecificUpdatePost(NotificationOptions optionsValue, UpdatePostExecute data, Uri url)
{
using var request = new HttpRequestMessage(HttpMethod.Post, url);

if (!string.IsNullOrWhiteSpace(optionsValue.UpdatePostUser)
&& !string.IsNullOrWhiteSpace(optionsValue.UpdatePostToken))
{
var authAsBytes = Encoding.ASCII.GetBytes(optionsValue.UpdatePostUser + ":" + optionsValue.UpdatePostToken);
var authAsBase64 = Convert.ToBase64String(authAsBytes);
var authHeader = new AuthenticationHeaderValue("Basic", authAsBase64);
request.Headers.Authorization = authHeader;
}

request.Content = JsonContent.Create(data);

using var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();

logger.LogTrace("Succeeded sending update post");
}

private sealed class DiscordWebhookExecute
{
public string? Content { get; set; }
Expand All @@ -76,4 +135,12 @@ private sealed class DiscordAllowedMentions
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.KebabCaseLower)]
[JsonSerializable(typeof(DiscordWebhookExecute))]
private partial class DiscordSourceGenerationContext : JsonSerializerContext;

private sealed class UpdatePostExecute
{
/// <summary>
/// The Watchdog instance that was updated.
/// </summary>
public required string InstanceId { get; set; }
}
}
21 changes: 20 additions & 1 deletion SS14.Watchdog/Components/Notifications/NotificationOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace SS14.Watchdog.Components.Notifications;
using System.Collections.Generic;

namespace SS14.Watchdog.Components.Notifications;

/// <summary>
/// Options for notifications the watchdog can send via various channels.
Expand All @@ -12,4 +14,21 @@ public sealed class NotificationOptions
/// A Discord webhook URL to send notifications like server crashes to.
/// </summary>
public string? DiscordWebhook { get; set; }

/// <summary>
/// A list of URLs that should be sent a POST request.
/// If specified, each one will be provided <see cref="UpdatePostToken"/> as authentication.
/// </summary>
/// <remarks>No suffixes or prefixes are added to the URL before sending the POST.</remarks>
public List<string> NotifyUrls { get; set; } = [];

/// <summary>
/// The username that will be passed through to each update URL as BasicAuthentication.
/// </summary>
public string UpdatePostUser { get; set; } = string.Empty;

/// <summary>
/// The token that will be passed through to each update URL as BasicAuthentication.
/// </summary>
public string UpdatePostToken { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ private async Task RunCommandUpdateAvailable(CommandUpdateAvailable command, Can
_updateOnRestart = command.UpdateAvailable;
if (command.UpdateAvailable)
{
// Should send an update regardless of what the server's state is.
await _notificationManager.SendHttpNotification(Key);

if (IsRunning)
{
_logger.LogTrace("Server is running, sending update notification.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ public void HandleUpdateCheck()
return;

_logger.LogDebug("Received update notification.");

_taskQueue.QueueTask(async cancel =>
{
var updateAvailable = await _updateProvider.CheckForUpdateAsync(_currentRevision, cancel);
Expand Down
2 changes: 1 addition & 1 deletion SS14.Watchdog/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void ConfigureServices(IServiceCollection services)

services.AddSingleton<NotificationManager>();

services.AddHttpClient(NotificationManager.DiscordHttpClient);
services.AddHttpClient(NotificationManager.NotificationHttpClient);
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand Down