using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using StreamPlayer.Desktop.Models; namespace StreamPlayer.Desktop.Services; public sealed class EventService { private static readonly Uri EventsUri = new("https://streamtpmedia.com/eventos.json"); private static readonly TimeSpan CacheDuration = TimeSpan.FromHours(24); private static readonly string CachePath = Path.Combine(GetDataDirectory(), "events-cache.json"); private static readonly TimeZoneInfo EventZone = ResolveEventZone(); private static readonly HttpClient HttpClient = CreateHttpClient(); public async Task> GetEventsAsync(bool forceRefresh, CancellationToken cancellationToken) { if (!forceRefresh && TryLoadFromCache(out var cached)) { return cached; } try { string json = await DownloadJsonAsync(cancellationToken).ConfigureAwait(false); var events = ParseEvents(json); SaveCache(json); return events; } catch { if (TryLoadFromCache(out var cachedEvents)) { return cachedEvents; } throw; } } private static bool TryLoadFromCache(out IReadOnlyList events) { events = Array.Empty(); if (!File.Exists(CachePath)) { return false; } var age = DateTimeOffset.UtcNow - File.GetLastWriteTimeUtc(CachePath); if (age > CacheDuration) { return false; } try { string json = File.ReadAllText(CachePath, Encoding.UTF8); events = ParseEvents(json); return true; } catch { return false; } } private static void SaveCache(string json) { try { Directory.CreateDirectory(Path.GetDirectoryName(CachePath)!); File.WriteAllText(CachePath, json, Encoding.UTF8); } catch { // Not critical if the cache cannot be persisted. } } private static async Task DownloadJsonAsync(CancellationToken cancellationToken) { using var response = await HttpClient.GetAsync(EventsUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); } private static IReadOnlyList ParseEvents(string json) { using var document = JsonDocument.Parse(json); var results = new List(); foreach (var item in document.RootElement.EnumerateArray()) { string title = item.GetPropertyOrDefault("title"); string time = item.GetPropertyOrDefault("time"); string category = item.GetPropertyOrDefault("category"); string status = item.GetPropertyOrDefault("status"); string link = NormalizeLink(item.GetPropertyOrDefault("link")); string channelName = ExtractChannelName(link); long startMillis = ParseEventTime(time); results.Add(new LiveEvent(title, time, category, status, link, channelName, startMillis)); } return results; } private static string NormalizeLink(string link) { if (string.IsNullOrWhiteSpace(link)) { return string.Empty; } return link.Replace("global1.php", "global2.php", StringComparison.OrdinalIgnoreCase); } private static string ExtractChannelName(string link) { if (string.IsNullOrWhiteSpace(link)) { return string.Empty; } int index = link.IndexOf("stream=", StringComparison.OrdinalIgnoreCase); if (index < 0 || index + 7 >= link.Length) { return string.Empty; } return link[(index + 7)..].Replace("_", " ").ToUpperInvariant(); } private static long ParseEventTime(string time) { if (string.IsNullOrWhiteSpace(time)) { return -1; } try { var parsed = DateTime.ParseExact(time.Trim(), "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None); var today = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, EventZone).Date; var localCandidate = new DateTime(today.Year, today.Month, today.Day, parsed.Hour, parsed.Minute, 0, DateTimeKind.Unspecified); var utcDateTime = TimeZoneInfo.ConvertTimeToUtc(localCandidate, EventZone); var start = new DateTimeOffset(utcDateTime, TimeSpan.Zero); if (start < DateTimeOffset.UtcNow.AddHours(-12)) { start = start.AddDays(1); } return start.ToUnixTimeMilliseconds(); } catch { return -1; } } private static string GetDataDirectory() { var folder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "StreamPlayerDesktop"); Directory.CreateDirectory(folder); return folder; } private static HttpClient CreateHttpClient() { var handler = new HttpClientHandler { AutomaticDecompression = System.Net.DecompressionMethods.All }; var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(15) }; client.DefaultRequestHeaders.UserAgent.ParseAdd("StreamPlayerDesktop/1.0"); client.DefaultRequestHeaders.ConnectionClose = false; return client; } private static TimeZoneInfo ResolveEventZone() { string[] candidates = { "America/Argentina/Buenos_Aires", "Argentina Standard Time" }; foreach (var id in candidates) { try { return TimeZoneInfo.FindSystemTimeZoneById(id); } catch { // try next } } return TimeZoneInfo.Local; } }