Files
barelyprofessional e2ae5c20c2 Log the DLive exception
2025-11-15 16:50:33 -06:00

202 lines
8.5 KiB
C#

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 DLive(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.DLiveCheckInterval)).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 DLive now");
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.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();
if (username == null)
{
_logger.Error($"Could not determine the DLive username from {stream.StreamUrl} in row {stream.Id}");
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
{
status = await IsLive(username, ct);
}
catch (Exception e)
{
_logger.Error($"Caught an exception while retrieving live status for {username}");
_logger.Error(e);
continue;
}
if (!status.IsLive)
{
currentlyLive.Remove(username);
continue;
}
// Already known to be live so do nothing
if (currentlyLive.Contains(username)) continue;
var identity = "A streamer";
if (stream.User != null)
{
identity = "@" + stream.User.KfUsername;
}
await kfChatBot.SendChatMessageAsync($"{identity} is live! {status.Title} {stream.StreamUrl}", true);
BaseMetaModel? meta = null;
if (stream.Metadata != null)
{
try
{
meta = JsonSerializer.Deserialize<BaseMetaModel>(stream.Metadata);
}
catch (Exception e)
{
_logger.Error($"Caught an exception when attempting to deserialize metadata for DLive stream {stream.StreamUrl}");
_logger.Error(e);
}
}
if (stream.AutoCapture && settings[BuiltIn.Keys.CaptureEnabled].ToBoolean())
{
_logger.Info($"{stream.StreamUrl} is live and set to auto capture");
_ = new StreamCapture(stream.StreamUrl, StreamCaptureMethods.Streamlink, meta?.CaptureOverrides, ct).CaptureAsync();
}
currentlyLive.Add(username);
}
_logger.Debug($"Persisting currently live streams, count is {currentlyLive.Count}");
await SettingsProvider.SetValueAsJsonObjectAsync(BuiltIn.Keys.DLivePersistedCurrentlyLiveStreams,
currentlyLive);
}
}
public static async Task<DLiveIsLiveModel> IsLive(string username, 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 DLive GraphQL request to {proxy.Value}");
}
var gql = "query { userByDisplayName(displayname:\"" + username + "\") { livestream " +
"{ content createdAt title thumbnailUrl watchingCount } username } }";
logger.Debug($"Built GraphQL query string: {gql}");
var jsonBody = new Dictionary<string, object>
{
{ "query", gql }
};
logger.Debug("Created dictionary object for the JSON payload, should serialize to following:");
logger.Debug(JsonSerializer.Serialize(jsonBody));
using var client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var postBody = JsonContent.Create(jsonBody);
var response = await client.PostAsync("https://graphigo.prd.dlive.tv/", postBody, ct);
var content = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: ct);
logger.Debug("DLive GraphQL endpoint returned the following JSON");
logger.Debug(content.GetRawText);
// Not live
// {"data":{"userByDisplayName":{"livestream":null,"username":"planesfan"}}}
// Live
// {
// "data": {
// "userByDisplayName": {
// "livestream": {
// "content": "",
// "createdAt": "1752699050000",
// "title": "HUNT FULL ACHAT VENEZ DONNER VOS CALL!!!",
// "thumbnailUrl": "https://images.prd.dlivecdn.com/live-thumbnail/a587c2a8-6288-11f0-90fc-d638708e4bb8",
// "watchingCount": 799
// },
// "username": "cashpistache1"
// }
// }
// }
try
{
var responseData = content.GetProperty("data").GetProperty("userByDisplayName");
if (responseData.ValueKind == JsonValueKind.Null)
{
logger.Error($"userByDisplayName was null for {username}. Just going to throw an exception because who knows what's going on");
logger.Error(content.GetRawText);
throw new NullReferenceException($"userByDisplayName for {username}");
}
var isLive = responseData.GetProperty("livestream").ValueKind == JsonValueKind.Object;
string? title = null;
if (isLive)
{
title = responseData.GetProperty("livestream").GetProperty("title").GetString();
}
return new DLiveIsLiveModel
{
IsLive = isLive,
Title = title,
Username = responseData.GetProperty("username").GetString() ?? "username was null in GraphQL response"
};
}
catch (Exception e)
{
logger.Error($"Bot shit itself while trying to check if {username} is live. JSON payload follows");
logger.Error(content.GetRawText);
logger.Error(e);
throw;
}
}
public void Dispose()
{
_liveStatusCheckTaskCts.Cancel();
_liveStatusCheckTask?.Dispose();
GC.SuppressFinalize(this);
}
}