Skip to content

Commit

Permalink
PoC Discord webhook support
Browse files Browse the repository at this point in the history
  • Loading branch information
3Mydlo3 committed Nov 17, 2024
1 parent 2c08d47 commit 74b077f
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 12 deletions.
1 change: 1 addition & 0 deletions ArmaForces.Arma.Server.Tests/Helpers/TestSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class TestSettings : ISettings
public string ServerExecutableName { get; set; } = "arma3.exe";
public string? SteamUser { get; set; } = "TEST_USER";
public string? SteamPassword { get; set; } = "TEST_PASSWORD";
public string? WebhookUrl { get; set; }

public Result LoadSettings() => throw new System.NotImplementedException();

Expand Down
5 changes: 5 additions & 0 deletions ArmaForces.Arma.Server/Config/ISettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public interface ISettings {
/// TODO: Handle no steam password
string? SteamPassword { get; }

/// <summary>
/// URL for sending webhook notifications.
/// </summary>
string? WebhookUrl { get; }

/// <summary>
/// Loads settings from configuration.
/// </summary>
Expand Down
7 changes: 6 additions & 1 deletion ArmaForces.Arma.Server/Config/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class Settings : ISettings
public string ServerExecutableName { get; set; } = "arma3server_x64.exe";
public string? SteamUser { get; set; }
public string? SteamPassword { get; set; }
public string? WebhookUrl { get; set; }

private readonly IFileSystem _fileSystem;
private readonly IRegistryReader _registryReader;
Expand Down Expand Up @@ -64,7 +65,8 @@ public Result LoadSettings()
.Tap(ObtainApiMissionsBaseUrl)
.Tap(ObtainApiModsetsBaseUrl)
.Tap(ObtainSteamUserName)
.Tap(ObtainSteamPassword);
.Tap(ObtainSteamPassword)
.Tap(ObtainWebhookUrl);
}

public Result ReloadSettings()
Expand All @@ -86,6 +88,7 @@ public async Task<Result> ReloadSettings(ISettings settings)
ServerExecutableName = settings.ServerExecutableName;
SteamUser = settings.SteamUser;
SteamPassword = settings.SteamPassword;
WebhookUrl = settings.WebhookUrl;

var json = JsonSerializer.Serialize(this, JsonOptions.Default);
await _fileSystem.File.WriteAllTextAsync(SettingsJsonPath, json);
Expand Down Expand Up @@ -113,6 +116,8 @@ private void ObtainModsDirectory()
private void ObtainSteamUserName() => SteamUser ??= _config["steamUserName"];

private void ObtainSteamPassword() => SteamPassword ??= _config["steamPassword"];

private void ObtainWebhookUrl() => WebhookUrl ??= _config["webhookUrl"];

