diff --git a/OF DL/Entities/Chats/ChatCollection.cs b/OF DL/Entities/Chats/ChatCollection.cs new file mode 100644 index 0000000..86d5325 --- /dev/null +++ b/OF DL/Entities/Chats/ChatCollection.cs @@ -0,0 +1,7 @@ +namespace OF_DL.Entities.Chats +{ + public class ChatCollection + { + public Dictionary Chats { get; set; } = []; + } +} diff --git a/OF DL/Entities/Chats/Chats.cs b/OF DL/Entities/Chats/Chats.cs new file mode 100644 index 0000000..ba695f4 --- /dev/null +++ b/OF DL/Entities/Chats/Chats.cs @@ -0,0 +1,20 @@ +namespace OF_DL.Entities.Chats +{ + public class Chats + { + public List list { get; set; } + public bool hasMore { get; set; } + public int nextOffset { get; set; } + + public class Chat + { + public User withUser { get; set; } + public int unreadMessagesCount { get; set; } + } + + public class User + { + public int id { get; set; } + } + } +} diff --git a/OF DL/Helpers/APIHelper.cs b/OF DL/Helpers/APIHelper.cs index 3df39b2..1974376 100644 --- a/OF DL/Helpers/APIHelper.cs +++ b/OF DL/Helpers/APIHelper.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OF_DL.Entities; using OF_DL.Entities.Archived; +using OF_DL.Entities.Chats; using OF_DL.Entities.Highlights; using OF_DL.Entities.Lists; using OF_DL.Entities.Messages; @@ -118,11 +119,11 @@ public class APIHelper : IAPIHelper } - private async Task BuildHeaderAndExecuteRequests(Dictionary getParams, string endpoint, HttpClient client) + private async Task BuildHeaderAndExecuteRequests(Dictionary getParams, string endpoint, HttpClient client, HttpMethod? method = null) { Log.Debug("Calling BuildHeaderAndExecuteRequests"); - HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint); + HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint, method); using var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); string body = await response.Content.ReadAsStringAsync(); @@ -133,15 +134,20 @@ public class APIHelper : IAPIHelper } - private async Task BuildHttpRequestMessage(Dictionary getParams, string endpoint) + private async Task BuildHttpRequestMessage(Dictionary getParams, string endpoint, HttpMethod? method = null) { Log.Debug("Calling BuildHttpRequestMessage"); - string queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); + method ??= HttpMethod.Get; + + string queryParams = ""; + + if (getParams.Any()) + queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); Dictionary headers = GetDynamicHeaders($"/api2/v2{endpoint}", queryParams); - HttpRequestMessage request = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{queryParams}"); + HttpRequestMessage request = new(method, $"{Constants.API_URL}{endpoint}{queryParams}"); Log.Debug($"Full request URL: {Constants.API_URL}{endpoint}{queryParams}"); @@ -2588,6 +2594,94 @@ public class APIHelper : IAPIHelper return null; } + public async Task GetChats(string endpoint, IDownloadConfig config, bool onlyUnread) + { + Log.Debug($"Calling GetChats - {endpoint}"); + + try + { + Chats chats = new(); + ChatCollection collection = new(); + + int limit = 60; + Dictionary getParams = new() + { + { "limit", $"{limit}" }, + { "offset", "0" }, + { "skip_users", "all" }, + { "order", "recent" } + }; + + if (onlyUnread) + getParams["filter"] = "unread"; + + string body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + chats = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); + + if (chats.hasMore) + { + getParams["offset"] = $"{chats.nextOffset}"; + + while (true) + { + string loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + Chats newChats = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); + + chats.list.AddRange(newChats.list); + + if (!newChats.hasMore) + break; + + getParams["offset"] = $"{newChats.nextOffset}"; + } + } + + foreach (Chats.Chat chat in chats.list) + collection.Chats.Add(chat.withUser.id, chat); + + return collection; + } + catch (Exception ex) + { + Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); + Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); + if (ex.InnerException != null) + { + Console.WriteLine("\nInner Exception:"); + Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); + Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); + } + } + + return null; + } + + public async Task MarkAsUnread(string endpoint, IDownloadConfig config) + { + Log.Debug($"Calling MarkAsUnread - {endpoint}"); + + try + { + var result = new { success = false }; + + string body = await BuildHeaderAndExecuteRequests([], endpoint, GetHttpClient(config), HttpMethod.Delete); + result = JsonConvert.DeserializeAnonymousType(body, result); + + if (result?.success != true) + Console.WriteLine($"Failed to mark chat as unread! Endpoint: {endpoint}"); + } + catch (Exception ex) + { + Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); + Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); + if (ex.InnerException != null) + { + Console.WriteLine("\nInner Exception:"); + Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); + Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); + } + } + } public async Task GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp) { diff --git a/OF DL/Program.cs b/OF DL/Program.cs index e7e0a3a..8479ee7 100644 --- a/OF DL/Program.cs +++ b/OF DL/Program.cs @@ -22,6 +22,7 @@ using Akka.Configuration; using System.Text; using static Akka.Actor.ProviderSelection; using System.Diagnostics; +using OF_DL.Entities.Chats; namespace OF_DL; @@ -1506,6 +1507,9 @@ public class Program { Log.Debug($"Calling DownloadMessages - {user.Key}"); + AnsiConsole.Markup($"[grey]Getting Unread Chats\n[/]"); + HashSet unreadChats = await GetUsersWithUnreadChats(downloadContext.ApiHelper, downloadContext.DownloadConfig); + MessageCollection messages = new MessageCollection(); await AnsiConsole.Status() @@ -1513,7 +1517,14 @@ public class Program { messages = await downloadContext.ApiHelper.GetMessages($"/chats/{user.Value}/messages", path, downloadContext.DownloadConfig!, ctx); }); - int oldMessagesCount = 0; + + if (unreadChats.Contains(user.Value)) + { + AnsiConsole.Markup($"[grey]Restoring unread state\n[/]"); + await downloadContext.ApiHelper.MarkAsUnread($"/chats/{user.Value}/mark-as-read", downloadContext.DownloadConfig); + } + + int oldMessagesCount = 0; int newMessagesCount = 0; if (messages != null && messages.Messages.Count > 0) { @@ -3262,7 +3273,18 @@ public class Program } } - static bool ValidateFilePath(string path) + private static async Task> GetUsersWithUnreadChats(APIHelper apiHelper, IDownloadConfig currentConfig) + { + ChatCollection chats = await apiHelper.GetChats($"/chats", currentConfig, onlyUnread: true); + + var unreadChats = chats.Chats + .Where(c => c.Value.unreadMessagesCount > 0) + .ToList(); + + return [.. unreadChats.Select(c => c.Key)]; + } + + static bool ValidateFilePath(string path) { char[] invalidChars = System.IO.Path.GetInvalidPathChars(); char[] foundInvalidChars = path.Where(c => invalidChars.Contains(c)).ToArray();