mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-05-02 04:22:04 -04:00
Added support for Kiwi PeerTube livestream notifications and capturing
This commit is contained in:
@@ -29,10 +29,16 @@ public enum StreamService
|
|||||||
{
|
{
|
||||||
Kick,
|
Kick,
|
||||||
Parti,
|
Parti,
|
||||||
DLive
|
DLive,
|
||||||
|
KiwiPeerTube
|
||||||
}
|
}
|
||||||
|
|
||||||
public class KickStreamMetaModel
|
public class KickStreamMetaModel
|
||||||
{
|
{
|
||||||
public required int ChannelId { get; set; }
|
public required int ChannelId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PeerTubeMetaModel
|
||||||
|
{
|
||||||
|
public required string AccountName { get; set; }
|
||||||
|
}
|
||||||
31
KfChatDotNetBot/Models/PeerTubeModels.cs
Normal file
31
KfChatDotNetBot/Models/PeerTubeModels.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace KfChatDotNetBot.Models;
|
||||||
|
|
||||||
|
public class PeerTubeVideoDataModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("uuid")]
|
||||||
|
public required string Uuid { get; set; }
|
||||||
|
[JsonPropertyName("shortUUID")]
|
||||||
|
public required string ShortUuid { get; set; }
|
||||||
|
[JsonPropertyName("url")]
|
||||||
|
public required string Url { get; set; }
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
[JsonPropertyName("category")]
|
||||||
|
public Dictionary<string, string>? Category { get; set; }
|
||||||
|
[JsonPropertyName("isLive")]
|
||||||
|
public required bool IsLive { get; set; }
|
||||||
|
[JsonPropertyName("account")]
|
||||||
|
public required PeerTubeAccountOrChannelModel Account { get; set; }
|
||||||
|
[JsonPropertyName("channel")]
|
||||||
|
public required PeerTubeAccountOrChannelModel Channel { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PeerTubeAccountOrChannelModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("displayName")]
|
||||||
|
public required string DisplayName { get; set; }
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ public class BotServices
|
|||||||
public AlmanacShill? AlmanacShill;
|
public AlmanacShill? AlmanacShill;
|
||||||
private Parti? _parti;
|
private Parti? _parti;
|
||||||
private DLive? _dliveStatusCheck;
|
private DLive? _dliveStatusCheck;
|
||||||
|
private PeerTube? _peerTubeStatusCheck;
|
||||||
|
|
||||||
private Task? _websocketWatchdog;
|
private Task? _websocketWatchdog;
|
||||||
private Task? _howlggGetUserTimer;
|
private Task? _howlggGetUserTimer;
|
||||||
@@ -83,7 +84,8 @@ public class BotServices
|
|||||||
BuildYeet(),
|
BuildYeet(),
|
||||||
BuildRainbet(),
|
BuildRainbet(),
|
||||||
BuildParti(),
|
BuildParti(),
|
||||||
BuildDLiveStatusCheck()
|
BuildDLiveStatusCheck(),
|
||||||
|
BuildPeerTubeLiveStatusCheck()
|
||||||
];
|
];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -324,6 +326,14 @@ public class BotServices
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task BuildPeerTubeLiveStatusCheck()
|
||||||
|
{
|
||||||
|
_peerTubeStatusCheck = new PeerTube(_chatBot);
|
||||||
|
_peerTubeStatusCheck.StartLiveStatusCheck();
|
||||||
|
_logger.Info("Built the PeerTube livestream status check task");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task BuildParti()
|
private async Task BuildParti()
|
||||||
{
|
{
|
||||||
var settings = await SettingsProvider.GetMultipleValuesAsync([BuiltIn.Keys.Proxy, BuiltIn.Keys.PartiEnabled]);
|
var settings = await SettingsProvider.GetMultipleValuesAsync([BuiltIn.Keys.Proxy, BuiltIn.Keys.PartiEnabled]);
|
||||||
|
|||||||
141
KfChatDotNetBot/Services/PeerTube.cs
Normal file
141
KfChatDotNetBot/Services/PeerTube.cs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using KfChatDotNetBot.Models;
|
||||||
|
using KfChatDotNetBot.Models.DbModels;
|
||||||
|
using KfChatDotNetBot.Settings;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace KfChatDotNetBot.Services;
|
||||||
|
|
||||||
|
public class PeerTube(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.KiwiPeerTubeCheckInterval)).ToType<int>();
|
||||||
|
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 PeerTube now");
|
||||||
|
await using var db = new ApplicationDbContext();
|
||||||
|
var streams = await db.Streams.Where(s => s.Service == StreamService.KiwiPeerTube).Include(s => s.User).ToListAsync(ct);
|
||||||
|
var settings = await SettingsProvider.GetMultipleValuesAsync([
|
||||||
|
BuiltIn.Keys.KiwiPeerTubePersistedCurrentlyLiveStreams, BuiltIn.Keys.CaptureEnabled, BuiltIn.Keys.KiwiPeerTubeEnabled,
|
||||||
|
BuiltIn.Keys.KiwiPeerTubeEnforceWhitelist
|
||||||
|
]);
|
||||||
|
if (!settings[BuiltIn.Keys.KiwiPeerTubeEnabled].ToBoolean())
|
||||||
|
{
|
||||||
|
_logger.Debug("PeerTube disabled");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var persistedLive = settings[BuiltIn.Keys.KiwiPeerTubePersistedCurrentlyLiveStreams].JsonDeserialize<List<string>>() ?? [];
|
||||||
|
List<PeerTubeVideoDataModel> currentlyLive;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
currentlyLive = await GetLiveStreams(ct);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Error("Caught an error while trying to get the currently live streams from Kiwi PeerTube");
|
||||||
|
_logger.Error(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach (var stream in currentlyLive)
|
||||||
|
{
|
||||||
|
if (persistedLive.Contains(stream.Uuid)) continue;
|
||||||
|
StreamDbModel? dbEntry = null;
|
||||||
|
foreach (var row in streams)
|
||||||
|
{
|
||||||
|
if (row.Metadata == null)
|
||||||
|
{
|
||||||
|
_logger.Error($"Stream ID {row.Id} has null metadata");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var meta = JsonSerializer.Deserialize<PeerTubeMetaModel>(row.Metadata);
|
||||||
|
if (meta == null)
|
||||||
|
{
|
||||||
|
_logger.Error($"Caught a null when deserializing the metadata for {row.Id}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (meta.AccountName == stream.Account.Name) dbEntry = row;
|
||||||
|
}
|
||||||
|
if (settings[BuiltIn.Keys.KiwiPeerTubeEnforceWhitelist].ToBoolean() && dbEntry == null)
|
||||||
|
{
|
||||||
|
_logger.Info($"{stream.Account.Name} is live but whitelisting is enforced and the username isn't in the stream database");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
persistedLive.Add(stream.Uuid);
|
||||||
|
await kfChatBot.SendChatMessageAsync($"{stream.Account.DisplayName} is live! {stream.Name} {stream.Url}", true);
|
||||||
|
|
||||||
|
if (settings[BuiltIn.Keys.CaptureEnabled].ToBoolean())
|
||||||
|
{
|
||||||
|
if (dbEntry != null && !dbEntry.AutoCapture)
|
||||||
|
{
|
||||||
|
_logger.Info($"{stream.Url} is live but auto capture is disabled for this stream");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_logger.Info($"{stream.Url} is live and set to auto capture (if configured)");
|
||||||
|
_ = new StreamCapture(stream.Url, StreamCaptureMethods.YtDlp, ct).CaptureAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ToList will create a copy of the list so we can work on the original one
|
||||||
|
foreach (var persisted in persistedLive.ToList())
|
||||||
|
{
|
||||||
|
var stream = currentlyLive.FirstOrDefault(l => l.Uuid == persisted);
|
||||||
|
if (stream == null) persistedLive.Remove(persisted);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug($"Persisting currently live streams, count is {currentlyLive.Count}");
|
||||||
|
await SettingsProvider.SetValueAsJsonObjectAsync(BuiltIn.Keys.KiwiPeerTubePersistedCurrentlyLiveStreams,
|
||||||
|
currentlyLive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<PeerTubeVideoDataModel>> GetLiveStreams(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 PeerTube 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://kiwifarms.tv/api/v1/videos?start=0&count=25&sort=-publishedAt&skipCount=true&nsfw=both&nsfwFlagsExcluded=0&nsfwFlagsIncluded=0&isLive=true", ct);
|
||||||
|
var content = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: ct);
|
||||||
|
logger.Debug("PeerTube endpoint returned the following JSON");
|
||||||
|
logger.Debug(content.GetRawText);
|
||||||
|
return content.GetProperty("data").Deserialize<List<PeerTubeVideoDataModel>>() ?? throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_liveStatusCheckTaskCts.Cancel();
|
||||||
|
_liveStatusCheckTask?.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -880,6 +880,37 @@ public static class BuiltIn
|
|||||||
Description = "Array of DLive streamers who are currently live for persistence between bot restarts",
|
Description = "Array of DLive streamers who are currently live for persistence between bot restarts",
|
||||||
Default = "[]",
|
Default = "[]",
|
||||||
ValueType = SettingValueType.Complex
|
ValueType = SettingValueType.Complex
|
||||||
|
},
|
||||||
|
new BuiltInSettingsModel
|
||||||
|
{
|
||||||
|
Key = Keys.KiwiPeerTubePersistedCurrentlyLiveStreams,
|
||||||
|
Description = "Array of Kiwi PeerTube stream GUIDs which are currently live for persistence between bot restarts",
|
||||||
|
Default = "[]",
|
||||||
|
ValueType = SettingValueType.Complex
|
||||||
|
},
|
||||||
|
new BuiltInSettingsModel
|
||||||
|
{
|
||||||
|
Key = Keys.KiwiPeerTubeEnabled,
|
||||||
|
Description = "Whether the Kiwi PeerTube live notification is enabled",
|
||||||
|
Default = "true",
|
||||||
|
ValueType = SettingValueType.Boolean,
|
||||||
|
Regex = "(true|false)"
|
||||||
|
},
|
||||||
|
new BuiltInSettingsModel
|
||||||
|
{
|
||||||
|
Key = Keys.KiwiPeerTubeCheckInterval,
|
||||||
|
Description = "Interval (in seconds) to check live streams",
|
||||||
|
Default = "10",
|
||||||
|
ValueType = SettingValueType.Text,
|
||||||
|
Regex = @"\d+"
|
||||||
|
},
|
||||||
|
new BuiltInSettingsModel
|
||||||
|
{
|
||||||
|
Key = Keys.KiwiPeerTubeEnforceWhitelist,
|
||||||
|
Description = "Whether to enforce the use of a whitelist (i.e. streamer must be in the database)",
|
||||||
|
Default = "false",
|
||||||
|
ValueType = SettingValueType.Boolean,
|
||||||
|
Regex = "(true|false)"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -979,5 +1010,9 @@ public static class BuiltIn
|
|||||||
public static string CaptureStreamlinkRemuxScript = "Capture.Streamlink.RemuxScript";
|
public static string CaptureStreamlinkRemuxScript = "Capture.Streamlink.RemuxScript";
|
||||||
public static string DLiveCheckInterval = "DLive.CheckInterval";
|
public static string DLiveCheckInterval = "DLive.CheckInterval";
|
||||||
public static string DLivePersistedCurrentlyLiveStreams = "DLive.PersistedCurrentlyLiveStreams";
|
public static string DLivePersistedCurrentlyLiveStreams = "DLive.PersistedCurrentlyLiveStreams";
|
||||||
|
public static string KiwiPeerTubePersistedCurrentlyLiveStreams = "KiwiPeerTube.PersistedCurrentlyLiveStreams";
|
||||||
|
public static string KiwiPeerTubeEnabled = "KiwiPeerTube.Enabled";
|
||||||
|
public static string KiwiPeerTubeCheckInterval = "KiwiPeerTube.CheckInterval";
|
||||||
|
public static string KiwiPeerTubeEnforceWhitelist = "KiwiPeerTube.EnforceWhitelist";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user