From 2b07a07ac568e18017709338d6cf262da7fc6ef6 Mon Sep 17 00:00:00 2001 From: barelyprofessional <150058423+barelyprofessional@users.noreply.github.com> Date: Sun, 24 Aug 2025 03:02:34 -0500 Subject: [PATCH] Added Owncast support --- KfChatDotNetBot/Services/BotServices.cs | 12 +++- KfChatDotNetBot/Services/Owncast.cs | 92 +++++++++++++++++++++++++ KfChatDotNetBot/Settings/BuiltIn.cs | 20 +++++- 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 KfChatDotNetBot/Services/Owncast.cs diff --git a/KfChatDotNetBot/Services/BotServices.cs b/KfChatDotNetBot/Services/BotServices.cs index 4e89bb1..c7e647c 100644 --- a/KfChatDotNetBot/Services/BotServices.cs +++ b/KfChatDotNetBot/Services/BotServices.cs @@ -37,6 +37,7 @@ public class BotServices private Parti? _parti; private DLive? _dliveStatusCheck; private PeerTube? _peerTubeStatusCheck; + private Owncast? _owncastStatusCheck; private Task? _websocketWatchdog; private Task? _howlggGetUserTimer; @@ -84,7 +85,8 @@ public class BotServices BuildRainbet(), BuildParti(), BuildDLiveStatusCheck(), - BuildPeerTubeLiveStatusCheck() + BuildPeerTubeLiveStatusCheck(), + BuildOwncastLiveStatusCheck() ]; try { @@ -334,6 +336,14 @@ public class BotServices return Task.CompletedTask; } + private Task BuildOwncastLiveStatusCheck() + { + _owncastStatusCheck = new Owncast(_chatBot); + _owncastStatusCheck.StartLiveStatusCheck(); + _logger.Info("Built the Owncast livestream status check task"); + return Task.CompletedTask; + } + private async Task BuildParti() { var settings = await SettingsProvider.GetMultipleValuesAsync([BuiltIn.Keys.Proxy, BuiltIn.Keys.PartiEnabled]); diff --git a/KfChatDotNetBot/Services/Owncast.cs b/KfChatDotNetBot/Services/Owncast.cs new file mode 100644 index 0000000..ab65ffa --- /dev/null +++ b/KfChatDotNetBot/Services/Owncast.cs @@ -0,0 +1,92 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using KfChatDotNetBot.Settings; +using NLog; + +namespace KfChatDotNetBot.Services; + +public class Owncast(ChatBot kfChatBot) : IDisposable +{ + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private Task? _liveStatusCheckTask; + private CancellationTokenSource _liveStatusCheckTaskCts = new(); + + public void StartLiveStatusCheck() + { + _liveStatusCheckTaskCts = new CancellationTokenSource(); + _liveStatusCheckTask = Task.Run(LiveStatusCheckTask, _liveStatusCheckTaskCts.Token); + } + + private async Task LiveStatusCheckTask() + { + var interval = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.OwncastCheckInterval)).ToType(); + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(interval)); + while (await timer.WaitForNextTickAsync(_liveStatusCheckTaskCts.Token)) + { + var ct = _liveStatusCheckTaskCts.Token; + _logger.Debug("Going to check if anyone is live on Owncast now"); + var persistedLive = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.OwncastPersistedCurrentlyLive)) + .ToBoolean(); + OwncastStatusModel status; + try + { + status = await GetLiveStatus(ct); + } + catch (Exception e) + { + _logger.Error("Caught an error while trying to get the currently live streams from Owncast"); + _logger.Error(e); + continue; + } + if (status.Online == persistedLive) continue; + await SettingsProvider.SetValueAsBooleanAsync(BuiltIn.Keys.OwncastPersistedCurrentlyLive, + status.Online); + if (!status.Online) continue; + await kfChatBot.SendChatMessageAsync("https://bossmanjack.tv restream is live!", true); + if (!(await SettingsProvider.GetValueAsync(BuiltIn.Keys.CaptureEnabled)).ToBoolean()) continue; + _ = new StreamCapture("https://bossmanjack.tv", StreamCaptureMethods.YtDlp, ct); + } + } + + public static async Task GetLiveStatus(CancellationToken ct = default) + { + var logger = LogManager.GetCurrentClassLogger(); + var proxy = await SettingsProvider.GetValueAsync(BuiltIn.Keys.Proxy); + var handler = new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.All, + + }; + if (proxy.Value != null) + { + handler.Proxy = new WebProxy(proxy.Value); + handler.UseProxy = true; + logger.Debug($"Set proxy for the Owncast API request to {proxy.Value}"); + } + + using var client = new HttpClient(handler); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await client.GetAsync("https://bossmanjack.tv/api/status", ct); + var content = await response.Content.ReadFromJsonAsync(cancellationToken: ct); + logger.Debug("Owncast endpoint returned the following JSON"); + logger.Debug(content.GetRawText); + return content.GetProperty("data").Deserialize() ?? throw new InvalidOperationException(); + } + + public void Dispose() + { + _liveStatusCheckTaskCts.Cancel(); + _liveStatusCheckTask?.Dispose(); + GC.SuppressFinalize(this); + } +} + +public class OwncastStatusModel +{ + [JsonPropertyName("online")] + public required bool Online { get; set; } +} \ No newline at end of file diff --git a/KfChatDotNetBot/Settings/BuiltIn.cs b/KfChatDotNetBot/Settings/BuiltIn.cs index 925c270..0cefcb3 100644 --- a/KfChatDotNetBot/Settings/BuiltIn.cs +++ b/KfChatDotNetBot/Settings/BuiltIn.cs @@ -976,7 +976,23 @@ public static class BuiltIn Description = "Special options for Twitch streams captured with Streamlink", Default = "--twitch-disable-ad --twitch-proxy-playlist=https://eu.luminous.dev,https://eu2.luminous.dev,https://as.luminous.dev,https://cdn.perfprod.com", ValueType = SettingValueType.Text - } + }, + new BuiltInSettingsModel + { + Key = Keys.OwncastCheckInterval, + Description = "Interval in seconds to check if someone is live on Owncast", + Default = "5", + ValueType = SettingValueType.Text, + Regex = WholeNumberRegex + }, + new BuiltInSettingsModel + { + Key = Keys.OwncastPersistedCurrentlyLive, + Description = "Whether someone is live on Owncast", + Default = "false", + ValueType = SettingValueType.Boolean, + Regex = BooleanRegex + }, ]; public static class Keys @@ -1087,5 +1103,7 @@ public static class BuiltIn public static string MoneyInitialBalance = "Money.InitialBalance"; public static string TwitchGraphQlCheckInterval = "TwitchGraphQl.CheckInterval"; public static string TwitchGraphQlPersistedCurrentlyLive = "TwitchGraphQl.PersistedCurrentlyLive"; + public static string OwncastCheckInterval = "Owncast.CheckInterval"; + public static string OwncastPersistedCurrentlyLive = "Owncast.PersistedCurrentlyLive"; } } \ No newline at end of file