private Result GetServerPath()
{
Expand Down
34 changes: 26 additions & 8 deletions ArmaForces.ArmaServerManager/Common/HttpClientBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using ArmaForces.Arma.Server.Constants;
Expand All @@ -19,21 +20,38 @@ protected async Task<Result<T>> HttpGetAsync<T>(string requestUrl)
{
var httpResponseMessage = await _httpClient.GetAsync(requestUrl);

var responseBody = await httpResponseMessage.Content.ReadAsStringAsync();

if (httpResponseMessage.IsSuccessStatusCode)
{
var responseBody = await httpResponseMessage.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(responseBody, JsonOptions.Default) ??
Result.Failure<T>($"Failed to deserialize response: {responseBody}");
}
else
{
var responseBody = await httpResponseMessage.Content.ReadAsStringAsync();
var error = string.IsNullOrWhiteSpace(responseBody)
? httpResponseMessage.ReasonPhrase
: responseBody;

var error = string.IsNullOrWhiteSpace(responseBody)
? httpResponseMessage.ReasonPhrase
: responseBody;

return Result.Failure<T>(error);
return Result.Failure<T>(error);
}

protected async Task<Result> HttpPostAsync<T>(string? requestUrl, T content)
{
var stringContent = new StringContent(JsonSerializer.Serialize(content, JsonSerializerOptions.Web), Encoding.UTF8, "application/json");
var httpResponseMessage = await _httpClient.PostAsync(requestUrl, stringContent);

var responseBody = await httpResponseMessage.Content.ReadAsStringAsync();

if (httpResponseMessage.IsSuccessStatusCode)
{
return Result.Success();
}

var error = string.IsNullOrWhiteSpace(responseBody)
? httpResponseMessage.ReasonPhrase
: responseBody;

return Result.Failure(error);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;

namespace ArmaForces.ArmaServerManager.Features.Webhooks.DTOs;

internal record DiscordWebhook
{
[JsonPropertyName("username")]
public string UserName { get; init; }

public string Content { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Threading.Tasks;

namespace ArmaForces.ArmaServerManager.Features.Webhooks;

public interface IWebhookClient
{
Task Send(string content);
}
22 changes: 22 additions & 0 deletions ArmaForces.ArmaServerManager/Features/Webhooks/WebhookClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Net.Http;
using System.Threading.Tasks;
using ArmaForces.ArmaServerManager.Common;
using ArmaForces.ArmaServerManager.Features.Webhooks.DTOs;

namespace ArmaForces.ArmaServerManager.Features.Webhooks;

internal class WebhookClient : HttpClientBase, IWebhookClient
{
public WebhookClient(HttpClient httpClient) : base(httpClient)
{
}

public async Task Send(string content)
{
await HttpPostAsync(requestUrl: null, content: new DiscordWebhook
{
UserName = "Arma Server",
Content = content
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Net.Http;
using ArmaForces.Arma.Server.Config;
using Microsoft.Extensions.DependencyInjection;

namespace ArmaForces.ArmaServerManager.Features.Webhooks;

internal static class WebhookServiceCollectionExtensions
{
public static IServiceCollection AddWebhookClient(this IServiceCollection services)
=> services.AddHttpClientForWebhookClient();

private static IServiceCollection AddHttpClientForWebhookClient(this IServiceCollection services)
{
services
.AddHttpClient<IWebhookClient, WebhookClient>()
.ConfigureHttpClient(SetBaseAddress());

return services;
}

private static Action<IServiceProvider, HttpClient> SetBaseAddress()
=> (services, client) => client.BaseAddress = new Uri(
services.GetRequiredService<ISettings>().WebhookUrl ??
throw new NotSupportedException("Missions API is required, provide valid url address."));
}
8 changes: 8 additions & 0 deletions ArmaForces.ArmaServerManager/Services/IWebhookService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Threading.Tasks;

namespace ArmaForces.ArmaServerManager.Services;

public interface IWebhookService
{
Task AnnounceStart();
}
8 changes: 5 additions & 3 deletions ArmaForces.ArmaServerManager/Services/StartupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,25 @@ namespace ArmaForces.ArmaServerManager.Services
public class StartupService : IHostedService
{
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly IWebhookService _webhookService;

/// <inheritdoc cref="StartupService"/>
public StartupService(IWebHostEnvironment webHostEnvironment)
public StartupService(IWebHostEnvironment webHostEnvironment, IWebhookService webhookService)
{
_webHostEnvironment = webHostEnvironment;
_webhookService = webhookService;
}

/// <inheritdoc />
public Task StartAsync(CancellationToken cancellationToken)
public async Task StartAsync(CancellationToken cancellationToken)
{
if (_webHostEnvironment.IsProduction())
{
RecurringJob.AddOrUpdate<MaintenanceService>(x => x.PerformMaintenance(CancellationToken.None),
Cron.Daily(4));
}

return Task.CompletedTask;
await _webhookService.AnnounceStart();
}

/// <inheritdoc />
Expand Down
30 changes: 30 additions & 0 deletions ArmaForces.ArmaServerManager/Services/WebhookService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using ArmaForces.Arma.Server.Config;
using ArmaForces.ArmaServerManager.Features.Webhooks;
using Microsoft.Extensions.Logging;

namespace ArmaForces.ArmaServerManager.Services;

public class WebhookService : IWebhookService
{
private readonly IWebhookClient _webhookClient;
private readonly ILogger<WebhookService> _logger;
private readonly string? _webhookUrl;

public WebhookService(IWebhookClient webhookClient, ISettings settings, ILogger<WebhookService> logger)
{
_webhookClient = webhookClient;
_logger = logger;
_webhookUrl = settings.WebhookUrl;
}

public async Task AnnounceStart()
{
_logger.LogInformation("Announce start");
if (_webhookUrl == null) return;
_logger.LogInformation("Webhook url: {WebhookUrl}", _webhookUrl);

await Task.Delay(10000);
await _webhookClient.Send("Manager started");
}
}
5 changes: 5 additions & 0 deletions ArmaForces.ArmaServerManager/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using ArmaForces.ArmaServerManager.Features.Servers;
using ArmaForces.ArmaServerManager.Features.Servers.Providers;
using ArmaForces.ArmaServerManager.Features.Status;
using ArmaForces.ArmaServerManager.Features.Webhooks;
using ArmaForces.ArmaServerManager.Infrastructure.Authentication;
using ArmaForces.ArmaServerManager.Infrastructure.Converters;
using ArmaForces.ArmaServerManager.Infrastructure.Documentation;
Expand Down Expand Up @@ -127,6 +128,10 @@ public void ConfigureServices(IServiceCollection services)
// Status
.AddSingleton<IAppStatusStore, AppStatusStore>()
.AddSingleton<IStatusProvider, StatusProvider>()

// Webhooks
.AddSingleton<IWebhookService, WebhookService>()
.AddWebhookClient()

// Hangfire
.AddSingleton<IJobsScheduler, JobsScheduler>()
Expand Down

0 comments on commit 74b077f

Please sign in to comment.