using System.Net; using DotBased.Logging; using DotBased.Monads; using Manager.Data.Entities.LibraryContext; using Manager.YouTube; using Manager.YouTube.Models.Innertube; namespace Manager.App.Services.System; public class ClientService(IServiceScopeFactory scopeFactory, ILogger logger) : ExtendedBackgroundService(nameof(ClientService), "Managing YouTube clients", logger, TimeSpan.FromMinutes(10)) { private readonly List _loadedClients = []; private CancellationToken _cancellationToken; private ILibraryService? _libraryService; protected override Task InitializeAsync(CancellationToken stoppingToken) { _cancellationToken = stoppingToken; stoppingToken.Register(CancellationRequested); using var scope = scopeFactory.CreateScope(); _libraryService = scope.ServiceProvider.GetRequiredService(); LogEvent("Initializing service..."); return Task.CompletedTask; } protected override Task ExecuteServiceAsync(CancellationToken stoppingToken) { return Task.CompletedTask; } private void CancellationRequested() { // Clear up } public async Task SaveClientAsync(YouTubeClient client, Channel? channelInfo = null, CancellationToken cancellationToken = default) { if (_libraryService == null) { return ResultError.Fail("Library service is not initialized!."); } if (string.IsNullOrWhiteSpace(client.Id)) { LogEvent("Failed to store client no ID!", LogSeverity.Warning); return ResultError.Fail("Client does not have an ID, cannot save to library database!"); } if (channelInfo != null) { var imagesResult = await _libraryService.FetchChannelImagesAsync(channelInfo); if (!imagesResult.IsSuccess) { logger.LogWarning("Failed to fetch channel images!"); } } var channelResult = await _libraryService.GetChannelByIdAsync(client.Id, cancellationToken); ChannelEntity? channelEntity; try { if (channelResult.IsSuccess) { channelEntity = channelResult.Value; UpdateChannelEntity(client, channelEntity, channelInfo); } else { channelEntity = CreateNewChannelFromClient(client, channelInfo); } } catch (Exception e) { LogEvent("Failed to save client: " + e.Message, LogSeverity.Warning); return ResultError.Error(e); } var saveResult = await _libraryService.SaveChannelAsync(channelEntity, cancellationToken); return saveResult; } private void UpdateChannelEntity(YouTubeClient client, ChannelEntity entity, Channel? channelInfo) { if (channelInfo != null) { entity.Name = channelInfo.ChannelName; entity.Handle = channelInfo.Handle; entity.Description = channelInfo.Description; } var clientAcc = entity.ClientAccount; if (clientAcc != null) { clientAcc.UserAgent = clientAcc.UserAgent; var currentCookies = client.CookieContainer.GetAllCookies(); foreach (var cookieEntity in clientAcc.HttpCookies.ToList()) { var cookie = currentCookies[cookieEntity.Name]; if (cookie == null) { clientAcc.HttpCookies.Remove(cookieEntity); continue; } if (!cookie.Domain.Equals(cookieEntity.Domain, StringComparison.InvariantCultureIgnoreCase)) { continue; } cookieEntity.Value = cookie.Value; cookieEntity.Path = cookie.Path; cookieEntity.Secure = cookie.Secure; cookieEntity.HttpOnly = cookie.HttpOnly; cookieEntity.ExpiresUtc = cookie.Expires == DateTime.MinValue ? null : cookie.Expires; } } } private ChannelEntity CreateNewChannelFromClient(YouTubeClient client, Channel? channelInfo) { if (channelInfo == null) { throw new ArgumentNullException(nameof(channelInfo), "Channel information is required to store new client/account."); } var cookies = new List(); foreach (var cookieObj in client.CookieContainer.GetAllCookies()) { if (cookieObj is not Cookie cookie) { continue; } var cookieEntity = new HttpCookieEntity { ClientId = client.Id, Name = cookie.Name, Value = cookie.Value, Domain = cookie.Domain, Path = cookie.Path, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly, ExpiresUtc = cookie.Expires == DateTime.MinValue ? null : cookie.Expires }; cookies.Add(cookieEntity); } var clientAcc = new ClientAccountEntity { Id = client.Id, UserAgent = client.UserAgent, HttpCookies = cookies }; var channel = new ChannelEntity { Id = channelInfo.Id, Name = channelInfo.ChannelName, Handle = channelInfo.Handle, Description = channelInfo.Description, ClientAccount = clientAcc }; return channel; } }