Added a stream capture locking feature due to DLive spuriously reporting a streamer as not live when they are and causing duplicate captures.

It works by having the capture script touch a file before it begins capturing, then remove the file when the capture is complete.

The bot will check if this file is present before checking if a DLive streamer is actually live which will reduce the amount of API hits and prevent it from going live twice.
This commit is contained in:
barelyprofessional
2025-11-08 09:01:38 -06:00
parent 3ca9e1278b
commit ccf26d24a2
3 changed files with 43 additions and 6 deletions

View File

@@ -33,9 +33,11 @@ public class DLive(ChatBot kfChatBot) : IDisposable
await using var db = new ApplicationDbContext();
var streams = await db.Streams.Where(s => s.Service == StreamService.DLive).Include(s => s.User).ToListAsync(ct);
var settings = await SettingsProvider.GetMultipleValuesAsync([
BuiltIn.Keys.DLivePersistedCurrentlyLiveStreams, BuiltIn.Keys.CaptureEnabled
BuiltIn.Keys.DLivePersistedCurrentlyLiveStreams, BuiltIn.Keys.CaptureEnabled,
BuiltIn.Keys.CaptureLockTable
]);
var currentlyLive = settings[BuiltIn.Keys.DLivePersistedCurrentlyLiveStreams].JsonDeserialize<List<string>>() ?? [];
var locks = settings[BuiltIn.Keys.CaptureLockTable].JsonDeserialize<Dictionary<string, string>>() ?? new Dictionary<string, string>();
foreach (var stream in streams)
{
var username = stream.StreamUrl.Split('/').LastOrDefault();
@@ -45,6 +47,15 @@ public class DLive(ChatBot kfChatBot) : IDisposable
continue;
}
if (locks.ContainsKey(stream.StreamUrl))
{
if (File.Exists(locks[stream.StreamUrl]))
{
_logger.Debug($"{stream.StreamUrl} has an existing lock at {locks[stream.StreamUrl]}, ignoring");
continue;
}
}
DLiveIsLiveModel status;
try
{

View File

@@ -13,10 +13,16 @@ namespace KfChatDotNetBot.Services;
public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod, CaptureOverridesModel? captureOverrides = null, CancellationToken ct = default)
{
private readonly Dictionary<string, Setting> _settings = SettingsProvider
.GetMultipleValuesAsync([BuiltIn.Keys.CaptureYtDlpBinaryPath, BuiltIn.Keys.CaptureYtDlpWorkingDirectory,
BuiltIn.Keys.CaptureYtDlpCookiesFromBrowser, BuiltIn.Keys.CaptureYtDlpOutputFormat, BuiltIn.Keys.CaptureYtDlpParentTerminal,
BuiltIn.Keys.CaptureYtDlpScriptPath, BuiltIn.Keys.CaptureYtDlpUserAgent, BuiltIn.Keys.CaptureStreamlinkBinaryPath,
BuiltIn.Keys.CaptureStreamlinkOutputFormat, BuiltIn.Keys.CaptureStreamlinkRemuxScript, BuiltIn.Keys.CaptureStreamlinkTwitchOptions]).Result;
.GetMultipleValuesAsync([
BuiltIn.Keys.CaptureYtDlpBinaryPath, BuiltIn.Keys.CaptureYtDlpWorkingDirectory,
BuiltIn.Keys.CaptureYtDlpCookiesFromBrowser, BuiltIn.Keys.CaptureYtDlpOutputFormat,
BuiltIn.Keys.CaptureYtDlpParentTerminal,
BuiltIn.Keys.CaptureYtDlpScriptPath, BuiltIn.Keys.CaptureYtDlpUserAgent,
BuiltIn.Keys.CaptureStreamlinkBinaryPath,
BuiltIn.Keys.CaptureStreamlinkOutputFormat, BuiltIn.Keys.CaptureStreamlinkRemuxScript,
BuiltIn.Keys.CaptureStreamlinkTwitchOptions,
BuiltIn.Keys.CaptureLockTable
]).Result;
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
@@ -97,11 +103,14 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
var random = Convert.ToHexString(Guid.NewGuid().ToByteArray()[..4]);
var scriptPath = Path.Join(_settings[captureOverrides?.CaptureYtDlpScriptPath ?? BuiltIn.Keys.CaptureYtDlpScriptPath].Value,
$"bot_ytdlp_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{random}.sh");
var lockFile = Path.Join(_settings[captureOverrides?.CaptureYtDlpScriptPath ?? BuiltIn.Keys.CaptureYtDlpScriptPath].Value,
$"capture_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{random}.lock");
if (OperatingSystem.IsWindows())
{
Path.ChangeExtension(scriptPath, ".bat");
}
_logger.Info($"Generated script path: {scriptPath}");
_logger.Info($"Generated lock file path: {lockFile}");
string captureLine;
if (captureMethod == StreamCaptureMethods.YtDlp)
@@ -141,17 +150,21 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
{
// GetPathRoot on Windows returns the top level directory, e.g. "C:\". Assuming the working directory is on another drive such as D:
// we'll need to swap to that drive letter, so this just trims off the \ to transform it to D: or whatever. UNC paths not supported
scriptContent = $"{Path.GetPathRoot(captureOverrides?.CaptureYtDlpWorkingDirectory ?? _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value)?.TrimEnd('\\')}{Environment.NewLine}" +
scriptContent = $"TYPE nul > {lockFile}{Environment.NewLine}" +
$"{Path.GetPathRoot(captureOverrides?.CaptureYtDlpWorkingDirectory ?? _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value)?.TrimEnd('\\')}{Environment.NewLine}" +
$"CD {captureOverrides?.CaptureYtDlpWorkingDirectory ?? _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value}{Environment.NewLine}" +
$"{captureLine}{Environment.NewLine}" +
$"DEL /q {lockFile}{Environment.NewLine}" +
$"{remuxLine}{Environment.NewLine}" +
$"PAUSE";
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
{
scriptContent = $"#!/bin/bash{Environment.NewLine}" +
$"touch {lockFile}{Environment.NewLine}" +
$"cd {captureOverrides?.CaptureYtDlpWorkingDirectory ?? _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value}{Environment.NewLine}" +
$"{captureLine}{Environment.NewLine}" +
$"rm -f {lockFile}{Environment.NewLine}" +
$"{remuxLine}{Environment.NewLine}" +
$"read -p \"Press enter to exit\"";
}
@@ -162,6 +175,17 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
_logger.Info("Wrote the script, contents follow this message");
_logger.Info(scriptContent);
_logger.Info("Updating locktable");
var table = _settings[BuiltIn.Keys.CaptureLockTable].JsonDeserialize<Dictionary<string, string>>() ??
new Dictionary<string, string>();
if (!table.TryAdd(streamUrl, lockFile))
{
_logger.Info($"{streamUrl} has a lockfile already pointing to {table[streamUrl]}, updating to {lockFile}");
table[streamUrl] = lockFile;
}
await SettingsProvider.SetValueAsJsonObjectAsync(BuiltIn.Keys.CaptureLockTable, table);
await File.WriteAllTextAsync(scriptPath, scriptContent, ct);
if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())