diff --git a/OF DL.Core/Crypto/Padding.cs b/OF DL.Core/Crypto/Padding.cs index 5f4641b..2927482 100644 --- a/OF DL.Core/Crypto/Padding.cs +++ b/OF DL.Core/Crypto/Padding.cs @@ -37,7 +37,7 @@ public class Padding return result; } - public static byte[]? AddPSSPadding(byte[] hash) + public static byte[] AddPssPadding(byte[] hash) { int modBits = 2048; int hLen = 20; @@ -49,10 +49,11 @@ public class Padding lmask = (lmask >> 1) | 0x80; } - if (emLen < hLen + hLen + 2) - { - return null; - } + // Commented out since the condition will always be false while emLen = 256 and hLen = 20 + // if (emLen < hLen + hLen + 2) + // { + // return null; + // } byte[] salt = new byte[hLen]; new Random().NextBytes(salt); @@ -102,7 +103,7 @@ public class Padding db[i] = (byte)(maskedDB[i] ^ dbMask[i]); } - int onePos = BitConverter.ToString(db[hLen..]).Replace("-", "").IndexOf("01") / 2; + int onePos = BitConverter.ToString(db[hLen..]).Replace("-", "").IndexOf("01", StringComparison.Ordinal) / 2; byte[] unpadded = db[(hLen + onePos + 1)..]; return unpadded; diff --git a/OF DL.Core/Models/Config/Config.cs b/OF DL.Core/Models/Config/Config.cs index a655711..50323d8 100644 --- a/OF DL.Core/Models/Config/Config.cs +++ b/OF DL.Core/Models/Config/Config.cs @@ -115,22 +115,22 @@ public class Config : IFileNameFormatConfig if (CreatorConfigs.TryGetValue(username, out CreatorConfig? creatorConfig)) { - if (creatorConfig?.PaidPostFileNameFormat != null) + if (creatorConfig.PaidPostFileNameFormat != null) { combinedFilenameFormatConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat; } - if (creatorConfig?.PostFileNameFormat != null) + if (creatorConfig.PostFileNameFormat != null) { combinedFilenameFormatConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat; } - if (creatorConfig?.PaidMessageFileNameFormat != null) + if (creatorConfig.PaidMessageFileNameFormat != null) { combinedFilenameFormatConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat; } - if (creatorConfig?.MessageFileNameFormat != null) + if (creatorConfig.MessageFileNameFormat != null) { combinedFilenameFormatConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat; } diff --git a/OF DL.Core/Models/Dtos/Common/DrmDto.cs b/OF DL.Core/Models/Dtos/Common/DrmDto.cs index 25271be..1290908 100644 --- a/OF DL.Core/Models/Dtos/Common/DrmDto.cs +++ b/OF DL.Core/Models/Dtos/Common/DrmDto.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json; -using OF_DL.Models.Dtos.Archived; namespace OF_DL.Models.Dtos.Common; diff --git a/OF DL.Core/Services/ApiService.cs b/OF DL.Core/Services/ApiService.cs index 89b6eab..eb57808 100644 --- a/OF DL.Core/Services/ApiService.cs +++ b/OF DL.Core/Services/ApiService.cs @@ -1,11 +1,11 @@ using System.Globalization; using System.Security.Cryptography; using System.Text; -using System.Text.Json; using System.Xml.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OF_DL.Models; +using OF_DL.Models.Entities.Common; using OF_DL.Enumerations; using ArchivedDtos = OF_DL.Models.Dtos.Archived; using HighlightDtos = OF_DL.Models.Dtos.Highlights; @@ -416,26 +416,30 @@ public class ApiService(IAuthService authService, IConfigService configService, string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); + if (string.IsNullOrWhiteSpace(body)) + { + Log.Warning("GetMedia returned empty response for {Endpoint}", endpoint); + return return_urls; + } if (mediatype == MediaType.Stories) { Log.Debug("Media Stories - " + endpoint); List? storiesDto = - JsonConvert.DeserializeObject>(body, s_mJsonSerializerSettings); + DeserializeJson>(body, s_mJsonSerializerSettings); List stories = StoriesMapper.FromDto(storiesDto); foreach (StoryEntities.Stories story in stories) { - if (story.Media[0].CreatedAt.HasValue) + DateTime? storyCreatedAt = story.Media.Count > 0 ? story.Media[0].CreatedAt : null; + if (storyCreatedAt.HasValue) { - await dbService.AddStory(folder, story.Id, "", "0", false, false, - story.Media[0].CreatedAt.Value); + await dbService.AddStory(folder, story.Id, "", "0", false, false, storyCreatedAt.Value); } else if (story.CreatedAt.HasValue) { - await dbService.AddStory(folder, story.Id, "", "0", false, false, - story.CreatedAt.Value); + await dbService.AddStory(folder, story.Id, "", "0", false, false, story.CreatedAt.Value); } else { @@ -446,22 +450,20 @@ public class ApiService(IAuthService authService, IConfigService configService, { foreach (StoryEntities.Medium medium in story.Media) { - if (medium.Files.Full == null || medium.Files.Full.Url == null) + string? mediaUrl = medium.Files.Full?.Url; + if (string.IsNullOrEmpty(mediaUrl)) { continue; } - string? mediaType = medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null; + string? mediaType = ResolveMediaType(medium.Type); if (mediaType == null) { continue; } - await dbService.AddMedia(folder, medium.Id, story.Id, medium.Files.Full.Url, null, null, - null, "Stories", - mediaType, false, false, null); + await dbService.AddMedia(folder, medium.Id, story.Id, mediaUrl, null, null, null, + "Stories", mediaType, false, false, null); if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -483,12 +485,9 @@ public class ApiService(IAuthService authService, IConfigService configService, continue; } - if (medium.CanView) + if (medium.CanView && !return_urls.ContainsKey(medium.Id)) { - if (!return_urls.ContainsKey(medium.Id)) - { - return_urls.Add(medium.Id, medium.Files.Full.Url); - } + return_urls.Add(medium.Id, mediaUrl); } } } @@ -498,7 +497,7 @@ public class ApiService(IAuthService authService, IConfigService configService, { List highlightIds = []; HighlightDtos.HighlightsDto? highlightsDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); HighlightEntities.Highlights highlights = HighlightsMapper.FromDto(highlightsDto); if (highlights.HasMore) @@ -510,9 +509,14 @@ public class ApiService(IAuthService authService, IConfigService configService, Log.Debug("Media Highlights - " + endpoint); string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); + if (string.IsNullOrWhiteSpace(loopbody)) + { + Log.Warning("Received empty body from API"); + break; + } + HighlightDtos.HighlightsDto? newHighlightsDto = - JsonConvert.DeserializeObject(loopbody, - s_mJsonSerializerSettings); + DeserializeJson(loopbody, s_mJsonSerializerSettings); HighlightEntities.Highlights newHighlights = HighlightsMapper.FromDto(newHighlightsDto); highlights.List.AddRange(newHighlights.List); @@ -553,43 +557,37 @@ public class ApiService(IAuthService authService, IConfigService configService, highlightResponse.EnsureSuccessStatusCode(); string highlightBody = await highlightResponse.Content.ReadAsStringAsync(); HighlightDtos.HighlightMediaDto? highlightMediaDto = - JsonConvert.DeserializeObject(highlightBody, - s_mJsonSerializerSettings); + DeserializeJson(highlightBody, s_mJsonSerializerSettings); HighlightEntities.HighlightMedia highlightMedia = HighlightsMapper.FromDto(highlightMediaDto); foreach (HighlightEntities.Story item in highlightMedia.Stories) { - if (item.Media != null && item.Media.Count > 0 && item.Media[0].CreatedAt.HasValue && - item.Media[0].CreatedAt != null) + DateTime? createdAt = item.Media != null && item.Media.Count > 0 + ? item.Media[0].CreatedAt + : null; + + if (createdAt.HasValue) { - DateTime? createdAt = item.Media[0].CreatedAt; - if (createdAt != null) - { - await dbService.AddStory(folder, item.Id, "", "0", false, false, - createdAt.Value); - } + await dbService.AddStory(folder, item.Id, "", "0", false, false, createdAt.Value); } else if (item.CreatedAt.HasValue) { - await dbService.AddStory(folder, item.Id, "", "0", false, false, - item.CreatedAt.Value); + await dbService.AddStory(folder, item.Id, "", "0", false, false, item.CreatedAt.Value); } else { - await dbService.AddStory(folder, item.Id, "", "0", false, false, - DateTime.Now); + await dbService.AddStory(folder, item.Id, "", "0", false, false, DateTime.Now); } if (item.Media != null && item.Media.Count > 0 && item.Media[0].CanView) { + string? storyUrl = item.Media[0].Files?.Full?.Url; + string storyUrlValue = storyUrl ?? string.Empty; foreach (HighlightEntities.Medium medium in item.Media) { - string storyUrl = item.Media[0].Files?.Full?.Url ?? ""; - await dbService.AddMedia(folder, medium.Id, item.Id, storyUrl, null, null, null, - "Stories", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, false, false, null); + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + await dbService.AddMedia(folder, medium.Id, item.Id, storyUrlValue, null, null, null, + "Stories", mediaType, false, false, null); if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; @@ -661,20 +659,18 @@ public class ApiService(IAuthService authService, IConfigService configService, string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); PurchasedDtos.PurchasedDto? paidPostsDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); PurchasedEntities.Purchased paidPosts = PurchasedMapper.FromDto(paidPostsDto); statusReporter.ReportStatus($"Getting Paid Posts - Found {paidPosts.List.Count}"); - if (paidPosts != null && paidPosts.HasMore) + if (paidPosts.HasMore) { getParams["offset"] = paidPosts.List.Count.ToString(); while (true) { - PurchasedEntities.Purchased newPaidPosts = new(); - string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); PurchasedDtos.PurchasedDto? newPaidPostsDto = - JsonConvert.DeserializeObject(loopbody, s_mJsonSerializerSettings); - newPaidPosts = PurchasedMapper.FromDto(newPaidPostsDto); + DeserializeJson(loopbody, s_mJsonSerializerSettings); + PurchasedEntities.Purchased newPaidPosts = PurchasedMapper.FromDto(newPaidPostsDto); paidPosts.List.AddRange(newPaidPosts.List); statusReporter.ReportStatus($"Getting Paid Posts - Found {paidPosts.List.Count}"); @@ -687,7 +683,8 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - foreach (PurchasedEntities.ListItem purchase in paidPosts.List) + List paidPostList = paidPosts.List; + foreach (PurchasedEntities.ListItem purchase in paidPostList) { if (purchase.ResponseType == "post" && purchase.Media != null && purchase.Media.Count > 0) { @@ -719,11 +716,11 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - await dbService.AddPost(folder, purchase.Id, purchase.Text != null ? purchase.Text : "", - purchase.Price != null ? purchase.Price : "0", - purchase.Price != null && purchase.IsOpened ? true : false, - purchase.IsArchived.HasValue ? purchase.IsArchived.Value : false, - purchase.CreatedAt != null ? purchase.CreatedAt.Value : purchase.PostedAt.Value); + DateTime createdAt = purchase.CreatedAt ?? purchase.PostedAt ?? DateTime.Now; + bool isArchived = purchase.IsArchived ?? false; + await dbService.AddPost(folder, purchase.Id, purchase.Text ?? "", + purchase.Price ?? "0", + purchase.Price != null && purchase.IsOpened, isArchived, createdAt); paidPostCollection.PaidPostObjects.Add(purchase); foreach (MessageEntities.Medium medium in purchase.Media) { @@ -752,69 +749,63 @@ public class ApiService(IAuthService authService, IConfigService configService, continue; } + string mediaType = ResolveMediaType(medium.Type) ?? ""; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = previewids.Contains(medium.Id); + if (previewids.Count > 0) { bool has = previewids.Any(cus => cus.Equals(medium.Id)); - if (!has && medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + if (!has && medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (!paidPostCollection.PaidPosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, purchase.Id, medium.Files.Full.Url, + await dbService.AddMedia(folder, medium.Id, purchase.Id, fullUrl, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); - paidPostCollection.PaidPosts.Add(medium.Id, medium.Files.Full.Url); + mediaType, isPreview, false, null); + paidPostCollection.PaidPosts.Add(medium.Id, fullUrl); paidPostCollection.PaidPostMedia.Add(medium); } } - else if (!has && medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (!has && medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (!paidPostCollection.PaidPosts.ContainsKey(medium.Id)) { await dbService.AddMedia(folder, medium.Id, purchase.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); + manifestDash, null, null, null, "Posts", mediaType, isPreview, false, null); paidPostCollection.PaidPosts.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{purchase.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{purchase.Id}"); paidPostCollection.PaidPostMedia.Add(medium); } } } else { - if (medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + if (medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (!paidPostCollection.PaidPosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, purchase.Id, medium.Files.Full.Url, + await dbService.AddMedia(folder, medium.Id, purchase.Id, fullUrl, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); - paidPostCollection.PaidPosts.Add(medium.Id, medium.Files.Full.Url); + mediaType, isPreview, false, null); + paidPostCollection.PaidPosts.Add(medium.Id, fullUrl); paidPostCollection.PaidPostMedia.Add(medium); } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (!paidPostCollection.PaidPosts.ContainsKey(medium.Id)) { await dbService.AddMedia(folder, medium.Id, purchase.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); + manifestDash, null, null, null, "Posts", mediaType, isPreview, false, null); paidPostCollection.PaidPosts.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{purchase.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{purchase.Id}"); paidPostCollection.PaidPostMedia.Add(medium); } } @@ -839,7 +830,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new PurchasedEntities.PaidPostCollection(); } @@ -886,10 +877,10 @@ public class ApiService(IAuthService authService, IConfigService configService, string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); PostDtos.PostDto? postsDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); PostEntities.Post posts = PostMapper.FromDto(postsDto); statusReporter.ReportStatus($"Getting Posts - Found {posts.List.Count}"); - if (posts != null && posts.HasMore) + if (posts.HasMore) { UpdateGetParamsForDateSelection( downloadDateSelection, @@ -898,12 +889,10 @@ public class ApiService(IAuthService authService, IConfigService configService, while (true) { - PostEntities.Post newposts = new(); - string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); PostDtos.PostDto? newPostsDto = - JsonConvert.DeserializeObject(loopbody, s_mJsonSerializerSettings); - newposts = PostMapper.FromDto(newPostsDto); + DeserializeJson(loopbody, s_mJsonSerializerSettings); + PostEntities.Post newposts = PostMapper.FromDto(newPostsDto); posts.List.AddRange(newposts.List); statusReporter.ReportStatus($"Getting Posts - Found {posts.List.Count}"); @@ -919,18 +908,21 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - foreach (PostEntities.ListItem post in posts.List) + List postList = posts.List; + foreach (PostEntities.ListItem post in postList) { if (configService.CurrentConfig.SkipAds) { - if (post.RawText != null && (post.RawText.Contains("#ad") || post.RawText.Contains("/trial/") || - post.RawText.Contains("#announcement"))) + if (!string.IsNullOrEmpty(post.RawText) && + (post.RawText.Contains("#ad") || post.RawText.Contains("/trial/") || + post.RawText.Contains("#announcement"))) { continue; } - if (post.Text != null && (post.Text.Contains("#ad") || post.Text.Contains("/trial/") || - post.Text.Contains("#announcement"))) + if (!string.IsNullOrEmpty(post.Text) && + (post.Text.Contains("#ad") || post.Text.Contains("/trial/") || + post.Text.Contains("#announcement"))) { continue; } @@ -951,8 +943,8 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - await dbService.AddPost(folder, post.Id, post.RawText != null ? post.RawText : "", - post.Price != null ? post.Price : "0", post.Price != null && post.IsOpened ? true : false, + await dbService.AddPost(folder, post.Id, !string.IsNullOrEmpty(post.RawText) ? post.RawText : "", + post.Price ?? "0", post.Price != null && post.IsOpened, post.IsArchived, post.PostedAt); postCollection.PostObjects.Add(post); if (post.Media != null && post.Media.Count > 0) @@ -979,62 +971,48 @@ public class ApiService(IAuthService authService, IConfigService configService, continue; } + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + string? previewUrl = medium.Files?.Preview?.Url; + bool isPreview = postPreviewIds.Contains(medium.Id); + if (medium.CanView && medium.Files?.Drm == null) { bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id)); - if (medium.Files!.Full != null && !string.IsNullOrEmpty(medium.Files!.Full.Url)) - { - if (!has) - { - if (!postCollection.Posts.ContainsKey(medium.Id)) - { - await dbService.AddMedia(folder, medium.Id, post.Id, medium.Files!.Full.Url, - null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - postPreviewIds.Contains(medium.Id) ? true : false, false, null); - postCollection.Posts.Add(medium.Id, medium.Files!.Full.Url); - postCollection.PostMedia.Add(medium); - } - } - } - else if (medium.Files.Preview != null && medium.Files!.Full == null) - { - if (!has) - { - if (!postCollection.Posts.ContainsKey(medium.Id)) - { - await dbService.AddMedia(folder, medium.Id, post.Id, medium.Files.Preview.Url, - null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - postPreviewIds.Contains(medium.Id) ? true : false, false, null); - postCollection.Posts.Add(medium.Id, medium.Files.Preview.Url); - postCollection.PostMedia.Add(medium); - } - } - } - } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) - { - bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id)); - if (!has && medium.Files != null && medium.Files.Drm != null) + if (!has && !string.IsNullOrEmpty(fullUrl)) { if (!postCollection.Posts.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, post.Id, medium.Files.Drm.Manifest.Dash, - null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - postPreviewIds.Contains(medium.Id) ? true : false, false, null); - postCollection.Posts.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{post.Id}"); + await dbService.AddMedia(folder, medium.Id, post.Id, fullUrl, null, null, null, + "Posts", mediaType, isPreview, false, null); + postCollection.Posts.Add(medium.Id, fullUrl); postCollection.PostMedia.Add(medium); } } + else if (!has && string.IsNullOrEmpty(fullUrl) && !string.IsNullOrEmpty(previewUrl)) + { + if (!postCollection.Posts.ContainsKey(medium.Id)) + { + await dbService.AddMedia(folder, medium.Id, post.Id, previewUrl, null, null, null, + "Posts", mediaType, isPreview, false, null); + postCollection.Posts.Add(medium.Id, previewUrl); + postCollection.PostMedia.Add(medium); + } + } + } + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, out string cloudFrontPolicy, + out string cloudFrontSignature, out string cloudFrontKeyPairId)) + { + bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id)); + if (!has && !postCollection.Posts.ContainsKey(medium.Id)) + { + await dbService.AddMedia(folder, medium.Id, post.Id, manifestDash, null, null, null, + "Posts", mediaType, isPreview, false, null); + postCollection.Posts.Add(medium.Id, + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{post.Id}"); + postCollection.PostMedia.Add(medium); + } } } } @@ -1056,7 +1034,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new PostEntities.PostCollection(); } public async Task GetPost(string endpoint, string folder) @@ -1065,14 +1043,13 @@ public class ApiService(IAuthService authService, IConfigService configService, try { - PostEntities.SinglePost singlePost = new(); SinglePostCollection singlePostCollection = new(); Dictionary getParams = new() { { "skip_users", "all" } }; string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); PostDtos.SinglePostDto? singlePostDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); - singlePost = PostMapper.FromDto(singlePostDto); + DeserializeJson(body, s_mJsonSerializerSettings); + PostEntities.SinglePost singlePost = PostMapper.FromDto(singlePostDto); if (singlePostDto != null) { @@ -1091,9 +1068,10 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - await dbService.AddPost(folder, singlePost.Id, singlePost.Text != null ? singlePost.Text : "", - singlePost.Price != null ? singlePost.Price : "0", - singlePost.Price != null && singlePost.IsOpened ? true : false, singlePost.IsArchived, + await dbService.AddPost(folder, singlePost.Id, + !string.IsNullOrEmpty(singlePost.Text) ? singlePost.Text : "", + singlePost.Price ?? "0", + singlePost.Price != null && singlePost.IsOpened, singlePost.IsArchived, singlePost.PostedAt); singlePostCollection.SinglePostObjects.Add(singlePost); if (singlePost.Media != null && singlePost.Media.Count > 0) @@ -1120,100 +1098,78 @@ public class ApiService(IAuthService authService, IConfigService configService, continue; } + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + bool isPreview = postPreviewIds.Contains(medium.Id); + string? fullUrl = medium.Files?.Full?.Url; + string? previewUrl = medium.Files?.Preview?.Url; + if (medium.CanView && medium.Files?.Drm == null) { switch (configService.CurrentConfig.DownloadVideoResolution) { case VideoResolution.source: - if (medium.Files!.Full != null && !string.IsNullOrEmpty(medium.Files!.Full.Url)) + if (!string.IsNullOrEmpty(fullUrl)) { if (!singlePostCollection.SinglePosts.ContainsKey(medium.Id)) { await dbService.AddMedia(folder, medium.Id, singlePost.Id, - medium.Files!.Full.Url, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - postPreviewIds.Contains(medium.Id) ? true : false, false, null); - singlePostCollection.SinglePosts.Add(medium.Id, medium.Files!.Full.Url); + fullUrl, null, null, null, "Posts", mediaType, isPreview, false, null); + singlePostCollection.SinglePosts.Add(medium.Id, fullUrl); singlePostCollection.SinglePostMedia.Add(medium); } } break; case VideoResolution._240: - if (medium.VideoSources != null) + string? video240 = medium.VideoSources?._240; + if (!string.IsNullOrEmpty(video240)) { - if (!string.IsNullOrEmpty(medium.VideoSources._240)) + if (!singlePostCollection.SinglePosts.ContainsKey(medium.Id)) { - if (!singlePostCollection.SinglePosts.ContainsKey(medium.Id)) - { - await dbService.AddMedia(folder, medium.Id, singlePost.Id, - medium.VideoSources._240, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - postPreviewIds.Contains(medium.Id) ? true : false, false, null); - singlePostCollection.SinglePosts.Add(medium.Id, - medium.VideoSources._240); - singlePostCollection.SinglePostMedia.Add(medium); - } + await dbService.AddMedia(folder, medium.Id, singlePost.Id, video240, null, + null, null, "Posts", mediaType, isPreview, false, null); + singlePostCollection.SinglePosts.Add(medium.Id, video240); + singlePostCollection.SinglePostMedia.Add(medium); } } break; case VideoResolution._720: - if (medium.VideoSources != null) + string? video720 = medium.VideoSources?._720; + if (!string.IsNullOrEmpty(video720)) { - if (!string.IsNullOrEmpty(medium.VideoSources._720)) + if (!singlePostCollection.SinglePosts.ContainsKey(medium.Id)) { - if (!singlePostCollection.SinglePosts.ContainsKey(medium.Id)) - { - await dbService.AddMedia(folder, medium.Id, singlePost.Id, - medium.VideoSources._720, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - postPreviewIds.Contains(medium.Id) ? true : false, false, null); - singlePostCollection.SinglePosts.Add(medium.Id, - medium.VideoSources._720); - singlePostCollection.SinglePostMedia.Add(medium); - } + await dbService.AddMedia(folder, medium.Id, singlePost.Id, video720, null, + null, null, "Posts", mediaType, isPreview, false, null); + singlePostCollection.SinglePosts.Add(medium.Id, video720); + singlePostCollection.SinglePostMedia.Add(medium); } } break; } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) - { - if (medium.Files != null && medium.Files.Drm != null) - { - if (!singlePostCollection.SinglePosts.ContainsKey(medium.Id)) - { - await dbService.AddMedia(folder, medium.Id, singlePost.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - postPreviewIds.Contains(medium.Id) ? true : false, false, null); - singlePostCollection.SinglePosts.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{singlePost.Id}"); - singlePostCollection.SinglePostMedia.Add(medium); - } - } - } - else if (medium.Files.Preview != null && medium.Files!.Full == null) + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, out string cloudFrontPolicy, + out string cloudFrontSignature, out string cloudFrontKeyPairId)) { if (!singlePostCollection.SinglePosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, singlePost.Id, medium.Files.Preview.Url, - null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - postPreviewIds.Contains(medium.Id) ? true : false, false, null); - singlePostCollection.SinglePosts.Add(medium.Id, medium.Files.Preview.Url); + await dbService.AddMedia(folder, medium.Id, singlePost.Id, manifestDash, null, null, + null, "Posts", mediaType, isPreview, false, null); + singlePostCollection.SinglePosts.Add(medium.Id, + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{singlePost.Id}"); + singlePostCollection.SinglePostMedia.Add(medium); + } + } + else if (!string.IsNullOrEmpty(previewUrl) && medium.Files?.Full == null) + { + if (!singlePostCollection.SinglePosts.ContainsKey(medium.Id)) + { + await dbService.AddMedia(folder, medium.Id, singlePost.Id, previewUrl, null, null, null, + "Posts", mediaType, isPreview, false, null); + singlePostCollection.SinglePosts.Add(medium.Id, previewUrl); singlePostCollection.SinglePostMedia.Add(medium); } } @@ -1237,7 +1193,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new SinglePostCollection(); } public async Task GetStreams(string endpoint, string folder, @@ -1272,10 +1228,10 @@ public class ApiService(IAuthService authService, IConfigService configService, string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); StreamsDtos.StreamsDto? streamsDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); StreamEntities.Streams streams = StreamsMapper.FromDto(streamsDto); statusReporter.ReportStatus($"Getting Streams - Found {streams.List.Count}"); - if (streams != null && streams.HasMore) + if (streams.HasMore) { UpdateGetParamsForDateSelection( downloadDateSelection, @@ -1284,12 +1240,10 @@ public class ApiService(IAuthService authService, IConfigService configService, while (true) { - StreamEntities.Streams newstreams = new(); - string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); StreamsDtos.StreamsDto? newStreamsDto = - JsonConvert.DeserializeObject(loopbody, s_mJsonSerializerSettings); - newstreams = StreamsMapper.FromDto(newStreamsDto); + DeserializeJson(loopbody, s_mJsonSerializerSettings); + StreamEntities.Streams newstreams = StreamsMapper.FromDto(newStreamsDto); streams.List.AddRange(newstreams.List); statusReporter.ReportStatus($"Getting Streams - Found {streams.List.Count}"); @@ -1305,7 +1259,8 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - foreach (StreamEntities.ListItem stream in streams.List) + List streamList = streams.List; + foreach (StreamEntities.ListItem stream in streamList) { List streamPreviewIds = new(); if (stream.Preview != null && stream.Preview.Count > 0) @@ -1322,8 +1277,8 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - await dbService.AddPost(folder, stream.Id, stream.Text != null ? stream.Text : "", - stream.Price != null ? stream.Price : "0", stream.Price != null && stream.IsOpened ? true : false, + await dbService.AddPost(folder, stream.Id, !string.IsNullOrEmpty(stream.Text) ? stream.Text : "", + stream.Price ?? "0", stream.Price != null && stream.IsOpened, stream.IsArchived, stream.PostedAt); streamsCollection.StreamObjects.Add(stream); if (stream.Media != null && stream.Media.Count > 0) @@ -1350,42 +1305,36 @@ public class ApiService(IAuthService authService, IConfigService configService, continue; } + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = streamPreviewIds.Contains(medium.Id); + if (medium.CanView && medium.Files?.Drm == null) { bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id)); - if (!has && medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + if (!has && !string.IsNullOrEmpty(fullUrl)) { if (!streamsCollection.Streams.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, stream.Id, medium.Files.Full.Url, null, - null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - streamPreviewIds.Contains(medium.Id) ? true : false, false, null); - streamsCollection.Streams.Add(medium.Id, medium.Files.Full.Url); + await dbService.AddMedia(folder, medium.Id, stream.Id, fullUrl, null, null, null, + "Posts", mediaType, isPreview, false, null); + streamsCollection.Streams.Add(medium.Id, fullUrl); streamsCollection.StreamMedia.Add(medium); } } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, out string cloudFrontPolicy, + out string cloudFrontSignature, out string cloudFrontKeyPairId)) { bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id)); - if (!has && medium.Files != null && medium.Files.Drm != null) + if (!has && !streamsCollection.Streams.ContainsKey(medium.Id)) { - if (!streamsCollection.Streams.ContainsKey(medium.Id)) - { - await dbService.AddMedia(folder, medium.Id, stream.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - streamPreviewIds.Contains(medium.Id) ? true : false, false, null); - streamsCollection.Streams.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{stream.Id}"); - streamsCollection.StreamMedia.Add(medium); - } + await dbService.AddMedia(folder, medium.Id, stream.Id, manifestDash, null, null, null, + "Posts", mediaType, isPreview, false, null); + streamsCollection.Streams.Add(medium.Id, + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{stream.Id}"); + streamsCollection.StreamMedia.Add(medium); } } } @@ -1408,7 +1357,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new StreamEntities.StreamsCollection(); } @@ -1450,7 +1399,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } ArchivedDtos.ArchivedDto? archivedDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); ArchivedEntities.Archived archived = ArchivedMapper.FromDto(archivedDto); statusReporter.ReportStatus($"Getting Archived Posts - Found {archived.List.Count}"); if (archived.HasMore) @@ -1468,7 +1417,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } ArchivedDtos.ArchivedDto? newarchivedDto = - JsonConvert.DeserializeObject(loopbody, s_mJsonSerializerSettings); + DeserializeJson(loopbody, s_mJsonSerializerSettings); ArchivedEntities.Archived newarchived = ArchivedMapper.FromDto(newarchivedDto); archived.List.AddRange(newarchived.List); @@ -1503,8 +1452,8 @@ public class ApiService(IAuthService authService, IConfigService configService, } await dbService.AddPost(folder, archive.Id, archive.Text != null ? archive.Text : "", - archive.Price != null ? archive.Price : "0", - archive.Price != null && archive.IsOpened ? true : false, archive.IsArchived, archive.PostedAt); + archive.Price ?? "0", + archive.Price != null && archive.IsOpened, archive.IsArchived, archive.PostedAt); archivedCollection.ArchivedPostObjects.Add(archive); if (archive.Media != null && archive.Media.Count > 0) { @@ -1530,33 +1479,30 @@ public class ApiService(IAuthService authService, IConfigService configService, continue; } - if (medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = previewids.Contains(medium.Id); + + if (medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (!archivedCollection.ArchivedPosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, archive.Id, medium.Files.Full.Url, null, - null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id) ? true : false, false, null); - archivedCollection.ArchivedPosts.Add(medium.Id, medium.Files.Full.Url); + await dbService.AddMedia(folder, medium.Id, archive.Id, fullUrl, null, null, null, + "Posts", mediaType, isPreview, false, null); + archivedCollection.ArchivedPosts.Add(medium.Id, fullUrl); archivedCollection.ArchivedPostMedia.Add(medium); } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, out string cloudFrontPolicy, + out string cloudFrontSignature, out string cloudFrontKeyPairId)) { if (!archivedCollection.ArchivedPosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, archive.Id, medium.Files.Drm.Manifest.Dash, - null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id) ? true : false, false, null); + await dbService.AddMedia(folder, medium.Id, archive.Id, manifestDash, null, null, null, + "Posts", mediaType, isPreview, false, null); archivedCollection.ArchivedPosts.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{archive.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{archive.Id}"); archivedCollection.ArchivedPostMedia.Add(medium); } } @@ -1580,7 +1526,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new ArchivedEntities.ArchivedCollection(); } @@ -1597,10 +1543,11 @@ public class ApiService(IAuthService authService, IConfigService configService, { { "limit", postLimit.ToString() }, { "order", "desc" }, { "skip_users", "all" } }; + int currentUserId = GetCurrentUserIdOrDefault(); string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); MessageDtos.MessagesDto? messagesDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); MessageEntities.Messages messages = MessagesMapper.FromDto(messagesDto); statusReporter.ReportStatus($"Getting Messages - Found {messages.List.Count}"); if (messages.HasMore) @@ -1608,12 +1555,10 @@ public class ApiService(IAuthService authService, IConfigService configService, getParams["id"] = messages.List[^1].Id.ToString(); while (true) { - MessageEntities.Messages newMessages = new(); - string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); MessageDtos.MessagesDto? newMessagesDto = - JsonConvert.DeserializeObject(loopbody, s_mJsonSerializerSettings); - newMessages = MessagesMapper.FromDto(newMessagesDto); + DeserializeJson(loopbody, s_mJsonSerializerSettings); + MessageEntities.Messages newMessages = MessagesMapper.FromDto(newMessagesDto); messages.List.AddRange(newMessages.List); statusReporter.ReportStatus($"Getting Messages - Found {messages.List.Count}"); @@ -1652,21 +1597,24 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - if (!configService.CurrentConfig.IgnoreOwnMessages || - list.FromUser?.Id != Convert.ToInt32(authService.CurrentAuth.UserId)) + if (!configService.CurrentConfig.IgnoreOwnMessages || list.FromUser?.Id != currentUserId) { + DateTime createdAt = list.CreatedAt ?? DateTime.Now; await dbService.AddMessage(folder, list.Id, list.Text ?? "", list.Price ?? "0", list.CanPurchaseReason == "opened" ? true : list.CanPurchaseReason != "opened" ? false : (bool?)null ?? false, false, - list.CreatedAt.HasValue ? list.CreatedAt.Value : DateTime.Now, + createdAt, list.FromUser?.Id ?? int.MinValue); messageCollection.MessageObjects.Add(list); if (list.CanPurchaseReason != "opened" && list.Media != null && list.Media.Count > 0) { - foreach (MessageEntities.Medium medium in list.Media) + foreach (MessageEntities.Medium medium in list.Media ?? new List()) { - if (medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = messagePreviewIds.Contains(medium.Id); + + if (medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -1690,17 +1638,16 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!messageCollection.Messages.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, list.Id, medium.Files.Full.Url, null, - null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - messagePreviewIds.Contains(medium.Id), false, null); - messageCollection.Messages.Add(medium.Id, medium.Files.Full.Url); + await dbService.AddMedia(folder, medium.Id, list.Id, fullUrl, null, null, null, + "Messages", mediaType, isPreview, false, null); + messageCollection.Messages.Add(medium.Id, fullUrl); messageCollection.MessageMedia.Add(medium); } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -1724,14 +1671,10 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!messageCollection.Messages.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, list.Id, medium.Files.Drm.Manifest.Dash, - null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - messagePreviewIds.Contains(medium.Id), false, null); + await dbService.AddMedia(folder, medium.Id, list.Id, manifestDash, null, null, null, + "Messages", mediaType, isPreview, false, null); messageCollection.Messages.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{list.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{list.Id}"); messageCollection.MessageMedia.Add(medium); } } @@ -1739,10 +1682,13 @@ public class ApiService(IAuthService authService, IConfigService configService, } else if (messagePreviewIds.Count > 0) { - foreach (MessageEntities.Medium medium in list.Media) + foreach (MessageEntities.Medium medium in list.Media ?? new List()) { - if (medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url) && messagePreviewIds.Contains(medium.Id)) + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = messagePreviewIds.Contains(medium.Id); + + if (medium.CanView && !string.IsNullOrEmpty(fullUrl) && isPreview) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -1766,18 +1712,16 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!messageCollection.Messages.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, list.Id, medium.Files.Full.Url, null, - null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - messagePreviewIds.Contains(medium.Id), false, null); - messageCollection.Messages.Add(medium.Id, medium.Files.Full.Url); + await dbService.AddMedia(folder, medium.Id, list.Id, fullUrl, null, null, null, + "Messages", mediaType, isPreview, false, null); + messageCollection.Messages.Add(medium.Id, fullUrl); messageCollection.MessageMedia.Add(medium); } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null && - messagePreviewIds.Contains(medium.Id)) + else if (medium.CanView && isPreview && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -1801,14 +1745,10 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!messageCollection.Messages.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, list.Id, medium.Files.Drm.Manifest.Dash, - null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - messagePreviewIds.Contains(medium.Id), false, null); + await dbService.AddMedia(folder, medium.Id, list.Id, manifestDash, null, null, null, + "Messages", mediaType, isPreview, false, null); messageCollection.Messages.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{list.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{list.Id}"); messageCollection.MessageMedia.Add(medium); } } @@ -1833,7 +1773,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new MessageEntities.MessageCollection(); } public async Task GetPaidMessage(string endpoint, string folder) @@ -1845,18 +1785,19 @@ public class ApiService(IAuthService authService, IConfigService configService, PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection = new(); const int postLimit = 50; Dictionary getParams = new() { { "limit", postLimit.ToString() }, { "order", "desc" } }; + int currentUserId = GetCurrentUserIdOrDefault(); string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); MessageDtos.SingleMessageDto? messageDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); MessageEntities.SingleMessage message = MessagesMapper.FromDto(messageDto); - if (!configService.CurrentConfig.IgnoreOwnMessages || - message.FromUser?.Id != Convert.ToInt32(authService.CurrentAuth.UserId)) + if (!configService.CurrentConfig.IgnoreOwnMessages || message.FromUser?.Id != currentUserId) { + DateTime createdAt = message.CreatedAt ?? DateTime.Now; await dbService.AddMessage(folder, message.Id, message.Text ?? "", - message.Price != null ? message.Price.ToString() : "0", true, false, - message.CreatedAt.HasValue ? message.CreatedAt.Value : DateTime.Now, + message.Price?.ToString() ?? "0", true, false, + createdAt, message.FromUser?.Id ?? int.MinValue); singlePaidMessageCollection.SingleMessageObjects.Add(message); List messagePreviewIds = new(); @@ -1878,8 +1819,11 @@ public class ApiService(IAuthService authService, IConfigService configService, { foreach (MessageEntities.Medium medium in message.Media) { - if (!messagePreviewIds.Contains(medium.Id) && medium.CanView && medium.Files != null && - medium.Files.Full != null && !string.IsNullOrEmpty(medium.Files.Full.Url)) + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = messagePreviewIds.Contains(medium.Id); + + if (!isPreview && medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -1903,18 +1847,13 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!singlePaidMessageCollection.SingleMessages.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, message.Id, medium.Files.Full.Url, null, - null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - messagePreviewIds.Contains(medium.Id), false, null); - singlePaidMessageCollection.SingleMessages.Add(medium.Id, medium.Files.Full.Url); + await dbService.AddMedia(folder, medium.Id, message.Id, fullUrl, null, null, null, + "Messages", mediaType, isPreview, false, null); + singlePaidMessageCollection.SingleMessages.Add(medium.Id, fullUrl); singlePaidMessageCollection.SingleMessageMedia.Add(medium); } } - else if (messagePreviewIds.Contains(medium.Id) && medium.CanView && medium.Files != null && - medium.Files.Full != null && !string.IsNullOrEmpty(medium.Files.Full.Url)) + else if (isPreview && medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -1938,18 +1877,15 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!singlePaidMessageCollection.PreviewSingleMessages.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, message.Id, medium.Files.Full.Url, null, - null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - messagePreviewIds.Contains(medium.Id), false, null); - singlePaidMessageCollection.PreviewSingleMessages.Add(medium.Id, medium.Files.Full.Url); + await dbService.AddMedia(folder, medium.Id, message.Id, fullUrl, null, null, null, + "Messages", mediaType, isPreview, false, null); + singlePaidMessageCollection.PreviewSingleMessages.Add(medium.Id, fullUrl); singlePaidMessageCollection.PreviewSingleMessageMedia.Add(medium); } } - else if (!messagePreviewIds.Contains(medium.Id) && medium.CanView && medium.Files != null && - medium.Files.Drm != null) + else if (!isPreview && medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, out string cloudFrontPolicy, + out string cloudFrontSignature, out string cloudFrontKeyPairId)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -1973,19 +1909,17 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!singlePaidMessageCollection.SingleMessages.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, message.Id, medium.Files.Drm.Manifest.Dash, - null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - messagePreviewIds.Contains(medium.Id), false, null); + await dbService.AddMedia(folder, medium.Id, message.Id, manifestDash, null, null, null, + "Messages", mediaType, isPreview, false, null); singlePaidMessageCollection.SingleMessages.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{message.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{message.Id}"); singlePaidMessageCollection.SingleMessageMedia.Add(medium); } } - else if (messagePreviewIds.Contains(medium.Id) && medium.CanView && medium.Files != null && - medium.Files.Drm != null) + else if (isPreview && medium.CanView && + TryGetDrmInfo(medium.Files, out string previewManifestDash, + out string previewCloudFrontPolicy, out string previewCloudFrontSignature, + out string previewCloudFrontKeyPairId)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -2009,14 +1943,10 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!singlePaidMessageCollection.PreviewSingleMessages.ContainsKey(medium.Id)) { - await dbService.AddMedia(folder, medium.Id, message.Id, medium.Files.Drm.Manifest.Dash, - null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - messagePreviewIds.Contains(medium.Id), false, null); + await dbService.AddMedia(folder, medium.Id, message.Id, previewManifestDash, null, null, + null, "Messages", mediaType, isPreview, false, null); singlePaidMessageCollection.PreviewSingleMessages.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{message.Id}"); + $"{previewManifestDash},{previewCloudFrontPolicy},{previewCloudFrontSignature},{previewCloudFrontKeyPairId},{medium.Id},{message.Id}"); singlePaidMessageCollection.PreviewSingleMessageMedia.Add(medium); } } @@ -2040,7 +1970,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new PurchasedEntities.SinglePaidMessageCollection(); } @@ -2062,19 +1992,20 @@ public class ApiService(IAuthService authService, IConfigService configService, { "author", username }, { "skip_users", "all" } }; + int currentUserId = GetCurrentUserIdOrDefault(); string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); PurchasedDtos.PurchasedDto? paidMessagesDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); PurchasedEntities.Purchased paidMessages = PurchasedMapper.FromDto(paidMessagesDto); statusReporter.ReportStatus($"Getting Paid Messages - Found {paidMessages.List.Count}"); - if (paidMessages != null && paidMessages.HasMore) + if (paidMessages.HasMore) { getParams["offset"] = paidMessages.List.Count.ToString(); while (true) { string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); - PurchasedEntities.Purchased newpaidMessages = new(); + PurchasedEntities.Purchased newpaidMessages; Dictionary loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams); HttpClient loopclient = GetHttpClient(); @@ -2091,8 +2022,7 @@ public class ApiService(IAuthService authService, IConfigService configService, loopresponse.EnsureSuccessStatusCode(); string loopbody = await loopresponse.Content.ReadAsStringAsync(); PurchasedDtos.PurchasedDto? newPaidMessagesDto = - JsonConvert.DeserializeObject(loopbody, - s_mJsonSerializerSettings); + DeserializeJson(loopbody, s_mJsonSerializerSettings); newpaidMessages = PurchasedMapper.FromDto(newPaidMessagesDto); } @@ -2107,29 +2037,22 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - if (paidMessages.List != null && paidMessages.List.Count > 0) + List paidMessageList = + paidMessages.List; + if (paidMessageList.Count > 0) { - foreach (PurchasedEntities.ListItem purchase in paidMessages.List + foreach (PurchasedEntities.ListItem purchase in paidMessageList .Where(p => p.ResponseType == "message") .OrderByDescending(p => p.PostedAt ?? p.CreatedAt)) { - if (!configService.CurrentConfig.IgnoreOwnMessages || - purchase.FromUser.Id != Convert.ToInt32(authService.CurrentAuth.UserId)) + long fromUserId = purchase.FromUser?.Id ?? long.MinValue; + if (!configService.CurrentConfig.IgnoreOwnMessages || fromUserId != currentUserId) { - if (purchase.PostedAt != null) - { - await dbService.AddMessage(folder, purchase.Id, - purchase.Text != null ? purchase.Text : "", - purchase.Price != null ? purchase.Price : "0", true, false, purchase.PostedAt.Value, - purchase.FromUser.Id); - } - else - { - await dbService.AddMessage(folder, purchase.Id, - purchase.Text != null ? purchase.Text : "", - purchase.Price != null ? purchase.Price : "0", true, false, purchase.CreatedAt.Value, - purchase.FromUser.Id); - } + DateTime createdAt = purchase.PostedAt ?? purchase.CreatedAt ?? DateTime.Now; + await dbService.AddMessage(folder, purchase.Id, + purchase.Text ?? "", + purchase.Price ?? "0", true, false, createdAt, + fromUserId); paidMessageCollection.PaidMessageObjects.Add(purchase); if (purchase.Media != null && purchase.Media.Count > 0) @@ -2164,11 +2087,14 @@ public class ApiService(IAuthService authService, IConfigService configService, foreach (MessageEntities.Medium medium in purchase.Media) { + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = previewids.Contains(medium.Id); + if (previewids.Count > 0) { bool has = previewids.Any(cus => cus.Equals(medium.Id)); - if (!has && medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + if (!has && medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -2193,16 +2119,16 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!paidMessageCollection.PaidMessages.ContainsKey(medium.Id)) { await dbService.AddMedia(folder, medium.Id, purchase.Id, - medium.Files.Full.Url, null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); - paidMessageCollection.PaidMessages.Add(medium.Id, medium.Files.Full.Url); + fullUrl, null, null, null, "Messages", mediaType, isPreview, false, + null); + paidMessageCollection.PaidMessages.Add(medium.Id, fullUrl); paidMessageCollection.PaidMessageMedia.Add(medium); } } - else if (!has && medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (!has && medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -2227,21 +2153,17 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!paidMessageCollection.PaidMessages.ContainsKey(medium.Id)) { await dbService.AddMedia(folder, medium.Id, purchase.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); + manifestDash, null, null, null, "Messages", mediaType, isPreview, false, + null); paidMessageCollection.PaidMessages.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{purchase.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{purchase.Id}"); paidMessageCollection.PaidMessageMedia.Add(medium); } } } else { - if (medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + if (medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -2266,16 +2188,16 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!paidMessageCollection.PaidMessages.ContainsKey(medium.Id)) { await dbService.AddMedia(folder, medium.Id, purchase.Id, - medium.Files.Full.Url, null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); - paidMessageCollection.PaidMessages.Add(medium.Id, medium.Files.Full.Url); + fullUrl, null, null, null, "Messages", mediaType, isPreview, false, + null); + paidMessageCollection.PaidMessages.Add(medium.Id, fullUrl); paidMessageCollection.PaidMessageMedia.Add(medium); } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) { @@ -2300,13 +2222,10 @@ public class ApiService(IAuthService authService, IConfigService configService, if (!paidMessageCollection.PaidMessages.ContainsKey(medium.Id)) { await dbService.AddMedia(folder, medium.Id, purchase.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); + manifestDash, null, null, null, "Messages", mediaType, isPreview, false, + null); paidMessageCollection.PaidMessages.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{purchase.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{purchase.Id}"); paidMessageCollection.PaidMessageMedia.Add(medium); } } @@ -2333,7 +2252,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new PurchasedEntities.PaidMessageCollection(); } public async Task> GetPurchasedTabUsers(string endpoint, Dictionary users) @@ -2359,7 +2278,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } PurchasedDtos.PurchasedDto? purchasedDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); PurchasedEntities.Purchased purchased = PurchasedMapper.FromDto(purchasedDto); if (purchased.HasMore) { @@ -2384,8 +2303,7 @@ public class ApiService(IAuthService authService, IConfigService configService, loopresponse.EnsureSuccessStatusCode(); string loopbody = await loopresponse.Content.ReadAsStringAsync(); PurchasedDtos.PurchasedDto? newPurchasedDto = - JsonConvert.DeserializeObject(loopbody, - s_mJsonSerializerSettings); + DeserializeJson(loopbody, s_mJsonSerializerSettings); newPurchased = PurchasedMapper.FromDto(newPurchasedDto); } @@ -2404,123 +2322,84 @@ public class ApiService(IAuthService authService, IConfigService configService, foreach (PurchasedEntities.ListItem purchase in purchased.List.OrderByDescending(p => p.PostedAt ?? p.CreatedAt)) { - // purchase.FromUser.Id is not nullable, so the default value is 0 - if (purchase.FromUser.Id != 0) + long fromUserId = purchase.FromUser?.Id ?? 0; + long authorId = purchase.Author?.Id ?? 0; + + if (fromUserId != 0) { - if (users.Values.Contains(purchase.FromUser.Id)) + if (users.Values.Contains(fromUserId)) { - if (!string.IsNullOrEmpty(users.FirstOrDefault(x => x.Value == purchase.FromUser.Id).Key)) + string? matchedUsername = users.FirstOrDefault(x => x.Value == fromUserId).Key; + if (!string.IsNullOrEmpty(matchedUsername)) { - if (!purchasedTabUsers.ContainsKey(users - .FirstOrDefault(x => x.Value == purchase.FromUser.Id).Key)) + if (!purchasedTabUsers.ContainsKey(matchedUsername)) { - purchasedTabUsers.Add( - users.FirstOrDefault(x => x.Value == purchase.FromUser.Id).Key, - purchase.FromUser.Id); + purchasedTabUsers.Add(matchedUsername, fromUserId); } } - else + else if (!purchasedTabUsers.ContainsKey($"Deleted User - {fromUserId}")) { - if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.FromUser.Id}")) - { - purchasedTabUsers.Add($"Deleted User - {purchase.FromUser.Id}", - purchase.FromUser.Id); - } + purchasedTabUsers.Add($"Deleted User - {fromUserId}", fromUserId); } } else { - JObject? user = await GetUserInfoById($"/users/list?x[]={purchase.FromUser.Id}"); + JObject? user = await GetUserInfoById($"/users/list?x[]={fromUserId}"); + string? fetchedUsername = user?[fromUserId.ToString()]?["username"]?.ToString(); - if (user == null) + if (string.IsNullOrEmpty(fetchedUsername)) { - if (!configService.CurrentConfig.BypassContentForCreatorsWhoNoLongerExist) + if (!configService.CurrentConfig.BypassContentForCreatorsWhoNoLongerExist && + !purchasedTabUsers.ContainsKey($"Deleted User - {fromUserId}")) { - if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.FromUser.Id}")) - { - purchasedTabUsers.Add($"Deleted User - {purchase.FromUser.Id}", - purchase.FromUser.Id); - } + purchasedTabUsers.Add($"Deleted User - {fromUserId}", fromUserId); } - Log.Debug("Content creator not longer exists - {0}", purchase.FromUser.Id); + Log.Debug("Content creator not longer exists - {0}", fromUserId); } - else if (!string.IsNullOrEmpty(user[purchase.FromUser.Id.ToString()]["username"] - .ToString())) + else if (!purchasedTabUsers.ContainsKey(fetchedUsername)) { - if (!purchasedTabUsers.ContainsKey(user[purchase.FromUser.Id.ToString()]["username"] - .ToString())) - { - purchasedTabUsers.Add(user[purchase.FromUser.Id.ToString()]["username"].ToString(), - purchase.FromUser.Id); - } - } - else - { - if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.FromUser.Id}")) - { - purchasedTabUsers.Add($"Deleted User - {purchase.FromUser.Id}", - purchase.FromUser.Id); - } + purchasedTabUsers.Add(fetchedUsername, fromUserId); } } } - // purchase.Author is not nullable, so we check against the Author's Id (default value 0) - else if (purchase.Author.Id != 0) + else if (authorId != 0) { - if (users.Values.Contains(purchase.Author.Id)) + if (users.ContainsValue(authorId)) { - if (!string.IsNullOrEmpty(users.FirstOrDefault(x => x.Value == purchase.Author.Id).Key)) + string? matchedUsername = users.FirstOrDefault(x => x.Value == authorId).Key; + if (!string.IsNullOrEmpty(matchedUsername)) { - if (!purchasedTabUsers.ContainsKey(users - .FirstOrDefault(x => x.Value == purchase.Author.Id).Key) && - users.ContainsKey(users.FirstOrDefault(x => x.Value == purchase.Author.Id).Key)) + if (!purchasedTabUsers.ContainsKey(matchedUsername) && + users.ContainsKey(matchedUsername)) { - purchasedTabUsers.Add(users.FirstOrDefault(x => x.Value == purchase.Author.Id).Key, - purchase.Author.Id); + purchasedTabUsers.Add(matchedUsername, authorId); } } - else + else if (!purchasedTabUsers.ContainsKey($"Deleted User - {authorId}")) { - if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.Author.Id}")) - { - purchasedTabUsers.Add($"Deleted User - {purchase.Author.Id}", purchase.Author.Id); - } + purchasedTabUsers.Add($"Deleted User - {authorId}", authorId); } } else { - JObject? user = await GetUserInfoById($"/users/list?x[]={purchase.Author.Id}"); + JObject? user = await GetUserInfoById($"/users/list?x[]={authorId}"); + string? fetchedUsername = user?[authorId.ToString()]?["username"]?.ToString(); - if (user is null) + if (string.IsNullOrEmpty(fetchedUsername)) { - if (!configService.CurrentConfig.BypassContentForCreatorsWhoNoLongerExist) + if (!configService.CurrentConfig.BypassContentForCreatorsWhoNoLongerExist && + !purchasedTabUsers.ContainsKey($"Deleted User - {authorId}")) { - if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.Author.Id}")) - { - purchasedTabUsers.Add($"Deleted User - {purchase.Author.Id}", - purchase.Author.Id); - } + purchasedTabUsers.Add($"Deleted User - {authorId}", authorId); } - Log.Debug("Content creator not longer exists - {0}", purchase.Author.Id); + Log.Debug("Content creator not longer exists - {0}", authorId); } - else if (!string.IsNullOrEmpty(user[purchase.Author.Id.ToString()]["username"].ToString())) + else if (!purchasedTabUsers.ContainsKey(fetchedUsername) && + users.ContainsKey(fetchedUsername)) { - if (!purchasedTabUsers.ContainsKey(user[purchase.Author.Id.ToString()]["username"] - .ToString()) && - users.ContainsKey(user[purchase.Author.Id.ToString()]["username"].ToString())) - { - purchasedTabUsers.Add(user[purchase.Author.Id.ToString()]["username"].ToString(), - purchase.Author.Id); - } - } - else - { - if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.Author.Id}")) - { - purchasedTabUsers.Add($"Deleted User - {purchase.Author.Id}", purchase.Author.Id); - } + purchasedTabUsers.Add(fetchedUsername, authorId); } } } @@ -2543,7 +2422,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return new Dictionary(); } public async Task> GetPurchasedTab(string endpoint, string folder, @@ -2566,7 +2445,7 @@ public class ApiService(IAuthService authService, IConfigService configService, string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); PurchasedDtos.PurchasedDto? purchasedDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); + DeserializeJson(body, s_mJsonSerializerSettings); PurchasedEntities.Purchased purchased = PurchasedMapper.FromDto(purchasedDto); if (purchased.HasMore) { @@ -2574,7 +2453,7 @@ public class ApiService(IAuthService authService, IConfigService configService, while (true) { string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); - PurchasedEntities.Purchased newPurchased = new(); + PurchasedEntities.Purchased newPurchased; Dictionary loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams); HttpClient loopclient = GetHttpClient(); @@ -2591,8 +2470,7 @@ public class ApiService(IAuthService authService, IConfigService configService, loopresponse.EnsureSuccessStatusCode(); string loopbody = await loopresponse.Content.ReadAsStringAsync(); PurchasedDtos.PurchasedDto? newPurchasedDto = - JsonConvert.DeserializeObject(loopbody, - s_mJsonSerializerSettings); + DeserializeJson(loopbody, s_mJsonSerializerSettings); newPurchased = PurchasedMapper.FromDto(newPurchasedDto); } @@ -2606,7 +2484,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - if (purchased.List != null && purchased.List.Count > 0) + if (purchased.List.Count > 0) { foreach (PurchasedEntities.ListItem purchase in purchased.List.OrderByDescending(p => p.PostedAt ?? p.CreatedAt)) @@ -2637,11 +2515,10 @@ public class ApiService(IAuthService authService, IConfigService configService, PurchasedEntities.PurchasedTabCollection purchasedTabCollection = new(); JObject? userObject = await GetUserInfoById($"/users/list?x[]={user.Key}"); purchasedTabCollection.UserId = user.Key; - purchasedTabCollection.Username = - userObject is not null && - !string.IsNullOrEmpty(userObject[user.Key.ToString()]["username"].ToString()) - ? userObject[user.Key.ToString()]["username"].ToString() - : $"Deleted User - {user.Key}"; + string? fetchedUsername = userObject?[user.Key.ToString()]?["username"]?.ToString(); + purchasedTabCollection.Username = !string.IsNullOrEmpty(fetchedUsername) + ? fetchedUsername + : $"Deleted User - {user.Key}"; string path = Path.Combine(folder, purchasedTabCollection.Username); if (Path.Exists(path)) { @@ -2687,12 +2564,14 @@ public class ApiService(IAuthService authService, IConfigService configService, } } + DateTime createdAt = purchase.CreatedAt ?? purchase.PostedAt ?? DateTime.Now; + bool isArchived = purchase.IsArchived ?? false; await dbService.AddPost(path, purchase.Id, - purchase.Text != null ? purchase.Text : "", - purchase.Price != null ? purchase.Price : "0", - purchase.Price != null && purchase.IsOpened ? true : false, - purchase.IsArchived.HasValue ? purchase.IsArchived.Value : false, - purchase.CreatedAt != null ? purchase.CreatedAt.Value : purchase.PostedAt.Value); + purchase.Text ?? "", + purchase.Price ?? "0", + purchase.Price != null && purchase.IsOpened, + isArchived, + createdAt); purchasedTabCollection.PaidPosts.PaidPostObjects.Add(purchase); foreach (MessageEntities.Medium medium in purchase.Media) { @@ -2716,72 +2595,61 @@ public class ApiService(IAuthService authService, IConfigService configService, continue; } + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = previewids.Contains(medium.Id); + if (previewids.Count > 0) { bool has = previewids.Any(cus => cus.Equals(medium.Id)); - if (!has && medium.CanView && medium.Files != null && - medium.Files.Full != null && !string.IsNullOrEmpty(medium.Files.Full.Url)) + if (!has && medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(path, medium.Id, purchase.Id, - medium.Files.Full.Url, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); - purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.Id, - medium.Files.Full.Url); + await dbService.AddMedia(path, medium.Id, purchase.Id, fullUrl, null, + null, null, "Posts", mediaType, isPreview, false, null); + purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.Id, fullUrl); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } } - else if (!has && medium.CanView && medium.Files != null && - medium.Files.Drm != null) + else if (!has && medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(path, medium.Id, purchase.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); + await dbService.AddMedia(path, medium.Id, purchase.Id, manifestDash, + null, null, null, "Posts", mediaType, isPreview, false, null); purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{purchase.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{purchase.Id}"); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } } } else { - if (medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + if (medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(path, medium.Id, purchase.Id, - medium.Files.Full.Url, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); - purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.Id, - medium.Files.Full.Url); + await dbService.AddMedia(path, medium.Id, purchase.Id, fullUrl, null, + null, null, "Posts", mediaType, isPreview, false, null); + purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.Id, fullUrl); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.Id)) { - await dbService.AddMedia(path, medium.Id, purchase.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Posts", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - previewids.Contains(medium.Id), false, null); + await dbService.AddMedia(path, medium.Id, purchase.Id, manifestDash, + null, null, null, "Posts", mediaType, isPreview, false, null); purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{purchase.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{purchase.Id}"); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } } @@ -2790,20 +2658,12 @@ public class ApiService(IAuthService authService, IConfigService configService, break; case "message": - if (purchase.PostedAt != null) - { - await dbService.AddMessage(path, purchase.Id, - purchase.Text != null ? purchase.Text : "", - purchase.Price != null ? purchase.Price : "0", true, false, - purchase.PostedAt.Value, purchase.FromUser.Id); - } - else - { - await dbService.AddMessage(path, purchase.Id, - purchase.Text != null ? purchase.Text : "", - purchase.Price != null ? purchase.Price : "0", true, false, - purchase.CreatedAt.Value, purchase.FromUser.Id); - } + DateTime messageCreatedAt = purchase.PostedAt ?? purchase.CreatedAt ?? DateTime.Now; + long fromUserId = purchase.FromUser?.Id ?? long.MinValue; + await dbService.AddMessage(path, purchase.Id, + purchase.Text ?? "", + purchase.Price ?? "0", true, false, + messageCreatedAt, fromUserId); purchasedTabCollection.PaidMessages.PaidMessageObjects.Add(purchase); if (purchase.Media != null && purchase.Media.Count > 0) @@ -2840,10 +2700,11 @@ public class ApiService(IAuthService authService, IConfigService configService, { if (paidMessagePreviewids.Count > 0) { + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = paidMessagePreviewids.Contains(medium.Id); bool has = paidMessagePreviewids.Any(cus => cus.Equals(medium.Id)); - if (!has && medium.CanView && medium.Files != null && - medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + if (!has && medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) @@ -2872,20 +2733,18 @@ public class ApiService(IAuthService authService, IConfigService configService, medium.Id)) { await dbService.AddMedia(path, medium.Id, purchase.Id, - medium.Files.Full.Url, null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - paidMessagePreviewids.Contains(medium.Id), false, - null); + fullUrl, null, null, null, "Messages", mediaType, + isPreview, false, null); purchasedTabCollection.PaidMessages.PaidMessages.Add(medium.Id, - medium.Files.Full.Url); + fullUrl); purchasedTabCollection.PaidMessages.PaidMessageMedia.Add( medium); } } - else if (!has && medium.CanView && medium.Files != null && - medium.Files.Drm != null) + else if (!has && medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) @@ -2914,14 +2773,10 @@ public class ApiService(IAuthService authService, IConfigService configService, medium.Id)) { await dbService.AddMedia(path, medium.Id, purchase.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - paidMessagePreviewids.Contains(medium.Id), false, - null); + manifestDash, null, null, null, "Messages", mediaType, + isPreview, false, null); purchasedTabCollection.PaidMessages.PaidMessages.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{purchase.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{purchase.Id}"); purchasedTabCollection.PaidMessages.PaidMessageMedia.Add( medium); } @@ -2929,8 +2784,11 @@ public class ApiService(IAuthService authService, IConfigService configService, } else { - if (medium.CanView && medium.Files != null && medium.Files.Full != null && - !string.IsNullOrEmpty(medium.Files.Full.Url)) + string mediaType = ResolveMediaType(medium.Type) ?? string.Empty; + string? fullUrl = medium.Files?.Full?.Url; + bool isPreview = paidMessagePreviewids.Contains(medium.Id); + + if (medium.CanView && !string.IsNullOrEmpty(fullUrl)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) @@ -2959,19 +2817,18 @@ public class ApiService(IAuthService authService, IConfigService configService, medium.Id)) { await dbService.AddMedia(path, medium.Id, purchase.Id, - medium.Files.Full.Url, null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - paidMessagePreviewids.Contains(medium.Id), false, - null); + fullUrl, null, null, null, "Messages", mediaType, + isPreview, false, null); purchasedTabCollection.PaidMessages.PaidMessages.Add(medium.Id, - medium.Files.Full.Url); + fullUrl); purchasedTabCollection.PaidMessages.PaidMessageMedia.Add( medium); } } - else if (medium.CanView && medium.Files != null && medium.Files.Drm != null) + else if (medium.CanView && + TryGetDrmInfo(medium.Files, out string manifestDash, + out string cloudFrontPolicy, out string cloudFrontSignature, + out string cloudFrontKeyPairId)) { if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages) @@ -3000,14 +2857,10 @@ public class ApiService(IAuthService authService, IConfigService configService, medium.Id)) { await dbService.AddMedia(path, medium.Id, purchase.Id, - medium.Files.Drm.Manifest.Dash, null, null, null, "Messages", - medium.Type == "photo" ? "Images" : - medium.Type == "video" || medium.Type == "gif" ? "Videos" : - medium.Type == "audio" ? "Audios" : null, - paidMessagePreviewids.Contains(medium.Id), false, - null); + manifestDash, null, null, null, "Messages", mediaType, + isPreview, false, null); purchasedTabCollection.PaidMessages.PaidMessages.Add(medium.Id, - $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{purchase.Id}"); + $"{manifestDash},{cloudFrontPolicy},{cloudFrontSignature},{cloudFrontKeyPairId},{medium.Id},{purchase.Id}"); purchasedTabCollection.PaidMessages.PaidMessageMedia.Add( medium); } @@ -3040,7 +2893,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return []; } @@ -3048,19 +2901,20 @@ public class ApiService(IAuthService authService, IConfigService configService, { try { - if (authService.CurrentAuth == null) + Auth? currentAuth = authService.CurrentAuth; + if (currentAuth == null || currentAuth.UserAgent == null || currentAuth.Cookie == null) { - throw new Exception("No current authentication available"); + throw new Exception("Auth service is missing required fields"); } string? pssh; HttpClient client = new(); HttpRequestMessage request = new(HttpMethod.Get, mpdUrl); - request.Headers.Add("user-agent", authService.CurrentAuth.UserAgent); + request.Headers.Add("user-agent", currentAuth.UserAgent); request.Headers.Add("Accept", "*/*"); request.Headers.Add("Cookie", - $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {authService.CurrentAuth.Cookie};"); + $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {currentAuth.Cookie};"); using (HttpResponseMessage response = await client.SendAsync(request)) { response.EnsureSuccessStatusCode(); @@ -3088,7 +2942,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return string.Empty; } @@ -3104,12 +2958,18 @@ public class ApiService(IAuthService authService, IConfigService configService, { DateTime lastmodified; + Auth? currentAuth = authService.CurrentAuth; + if (currentAuth == null || currentAuth.UserAgent == null || currentAuth.Cookie == null) + { + throw new Exception("Auth service is missing required fields"); + } + HttpClient client = new(); HttpRequestMessage request = new(HttpMethod.Get, mpdUrl); - request.Headers.Add("user-agent", authService.CurrentAuth.UserAgent); + request.Headers.Add("user-agent", currentAuth.UserAgent); request.Headers.Add("Accept", "*/*"); request.Headers.Add("Cookie", - $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {authService.CurrentAuth.Cookie};"); + $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {currentAuth.Cookie};"); using (HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) { @@ -3198,10 +3058,10 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return string.Empty; } - public async Task? GetDecryptionKeyCDM(Dictionary drmHeaders, string licenceURL, + public async Task GetDecryptionKeyCDM(Dictionary drmHeaders, string licenceURL, string pssh) { Log.Debug("Calling GetDecryptionKeyCDM"); @@ -3246,7 +3106,7 @@ public class ApiService(IAuthService authService, IConfigService configService, } } - return null; + return string.Empty; } @@ -3302,7 +3162,7 @@ public class ApiService(IAuthService authService, IConfigService configService, private HttpClient GetHttpClient() { HttpClient client = new(); - if (configService.CurrentConfig?.Timeout != null && configService.CurrentConfig.Timeout > 0) + if (configService.CurrentConfig.Timeout != null && configService.CurrentConfig.Timeout > 0) { client.Timeout = TimeSpan.FromSeconds(configService.CurrentConfig.Timeout.Value); } @@ -3310,12 +3170,73 @@ public class ApiService(IAuthService authService, IConfigService configService, return client; } + private static T? DeserializeJson(string? body, JsonSerializerSettings? settings = null) + { + if (string.IsNullOrWhiteSpace(body)) + { + return default; + } + + return settings == null + ? JsonConvert.DeserializeObject(body) + : JsonConvert.DeserializeObject(body, settings); + } + + private static string? ResolveMediaType(string? type) => + type switch + { + "photo" => "Images", + "video" => "Videos", + "gif" => "Videos", + "audio" => "Audios", + _ => null + }; + + private static bool TryGetDrmInfo(Files? files, out string manifestDash, out string cloudFrontPolicy, + out string cloudFrontSignature, out string cloudFrontKeyPairId) + { + manifestDash = string.Empty; + cloudFrontPolicy = string.Empty; + cloudFrontSignature = string.Empty; + cloudFrontKeyPairId = string.Empty; + + string? dash = files?.Drm?.Manifest?.Dash; + Dash? signatureDash = files?.Drm?.Signature?.Dash; + if (string.IsNullOrEmpty(dash) || signatureDash == null) + { + return false; + } + + if (string.IsNullOrEmpty(signatureDash.CloudFrontPolicy) || + string.IsNullOrEmpty(signatureDash.CloudFrontSignature) || + string.IsNullOrEmpty(signatureDash.CloudFrontKeyPairId)) + { + return false; + } + + manifestDash = dash; + cloudFrontPolicy = signatureDash.CloudFrontPolicy; + cloudFrontSignature = signatureDash.CloudFrontSignature; + cloudFrontKeyPairId = signatureDash.CloudFrontKeyPairId; + return true; + } + + private int GetCurrentUserIdOrDefault() + { + if (authService.CurrentAuth?.UserId == null) + { + return int.MinValue; + } + + return int.TryParse(authService.CurrentAuth.UserId, out int userId) ? userId : int.MinValue; + } + /// /// this one is used during initialization only - /// if the config option is not available then no modificatiotns will be done on the getParams + /// if the config option is not available, then no modifications will be done on the getParams /// - /// + /// /// /// private static void UpdateGetParamsForDateSelection(DownloadDateSelection downloadDateSelection, @@ -3336,8 +3257,13 @@ public class ApiService(IAuthService authService, IConfigService configService, } private static void UpdateGetParamsForDateSelection(DownloadDateSelection downloadDateSelection, - ref Dictionary getParams, string unixTimeStampInMicrosec) + ref Dictionary getParams, string? unixTimeStampInMicrosec) { + if (string.IsNullOrWhiteSpace(unixTimeStampInMicrosec)) + { + return; + } + switch (downloadDateSelection) { case DownloadDateSelection.before: @@ -3357,29 +3283,27 @@ public class ApiService(IAuthService authService, IConfigService configService, try { Dictionary users = new(); - SubscriptionEntities.Subscriptions subscriptions = new(); Log.Debug("Calling GetAllSubscrptions"); string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); SubscriptionsDtos.SubscriptionsDto? subscriptionsDto = - JsonConvert.DeserializeObject(body, s_mJsonSerializerSettings); - subscriptions = SubscriptionsMapper.FromDto(subscriptionsDto); + DeserializeJson(body, s_mJsonSerializerSettings); + SubscriptionEntities.Subscriptions subscriptions = SubscriptionsMapper.FromDto(subscriptionsDto); if (subscriptions.HasMore) { getParams["offset"] = subscriptions.List.Count.ToString(); while (true) { - SubscriptionEntities.Subscriptions newSubscriptions = new(); + SubscriptionEntities.Subscriptions newSubscriptions; string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); if (!string.IsNullOrEmpty(loopbody) && (!loopbody.Contains("[]") || loopbody.Trim() != "[]")) { SubscriptionsDtos.SubscriptionsDto? newSubscriptionsDto = - JsonConvert.DeserializeObject(loopbody, - s_mJsonSerializerSettings); + DeserializeJson(loopbody, s_mJsonSerializerSettings); newSubscriptions = SubscriptionsMapper.FromDto(newSubscriptionsDto); } else diff --git a/OF DL.Core/Services/AuthService.cs b/OF DL.Core/Services/AuthService.cs index 2755bfb..45c7afc 100644 --- a/OF DL.Core/Services/AuthService.cs +++ b/OF DL.Core/Services/AuthService.cs @@ -136,6 +136,11 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService return; } + if (string.IsNullOrWhiteSpace(CurrentAuth.Cookie)) + { + return; + } + string pattern = @"(auth_id=\d+)|(sess=[^;]+)"; MatchCollection matches = Regex.Matches(CurrentAuth.Cookie, pattern); @@ -176,7 +181,7 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService } } - private async Task GetAuthFromBrowser(bool isDocker = false) + private async Task GetAuthFromBrowser() { try { diff --git a/OF DL.Core/Services/ConfigService.cs b/OF DL.Core/Services/ConfigService.cs index d34b3d3..d49a987 100644 --- a/OF DL.Core/Services/ConfigService.cs +++ b/OF DL.Core/Services/ConfigService.cs @@ -44,7 +44,7 @@ public class ConfigService(ILoggingService loggingService) : IConfigService } // Check for command-line arguments - if (args != null && args.Length > 0) + if (args.Length > 0) { const string NON_INTERACTIVE_ARG = "--non-interactive"; if (args.Any(a => a.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase))) @@ -71,12 +71,6 @@ public class ConfigService(ILoggingService loggingService) : IConfigService public async Task SaveConfigurationAsync(string filePath = "config.conf") { - if (CurrentConfig == null) - { - Log.Warning("Attempted to save null config to file"); - return; - } - try { string hoconConfig = BuildHoconFromConfig(CurrentConfig); @@ -409,7 +403,13 @@ public class ConfigService(ILoggingService loggingService) : IConfigService ToggleableConfigAttribute? attr = propInfo.GetCustomAttribute(); if (attr != null) { - result.Add((propInfo.Name, (bool)propInfo.GetValue(CurrentConfig)!)); + bool? value = (bool?)propInfo.GetValue(CurrentConfig); + if (value == null) + { + continue; + } + + result.Add((propInfo.Name, value.Value)); } } @@ -427,10 +427,16 @@ public class ConfigService(ILoggingService loggingService) : IConfigService if (attr != null) { bool newValue = selectedNames.Contains(propInfo.Name); - bool oldValue = (bool)propInfo.GetValue(CurrentConfig)!; + bool? oldValue = (bool?)propInfo.GetValue(CurrentConfig); + + if (oldValue == null) + { + continue; + } + propInfo.SetValue(newConfig, newValue); - if (newValue != oldValue) + if (newValue != oldValue.Value) { configChanged = true; } diff --git a/OF DL.Core/Services/DBService.cs b/OF DL.Core/Services/DBService.cs index 2ff6c29..c9b6df0 100644 --- a/OF DL.Core/Services/DBService.cs +++ b/OF DL.Core/Services/DBService.cs @@ -219,17 +219,17 @@ public class DBService(IConfigService configService) : IDBService connection)) { checkCmd.Parameters.AddWithValue("@userId", user.Value); - using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync()) + await using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync()) { if (reader.Read()) { - long storedUserId = reader.GetInt64(0); string storedUsername = reader.GetString(1); if (storedUsername != user.Key) { - using (SqliteCommand updateCmd = - new("UPDATE users SET username = @newUsername WHERE user_id = @userId;", connection)) + await using (SqliteCommand updateCmd = + new("UPDATE users SET username = @newUsername WHERE user_id = @userId;", + connection)) { updateCmd.Parameters.AddWithValue("@newUsername", user.Key); updateCmd.Parameters.AddWithValue("@userId", user.Value); @@ -278,13 +278,13 @@ public class DBService(IConfigService configService) : IDBService if (count == 0) { // If the record doesn't exist, insert a new one - using SqliteCommand insertCmd = + await using SqliteCommand insertCmd = new( "INSERT INTO messages(post_id, text, price, paid, archived, created_at, user_id, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @user_id, @record_created_at)", connection); insertCmd.Parameters.AddWithValue("@post_id", post_id); - insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value); - insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value); + insertCmd.Parameters.AddWithValue("@message_text", message_text); + insertCmd.Parameters.AddWithValue("@price", price); insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@created_at", created_at); @@ -328,8 +328,8 @@ public class DBService(IConfigService configService) : IDBService "INSERT INTO posts(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)", connection); insertCmd.Parameters.AddWithValue("@post_id", post_id); - insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value); - insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value); + insertCmd.Parameters.AddWithValue("@message_text", message_text); + insertCmd.Parameters.AddWithValue("@price", price); insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@created_at", created_at); @@ -372,8 +372,8 @@ public class DBService(IConfigService configService) : IDBService "INSERT INTO stories(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)", connection); insertCmd.Parameters.AddWithValue("@post_id", post_id); - insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value); - insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value); + insertCmd.Parameters.AddWithValue("@message_text", message_text); + insertCmd.Parameters.AddWithValue("@price", price); insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@created_at", created_at); @@ -445,23 +445,21 @@ public class DBService(IConfigService configService) : IDBService { try { - bool downloaded = false; + bool downloaded; - using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db")) + await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); + StringBuilder sql = new("SELECT downloaded FROM medias WHERE media_id=@media_id"); + if (configService.CurrentConfig.DownloadDuplicatedMedia) { - StringBuilder sql = new("SELECT downloaded FROM medias WHERE media_id=@media_id"); - if (configService.CurrentConfig.DownloadDuplicatedMedia) - { - sql.Append(" and api_type=@api_type"); - } - - connection.Open(); - using SqliteCommand cmd = new(sql.ToString(), connection); - cmd.Parameters.AddWithValue("@media_id", media_id); - cmd.Parameters.AddWithValue("@api_type", api_type); - downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync()); + sql.Append(" and api_type=@api_type"); } + connection.Open(); + await using SqliteCommand cmd = new(sql.ToString(), connection); + cmd.Parameters.AddWithValue("@media_id", media_id); + cmd.Parameters.AddWithValue("@api_type", api_type); + downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync()); + return downloaded; } catch (Exception ex) diff --git a/OF DL.Core/Services/DownloadOrchestrationService.cs b/OF DL.Core/Services/DownloadOrchestrationService.cs index 288d7c4..ebf2d08 100644 --- a/OF DL.Core/Services/DownloadOrchestrationService.cs +++ b/OF DL.Core/Services/DownloadOrchestrationService.cs @@ -144,9 +144,9 @@ public class DownloadOrchestrationService( counts.PaidPostCount = await DownloadContentTypeAsync("Paid Posts", async statusReporter => await apiService.GetPaidPosts("/posts/paid/post", path, username, PaidPostIds, statusReporter), - posts => posts?.PaidPosts?.Count ?? 0, - posts => posts?.PaidPostObjects?.Count ?? 0, - posts => posts?.PaidPosts?.Values?.ToList(), + posts => posts.PaidPosts.Count, + posts => posts.PaidPostObjects.Count, + posts => posts.PaidPosts.Values.ToList(), async (posts, reporter) => await downloadService.DownloadPaidPosts(username, userId, path, users, clientIdBlobMissing, devicePrivateKeyMissing, posts, reporter), eventHandler); @@ -161,9 +161,9 @@ public class DownloadOrchestrationService( counts.PostCount = await DownloadContentTypeAsync("Posts", async statusReporter => await apiService.GetPosts($"/users/{userId}/posts", path, PaidPostIds, statusReporter), - posts => posts?.Posts?.Count ?? 0, - posts => posts?.PostObjects?.Count ?? 0, - posts => posts?.Posts?.Values?.ToList(), + posts => posts.Posts.Count, + posts => posts.PostObjects.Count, + posts => posts.Posts.Values.ToList(), async (posts, reporter) => await downloadService.DownloadFreePosts(username, userId, path, users, clientIdBlobMissing, devicePrivateKeyMissing, posts, reporter), eventHandler); @@ -174,9 +174,9 @@ public class DownloadOrchestrationService( counts.ArchivedCount = await DownloadContentTypeAsync("Archived Posts", async statusReporter => await apiService.GetArchived($"/users/{userId}/posts", path, statusReporter), - archived => archived?.ArchivedPosts?.Count ?? 0, - archived => archived?.ArchivedPostObjects?.Count ?? 0, - archived => archived?.ArchivedPosts?.Values?.ToList(), + archived => archived.ArchivedPosts.Count, + archived => archived.ArchivedPostObjects.Count, + archived => archived.ArchivedPosts.Values.ToList(), async (archived, reporter) => await downloadService.DownloadArchived(username, userId, path, users, clientIdBlobMissing, devicePrivateKeyMissing, archived, reporter), eventHandler); @@ -187,9 +187,9 @@ public class DownloadOrchestrationService( counts.StreamsCount = await DownloadContentTypeAsync("Streams", async statusReporter => await apiService.GetStreams($"/users/{userId}/posts/streams", path, PaidPostIds, statusReporter), - streams => streams?.Streams?.Count ?? 0, - streams => streams?.StreamObjects?.Count ?? 0, - streams => streams?.Streams?.Values?.ToList(), + streams => streams.Streams.Count, + streams => streams.StreamObjects.Count, + streams => streams.Streams.Values.ToList(), async (streams, reporter) => await downloadService.DownloadStreams(username, userId, path, users, clientIdBlobMissing, devicePrivateKeyMissing, streams, reporter), eventHandler); @@ -256,9 +256,9 @@ public class DownloadOrchestrationService( counts.MessagesCount = await DownloadContentTypeAsync("Messages", async statusReporter => await apiService.GetMessages($"/chats/{userId}/messages", path, statusReporter), - messages => messages.Messages?.Count ?? 0, - messages => messages.MessageObjects?.Count ?? 0, - messages => messages?.Messages.Values.ToList(), + messages => messages.Messages.Count, + messages => messages.MessageObjects.Count, + messages => messages.Messages.Values.ToList(), async (messages, reporter) => await downloadService.DownloadMessages(username, userId, path, users, clientIdBlobMissing, devicePrivateKeyMissing, messages, reporter), eventHandler); @@ -269,9 +269,9 @@ public class DownloadOrchestrationService( counts.PaidMessagesCount = await DownloadContentTypeAsync("Paid Messages", async statusReporter => await apiService.GetPaidMessages("/posts/paid/chat", path, username, statusReporter), - paidMessages => paidMessages?.PaidMessages?.Count ?? 0, - paidMessages => paidMessages?.PaidMessageObjects?.Count ?? 0, - paidMessages => paidMessages?.PaidMessages?.Values?.ToList(), + paidMessages => paidMessages.PaidMessages.Count, + paidMessages => paidMessages.PaidMessageObjects.Count, + paidMessages => paidMessages.PaidMessages.Values.ToList(), async (paidMessages, reporter) => await downloadService.DownloadPaidMessages(username, path, users, clientIdBlobMissing, devicePrivateKeyMissing, paidMessages, reporter), eventHandler); @@ -368,7 +368,7 @@ public class DownloadOrchestrationService( int paidMessagesCount = 0; // Download paid posts - if (purchasedTabCollection.PaidPosts?.PaidPosts?.Count > 0) + if (purchasedTabCollection.PaidPosts.PaidPosts.Count > 0) { eventHandler.OnContentFound("Paid Posts", purchasedTabCollection.PaidPosts.PaidPosts.Count, @@ -396,7 +396,7 @@ public class DownloadOrchestrationService( } // Download paid messages - if (purchasedTabCollection.PaidMessages?.PaidMessages?.Count > 0) + if (purchasedTabCollection.PaidMessages.PaidMessages.Count > 0) { eventHandler.OnContentFound("Paid Messages", purchasedTabCollection.PaidMessages.PaidMessages.Count, diff --git a/OF DL.Core/Services/DownloadService.cs b/OF DL.Core/Services/DownloadService.cs index eaee01f..0fb1f80 100644 --- a/OF DL.Core/Services/DownloadService.cs +++ b/OF DL.Core/Services/DownloadService.cs @@ -25,7 +25,7 @@ public class DownloadService( IAPIService apiService) : IDownloadService { - private TaskCompletionSource _completionSource; + private TaskCompletionSource _completionSource = new(); public async Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username) { @@ -201,7 +201,7 @@ public class DownloadService( Engine ffmpeg = new(configService.CurrentConfig.FFmpegPath); ffmpeg.Error += OnError; - ffmpeg.Complete += async (sender, args) => + ffmpeg.Complete += async (_, _) => { _completionSource.TrySetResult(true); await OnFFMPEGDownloadComplete(tempFilename, lastModified, folder, path, customFileName, filename, @@ -275,14 +275,14 @@ public class DownloadService( } } - private void OnError(object sender, ConversionErrorEventArgs e) + private void OnError(object? sender, ConversionErrorEventArgs e) { // Guard all fields to avoid NullReference exceptions from FFmpeg.NET - string input = e?.Input?.Name ?? ""; - string output = e?.Output?.Name ?? ""; - string exitCode = e?.Exception?.ExitCode.ToString() ?? ""; - string message = e?.Exception?.Message ?? ""; - string inner = e?.Exception?.InnerException?.Message ?? ""; + string input = e.Input?.Name ?? ""; + string output = e.Output?.Name ?? ""; + string exitCode = e.Exception?.ExitCode.ToString() ?? ""; + string message = e.Exception?.Message ?? ""; + string inner = e.Exception?.InnerException?.Message ?? ""; Log.Error("FFmpeg failed. Input={Input} Output={Output} ExitCode={ExitCode} Message={Message} Inner={Inner}", input, output, exitCode, message, inner); @@ -310,7 +310,7 @@ public class DownloadService( string body = await response.Content.ReadAsStringAsync(); XDocument doc = XDocument.Parse(body); XNamespace ns = "urn:mpeg:dash:schema:mpd:2011"; - XNamespace cenc = "urn:mpeg:cenc:2013"; + // XNamespace cenc = "urn:mpeg:cenc:2013"; XElement? videoAdaptationSet = doc .Descendants(ns + "AdaptationSet") .FirstOrDefault(e => (string?)e.Attribute("mimeType") == "video/mp4"); @@ -392,7 +392,6 @@ public class DownloadService( { try { - string customFileName = ""; if (!Directory.Exists(folder + path)) { Directory.CreateDirectory(folder + path); @@ -580,19 +579,19 @@ public class DownloadService( public static async Task GetDRMVideoLastModified(string url, Auth auth) { string[] messageUrlParsed = url.Split(','); - string mpdURL = messageUrlParsed[0]; + string mpdUrl = messageUrlParsed[0]; string policy = messageUrlParsed[1]; string signature = messageUrlParsed[2]; string kvp = messageUrlParsed[3]; - mpdURL = mpdURL.Replace(".mpd", "_source.mp4"); + mpdUrl = mpdUrl.Replace(".mpd", "_source.mp4"); using HttpClient client = new(); client.DefaultRequestHeaders.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.Cookie}"); client.DefaultRequestHeaders.Add("User-Agent", auth.UserAgent); - using HttpResponseMessage response = await client.GetAsync(mpdURL, HttpCompletionOption.ResponseHeadersRead); + using HttpResponseMessage response = await client.GetAsync(mpdUrl, HttpCompletionOption.ResponseHeadersRead); if (response.IsSuccessStatusCode) { if (response.Content.Headers.LastModified != null) @@ -624,8 +623,8 @@ public class DownloadService( /// Processes the download and database update of media. /// /// The folder where the media is stored. - /// The ID of the media. - /// + /// The ID of the media. + /// /// The URL from where to download the media. /// The relative path to the media. /// @@ -634,8 +633,8 @@ public class DownloadService( /// /// A Task resulting in a boolean indicating whether the media is newly downloaded or not. public async Task ProcessMediaDownload(string folder, - long media_id, - string api_type, + long mediaId, + string apiType, string url, string path, string serverFilename, @@ -645,11 +644,11 @@ public class DownloadService( { try { - if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + if (!await dbService.CheckDownloaded(folder, mediaId, apiType)) { return await HandleNewMedia(folder, - media_id, - api_type, + mediaId, + apiType, url, path, serverFilename, @@ -658,11 +657,11 @@ public class DownloadService( progressReporter); } - bool status = await HandlePreviouslyDownloadedMediaAsync(folder, media_id, api_type, progressReporter); + bool status = await HandlePreviouslyDownloadedMediaAsync(folder, mediaId, apiType, progressReporter); if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && serverFilename != resolvedFilename) { - await HandleRenamingOfExistingFilesAsync(folder, media_id, api_type, path, serverFilename, + await HandleRenamingOfExistingFilesAsync(folder, mediaId, apiType, path, serverFilename, resolvedFilename, extension); } @@ -677,9 +676,9 @@ public class DownloadService( } - private async Task HandleRenamingOfExistingFilesAsync(string folder, - long media_id, - string api_type, + private async Task HandleRenamingOfExistingFilesAsync(string folder, + long mediaId, + string apiType, string path, string serverFilename, string resolvedFilename, @@ -689,7 +688,7 @@ public class DownloadService( string fullPathWithTheNewFileName = $"{folder}{path}/{resolvedFilename}{extension}"; if (!File.Exists(fullPathWithTheServerFileName)) { - return false; + return; } try @@ -699,14 +698,13 @@ public class DownloadService( catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); - return false; + return; } - long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + long size = await dbService.GetStoredFileSize(folder, mediaId, apiType); DateTime lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName); - await dbService.UpdateMedia(folder, media_id, api_type, folder + path, resolvedFilename + extension, size, true, + await dbService.UpdateMedia(folder, mediaId, apiType, folder + path, resolvedFilename + extension, size, true, lastModified); - return true; } @@ -1084,17 +1082,13 @@ public class DownloadService( } public async Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo( - string mpdURL, string policy, string signature, string kvp, + string mpdUrl, string policy, string signature, string kvp, string mediaId, string contentId, string drmType, bool clientIdBlobMissing, bool devicePrivateKeyMissing) { - string? pssh = await apiService.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); - if (pssh == null) - { - return null; - } + string pssh = await apiService.GetDRMMPDPSSH(mpdUrl, policy, signature, kvp); - DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); + DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdUrl, policy, signature, kvp); Dictionary drmHeaders = apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/{drmType}/{contentId}", "?type=widevine"); @@ -1220,7 +1214,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadArchived - {username}"); - if (archived == null || archived.ArchivedPosts.Count == 0) + if (archived.ArchivedPosts.Count == 0) { Log.Debug("Found 0 Archived Posts"); return new DownloadResult @@ -1244,8 +1238,9 @@ public class DownloadService( bool isNew; ArchivedEntities.Medium? mediaInfo = archived.ArchivedPostMedia.FirstOrDefault(m => m.Id == archivedKVP.Key); - ArchivedEntities.ListItem? postInfo = - archived.ArchivedPostObjects.FirstOrDefault(p => p?.Media?.Contains(mediaInfo) == true); + ArchivedEntities.ListItem? postInfo = mediaInfo == null + ? null + : archived.ArchivedPostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; @@ -1300,7 +1295,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadMessages - {username}"); - if (messages == null || messages.Messages.Count == 0) + if (messages.Messages.Count == 0) { Log.Debug("Found 0 Messages"); return new DownloadResult @@ -1323,11 +1318,11 @@ public class DownloadService( bool isNew; MessageEntities.Medium? mediaInfo = messages.MessageMedia.FirstOrDefault(m => m.Id == messageKVP.Key); MessageEntities.ListItem? messageInfo = messages.MessageObjects.FirstOrDefault(p => - p?.Media?.Any(m => m.Id == messageKVP.Key) == true); + p.Media?.Any(m => m.Id == messageKVP.Key) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? ""; string messagePath = configService.CurrentConfig.FolderPerMessage && messageInfo != null && - messageInfo?.Id is not null && messageInfo?.CreatedAt is not null + messageInfo.Id != 0 && messageInfo.CreatedAt is not null ? $"/Messages/Free/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}" : "/Messages/Free"; @@ -1383,7 +1378,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadPaidMessages - {username}"); - if (paidMessageCollection == null || paidMessageCollection.PaidMessages.Count == 0) + if (paidMessageCollection.PaidMessages.Count == 0) { Log.Debug("Found 0 Paid Messages"); return new DownloadResult @@ -1408,11 +1403,11 @@ public class DownloadService( MessageEntities.Medium? mediaInfo = paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.Id == kvpEntry.Key); PurchasedEntities.ListItem? messageInfo = paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => - p?.Media?.Any(m => m.Id == kvpEntry.Key) == true); + p.Media?.Any(m => m.Id == kvpEntry.Key) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? ""; string paidMsgPath = configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null && - messageInfo?.Id is not null && messageInfo?.CreatedAt is not null + messageInfo.Id != 0 && messageInfo.CreatedAt is not null ? $"/Messages/Paid/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}" : "/Messages/Paid"; @@ -1465,7 +1460,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadStreams - {username}"); - if (streams == null || streams.Streams.Count == 0) + if (streams.Streams.Count == 0) { Log.Debug("Found 0 Streams"); return new DownloadResult @@ -1487,12 +1482,12 @@ public class DownloadService( { bool isNew; StreamEntities.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.Id == kvpEntry.Key); - StreamEntities.ListItem? streamInfo = - streams.StreamObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); + StreamEntities.ListItem? streamInfo = mediaInfo == null + ? null + : streams.StreamObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; - string streamPath = configService.CurrentConfig.FolderPerPost && streamInfo != null && - streamInfo?.Id is not null && streamInfo?.PostedAt is not null + string streamPath = configService.CurrentConfig.FolderPerPost && streamInfo != null && streamInfo.Id != 0 ? $"/Posts/Free/{streamInfo.Id} {streamInfo.PostedAt:yyyy-MM-dd HH-mm-ss}" : "/Posts/Free"; @@ -1546,7 +1541,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadFreePosts - {username}"); - if (posts == null || posts.Posts.Count == 0) + if (posts.Posts.Count == 0) { Log.Debug("Found 0 Posts"); return new DownloadResult @@ -1566,13 +1561,13 @@ public class DownloadService( foreach (KeyValuePair postKVP in posts.Posts) { bool isNew; - PostEntities.Medium? mediaInfo = posts.PostMedia.FirstOrDefault(m => m?.Id == postKVP.Key); - PostEntities.ListItem? postInfo = - posts.PostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); + PostEntities.Medium? mediaInfo = posts.PostMedia.FirstOrDefault(m => m.Id == postKVP.Key); + PostEntities.ListItem? postInfo = mediaInfo == null + ? null + : posts.PostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; - string postPath = configService.CurrentConfig.FolderPerPost && postInfo != null && - postInfo?.Id is not null && postInfo?.PostedAt is not null + string postPath = configService.CurrentConfig.FolderPerPost && postInfo != null && postInfo.Id != 0 ? $"/Posts/Free/{postInfo.Id} {postInfo.PostedAt:yyyy-MM-dd HH-mm-ss}" : "/Posts/Free"; @@ -1625,7 +1620,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadPaidPosts - {username}"); - if (purchasedPosts == null || purchasedPosts.PaidPosts.Count == 0) + if (purchasedPosts.PaidPosts.Count == 0) { Log.Debug("Found 0 Paid Posts"); return new DownloadResult @@ -1649,11 +1644,11 @@ public class DownloadService( MessageEntities.Medium? mediaInfo = purchasedPosts.PaidPostMedia.FirstOrDefault(m => m.Id == postKVP.Key); PurchasedEntities.ListItem? postInfo = - purchasedPosts.PaidPostObjects.FirstOrDefault(p => p?.Media?.Any(m => m.Id == postKVP.Key) == true); + purchasedPosts.PaidPostObjects.FirstOrDefault(p => p.Media?.Any(m => m.Id == postKVP.Key) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; string paidPostPath = configService.CurrentConfig.FolderPerPaidPost && postInfo != null && - postInfo?.Id is not null && postInfo?.PostedAt is not null + postInfo.Id != 0 && postInfo.PostedAt is not null ? $"/Posts/Paid/{postInfo.Id} {postInfo.PostedAt.Value:yyyy-MM-dd HH-mm-ss}" : "/Posts/Paid"; @@ -1706,7 +1701,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadPaidPostsPurchasedTab - {username}"); - if (purchasedPosts == null || purchasedPosts.PaidPosts.Count == 0) + if (purchasedPosts.PaidPosts.Count == 0) { Log.Debug("Found 0 Paid Posts"); return new DownloadResult { TotalCount = 0, MediaType = "Paid Posts", Success = true }; @@ -1721,12 +1716,12 @@ public class DownloadService( purchasedPosts?.PaidPostMedia?.FirstOrDefault(m => m.Id == purchasedPostKVP.Key); PurchasedEntities.ListItem? postInfo = mediaInfo != null ? purchasedPosts?.PaidPostObjects?.FirstOrDefault(p => - p?.Media?.Any(m => m.Id == purchasedPostKVP.Key) == true) + p.Media?.Any(m => m.Id == purchasedPostKVP.Key) == true) : null; string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) .PaidPostFileNameFormat ?? ""; string paidPostPath = configService.CurrentConfig.FolderPerPaidPost && postInfo != null && - postInfo?.Id is not null && postInfo?.PostedAt is not null + postInfo.Id != 0 && postInfo.PostedAt is not null ? $"/Posts/Paid/{postInfo.Id} {postInfo.PostedAt.Value:yyyy-MM-dd HH-mm-ss}" : "/Posts/Paid"; @@ -1766,7 +1761,7 @@ public class DownloadService( Log.Debug($"Paid Posts Already Downloaded: {oldCount} New Paid Posts Downloaded: {newCount}"); return new DownloadResult { - TotalCount = purchasedPosts.PaidPosts.Count, + TotalCount = purchasedPosts?.PaidPosts.Count ?? 0, NewDownloads = newCount, ExistingDownloads = oldCount, MediaType = "Paid Posts", @@ -1780,7 +1775,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadPaidMessagesPurchasedTab - {username}"); - if (paidMessageCollection == null || paidMessageCollection.PaidMessages.Count == 0) + if (paidMessageCollection.PaidMessages.Count == 0) { Log.Debug("Found 0 Paid Messages"); return new DownloadResult { TotalCount = 0, MediaType = "Paid Messages", Success = true }; @@ -1795,11 +1790,11 @@ public class DownloadService( paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.Id == paidMessageKVP.Key); PurchasedEntities.ListItem? messageInfo = paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => - p?.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); + p.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) .PaidMessageFileNameFormat ?? ""; string paidMsgPath = configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null && - messageInfo?.Id is not null && messageInfo?.CreatedAt is not null + messageInfo.Id != 0 && messageInfo.CreatedAt is not null ? $"/Messages/Paid/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}" : "/Messages/Paid"; @@ -1853,7 +1848,7 @@ public class DownloadService( { Log.Debug($"Calling DownloadSinglePost - {username}"); - if (post == null || post.SinglePosts.Count == 0) + if (post.SinglePosts.Count == 0) { Log.Debug("Couldn't find post"); return new DownloadResult { TotalCount = 0, MediaType = "Posts", Success = true }; @@ -1864,12 +1859,12 @@ public class DownloadService( foreach (KeyValuePair postKVP in post.SinglePosts) { PostEntities.Medium? mediaInfo = post.SinglePostMedia.FirstOrDefault(m => m.Id == postKVP.Key); - PostEntities.SinglePost? postInfo = - post.SinglePostObjects.FirstOrDefault(p => p?.Media?.Contains(mediaInfo) == true); + PostEntities.SinglePost? postInfo = mediaInfo == null + ? null + : post.SinglePostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) .PostFileNameFormat ?? ""; - string postPath = configService.CurrentConfig.FolderPerPost && postInfo != null && - postInfo?.Id is not null && postInfo?.PostedAt is not null + string postPath = configService.CurrentConfig.FolderPerPost && postInfo != null && postInfo.Id != 0 ? $"/Posts/Free/{postInfo.Id} {postInfo.PostedAt:yyyy-MM-dd HH-mm-ss}" : "/Posts/Free"; @@ -1930,12 +1925,7 @@ public class DownloadService( PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection, IProgressReporter progressReporter) { - Log.Debug($"Calling DownloadSinglePaidMessage - {username}"); - - if (singlePaidMessageCollection == null) - { - return new DownloadResult { TotalCount = 0, MediaType = "Paid Messages", Success = true }; - } + Log.Debug("Calling DownloadSinglePaidMessage - {Username}", username); int totalNew = 0, totalOld = 0; @@ -1949,11 +1939,11 @@ public class DownloadService( m.Id == paidMessageKVP.Key); MessageEntities.SingleMessage? messageInfo = singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p => - p?.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); + p.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) .PaidMessageFileNameFormat ?? ""; string previewMsgPath = configService.CurrentConfig.FolderPerMessage && messageInfo != null && - messageInfo?.Id is not null && messageInfo?.CreatedAt is not null + messageInfo.Id != 0 && messageInfo.CreatedAt is not null ? $"/Messages/Free/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}" : "/Messages/Free"; @@ -2002,12 +1992,12 @@ public class DownloadService( m.Id == paidMessageKVP.Key); MessageEntities.SingleMessage? messageInfo = singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p => - p?.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); + p.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) .PaidMessageFileNameFormat ?? ""; string singlePaidMsgPath = configService.CurrentConfig.FolderPerPaidMessage && - messageInfo != null && messageInfo?.Id is not null && - messageInfo?.CreatedAt is not null + messageInfo != null && messageInfo.Id != 0 && + messageInfo.CreatedAt is not null ? $"/Messages/Paid/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}" : "/Messages/Paid"; diff --git a/OF DL.Core/Services/StartupService.cs b/OF DL.Core/Services/StartupService.cs index 6769345..b33747d 100644 --- a/OF DL.Core/Services/StartupService.cs +++ b/OF DL.Core/Services/StartupService.cs @@ -38,7 +38,7 @@ public class StartupService(IConfigService configService, IAuthService authServi !result.FfmpegPath.Contains(@":\\")) { result.FfmpegPath = result.FfmpegPath.Replace(@"\", @"\\"); - configService.CurrentConfig!.FFmpegPath = result.FfmpegPath; + configService.CurrentConfig.FFmpegPath = result.FfmpegPath; } // Get FFmpeg version @@ -144,7 +144,7 @@ public class StartupService(IConfigService configService, IAuthService authServi private void DetectFfmpeg(StartupResult result) { - if (!string.IsNullOrEmpty(configService.CurrentConfig!.FFmpegPath) && + if (!string.IsNullOrEmpty(configService.CurrentConfig.FFmpegPath) && ValidateFilePath(configService.CurrentConfig.FFmpegPath)) { result.FfmpegFound = true; diff --git a/OF DL.Core/Utils/ThrottledStream.cs b/OF DL.Core/Utils/ThrottledStream.cs index b53b712..0e1b128 100644 --- a/OF DL.Core/Utils/ThrottledStream.cs +++ b/OF DL.Core/Utils/ThrottledStream.cs @@ -67,7 +67,7 @@ public class ThrottledStream : Stream } } - protected async Task ThrottleAsync(int bytes) + private async Task ThrottleAsync(int bytes) { if (!shouldThrottle) { diff --git a/OF DL.Core/Utils/XmlUtils.cs b/OF DL.Core/Utils/XmlUtils.cs index 08d3864..4f9bbf6 100644 --- a/OF DL.Core/Utils/XmlUtils.cs +++ b/OF DL.Core/Utils/XmlUtils.cs @@ -21,6 +21,7 @@ internal static class XmlUtils } catch { + // ignored } return ""; diff --git a/OF DL.Core/Widevine/CDM.cs b/OF DL.Core/Widevine/CDM.cs index 9cb20c3..4dc3cb0 100644 --- a/OF DL.Core/Widevine/CDM.cs +++ b/OF DL.Core/Widevine/CDM.cs @@ -112,7 +112,7 @@ public class CDM { //needed for HBO Max - PSSHBox psshBox = PSSHBox.FromByteArray(initData); + PsshBox psshBox = PsshBox.FromByteArray(initData); cencHeader = Serializer.Deserialize(new MemoryStream(psshBox.Data)); } catch @@ -278,7 +278,7 @@ public class CDM encryptedClientIdProto.EncryptedClientId = mstream.ToArray(); using RSACryptoServiceProvider RSA = new(); - RSA.ImportRSAPublicKey(session.ServiceCertificate.DeviceCertificate.PublicKey, out int bytesRead); + RSA.ImportRSAPublicKey(session.ServiceCertificate.DeviceCertificate.PublicKey, out int _); encryptedClientIdProto.EncryptedPrivacyKey = RSA.Encrypt(aesProvider.Key, RSAEncryptionPadding.OaepSHA1); encryptedClientIdProto.EncryptedClientIdIv = aesProvider.IV; encryptedClientIdProto.ServiceId = diff --git a/OF DL.Core/Widevine/CDMApi.cs b/OF DL.Core/Widevine/CDMApi.cs index f00c83b..ba952dc 100644 --- a/OF DL.Core/Widevine/CDMApi.cs +++ b/OF DL.Core/Widevine/CDMApi.cs @@ -29,5 +29,13 @@ public class CDMApi CDM.ProvideLicense(SessionId, Convert.FromBase64String(licenseB64)); } - public List GetKeys() => CDM.GetKeys(SessionId); + public List GetKeys() + { + if (SessionId == null) + { + throw new Exception("No session ID set. Could not get keys"); + } + + return CDM.GetKeys(SessionId); + } } diff --git a/OF DL.Core/Widevine/CDMDevice.cs b/OF DL.Core/Widevine/CDMDevice.cs index 61ba112..c17f5eb 100644 --- a/OF DL.Core/Widevine/CDMDevice.cs +++ b/OF DL.Core/Widevine/CDMDevice.cs @@ -43,6 +43,10 @@ public class CDMDevice using StreamReader reader = File.OpenText(privateKeyPath); DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject(); } + else + { + throw new Exception("No device private key found"); + } if (vmpBytes != null) { diff --git a/OF DL.Core/Widevine/PSSHBox.cs b/OF DL.Core/Widevine/PsshBox.cs similarity index 64% rename from OF DL.Core/Widevine/PSSHBox.cs rename to OF DL.Core/Widevine/PsshBox.cs index 0e59099..1291f0a 100644 --- a/OF DL.Core/Widevine/PSSHBox.cs +++ b/OF DL.Core/Widevine/PsshBox.cs @@ -1,34 +1,34 @@ namespace OF_DL.Widevine; -internal class PSSHBox +internal class PsshBox { - private static readonly byte[] PSSH_HEADER = new byte[] { 0x70, 0x73, 0x73, 0x68 }; + private static readonly byte[] s_psshHeader = [0x70, 0x73, 0x73, 0x68]; - private PSSHBox(List kids, byte[] data) + private PsshBox(List kids, byte[] data) { KIDs = kids; Data = data; } - public List KIDs { get; set; } = new(); + public List KIDs { get; set; } public byte[] Data { get; set; } - public static PSSHBox FromByteArray(byte[] psshbox) + public static PsshBox FromByteArray(byte[] psshbox) { using MemoryStream stream = new(psshbox); stream.Seek(4, SeekOrigin.Current); byte[] header = new byte[4]; - stream.Read(header, 0, 4); + stream.ReadExactly(header, 0, 4); - if (!header.SequenceEqual(PSSH_HEADER)) + if (!header.SequenceEqual(s_psshHeader)) { throw new Exception("Not a pssh box"); } stream.Seek(20, SeekOrigin.Current); byte[] kidCountBytes = new byte[4]; - stream.Read(kidCountBytes, 0, 4); + stream.ReadExactly(kidCountBytes, 0, 4); if (BitConverter.IsLittleEndian) { @@ -41,12 +41,12 @@ internal class PSSHBox for (int i = 0; i < kidCount; i++) { byte[] kid = new byte[16]; - stream.Read(kid); + stream.ReadExactly(kid); kids.Add(kid); } byte[] dataLengthBytes = new byte[4]; - stream.Read(dataLengthBytes); + stream.ReadExactly(dataLengthBytes); if (BitConverter.IsLittleEndian) { @@ -57,12 +57,12 @@ internal class PSSHBox if (dataLength == 0) { - return new PSSHBox(kids, []); + return new PsshBox(kids, []); } byte[] data = new byte[dataLength]; - stream.Read(data); + stream.ReadExactly(data); - return new PSSHBox(kids, data); + return new PsshBox(kids, data); } }