using System.Net.Http.Json; using System.Text.Json.Serialization; using Backend.Data; using Microsoft.EntityFrameworkCore; namespace Backend.Services; public sealed class TwitchTrackerViewerStatsProvider( IHttpClientFactory httpClientFactory, AwardsDbContext db, ILogger logger) : IViewerStatsProvider { public async Task GetChannelSummaryAsync(string twitchLogin, CancellationToken cancellationToken) { var login = twitchLogin.Trim().TrimStart('@').ToLowerInvariant(); if (string.IsNullOrWhiteSpace(login)) { return null; } try { using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeout.CancelAfter(TimeSpan.FromSeconds(3)); var settings = await db.SiteSettings .AsNoTracking() .FirstOrDefaultAsync(item => item.Id == 1, timeout.Token); var baseUrl = TrackingRulesSettings.NormalizeBaseUrl(settings?.ViewerStatsProviderBaseUrl); var client = httpClientFactory.CreateClient("TwitchTracker"); var response = await client.GetFromJsonAsync( $"{baseUrl}/channels/summary/{Uri.EscapeDataString(login)}", timeout.Token); if (response is null) { return null; } return new ViewerStatsSnapshot( response.AverageViewers, Math.Max(0, response.MinutesStreamed / 60), response.HoursWatched, response.PeakViewers, response.FollowersGained, TrackingRulesSettings.Window30d); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { logger.LogInformation("TwitchTracker API lookup timed out for {TwitchLogin}.", login); return null; } catch (Exception ex) { logger.LogInformation(ex, "TwitchTracker API lookup failed for {TwitchLogin}.", login); return null; } } private sealed class TwitchTrackerChannelSummaryResponse { [JsonPropertyName("rank")] public int Rank { get; init; } [JsonPropertyName("minutes_streamed")] public int MinutesStreamed { get; init; } [JsonPropertyName("avg_viewers")] public int AverageViewers { get; init; } [JsonPropertyName("max_viewers")] public int PeakViewers { get; init; } [JsonPropertyName("hours_watched")] public int HoursWatched { get; init; } [JsonPropertyName("followers")] public int FollowersGained { get; init; } [JsonPropertyName("followers_total")] public int FollowersTotal { get; init; } } }