mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-04-30 03:22:04 -04:00
154 lines
6.5 KiB
C#
154 lines
6.5 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using KfChatDotNetBot.Models.DbModels;
|
|
using KfChatDotNetBot.Settings;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using NLog;
|
|
|
|
namespace KfChatDotNetBot.Services;
|
|
|
|
public class TwitchGraphQl(string? proxy = null, CancellationToken cancellationToken = default) : IDisposable
|
|
{
|
|
private Logger _logger = LogManager.GetCurrentClassLogger();
|
|
private Uri _gqlEndpoint = new Uri("https://gql.twitch.tv/gql");
|
|
private string _gqlClientId = "kimne78kx3ncx6brgo4mv6wki5h1ko";
|
|
private string? _proxy = proxy;
|
|
private CancellationToken _cancellationToken = cancellationToken;
|
|
private Task? _liveStatusCheckTask;
|
|
private CancellationTokenSource _liveStatusCheckTaskCts = new();
|
|
|
|
public delegate void OnStreamStateUpdateEventHandler(object sender, string channelName, bool isLive);
|
|
public event OnStreamStateUpdateEventHandler? OnStreamStateUpdated;
|
|
|
|
public void StartLiveStatusCheck()
|
|
{
|
|
_liveStatusCheckTaskCts = new CancellationTokenSource();
|
|
_liveStatusCheckTask = Task.Run(LiveStatusCheckTask, _liveStatusCheckTaskCts.Token);
|
|
}
|
|
|
|
public bool IsTaskRunning()
|
|
{
|
|
if (_liveStatusCheckTask?.Status is not (TaskStatus.Running or TaskStatus.WaitingForActivation)) return false;
|
|
return !_liveStatusCheckTaskCts.IsCancellationRequested;
|
|
}
|
|
|
|
private async Task LiveStatusCheckTask()
|
|
{
|
|
var interval = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.TwitchGraphQlCheckInterval)).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 Bossman is live right now");
|
|
var settings = await SettingsProvider.GetMultipleValuesAsync([
|
|
BuiltIn.Keys.TwitchBossmanJackUsername, BuiltIn.Keys.TwitchGraphQlPersistedCurrentlyLive
|
|
]);
|
|
TwitchGraphQlModel stream;
|
|
try
|
|
{
|
|
stream = await GetStream(settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value!);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Error("Caught exception when trying to check if Bossman is live on Twitch");
|
|
_logger.Error(e);
|
|
continue;
|
|
}
|
|
|
|
if (stream.IsLive)
|
|
{
|
|
await using var db = new ApplicationDbContext();
|
|
var mostRecentViewCount = await db.TwitchViewCounts.OrderBy(x => x.Id).LastOrDefaultAsync(ct);
|
|
if (mostRecentViewCount == null || mostRecentViewCount.Viewers != stream.ViewerCount!.Value || mostRecentViewCount.Topic != stream.Id)
|
|
{
|
|
_logger.Info("Updating DB with fresh view count");
|
|
await db.TwitchViewCounts.AddAsync(new TwitchViewCountDbModel
|
|
{
|
|
Topic = stream.Id!,
|
|
Viewers = stream.ViewerCount!.Value,
|
|
Time = DateTimeOffset.UtcNow,
|
|
ServerTime = 0
|
|
}, ct);
|
|
await db.SaveChangesAsync(ct);
|
|
}
|
|
}
|
|
var persistedLive = settings[BuiltIn.Keys.TwitchGraphQlPersistedCurrentlyLive].ToBoolean();
|
|
if (stream.IsLive == persistedLive) continue;
|
|
OnStreamStateUpdated?.Invoke(this, settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value!, stream.IsLive);
|
|
await SettingsProvider.SetValueAsBooleanAsync(BuiltIn.Keys.TwitchGraphQlPersistedCurrentlyLive,
|
|
stream.IsLive);
|
|
}
|
|
}
|
|
|
|
public async Task<TwitchGraphQlModel> GetStream(string channel)
|
|
{
|
|
var graphQl = "query {\n user(login: \"" + channel + "\") {\n stream {\n id\n viewersCount\n }\n }\n}";
|
|
_logger.Debug($"Built GraphQL query string: {graphQl}");
|
|
var jsonBody = new Dictionary<string, object>
|
|
{
|
|
{ "query", graphQl },
|
|
{ "variables", new object() }
|
|
};
|
|
_logger.Debug("Created dictionary object for the JSON payload, should serialize to following value:");
|
|
_logger.Debug(JsonSerializer.Serialize(jsonBody));
|
|
var handler = new HttpClientHandler {AutomaticDecompression = DecompressionMethods.All};
|
|
if (_proxy != null)
|
|
{
|
|
handler.UseProxy = true;
|
|
handler.Proxy = new WebProxy(_proxy);
|
|
_logger.Debug($"Configured to use proxy {_proxy}");
|
|
}
|
|
|
|
using var client = new HttpClient(handler);
|
|
client.DefaultRequestHeaders.Add("client-id", _gqlClientId);
|
|
var postBody = JsonContent.Create(jsonBody);
|
|
var response = await client.PostAsync(_gqlEndpoint, postBody, _cancellationToken);
|
|
var responseContent = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: _cancellationToken);
|
|
_logger.Debug("Twitch API returned following JSON");
|
|
_logger.Debug(responseContent.GetRawText);
|
|
var returnData = new TwitchGraphQlModel { IsLive = false};
|
|
if (responseContent.GetProperty("data").GetProperty("user").ValueKind == JsonValueKind.Null)
|
|
{
|
|
_logger.Error("data.user was null");
|
|
return returnData;
|
|
}
|
|
|
|
if (responseContent.GetProperty("data").GetProperty("user").GetProperty("stream").ValueKind ==
|
|
JsonValueKind.Null)
|
|
{
|
|
_logger.Debug("stream property was null. Means streamer is not live");
|
|
return returnData;
|
|
}
|
|
|
|
returnData.IsLive = true;
|
|
returnData.Id = responseContent.GetProperty("data").GetProperty("user").GetProperty("stream").GetProperty("id")
|
|
.GetString();
|
|
returnData.ViewerCount = responseContent.GetProperty("data").GetProperty("user").GetProperty("stream").GetProperty("viewersCount")
|
|
.GetInt32();
|
|
return returnData;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_liveStatusCheckTaskCts.Cancel();
|
|
_liveStatusCheckTask?.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
|
|
public class TwitchGraphQlModel
|
|
{
|
|
/// <summary>
|
|
/// Whether the streamer is live
|
|
/// </summary>
|
|
public required bool IsLive { get; set; }
|
|
/// <summary>
|
|
/// Viewer count, null if not live
|
|
/// </summary>
|
|
public int? ViewerCount { get; set; }
|
|
/// <summary>
|
|
/// Stream ID returned by the GraphQL endpoint. null if none
|
|
/// </summary>
|
|
public string? Id { get; set; }
|
|
} |