From 0435940947ff3ab7d4f8830b36d3e857c46edc54 Mon Sep 17 00:00:00 2001 From: Casper Sparre Date: Thu, 19 Feb 2026 20:59:02 +0100 Subject: [PATCH] Implemented unread chats handling when fetching messages --- .../CLI/CajetanDownloadEventHandler.cs | 10 +- .../Models/Dtos/Messages/ChatsDto.cs | 19 +++ Cajetan.OF-DL/ProgramCajetan.cs | 29 ++-- Cajetan.OF-DL/Properties/GlobalUsings.cs | 1 - Cajetan.OF-DL/Services/CajetanApiService.cs | 133 +++++++++++++++++- .../CajetanDownloadOrchestrationService.cs | 6 + .../Services/CajetanDownloadService.cs | 6 + .../ICajetanDownloadOrchestrationService.cs | 6 + .../Services/ICajetanDownloadService.cs | 6 + 9 files changed, 199 insertions(+), 17 deletions(-) create mode 100644 Cajetan.OF-DL/Models/Dtos/Messages/ChatsDto.cs create mode 100644 Cajetan.OF-DL/Services/CajetanDownloadOrchestrationService.cs create mode 100644 Cajetan.OF-DL/Services/CajetanDownloadService.cs create mode 100644 Cajetan.OF-DL/Services/ICajetanDownloadOrchestrationService.cs create mode 100644 Cajetan.OF-DL/Services/ICajetanDownloadService.cs diff --git a/Cajetan.OF-DL/CLI/CajetanDownloadEventHandler.cs b/Cajetan.OF-DL/CLI/CajetanDownloadEventHandler.cs index a3947a3..90c24f3 100644 --- a/Cajetan.OF-DL/CLI/CajetanDownloadEventHandler.cs +++ b/Cajetan.OF-DL/CLI/CajetanDownloadEventHandler.cs @@ -2,7 +2,12 @@ using OF_DL.Models.Downloads; namespace OF_DL.CLI; -internal class CajetanDownloadEventHandler : IDownloadEventHandler +public interface ICajetanDownloadEventHandler : IDownloadEventHandler +{ + void OnMessage(string message, string color); +} + +public class CajetanDownloadEventHandler : ICajetanDownloadEventHandler { private readonly SpectreDownloadEventHandler _eventHandler = new(); @@ -15,6 +20,9 @@ internal class CajetanDownloadEventHandler : IDownloadEventHandler public void OnMessage(string message) => _eventHandler.OnMessage(message); + public void OnMessage(string message, string color) + => AnsiConsole.Markup($"[{color.ToLowerInvariant()}]{Markup.Escape(message)}\n[/]"); + public void OnNoContentFound(string contentType) => _eventHandler.OnNoContentFound(contentType); diff --git a/Cajetan.OF-DL/Models/Dtos/Messages/ChatsDto.cs b/Cajetan.OF-DL/Models/Dtos/Messages/ChatsDto.cs new file mode 100644 index 0000000..df90575 --- /dev/null +++ b/Cajetan.OF-DL/Models/Dtos/Messages/ChatsDto.cs @@ -0,0 +1,19 @@ +namespace OF_DL.Models.Dtos.Messages; + +public class ChatsDto +{ + [JsonProperty("list")] public List List { get; set; } = []; + [JsonProperty("hasMore")] public bool HasMore { get; set; } + [JsonProperty("nextOffset")] public int NextOffset { get; set; } +} + +public class ChatItemDto +{ + [JsonProperty("withUser")] public ChatUserDto WithUser { get; set; } = new(); + [JsonProperty("unreadMessagesCount")] public int UnreadMessagesCount { get; set; } +} + +public class ChatUserDto +{ + [JsonProperty("id")] public long Id { get; set; } +} diff --git a/Cajetan.OF-DL/ProgramCajetan.cs b/Cajetan.OF-DL/ProgramCajetan.cs index eb5296e..2bfa3b0 100644 --- a/Cajetan.OF-DL/ProgramCajetan.cs +++ b/Cajetan.OF-DL/ProgramCajetan.cs @@ -3,14 +3,18 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; AnsiConsole.Write(new FigletText("Welcome to OF-DL").Color(Color.Red)); +await RunAsync(args); -ServiceCollection services = await ConfigureServices(args); -ServiceProvider serviceProvider = services.BuildServiceProvider(); +static async Task RunAsync(string[] args) +{ + ServiceCollection services = await ConfigureServices(args); + ServiceProvider serviceProvider = services.BuildServiceProvider(); -ExitIfOtherProcess(serviceProvider); + ExitIfOtherProcess(serviceProvider); -Worker worker = serviceProvider.GetRequiredService(); -await worker.RunAsync(); + Worker worker = serviceProvider.GetRequiredService(); + await worker.RunAsync(); +} static async Task ConfigureServices(string[] args) { @@ -62,6 +66,11 @@ static async Task ConfigureServices(string[] args) services.AddSingleton(cajetanConfig); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); @@ -69,10 +78,12 @@ static async Task ConfigureServices(string[] args) services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(sp => sp.GetRequiredService()); + + services.AddSingleton(); + services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(); diff --git a/Cajetan.OF-DL/Properties/GlobalUsings.cs b/Cajetan.OF-DL/Properties/GlobalUsings.cs index 458741a..ef39d9b 100644 --- a/Cajetan.OF-DL/Properties/GlobalUsings.cs +++ b/Cajetan.OF-DL/Properties/GlobalUsings.cs @@ -1,7 +1,6 @@ global using Newtonsoft.Json; global using OF_DL; global using OF_DL.CLI; -global using OF_DL.Crypto; global using OF_DL.Enumerations; global using OF_DL.Exceptions; global using OF_DL.Helpers; diff --git a/Cajetan.OF-DL/Services/CajetanApiService.cs b/Cajetan.OF-DL/Services/CajetanApiService.cs index 44b8f57..db7e91f 100644 --- a/Cajetan.OF-DL/Services/CajetanApiService.cs +++ b/Cajetan.OF-DL/Services/CajetanApiService.cs @@ -1,13 +1,10 @@ namespace OF_DL.Services; -public interface ICajetanApiService : IApiService -{ - Task GetDetailedUserInfo(string endpoint); -} - -public class CajetanApiService(IAuthService authService, IConfigService configService, ICajetanDbService dbService) +public class CajetanApiService(IAuthService authService, IConfigService configService, ICajetanDbService dbService, ICajetanDownloadEventHandler eventHandler) : ApiService(authService, configService, dbService), ICajetanApiService { + private readonly ICajetanDownloadEventHandler _eventHandler = eventHandler; + public new async Task GetUserInfo(string endpoint) { UserEntities.UserInfo? userInfo = await GetDetailedUserInfoAsync(endpoint); @@ -55,6 +52,130 @@ public class CajetanApiService(IAuthService authService, IConfigService configSe return null; } + public new async Task GetMessages(string endpoint, string folder, IStatusReporter statusReporter) + { + (bool couldExtract, long userId) = ExtractUserId(endpoint); + + _eventHandler.OnMessage("Getting Unread Chats", "grey"); + HashSet usersWithUnread = couldExtract ? await GetUsersWithUnreadMessagesAsync() : []; + + MessageEntities.MessageCollection messages = await base.GetMessages(endpoint, folder, statusReporter); + + if (usersWithUnread.Contains(userId)) + { + _eventHandler.OnMessage("Restoring unread state", "grey"); + await MarkAsUnreadAsync($"/chats/{userId}/mark-as-read"); + } + + return messages; + + static (bool couldExtract, long userId) ExtractUserId(string endpoint) + { + string withoutChatsAndMessages = endpoint + .Replace("chats", "", StringComparison.OrdinalIgnoreCase) + .Replace("messages", "", StringComparison.OrdinalIgnoreCase); + string trimmed = withoutChatsAndMessages.Trim(' ', '/', '\\'); + + if (long.TryParse(trimmed, out long userId)) + return (true, userId); + + return (false, default); + } + } + + public async Task> GetUsersWithUnreadMessagesAsync() + { + MessageDtos.ChatsDto unreadChats = await GetChatsAsync("/chats", onlyUnread: true); + HashSet userWithUnread = []; + + foreach (MessageDtos.ChatItemDto chatItem in unreadChats.List) + { + if (chatItem?.WithUser?.Id is null) + continue; + + if (chatItem.UnreadMessagesCount <= 0) + continue; + + userWithUnread.Add(chatItem.WithUser.Id); + } + + return userWithUnread; + } + + public async Task MarkAsUnreadAsync(string endpoint) + { + Log.Debug($"Calling MarkAsUnread - {endpoint}"); + + try + { + var result = new { success = false }; + + string? body = await BuildHeaderAndExecuteRequests([], endpoint, GetHttpClient(), HttpMethod.Delete); + + if (!string.IsNullOrWhiteSpace(body)) + result = JsonConvert.DeserializeAnonymousType(body, result); + + if (result?.success != true) + _eventHandler.OnMessage($"Failed to mark chat as unread! Endpoint: {endpoint}", "yellow"); + } + catch (Exception ex) + { + ExceptionLoggerHelper.LogException(ex); + } + } + + private async Task GetChatsAsync(string endpoint, bool onlyUnread) + { + Log.Debug($"Calling GetChats - {endpoint}"); + + MessageDtos.ChatsDto allChats = new(); + + try + { + const int limit = 60; + int offset = 0; + + Dictionary getParams = new() + { + ["order"] = "recent", + ["skip_users"] = "all", + ["filter"] = "unread", + ["limit"] = $"{limit}", + }; + + if (onlyUnread is false) + getParams.Remove("filter"); + + while (true) + { + getParams["offset"] = $"{offset}"; + + string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); + + if (string.IsNullOrWhiteSpace(body)) + break; + + MessageDtos.ChatsDto? chats = DeserializeJson(body, s_mJsonSerializerSettings); + + if (chats?.List is null) + break; + + allChats.List.AddRange(chats.List); + + if (chats.HasMore is false) + break; + + offset = chats.NextOffset; + } + } + catch (Exception ex) + { + ExceptionLoggerHelper.LogException(ex); + } + + return allChats; + } + private static UserEntities.UserInfo FromDto(UserDtos.UserDto? userDto) { if (userDto is null) diff --git a/Cajetan.OF-DL/Services/CajetanDownloadOrchestrationService.cs b/Cajetan.OF-DL/Services/CajetanDownloadOrchestrationService.cs new file mode 100644 index 0000000..9393765 --- /dev/null +++ b/Cajetan.OF-DL/Services/CajetanDownloadOrchestrationService.cs @@ -0,0 +1,6 @@ +namespace OF_DL.Services; + +public class CajetanDownloadOrchestrationService(ICajetanApiService apiService, IConfigService configService, ICajetanDownloadService downloadService, ICajetanDbService dbService) + : DownloadOrchestrationService(apiService, configService, downloadService, dbService), ICajetanDownloadOrchestrationService +{ +} diff --git a/Cajetan.OF-DL/Services/CajetanDownloadService.cs b/Cajetan.OF-DL/Services/CajetanDownloadService.cs new file mode 100644 index 0000000..b0d9be0 --- /dev/null +++ b/Cajetan.OF-DL/Services/CajetanDownloadService.cs @@ -0,0 +1,6 @@ +namespace OF_DL.Services; + +public class CajetanDownloadService(IAuthService authService, IConfigService configService, ICajetanDbService dbService, IFileNameService fileNameService, ICajetanApiService apiService) + : DownloadService(authService, configService, dbService, fileNameService, apiService), ICajetanDownloadService +{ +} diff --git a/Cajetan.OF-DL/Services/ICajetanDownloadOrchestrationService.cs b/Cajetan.OF-DL/Services/ICajetanDownloadOrchestrationService.cs new file mode 100644 index 0000000..3c81b0e --- /dev/null +++ b/Cajetan.OF-DL/Services/ICajetanDownloadOrchestrationService.cs @@ -0,0 +1,6 @@ +namespace OF_DL.Services; + +public interface ICajetanDownloadOrchestrationService : IDownloadOrchestrationService +{ + +} diff --git a/Cajetan.OF-DL/Services/ICajetanDownloadService.cs b/Cajetan.OF-DL/Services/ICajetanDownloadService.cs new file mode 100644 index 0000000..08b7310 --- /dev/null +++ b/Cajetan.OF-DL/Services/ICajetanDownloadService.cs @@ -0,0 +1,6 @@ +namespace OF_DL.Services; + +public interface ICajetanDownloadService : IDownloadService +{ + +}