using System.Net.Http; using System.Text.Json; using ytLive.Models; namespace ytLive.Services; /// /// Polls YouTube Live Chat API for messages. /// YouTube doesn't have WebSocket for chat — polling is the only option. /// public class YouTubeChatService : IDisposable { private readonly YouTubeAuthService _auth; private readonly HttpClient _http = new(); private Timer? _pollTimer; private string? _nextPageToken; private string? _liveChatId; private bool _isRunning; public event Action? MessageReceived; private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true }; public YouTubeChatService(YouTubeAuthService auth) { _auth = auth; } public void Start(string liveChatId, int pollIntervalMs = 2000) { _liveChatId = liveChatId; _isRunning = true; _nextPageToken = null; _pollTimer = new(async _ => await Poll(), null, 0, pollIntervalMs); } public void Stop() { _isRunning = false; _pollTimer?.Dispose(); _pollTimer = null; } private async Task Poll() { if (!_isRunning || _liveChatId == null || _auth.CurrentChannel == null) return; try { if (_auth.CurrentChannel.TokenExpiry <= DateTime.UtcNow.AddMinutes(5)) await _auth.RefreshToken(); var url = $"https://www.googleapis.com/youtube/v3/liveChat/messages?liveChatId={_liveChatId}&part=snippet,authorDetails&maxResults=50"; if (_nextPageToken != null) url += $"&pageToken={_nextPageToken}"; _http.DefaultRequestHeaders.Authorization = new("Bearer", _auth.CurrentChannel.AccessToken); var response = await _http.GetAsync(url); if (!response.IsSuccessStatusCode) return; var json = await response.Content.ReadAsStringAsync(); var data = JsonSerializer.Deserialize(json); if (data.TryGetProperty("nextPageToken", out var token)) _nextPageToken = token.GetString(); if (!data.TryGetProperty("items", out var items)) return; foreach (var item in items.EnumerateArray()) { var snippet = item.GetProperty("snippet"); var author = item.GetProperty("authorDetails"); var message = new ChatMessage { Id = item.GetProperty("id").GetString()!, AuthorName = author.GetProperty("displayName").GetString()!, AuthorChannelId = author.GetProperty("channelId").GetString()!, AuthorImageUrl = author.GetProperty("profileImageUrl").GetString()!, Message = snippet.GetProperty("textMessageDetails").GetProperty("messageText").GetString()!, Timestamp = DateTime.Parse(snippet.GetProperty("publishedAt").GetString()!), IsMember = author.GetProperty("isChatSponsor").GetBoolean() }; MessageReceived?.Invoke(message); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Chat poll error: {ex.Message}"); } } public void Dispose() { Stop(); _http.Dispose(); } }