diff --git a/NostrStreamer/Config.cs b/NostrStreamer/Config.cs index e6bfe5a..941e090 100644 --- a/NostrStreamer/Config.cs +++ b/NostrStreamer/Config.cs @@ -54,6 +54,8 @@ public class Config public string Redis { get; init; } = null!; public Uri SnortApi { get; init; } = null!; + + public Uri? DiscordLiveWebhook { get; init; } } public class VapidKeyDetails diff --git a/NostrStreamer/Program.cs b/NostrStreamer/Program.cs index 49b23df..2dd5255 100644 --- a/NostrStreamer/Program.cs +++ b/NostrStreamer/Program.cs @@ -120,6 +120,9 @@ internal static class Program services.AddHostedService(); services.AddHostedService(); + // webhooks + services.AddTransient(); + // snort api services.AddTransient(); diff --git a/NostrStreamer/Services/DiscordWebhook.cs b/NostrStreamer/Services/DiscordWebhook.cs new file mode 100644 index 0000000..13578f3 --- /dev/null +++ b/NostrStreamer/Services/DiscordWebhook.cs @@ -0,0 +1,21 @@ +using System.Net.Http.Formatting; + +namespace NostrStreamer.Services; + +public class DiscordWebhook +{ + private readonly HttpClient _client; + + public DiscordWebhook(HttpClient client) + { + _client = client; + } + + public async Task SendMessage(Uri webhook, string msg) + { + await _client.PostAsync(webhook, new + { + content = msg + }, new JsonMediaTypeFormatter()); + } +} diff --git a/NostrStreamer/Services/StreamManager/NostrStreamManager.cs b/NostrStreamer/Services/StreamManager/NostrStreamManager.cs index 6ee7b0d..570d756 100644 --- a/NostrStreamer/Services/StreamManager/NostrStreamManager.cs +++ b/NostrStreamer/Services/StreamManager/NostrStreamManager.cs @@ -1,7 +1,10 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; +using Nostr.Client.Identifiers; using Nostr.Client.Json; +using Nostr.Client.Messages; +using Nostr.Client.Utils; using NostrStreamer.Database; using NostrStreamer.Services.Dvr; @@ -14,6 +17,8 @@ public class NostrStreamManager : IStreamManager private readonly StreamEventBuilder _eventBuilder; private readonly IDvrStore _dvrStore; private readonly Config _config; + private readonly DiscordWebhook _webhook; + private readonly SnortApi _snortApi; private readonly IDataProtectionProvider _dataProtectionProvider; public NostrStreamManager(ILogger logger, StreamManagerContext context, IServiceProvider serviceProvider) @@ -24,6 +29,8 @@ public class NostrStreamManager : IStreamManager _dvrStore = serviceProvider.GetRequiredService(); _config = serviceProvider.GetRequiredService(); _dataProtectionProvider = serviceProvider.GetRequiredService(); + _webhook = serviceProvider.GetRequiredService(); + _snortApi = serviceProvider.GetRequiredService(); } public UserStream GetStream() @@ -75,6 +82,21 @@ public class NostrStreamManager : IStreamManager TestCanStream(); await UpdateStreamState(UserStreamState.Live); + + if (_config.DiscordLiveWebhook != default) + { + try + { + var profile = await _snortApi.Profile(_context.User.PubKey); + var name = profile?.Name ?? NostrConverter.ToBech32(_context.User.PubKey, "npub"); + var id = new NostrAddressIdentifier(_context.UserStream.Id.ToString(), _context.User.PubKey, null, NostrKind.LiveEvent); + await _webhook.SendMessage(_config.DiscordLiveWebhook, $"{name} went live!\nhttps://zap.stream/{id.ToBech32()}"); + } + catch (Exception ex) + { + _logger.LogWarning("Failed to send notification {msg}", ex.Message); + } + } } public async Task StreamStopped() @@ -196,7 +218,7 @@ public class NostrStreamManager : IStreamManager } } - private async Task UpdateStreamState(UserStreamState state) + private async Task UpdateStreamState(UserStreamState state) { DateTime? ends = state == UserStreamState.Ended ? DateTime.UtcNow : null; _context.UserStream.State = state; @@ -209,5 +231,6 @@ public class NostrStreamManager : IStreamManager .SetProperty(v => v.Ends, ends)); _eventBuilder.BroadcastEvent(ev); + return ev; } }