using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OF_DL.Entities; using OF_DL.Entities.Archived; using OF_DL.Entities.Highlights; using OF_DL.Entities.Lists; using OF_DL.Entities.Messages; using OF_DL.Entities.Post; using OF_DL.Entities.Purchased; using OF_DL.Entities.Stories; using OF_DL.Entities.Streams; using OF_DL.Enumerations; using OF_DL.Enumurations; using Serilog; using Spectre.Console; using System.Globalization; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Xml.Linq; using WidevineClient.Widevine; using static WidevineClient.HttpUtil; namespace OF_DL.Helpers; public class APIHelper : IAPIHelper { private static readonly JsonSerializerSettings m_JsonSerializerSettings; private readonly IDBHelper m_DBHelper; private readonly IDownloadConfig downloadConfig; private readonly Auth auth; private static DateTime? cachedDynamicRulesExpiration; private static DynamicRules? cachedDynamicRules; static APIHelper() { m_JsonSerializerSettings = new() { MissingMemberHandling = MissingMemberHandling.Ignore }; } public APIHelper(Auth auth, IDownloadConfig downloadConfig) { this.auth = auth; m_DBHelper = new DBHelper(downloadConfig); this.downloadConfig = downloadConfig; } public Dictionary GetDynamicHeaders(string path, string queryParams) { Log.Debug("Calling GetDynamicHeaders"); Log.Debug($"Path: {path}"); Log.Debug($"Query Params: {queryParams}"); DynamicRules? root; //Check if we have a cached version of the dynamic rules if (cachedDynamicRules != null && cachedDynamicRulesExpiration.HasValue && DateTime.UtcNow < cachedDynamicRulesExpiration) { Log.Debug("Using cached dynamic rules"); root = cachedDynamicRules; } else { //Get rules from GitHub and fallback to local file string? dynamicRulesJSON = GetDynamicRules(); if (!string.IsNullOrEmpty(dynamicRulesJSON)) { Log.Debug("Using dynamic rules from GitHub"); root = JsonConvert.DeserializeObject(dynamicRulesJSON); // Cache the GitHub response for 15 minutes cachedDynamicRules = root; cachedDynamicRulesExpiration = DateTime.UtcNow.AddMinutes(15); } else { Log.Debug("Using dynamic rules from local file"); root = JsonConvert.DeserializeObject(File.ReadAllText("rules.json")); // Cache the dynamic rules from local file to prevent unnecessary disk // operations and frequent call to GitHub. Since the GitHub dynamic rules // are preferred to the local file, the cache time is shorter than when dynamic rules // are successfully retrieved from GitHub. cachedDynamicRules = root; cachedDynamicRulesExpiration = DateTime.UtcNow.AddMinutes(5); } } DateTimeOffset dto = (DateTimeOffset)DateTime.UtcNow; long timestamp = dto.ToUnixTimeMilliseconds(); string input = $"{root!.StaticParam}\n{timestamp}\n{path + queryParams}\n{auth.USER_ID}"; byte[] inputBytes = Encoding.UTF8.GetBytes(input); byte[] hashBytes = SHA1.HashData(inputBytes); string hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); var checksum = root.ChecksumIndexes.Aggregate(0, (current, number) => current + hashString[number]) + root.ChecksumConstant!.Value; var sign = $"{root.Prefix}:{hashString}:{checksum.ToString("X").ToLower()}:{root.Suffix}"; Dictionary headers = new() { { "accept", "application/json, text/plain" }, { "app-token", root.AppToken! }, { "cookie", auth!.COOKIE! }, { "sign", sign }, { "time", timestamp.ToString() }, { "user-id", auth!.USER_ID! }, { "user-agent", auth!.USER_AGENT! }, { "x-bc", auth!.X_BC! } }; return headers; } private async Task BuildHeaderAndExecuteRequests(Dictionary getParams, string endpoint, HttpClient client) { Log.Debug("Calling BuildHeaderAndExecuteRequests"); HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint); using var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); string body = await response.Content.ReadAsStringAsync(); Log.Debug(body); return body; } private async Task BuildHttpRequestMessage(Dictionary getParams, string endpoint) { Log.Debug("Calling BuildHttpRequestMessage"); string queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); Dictionary headers = GetDynamicHeaders($"/api2/v2{endpoint}", queryParams); HttpRequestMessage request = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{queryParams}"); Log.Debug($"Full request URL: {Constants.API_URL}{endpoint}{queryParams}"); foreach (KeyValuePair keyValuePair in headers) { request.Headers.Add(keyValuePair.Key, keyValuePair.Value); } return request; } private static double ConvertToUnixTimestampWithMicrosecondPrecision(DateTime date) { DateTime origin = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); TimeSpan diff = date.ToUniversalTime() - origin; return diff.TotalSeconds; // This gives the number of seconds. If you need milliseconds, use diff.TotalMilliseconds } public static bool IsStringOnlyDigits(string input) { return input.All(char.IsDigit); } private static HttpClient GetHttpClient(IDownloadConfig? config = null) { var client = new HttpClient(); if (config?.Timeout != null && config.Timeout > 0) { client.Timeout = TimeSpan.FromSeconds(config.Timeout.Value); } return client; } /// /// this one is used during initialization only /// if the config option is not available then no modificatiotns will be done on the getParams /// /// /// /// private static void UpdateGetParamsForDateSelection(Enumerations.DownloadDateSelection downloadDateSelection, ref Dictionary getParams, DateTime? dt) { //if (config.DownloadOnlySpecificDates && dt.HasValue) //{ if (dt.HasValue) { UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, ConvertToUnixTimestampWithMicrosecondPrecision(dt.Value).ToString("0.000000", CultureInfo.InvariantCulture) ); } //} } private static void UpdateGetParamsForDateSelection(Enumerations.DownloadDateSelection downloadDateSelection, ref Dictionary getParams, string unixTimeStampInMicrosec) { switch (downloadDateSelection) { case Enumerations.DownloadDateSelection.before: getParams["beforePublishTime"] = unixTimeStampInMicrosec; break; case Enumerations.DownloadDateSelection.after: getParams["order"] = "publish_date_asc"; getParams["afterPublishTime"] = unixTimeStampInMicrosec; break; } } public async Task GetUserInfo(string endpoint) { Log.Debug($"Calling GetUserInfo: {endpoint}"); try { Entities.User? user = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "publish_date_asc" } }; HttpClient client = new(); HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint); using var response = await client.SendAsync(request); if (!response.IsSuccessStatusCode) { return user; } response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); user = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); return user; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetUserInfoById(string endpoint) { try { HttpClient client = new(); HttpRequestMessage request = await BuildHttpRequestMessage(new Dictionary(), endpoint); using var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); //if the content creator doesnt exist, we get a 200 response, but the content isnt usable //so let's not throw an exception, since "content creator no longer exists" is handled elsewhere //which means we wont get loads of exceptions if (body.Equals("[]")) return null; JObject jObject = JObject.Parse(body); return jObject; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task?> GetAllSubscriptions(Dictionary getParams, string endpoint, bool includeRestricted, IDownloadConfig config) { try { Dictionary users = new(); Subscriptions subscriptions = new(); Log.Debug("Calling GetAllSubscrptions"); string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); subscriptions = JsonConvert.DeserializeObject(body); if (subscriptions != null && subscriptions.hasMore) { getParams["offset"] = subscriptions.list.Count.ToString(); while (true) { Subscriptions newSubscriptions = new(); string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); if (!string.IsNullOrEmpty(loopbody) && (!loopbody.Contains("[]") || loopbody.Trim() != "[]")) { newSubscriptions = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); } else { break; } subscriptions.list.AddRange(newSubscriptions.list); if (!newSubscriptions.hasMore) { break; } getParams["offset"] = subscriptions.list.Count.ToString(); } } foreach (Subscriptions.List subscription in subscriptions.list) { if ((!(subscription.isRestricted ?? false) || ((subscription.isRestricted ?? false) && includeRestricted)) && !users.ContainsKey(subscription.username)) { users.Add(subscription.username, subscription.id); } } return users; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task?> GetActiveSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config) { Dictionary getParams = new() { { "offset", "0" }, { "limit", "50" }, { "type", "active" }, { "format", "infinite"} }; return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config); } public async Task?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config) { Dictionary getParams = new() { { "offset", "0" }, { "limit", "50" }, { "type", "expired" }, { "format", "infinite"} }; Log.Debug("Calling GetExpiredSubscriptions"); return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config); } public async Task> GetLists(string endpoint, IDownloadConfig config) { Log.Debug("Calling GetLists"); try { int offset = 0; Dictionary getParams = new() { { "offset", offset.ToString() }, { "skip_users", "all" }, { "limit", "50" }, { "format", "infinite" } }; Dictionary lists = new(); while (true) { string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); if (body == null) { break; } UserList userList = JsonConvert.DeserializeObject(body); if (userList == null) { break; } foreach (UserList.List l in userList.list) { if (IsStringOnlyDigits(l.id) && !lists.ContainsKey(l.name)) { lists.Add(l.name, Convert.ToInt32(l.id)); } } if (userList.hasMore.Value) { offset += 50; getParams["offset"] = Convert.ToString(offset); } else { break; } } return lists; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task?> GetListUsers(string endpoint, IDownloadConfig config) { Log.Debug($"Calling GetListUsers - {endpoint}"); try { int offset = 0; Dictionary getParams = new() { { "offset", offset.ToString() }, { "limit", "50" } }; List users = new(); while (true) { var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); if (body == null) { break; } List? usersList = JsonConvert.DeserializeObject>(body); if (usersList == null || usersList.Count <= 0) { break; } foreach (UsersList ul in usersList) { users.Add(ul.username); } if (users.Count < 50) { break; } offset += 50; getParams["offset"] = Convert.ToString(offset); } return users; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, IDownloadConfig config, List paid_post_ids) { Log.Debug($"Calling GetMedia - {username}"); try { Dictionary return_urls = new(); int post_limit = 50; int limit = 5; int offset = 0; Dictionary getParams = new(); switch (mediatype) { case MediaType.Stories: getParams = new Dictionary { { "limit", post_limit.ToString() }, { "order", "publish_date_desc" } }; break; case MediaType.Highlights: getParams = new Dictionary { { "limit", limit.ToString() }, { "offset", offset.ToString() } }; break; } var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); if (mediatype == MediaType.Stories) { Log.Debug("Media Stories - " + endpoint); var stories = JsonConvert.DeserializeObject>(body, m_JsonSerializerSettings) ?? new List(); foreach (Stories story in stories) { if (story.media[0].createdAt.HasValue) { await m_DBHelper.AddStory(folder, story.id, string.Empty, "0", false, false, story.media[0].createdAt.Value); } else if (story.createdAt.HasValue) { await m_DBHelper.AddStory(folder, story.id, string.Empty, "0", false, false, story.createdAt.Value); } else { await m_DBHelper.AddStory(folder, story.id, string.Empty, "0", false, false, DateTime.Now); } if (story.media != null && story.media.Count > 0) { foreach (Stories.Medium medium in story.media) { await m_DBHelper.AddMedia(folder, medium.id, story.id, medium.files.full.url, null, null, null, "Stories", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), false, false, null); if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (medium.canView && !medium.files.full.url.Contains("upload")) { if (!return_urls.ContainsKey(medium.id)) { return_urls.Add(medium.id, medium.files.full.url); } } } } } } else if (mediatype == MediaType.Highlights) { List highlight_ids = new(); var highlights = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings) ?? new Highlights(); if (highlights.hasMore) { offset += 5; getParams["offset"] = offset.ToString(); while (true) { Highlights newhighlights = new(); Log.Debug("Media Highlights - " + endpoint); var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); newhighlights = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); highlights.list.AddRange(newhighlights.list); if (!newhighlights.hasMore) { break; } offset += 5; getParams["offset"] = offset.ToString(); } } foreach (Highlights.List list in highlights.list) { if (!highlight_ids.Contains(list.id.ToString())) { highlight_ids.Add(list.id.ToString()); } } foreach (string highlight_id in highlight_ids) { HighlightMedia highlightMedia = new(); Dictionary highlight_headers = GetDynamicHeaders("/api2/v2/stories/highlights/" + highlight_id, string.Empty); HttpClient highlight_client = GetHttpClient(config); HttpRequestMessage highlight_request = new(HttpMethod.Get, $"https://onlyfans.com/api2/v2/stories/highlights/{highlight_id}"); foreach (KeyValuePair keyValuePair in highlight_headers) { highlight_request.Headers.Add(keyValuePair.Key, keyValuePair.Value); } using var highlightResponse = await highlight_client.SendAsync(highlight_request); highlightResponse.EnsureSuccessStatusCode(); var highlightBody = await highlightResponse.Content.ReadAsStringAsync(); highlightMedia = JsonConvert.DeserializeObject(highlightBody, m_JsonSerializerSettings); if (highlightMedia != null) { foreach (HighlightMedia.Story item in highlightMedia.stories) { if (item.media[0].createdAt.HasValue) { await m_DBHelper.AddStory(folder, item.id, string.Empty, "0", false, false, item.media[0].createdAt.Value); } else if (item.createdAt.HasValue) { await m_DBHelper.AddStory(folder, item.id, string.Empty, "0", false, false, item.createdAt.Value); } else { await m_DBHelper.AddStory(folder, item.id, string.Empty, "0", false, false, DateTime.Now); } if (item.media.Count > 0 && item.media[0].canView) { foreach (HighlightMedia.Medium medium in item.media) { await m_DBHelper.AddMedia(folder, medium.id, item.id, item.media[0].files.full.url, null, null, null, "Stories", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), false, false, null); if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!return_urls.ContainsKey(medium.id)) { return_urls.Add(medium.id, item.media[0].files.full.url); } } } } } } } return return_urls; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetPaidPosts(string endpoint, string folder, string username, IDownloadConfig config, List paid_post_ids, StatusContext ctx) { Log.Debug($"Calling GetPaidPosts - {username}"); try { Purchased paidPosts = new(); PaidPostCollection paidPostCollection = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "publish_date_desc" }, { "format", "infinite" }, { "user_id", username } }; var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); paidPosts = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Paid Posts\n[/] [red]Found {paidPosts.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (paidPosts != null && paidPosts.hasMore) { getParams["offset"] = paidPosts.list.Count.ToString(); while (true) { Purchased newPaidPosts = new(); var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); newPaidPosts = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); paidPosts.list.AddRange(newPaidPosts.list); ctx.Status($"[red]Getting Paid Posts\n[/] [red]Found {paidPosts.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (!newPaidPosts.hasMore) { break; } getParams["offset"] = Convert.ToString(Convert.ToInt32(getParams["offset"]) + post_limit); } } foreach (Purchased.List purchase in paidPosts.list) { if (purchase.responseType == "post" && purchase.media != null && purchase.media.Count > 0) { List previewids = new(); if (purchase.previews != null) { for (int i = 0; i < purchase.previews.Count; i++) { if (purchase.previews[i] is long previewId) { if (!previewids.Contains(previewId)) { previewids.Add(previewId); } } } } else if (purchase.preview != null) { for (int i = 0; i < purchase.preview.Count; i++) { if (purchase.preview[i] is long previewId) { if (!previewids.Contains(previewId)) { previewids.Add(previewId); } } } } await m_DBHelper.AddPost(folder, purchase.id, purchase.text != null ? purchase.text : string.Empty, purchase.price != null ? purchase.price.ToString() : "0", purchase.price != null && purchase.isOpened ? true : false, purchase.isArchived.HasValue ? purchase.isArchived.Value : false, purchase.createdAt != null ? purchase.createdAt.Value : purchase.postedAt.Value); paidPostCollection.PaidPostObjects.Add(purchase); foreach (Messages.Medium medium in purchase.media) { if (!previewids.Contains(medium.id)) { paid_post_ids.Add(medium.id); } if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } 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) && !medium.files.full.url.Contains("upload")) { if (!paidPostCollection.PaidPosts.ContainsKey(medium.id)) { await m_DBHelper.AddMedia(folder, 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) ? true : false, false, null); paidPostCollection.PaidPosts.Add(medium.id, medium.files.full.url); paidPostCollection.PaidPostMedia.Add(medium); } } else if (!has && medium.canView && medium.files != null && medium.files.drm != null) { if (!paidPostCollection.PaidPosts.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); paidPostCollection.PaidPostMedia.Add(medium); } } } else { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url) && !medium.files.full.url.Contains("upload")) { if (!paidPostCollection.PaidPosts.ContainsKey(medium.id)) { await m_DBHelper.AddMedia(folder, 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) ? true : false, false, null); paidPostCollection.PaidPosts.Add(medium.id, medium.files.full.url); paidPostCollection.PaidPostMedia.Add(medium); } } else if (medium.canView && medium.files != null && medium.files.drm != null) { if (!paidPostCollection.PaidPosts.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); paidPostCollection.PaidPostMedia.Add(medium); } } } } } } return paidPostCollection; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetPosts(string endpoint, string folder, IDownloadConfig config, List paid_post_ids, StatusContext ctx) { Log.Debug($"Calling GetPosts - {endpoint}"); try { Post posts = new(); PostCollection postCollection = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "publish_date_desc" }, { "format", "infinite" } }; Enumerations.DownloadDateSelection downloadDateSelection = Enumerations.DownloadDateSelection.before; DateTime? downloadAsOf = null; if (config.DownloadOnlySpecificDates && config.CustomDate.HasValue) { downloadDateSelection = config.DownloadDateSelection; downloadAsOf = config.CustomDate; } else if (config.DownloadPostsIncrementally) { var mostRecentPostDate = await m_DBHelper.GetMostRecentPostDate(folder); if (mostRecentPostDate.HasValue) { downloadDateSelection = Enumerations.DownloadDateSelection.after; downloadAsOf = mostRecentPostDate.Value.AddMinutes(-5); // Back track a little for a margin of error } } UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, downloadAsOf); var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); posts = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Posts (this may take a long time, depending on the number of Posts the creator has)\n[/] [red]Found {posts.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (posts != null && posts.hasMore) { UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, posts.tailMarker); while (true) { Post newposts = new(); var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); newposts = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); posts.list.AddRange(newposts.list); ctx.Status($"[red]Getting Posts (this may take a long time, depending on the number of Posts the creator has)\n[/] [red]Found {posts.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (!newposts.hasMore) { break; } UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, newposts.tailMarker); } } foreach (Post.List post in posts.list) { if (config.SkipAds) { if (post.rawText != null && (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"))) { continue; } } List postPreviewIds = new(); if (post.preview != null && post.preview.Count > 0) { for (int i = 0; i < post.preview.Count; i++) { if (post.preview[i] is long previewId) { if (!postPreviewIds.Contains(previewId)) { postPreviewIds.Add(previewId); } } } } await m_DBHelper.AddPost(folder, post.id, post.rawText != null ? post.rawText : string.Empty, post.price != null ? post.price.ToString() : "0", post.price != null && post.isOpened ? true : false, post.isArchived, post.postedAt); postCollection.PostObjects.Add(post); if (post.media != null && post.media.Count > 0) { foreach (Post.Medium medium in post.media) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } 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 && !medium.files!.full.url.Contains("upload")) { if (!postCollection.Posts.ContainsKey(medium.id)) { await m_DBHelper.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((long)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 && !medium.files.preview.url.Contains("upload")) { if (!postCollection.Posts.ContainsKey(medium.id)) { await m_DBHelper.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((long)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 (!postCollection.Posts.ContainsKey(medium.id)) { await m_DBHelper.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((long)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}"); postCollection.PostMedia.Add(medium); } } } } } } return postCollection; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetPost(string endpoint, string folder, IDownloadConfig config) { Log.Debug($"Calling GetPost - {endpoint}"); try { SinglePost singlePost = new(); SinglePostCollection singlePostCollection = new(); Dictionary getParams = new() { { "skip_users", "all" } }; var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); singlePost = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); if (singlePost != null) { List postPreviewIds = new(); if (singlePost.preview != null && singlePost.preview.Count > 0) { for (int i = 0; i < singlePost.preview.Count; i++) { if (singlePost.preview[i] is long previewId) { if (!postPreviewIds.Contains(previewId)) { postPreviewIds.Add(previewId); } } } } await m_DBHelper.AddPost(folder, singlePost.id, singlePost.text != null ? singlePost.text : string.Empty, singlePost.price != null ? singlePost.price.ToString() : "0", singlePost.price != null && singlePost.isOpened ? true : false, singlePost.isArchived, singlePost.postedAt); singlePostCollection.SinglePostObjects.Add(singlePost); if (singlePost.media != null && singlePost.media.Count > 0) { foreach (SinglePost.Medium medium in singlePost.media) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (medium.canView && medium.files?.drm == null) { switch (downloadConfig.DownloadVideoResolution) { case VideoResolution.source: if (medium.files!.full != null && !string.IsNullOrEmpty(medium.files!.full.url)) { if (!medium.files!.full.url.Contains("upload")) { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { await m_DBHelper.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((long)medium.id) ? true : false, false, null); singlePostCollection.SinglePosts.Add(medium.id, medium.files!.full.url); singlePostCollection.SinglePostMedia.Add(medium); } } } break; case VideoResolution._240: if(medium.videoSources != null) { if (!string.IsNullOrEmpty(medium.videoSources._240)) { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { await m_DBHelper.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((long)medium.id) ? true : false, false, null); singlePostCollection.SinglePosts.Add(medium.id, medium.videoSources._240); singlePostCollection.SinglePostMedia.Add(medium); } } } break; case VideoResolution._720: if (medium.videoSources != null) { if (!string.IsNullOrEmpty(medium.videoSources._720)) { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { await m_DBHelper.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((long)medium.id) ? true : false, false, null); singlePostCollection.SinglePosts.Add(medium.id, medium.videoSources._720); 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 m_DBHelper.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((long)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) { if (!medium.files.preview.url.Contains("upload")) { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { await m_DBHelper.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((long)medium.id) ? true : false, false, null); singlePostCollection.SinglePosts.Add(medium.id, medium.files.preview.url); singlePostCollection.SinglePostMedia.Add(medium); } } } } } } return singlePostCollection; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetStreams(string endpoint, string folder, IDownloadConfig config, List paid_post_ids, StatusContext ctx) { Log.Debug($"Calling GetStreams - {endpoint}"); try { Streams streams = new(); StreamsCollection streamsCollection = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "publish_date_desc" }, { "format", "infinite" } }; Enumerations.DownloadDateSelection downloadDateSelection = Enumerations.DownloadDateSelection.before; if (config.DownloadOnlySpecificDates && config.CustomDate.HasValue) { downloadDateSelection = config.DownloadDateSelection; } UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, config.CustomDate); var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); streams = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (streams != null && streams.hasMore) { UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, streams.tailMarker); while (true) { Streams newstreams = new(); var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); newstreams = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); streams.list.AddRange(newstreams.list); ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (!newstreams.hasMore) { break; } UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, newstreams.tailMarker); } } foreach (Streams.List stream in streams.list) { List streamPreviewIds = new(); if (stream.preview != null && stream.preview.Count > 0) { for (int i = 0; i < stream.preview.Count; i++) { if (stream.preview[i] is long previewId) { if (!streamPreviewIds.Contains(previewId)) { streamPreviewIds.Add(previewId); } } } } await m_DBHelper.AddPost(folder, stream.id, stream.text != null ? stream.text : string.Empty, stream.price != null ? stream.price.ToString() : "0", stream.price != null && stream.isOpened ? true : false, stream.isArchived, stream.postedAt); streamsCollection.StreamObjects.Add(stream); if (stream.media != null && stream.media.Count > 0) { foreach (Streams.Medium medium in stream.media) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } 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) && !medium.files.full.url.Contains("upload")) { if (!streamsCollection.Streams.ContainsKey(medium.id)) { await m_DBHelper.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((long)medium.id) ? true : false, false, null); streamsCollection.Streams.Add(medium.id, medium.files.full.url); streamsCollection.StreamMedia.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 (!streamsCollection.Streams.ContainsKey(medium.id)) { await m_DBHelper.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((long)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); } } } } } } return streamsCollection; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetArchived(string endpoint, string folder, IDownloadConfig config, StatusContext ctx) { Log.Debug($"Calling GetArchived - {endpoint}"); try { Archived archived = new(); ArchivedCollection archivedCollection = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "publish_date_desc" }, { "skip_users", "all" }, { "format", "infinite" }, { "label", "archived" }, { "counters", "1" } }; Enumerations.DownloadDateSelection downloadDateSelection = Enumerations.DownloadDateSelection.before; if (config.DownloadOnlySpecificDates && config.CustomDate.HasValue) { downloadDateSelection = config.DownloadDateSelection; } UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, config.CustomDate); var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); archived = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Archived Posts\n[/] [red]Found {archived.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (archived != null && archived.hasMore) { UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, archived.tailMarker); while (true) { Archived newarchived = new(); var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); newarchived = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); archived.list.AddRange(newarchived.list); ctx.Status($"[red]Getting Archived Posts\n[/] [red]Found {archived.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (!newarchived.hasMore) { break; } UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, newarchived.tailMarker); } } foreach (Archived.List archive in archived.list) { List previewids = new(); if (archive.preview != null) { for (int i = 0; i < archive.preview.Count; i++) { if (archive.preview[i] is long previewId) { if (!previewids.Contains(previewId)) { previewids.Add(previewId); } } } } await m_DBHelper.AddPost(folder, archive.id, archive.text != null ? archive.text : string.Empty, archive.price != null ? archive.price.ToString() : "0", archive.price != null && archive.isOpened ? true : false, archive.isArchived, archive.postedAt); archivedCollection.ArchivedPostObjects.Add(archive); if (archive.media != null && archive.media.Count > 0) { foreach (Archived.Medium medium in archive.media) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url) && !medium.files.full.url.Contains("upload")) { if (!archivedCollection.ArchivedPosts.ContainsKey(medium.id)) { await m_DBHelper.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); archivedCollection.ArchivedPostMedia.Add(medium); } } else if (medium.canView && medium.files != null && medium.files.drm != null) { if (!archivedCollection.ArchivedPosts.ContainsKey(medium.id)) { await m_DBHelper.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); 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}"); archivedCollection.ArchivedPostMedia.Add(medium); } } } } } return archivedCollection; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetMessages(string endpoint, string folder, IDownloadConfig config, StatusContext ctx) { Log.Debug($"Calling GetMessages - {endpoint}"); try { Messages messages = new(); MessageCollection messageCollection = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "desc" } }; var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); messages = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Messages\n[/] [red]Found {messages.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (messages.hasMore) { getParams["id"] = messages.list[^1].id.ToString(); while (true) { Messages newmessages = new(); var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); newmessages = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); messages.list.AddRange(newmessages.list); ctx.Status($"[red]Getting Messages\n[/] [red]Found {messages.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (!newmessages.hasMore) { break; } getParams["id"] = newmessages.list[newmessages.list.Count - 1].id.ToString(); } } foreach (Messages.List list in messages.list) { if (config.SkipAds) { if (list.text != null && (list.text.Contains("#ad") || list.text.Contains("/trial/"))) { continue; } } List messagePreviewIds = new(); if (list.previews != null && list.previews.Count > 0) { for (int i = 0; i < list.previews.Count; i++) { if (list.previews[i] is long previewId) { if (!messagePreviewIds.Contains(previewId)) { messagePreviewIds.Add(previewId); } } } } if (!config.IgnoreOwnMessages || list.fromUser.id != Convert.ToInt32(auth.USER_ID)) { await m_DBHelper.AddMessage(folder, list.id, list.text != null ? list.text : string.Empty, list.price != null ? list.price.ToString() : "0", list.canPurchaseReason == "opened" ? true : list.canPurchaseReason != "opened" ? false : (bool?)null ?? false, false, list.createdAt.HasValue ? list.createdAt.Value : DateTime.Now, list.fromUser != null && list.fromUser.id != null ? list.fromUser.id.Value : int.MinValue); messageCollection.MessageObjects.Add(list); if (list.canPurchaseReason != "opened" && list.media != null && list.media.Count > 0) { foreach (Messages.Medium medium in list.media) { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url) && !medium.files.full.url.Contains("upload")) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!messageCollection.Messages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); messageCollection.Messages.Add(medium.id, medium.files.full.url); messageCollection.MessageMedia.Add(medium); } } else if (medium.canView && medium.files != null && medium.files.drm != null) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!messageCollection.Messages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); messageCollection.MessageMedia.Add(medium); } } } } else if (messagePreviewIds.Count > 0) { foreach (Messages.Medium medium in list.media) { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url) && !medium.files.full.url.Contains("upload") && messagePreviewIds.Contains(medium.id)) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!messageCollection.Messages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); messageCollection.Messages.Add(medium.id, medium.files.full.url); messageCollection.MessageMedia.Add(medium); } } else if (medium.canView && medium.files != null && medium.files.drm != null && messagePreviewIds.Contains(medium.id)) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!messageCollection.Messages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); messageCollection.MessageMedia.Add(medium); } } } } } } return messageCollection; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetPaidMessage(string endpoint, string folder, IDownloadConfig config) { Log.Debug($"Calling GetPaidMessage - {endpoint}"); try { SingleMessage message = new(); SinglePaidMessageCollection singlePaidMessageCollection = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "desc" } }; var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); message = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); if (!config.IgnoreOwnMessages || message.fromUser.id != Convert.ToInt32(auth.USER_ID)) { await m_DBHelper.AddMessage(folder, message.id, message.text != null ? message.text : string.Empty, message.price != null ? message.price.ToString() : "0", true, false, message.createdAt.HasValue ? message.createdAt.Value : DateTime.Now, message.fromUser != null && message.fromUser.id != null ? message.fromUser.id.Value : int.MinValue); singlePaidMessageCollection.SingleMessageObjects.Add(message); List messagePreviewIds = new(); if (message.previews != null && message.previews.Count > 0) { for (int i = 0; i < message.previews.Count; i++) { if (message.previews[i] is long previewId) { if (!messagePreviewIds.Contains(previewId)) { messagePreviewIds.Add(previewId); } } } } if (message.media != null && message.media.Count > 0) { foreach (Messages.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) && !medium.files.full.url.Contains("upload")) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!singlePaidMessageCollection.SingleMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); singlePaidMessageCollection.SingleMessages.Add(medium.id, medium.files.full.url.ToString()); singlePaidMessageCollection.SingleMessageMedia.Add(medium); } } else if (!messagePreviewIds.Contains(medium.id) && medium.canView && medium.files != null && medium.files.drm != null) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!singlePaidMessageCollection.SingleMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); singlePaidMessageCollection.SingleMessageMedia.Add(medium); } } } } } return singlePaidMessageCollection; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetPaidMessages(string endpoint, string folder, string username, IDownloadConfig config, StatusContext ctx) { Log.Debug($"Calling GetPaidMessages - {username}"); try { Purchased paidMessages = new(); PaidMessageCollection paidMessageCollection = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "publish_date_desc" }, { "format", "infinite" }, { "user_id", username } }; var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); paidMessages = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Paid Messages\n[/] [red]Found {paidMessages.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (paidMessages != null && paidMessages.hasMore) { getParams["offset"] = paidMessages.list.Count.ToString(); while (true) { string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); Purchased newpaidMessages = new(); Dictionary loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams); HttpClient loopclient = GetHttpClient(config); HttpRequestMessage looprequest = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{loopqueryParams}"); foreach (KeyValuePair keyValuePair in loopheaders) { looprequest.Headers.Add(keyValuePair.Key, keyValuePair.Value); } using (var loopresponse = await loopclient.SendAsync(looprequest)) { loopresponse.EnsureSuccessStatusCode(); var loopbody = await loopresponse.Content.ReadAsStringAsync(); newpaidMessages = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); } paidMessages.list.AddRange(newpaidMessages.list); ctx.Status($"[red]Getting Paid Messages\n[/] [red]Found {paidMessages.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("blue")); if (!newpaidMessages.hasMore) { break; } getParams["offset"] = Convert.ToString(Convert.ToInt32(getParams["offset"]) + post_limit); } } if (paidMessages.list != null && paidMessages.list.Count > 0) { foreach (Purchased.List purchase in paidMessages.list.Where(p => p.responseType == "message").OrderByDescending(p => p.postedAt ?? p.createdAt)) { if (!config.IgnoreOwnMessages || purchase.fromUser.id != Convert.ToInt32(auth.USER_ID)) { if (purchase.postedAt != null) { await m_DBHelper.AddMessage(folder, purchase.id, purchase.text != null ? purchase.text : string.Empty, purchase.price != null ? purchase.price : "0", true, false, purchase.postedAt.Value, purchase.fromUser.id); } else { await m_DBHelper.AddMessage(folder, purchase.id, purchase.text != null ? purchase.text : string.Empty, purchase.price != null ? purchase.price : "0", true, false, purchase.createdAt.Value, purchase.fromUser.id); } paidMessageCollection.PaidMessageObjects.Add(purchase); if (purchase.media != null && purchase.media.Count > 0) { List previewids = new(); if (purchase.previews != null) { for (int i = 0; i < purchase.previews.Count; i++) { if (purchase.previews[i] is long previewId) { if (!previewids.Contains(previewId)) { previewids.Add(previewId); } } } } else if (purchase.preview != null) { for (int i = 0; i < purchase.preview.Count; i++) { if (purchase.preview[i] is long previewId) { if (!previewids.Contains(previewId)) { previewids.Add(previewId); } } } } foreach (Messages.Medium medium in purchase.media) { 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) && !medium.files.full.url.Contains("upload")) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!paidMessageCollection.PaidMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); paidMessageCollection.PaidMessages.Add(medium.id, medium.files.full.url); paidMessageCollection.PaidMessageMedia.Add(medium); } } else if (!has && medium.canView && medium.files != null && medium.files.drm != null) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!paidMessageCollection.PaidMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); paidMessageCollection.PaidMessageMedia.Add(medium); } } } else { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url) && !medium.files.full.url.Contains("upload")) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!paidMessageCollection.PaidMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); paidMessageCollection.PaidMessages.Add(medium.id, medium.files.full.url); paidMessageCollection.PaidMessageMedia.Add(medium); } } else if (medium.canView && medium.files != null && medium.files.drm != null) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!paidMessageCollection.PaidMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); paidMessageCollection.PaidMessageMedia.Add(medium); } } } } } } } } return paidMessageCollection; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary users) { Log.Debug($"Calling GetPurchasedTabUsers - {endpoint}"); try { Dictionary purchasedTabUsers = new(); Purchased purchased = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "publish_date_desc" }, { "format", "infinite" } }; var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); purchased = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); if (purchased != null && purchased.hasMore) { getParams["offset"] = purchased.list.Count.ToString(); while (true) { string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); Purchased newPurchased = new(); Dictionary loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams); HttpClient loopclient = GetHttpClient(config); HttpRequestMessage looprequest = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{loopqueryParams}"); foreach (KeyValuePair keyValuePair in loopheaders) { looprequest.Headers.Add(keyValuePair.Key, keyValuePair.Value); } using (var loopresponse = await loopclient.SendAsync(looprequest)) { loopresponse.EnsureSuccessStatusCode(); var loopbody = await loopresponse.Content.ReadAsStringAsync(); newPurchased = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); } purchased.list.AddRange(newPurchased.list); if (!newPurchased.hasMore) { break; } getParams["offset"] = Convert.ToString(Convert.ToInt32(getParams["offset"]) + post_limit); } } if (purchased.list != null && purchased.list.Count > 0) { foreach (Purchased.List purchase in purchased.list.OrderByDescending(p => p.postedAt ?? p.createdAt)) { if (purchase.fromUser != null) { if (users.Values.Contains(purchase.fromUser.id)) { if (!string.IsNullOrEmpty(users.FirstOrDefault(x => x.Value == purchase.fromUser.id).Key)) { if (!purchasedTabUsers.ContainsKey(users.FirstOrDefault(x => x.Value == purchase.fromUser.id).Key)) { purchasedTabUsers.Add(users.FirstOrDefault(x => x.Value == purchase.fromUser.id).Key, purchase.fromUser.id); } } else { if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}")) { purchasedTabUsers.Add($"Deleted User - {purchase.fromUser.id}", purchase.fromUser.id); } } } else { JObject user = await GetUserInfoById($"/users/list?x[]={purchase.fromUser.id}"); if(user is null) { if (!config.BypassContentForCreatorsWhoNoLongerExist) { if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}")) { purchasedTabUsers.Add($"Deleted User - {purchase.fromUser.id}", purchase.fromUser.id); } } Log.Debug("Content creator not longer exists - {0}", purchase.fromUser.id); } else if (!string.IsNullOrEmpty(user[purchase.fromUser.id.ToString()]["username"].ToString())) { 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); } } } } else if (purchase.author != null) { if (users.Values.Contains(purchase.author.id)) { if (!string.IsNullOrEmpty(users.FirstOrDefault(x => x.Value == purchase.author.id).Key)) { if (!purchasedTabUsers.ContainsKey(users.FirstOrDefault(x => x.Value == purchase.author.id).Key) && users.ContainsKey(users.FirstOrDefault(x => x.Value == purchase.author.id).Key)) { purchasedTabUsers.Add(users.FirstOrDefault(x => x.Value == purchase.author.id).Key, purchase.author.id); } } else { if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}")) { purchasedTabUsers.Add($"Deleted User - {purchase.author.id}", purchase.author.id); } } } else { JObject user = await GetUserInfoById($"/users/list?x[]={purchase.author.id}"); if (user is null) { if (!config.BypassContentForCreatorsWhoNoLongerExist) { if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}")) { purchasedTabUsers.Add($"Deleted User - {purchase.author.id}", purchase.author.id); } } Log.Debug("Content creator not longer exists - {0}", purchase.author.id); } else if (!string.IsNullOrEmpty(user[purchase.author.id.ToString()]["username"].ToString())) { 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); } } } } } } return purchasedTabUsers; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary users) { Log.Debug($"Calling GetPurchasedTab - {endpoint}"); try { Dictionary> userPurchases = new Dictionary>(); List purchasedTabCollections = new(); Purchased purchased = new(); int post_limit = 50; Dictionary getParams = new() { { "limit", post_limit.ToString() }, { "order", "publish_date_desc" }, { "format", "infinite" } }; var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); purchased = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); if (purchased != null && purchased.hasMore) { getParams["offset"] = purchased.list.Count.ToString(); while (true) { string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); Purchased newPurchased = new(); Dictionary loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams); HttpClient loopclient = GetHttpClient(config); HttpRequestMessage looprequest = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{loopqueryParams}"); foreach (KeyValuePair keyValuePair in loopheaders) { looprequest.Headers.Add(keyValuePair.Key, keyValuePair.Value); } using (var loopresponse = await loopclient.SendAsync(looprequest)) { loopresponse.EnsureSuccessStatusCode(); var loopbody = await loopresponse.Content.ReadAsStringAsync(); newPurchased = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); } purchased.list.AddRange(newPurchased.list); if (!newPurchased.hasMore) { break; } getParams["offset"] = Convert.ToString(Convert.ToInt32(getParams["offset"]) + post_limit); } } if (purchased.list != null && purchased.list.Count > 0) { foreach (Purchased.List purchase in purchased.list.OrderByDescending(p => p.postedAt ?? p.createdAt)) { if (purchase.fromUser != null) { if (!userPurchases.ContainsKey(purchase.fromUser.id)) { userPurchases.Add(purchase.fromUser.id, new List()); } userPurchases[purchase.fromUser.id].Add(purchase); } else if (purchase.author != null) { if (!userPurchases.ContainsKey(purchase.author.id)) { userPurchases.Add(purchase.author.id, new List()); } userPurchases[purchase.author.id].Add(purchase); } } } foreach (KeyValuePair> user in userPurchases) { PurchasedTabCollection purchasedTabCollection = new PurchasedTabCollection(); 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 path = System.IO.Path.Combine(folder, purchasedTabCollection.Username); if (Path.Exists(path)) { foreach (Purchased.List purchase in user.Value) { switch (purchase.responseType) { case "post": List previewids = new(); if (purchase.previews != null) { for (int i = 0; i < purchase.previews.Count; i++) { if (purchase.previews[i] is long previewId) { if (!previewids.Contains(previewId)) { previewids.Add(previewId); } } } } else if (purchase.preview != null) { for (int i = 0; i < purchase.preview.Count; i++) { if (purchase.preview[i] is long previewId) { if (!previewids.Contains(previewId)) { previewids.Add(previewId); } } } } await m_DBHelper.AddPost(path, purchase.id, purchase.text != null ? purchase.text : string.Empty, purchase.price != null ? purchase.price.ToString() : "0", purchase.price != null && purchase.isOpened ? true : false, purchase.isArchived.HasValue ? purchase.isArchived.Value : false, purchase.createdAt != null ? purchase.createdAt.Value : purchase.postedAt.Value); purchasedTabCollection.PaidPosts.PaidPostObjects.Add(purchase); foreach (Messages.Medium medium in purchase.media) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } 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) && !medium.files.full.url.Contains("upload")) { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.id, medium.files.full.url); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } } else if (!has && medium.canView && medium.files != null && medium.files.drm != null) { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } } } else { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url) && !medium.files.full.url.Contains("upload")) { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.id, medium.files.full.url); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } } else if (medium.canView && medium.files != null && medium.files.drm != null) { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } } } } break; case "message": if (purchase.postedAt != null) { await m_DBHelper.AddMessage(path, purchase.id, purchase.text != null ? purchase.text : string.Empty, purchase.price != null ? purchase.price : "0", true, false, purchase.postedAt.Value, purchase.fromUser.id); } else { await m_DBHelper.AddMessage(path, purchase.id, purchase.text != null ? purchase.text : string.Empty, purchase.price != null ? purchase.price : "0", true, false, purchase.createdAt.Value, purchase.fromUser.id); } purchasedTabCollection.PaidMessages.PaidMessageObjects.Add(purchase); if (purchase.media != null && purchase.media.Count > 0) { List paidMessagePreviewids = new(); if (purchase.previews != null) { for (int i = 0; i < purchase.previews.Count; i++) { if (purchase.previews[i] is long previewId) { if (!paidMessagePreviewids.Contains(previewId)) { paidMessagePreviewids.Add(previewId); } } } } else if (purchase.preview != null) { for (int i = 0; i < purchase.preview.Count; i++) { if (purchase.preview[i] is long previewId) { if (!paidMessagePreviewids.Contains(previewId)) { paidMessagePreviewids.Add(previewId); } } } } foreach (Messages.Medium medium in purchase.media) { if (paidMessagePreviewids.Count > 0) { 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) && !medium.files.full.url.Contains("upload")) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!purchasedTabCollection.PaidMessages.PaidMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); purchasedTabCollection.PaidMessages.PaidMessages.Add(medium.id, medium.files.full.url); purchasedTabCollection.PaidMessages.PaidMessageMedia.Add(medium); } } else if (!has && medium.canView && medium.files != null && medium.files.drm != null) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!purchasedTabCollection.PaidMessages.PaidMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); purchasedTabCollection.PaidMessages.PaidMessageMedia.Add(medium); } } } else { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url) && !medium.files.full.url.Contains("upload")) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!purchasedTabCollection.PaidMessages.PaidMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, false, null); purchasedTabCollection.PaidMessages.PaidMessages.Add(medium.id, medium.files.full.url); purchasedTabCollection.PaidMessages.PaidMessageMedia.Add(medium); } } else if (medium.canView && medium.files != null && medium.files.drm != null) { if (medium.type == "photo" && !config.DownloadImages) { continue; } if (medium.type == "video" && !config.DownloadVideos) { continue; } if (medium.type == "gif" && !config.DownloadVideos) { continue; } if (medium.type == "audio" && !config.DownloadAudios) { continue; } if (!purchasedTabCollection.PaidMessages.PaidMessages.ContainsKey(medium.id)) { await m_DBHelper.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) ? true : false, 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}"); purchasedTabCollection.PaidMessages.PaidMessageMedia.Add(medium); } } } } } break; } } purchasedTabCollections.Add(purchasedTabCollection); } } return purchasedTabCollections; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp) { try { string pssh = null; HttpClient client = new(); HttpRequestMessage request = new(HttpMethod.Get, mpdUrl); request.Headers.Add("user-agent", auth.USER_AGENT); request.Headers.Add("Accept", "*/*"); request.Headers.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE};"); using (var response = await client.SendAsync(request)) { response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); XNamespace ns = "urn:mpeg:dash:schema:mpd:2011"; XNamespace cenc = "urn:mpeg:cenc:2013"; XDocument xmlDoc = XDocument.Parse(body); var psshElements = xmlDoc.Descendants(cenc + "pssh"); pssh = psshElements.ElementAt(1).Value; } return pssh; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp) { Log.Debug("Calling GetDRMMPDLastModified"); Log.Debug($"mpdUrl: {mpdUrl}"); Log.Debug($"policy: {policy}"); Log.Debug($"signature: {signature}"); Log.Debug($"kvp: {kvp}"); try { DateTime lastmodified; HttpClient client = new(); HttpRequestMessage request = new(HttpMethod.Get, mpdUrl); request.Headers.Add("user-agent", auth.USER_AGENT); request.Headers.Add("Accept", "*/*"); request.Headers.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE};"); using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); lastmodified = response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now; Log.Debug($"Last modified: {lastmodified}"); } return lastmodified; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return DateTime.Now; } public async Task GetDecryptionKeyCDRMProject(Dictionary drmHeaders, string licenceURL, string pssh) { Log.Debug("Calling GetDecryptionKey"); const int maxAttempts = 30; const int delayBetweenAttempts = 3000; int attempt = 0; try { string dcValue = string.Empty; HttpClient client = new(); CDRMProjectRequest cdrmProjectRequest = new CDRMProjectRequest { PSSH = pssh, LicenseURL = licenceURL, Headers = JsonConvert.SerializeObject(drmHeaders), Cookies = "", Data = "" }; string json = JsonConvert.SerializeObject(cdrmProjectRequest); Log.Debug($"Posting to CDRM Project: {json}"); while (attempt < maxAttempts) { attempt++; HttpRequestMessage request = new(HttpMethod.Post, "https://cdrm-project.com/api/decrypt") { Content = new StringContent(json, Encoding.UTF8, "application/json") }; using var response = await client.SendAsync(request); Log.Debug($"CDRM Project Response (Attempt {attempt}): {response.Content.ReadAsStringAsync().Result}"); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); var doc = JsonDocument.Parse(body); if (doc.RootElement.TryGetProperty("status", out JsonElement status)) { if (status.ToString().Trim().Equals("success", StringComparison.OrdinalIgnoreCase)) { dcValue = doc.RootElement.GetProperty("message").GetString().Trim(); return dcValue; } else { Log.Debug($"CDRM response status not successful. Retrying... Attempt {attempt} of {maxAttempts}"); if (attempt < maxAttempts) { await Task.Delay(delayBetweenAttempts); } } } else { Log.Debug($"Status not in CDRM response. Retrying... Attempt {attempt} of {maxAttempts}"); if (attempt < maxAttempts) { await Task.Delay(delayBetweenAttempts); } } } throw new Exception("Maximum retry attempts reached. Unable to get a valid decryption key."); } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetDecryptionKeyOFDL(Dictionary drmHeaders, string licenceURL, string pssh) { Log.Debug("Calling GetDecryptionOFDL"); try { string dcValue = string.Empty; HttpClient client = new(); OFDLRequest ofdlRequest = new OFDLRequest { PSSH = pssh, LicenseURL = licenceURL, Headers = JsonConvert.SerializeObject(drmHeaders) }; string json = JsonConvert.SerializeObject(ofdlRequest); Log.Debug($"Posting to ofdl.tools: {json}"); HttpRequestMessage request = new(HttpMethod.Post, "https://ofdl.tools/WV") { Content = new StringContent(json, Encoding.UTF8, "application/json") }; using var response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { string body = await response.Content.ReadAsStringAsync(); return body; } } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public async Task GetDecryptionKeyCDM(Dictionary drmHeaders, string licenceURL, string pssh) { Log.Debug("Calling GetDecryptionKeyCDM"); try { var resp1 = PostData(licenceURL, drmHeaders, new byte[] { 0x08, 0x04 }); var certDataB64 = Convert.ToBase64String(resp1); var cdm = new CDMApi(); var challenge = cdm.GetChallenge(pssh, certDataB64, false, false); var resp2 = PostData(licenceURL, drmHeaders, challenge); var licenseB64 = Convert.ToBase64String(resp2); Log.Debug($"resp1: {resp1}"); Log.Debug($"certDataB64: {certDataB64}"); Log.Debug($"challenge: {challenge}"); Log.Debug($"resp2: {resp2}"); Log.Debug($"licenseB64: {licenseB64}"); cdm.ProvideLicense(licenseB64); List keys = cdm.GetKeys(); if (keys.Count > 0) { Log.Debug($"GetDecryptionKeyCDM Key: {keys[0].ToString()}"); return keys[0].ToString(); } } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } public static string? GetDynamicRules() { Log.Debug("Calling GetDynamicRules"); try { HttpClient client = new HttpClient(); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://git.ofdl.tools/sim0n00ps/dynamic-rules/raw/branch/main/rules.json"); using var response = client.Send(request); if (!response.IsSuccessStatusCode) { Log.Debug("GetDynamicRules did not return a Success Status Code"); return null; } var body = response.Content.ReadAsStringAsync().Result; Log.Debug("GetDynamicRules Response: "); Log.Debug(body); return body; } catch (Exception ex) { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } } return null; } }