namespace OF_DL.Services; 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); if (userInfo is not null && !endpoint.EndsWith("/me")) await dbService.UpdateUserInfoAsync(userInfo); return userInfo; } public async Task GetDetailedUserInfoAsync(string endpoint) { Log.Debug($"Calling GetDetailedUserInfo: {endpoint}"); if (!HasSignedRequestAuth()) return null; try { UserEntities.UserInfo userInfo = new(); Dictionary getParams = new() { { "limit", Constants.ApiPageSize.ToString() }, { "order", "publish_date_asc" } }; HttpClient client = new(); HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint); using HttpResponseMessage response = await client.SendAsync(request); if (!response.IsSuccessStatusCode) return userInfo; response.EnsureSuccessStatusCode(); string body = await response.Content.ReadAsStringAsync(); UserDtos.UserDto? userDto = JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); userInfo = FromDto(userDto); return userInfo; } catch (Exception ex) { ExceptionLoggerHelper.LogException(ex); } return null; } public async Task> GetUsersWithProgressAsync(string typeDisplay, string endpoint, string? typeParam, bool offsetByCount) { Dictionary usersOfType = await _eventHandler.WithStatusAsync( statusMessage: $"Getting {typeDisplay} Users", work: FetchAsync ); return usersOfType; async Task> FetchAsync(IStatusReporter statusReporter) { Dictionary users = []; int limit = 50; int offset = 0; bool includeRestricted = true; Dictionary getParams = new() { ["format"] = "infinite", ["limit"] = limit.ToString(), ["offset"] = offset.ToString() }; if (!string.IsNullOrWhiteSpace(typeParam)) getParams["type"] = typeParam; try { Log.Debug("Calling GetUsersWithProgress"); HttpClient client = GetHttpClient(); bool isLastLoop = false; while (true) { string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, client); if (string.IsNullOrWhiteSpace(body)) break; SubscriptionDtos.SubscriptionsDto? subscriptions = DeserializeJson(body, s_mJsonSerializerSettings); if (subscriptions?.List is null) break; foreach (SubscriptionDtos.ListItemDto item in subscriptions.List) { if (string.IsNullOrWhiteSpace(item?.Username)) continue; if (users.ContainsKey(item.Username)) continue; bool isRestricted = item.IsRestricted ?? false; bool isRestrictedButAllowed = isRestricted && includeRestricted; if (!isRestricted || isRestrictedButAllowed) users.Add(item.Username, item.Id); } statusReporter.ReportStatus($"[blue]Getting {typeDisplay} Users\n[/] [blue]Found {users.Count}[/]"); if (isLastLoop) break; if (!subscriptions.HasMore || subscriptions.List.Count == 0) isLastLoop = true; offset += offsetByCount ? subscriptions.List.Count : limit; getParams["offset"] = offset.ToString(); } } catch (Exception ex) { ExceptionLoggerHelper.LogException(ex); } return users; } } 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 { 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()); MessageDtos.ChatsDto? chats = DeserializeJson(body, s_mJsonSerializerSettings); if (chats is null) return allChats; if (chats.HasMore) { getParams["offset"] = $"{chats.NextOffset}"; while (true) { string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); MessageDtos.ChatsDto? newChats = DeserializeJson(loopbody, s_mJsonSerializerSettings); if (newChats is null) break; allChats.List.AddRange(newChats.List); if (!newChats.HasMore) break; getParams["offset"] = $"{newChats.NextOffset}"; } } } catch (Exception ex) { ExceptionLoggerHelper.LogException(ex); } return allChats; } private static UserEntities.UserInfo FromDto(UserDtos.UserDto? userDto) { if (userDto is null) return new(); return new() { Id = userDto.Id, Avatar = userDto.Avatar, Header = userDto.Header, Name = userDto.Name, Username = userDto.Username, SubscribePrice = userDto.SubscribePrice, CurrentSubscribePrice = userDto.CurrentSubscribePrice, IsPaywallRequired = userDto.IsPaywallRequired, IsRestricted = userDto.IsRestricted, SubscribedBy = userDto.SubscribedBy, SubscribedByExpire = userDto.SubscribedByExpire, SubscribedByExpireDate = userDto.SubscribedByExpireDate, SubscribedByAutoprolong = userDto.SubscribedByAutoprolong, SubscribedIsExpiredNow = userDto.SubscribedIsExpiredNow, SubscribedOn = userDto.SubscribedOn, SubscribedOnExpiredNow = userDto.SubscribedOnExpiredNow, SubscribedOnDuration = userDto.SubscribedOnDuration, About = userDto.About, PostsCount = userDto.PostsCount, ArchivedPostsCount = userDto.ArchivedPostsCount, PrivateArchivedPostsCount = userDto.PrivateArchivedPostsCount, PhotosCount = userDto.PhotosCount, VideosCount = userDto.VideosCount, AudiosCount = userDto.AudiosCount, MediasCount = userDto.MediasCount, }; } }