From 4711c537463db4783035cfd5081c6c8ea8a3af3c Mon Sep 17 00:00:00 2001 From: whimsical-c4lic0 Date: Fri, 6 Feb 2026 00:59:07 -0600 Subject: [PATCH] Replace helper classes with services --- OF DL/CLI/SpectreProgressReporter.cs | 28 + OF DL/Entities/Config.cs | 36 +- OF DL/Entities/DownloadResult.cs | 37 + OF DL/Entities/IDownloadConfig.cs | 56 - OF DL/Helpers/AuthHelper.cs | 167 -- OF DL/Helpers/DownloadContext.cs | 24 +- OF DL/Helpers/DownloadHelper.cs | 2040 ------------- OF DL/Helpers/Interfaces/IAuthHelper.cs | 10 - OF DL/Helpers/Interfaces/IDownloadHelper.cs | 45 - OF DL/Program.cs | 1401 +++------ .../APIHelper.cs => Services/APIService.cs} | 454 ++- OF DL/Services/AuthService.cs | 166 +- OF DL/Services/ConfigService.cs | 2 +- .../DBHelper.cs => Services/DBService.cs} | 24 +- OF DL/Services/DownloadService.cs | 2630 +++++++++++++++++ .../FileNameService.cs} | 23 +- .../IAPIHelper.cs => Services/IAPIService.cs} | 34 +- OF DL/Services/IConfigService.cs | 2 +- .../IDBHelper.cs => Services/IDBService.cs} | 4 +- OF DL/Services/IDownloadService.cs | 50 + OF DL/Services/IFileNameService.cs | 8 + OF DL/Services/IProgressReporter.cs | 20 + OF DL/{ => Widevine}/CDMApi.cs | 5 +- 23 files changed, 3657 insertions(+), 3609 deletions(-) create mode 100644 OF DL/CLI/SpectreProgressReporter.cs create mode 100644 OF DL/Entities/DownloadResult.cs delete mode 100644 OF DL/Entities/IDownloadConfig.cs delete mode 100644 OF DL/Helpers/AuthHelper.cs delete mode 100644 OF DL/Helpers/DownloadHelper.cs delete mode 100644 OF DL/Helpers/Interfaces/IAuthHelper.cs delete mode 100644 OF DL/Helpers/Interfaces/IDownloadHelper.cs rename OF DL/{Helpers/APIHelper.cs => Services/APIService.cs} (84%) rename OF DL/{Helpers/DBHelper.cs => Services/DBService.cs} (97%) create mode 100644 OF DL/Services/DownloadService.cs rename OF DL/{Helpers/FileNameHelper.cs => Services/FileNameService.cs} (93%) rename OF DL/{Helpers/Interfaces/IAPIHelper.cs => Services/IAPIService.cs} (60%) rename OF DL/{Helpers/Interfaces/IDBHelper.cs => Services/IDBService.cs} (95%) create mode 100644 OF DL/Services/IDownloadService.cs create mode 100644 OF DL/Services/IFileNameService.cs create mode 100644 OF DL/Services/IProgressReporter.cs rename OF DL/{ => Widevine}/CDMApi.cs (89%) diff --git a/OF DL/CLI/SpectreProgressReporter.cs b/OF DL/CLI/SpectreProgressReporter.cs new file mode 100644 index 0000000..f6c65ab --- /dev/null +++ b/OF DL/CLI/SpectreProgressReporter.cs @@ -0,0 +1,28 @@ +using OF_DL.Services; +using Spectre.Console; + +namespace OF_DL.CLI; + +/// +/// Implementation of IProgressReporter that uses Spectre.Console's ProgressTask for CLI output. +/// +public class SpectreProgressReporter : IProgressReporter +{ + private readonly ProgressTask _task; + + public SpectreProgressReporter(ProgressTask task) + { + _task = task ?? throw new ArgumentNullException(nameof(task)); + } + + public void ReportProgress(long increment) + { + _task.Increment(increment); + } + + public void ReportStatus(string message) + { + // Optionally update task description or handle status messages + // For now, we'll leave this empty as the task description is set when creating the progress bar + } +} diff --git a/OF DL/Entities/Config.cs b/OF DL/Entities/Config.cs index 0934422..7fbc8c9 100644 --- a/OF DL/Entities/Config.cs +++ b/OF DL/Entities/Config.cs @@ -1,11 +1,12 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using OF_DL.Enumerations; +using Serilog; namespace OF_DL.Entities { - public class Config : IDownloadConfig, IFileNameFormatConfig + public class Config : IFileNameFormatConfig { [ToggleableConfig] public bool DownloadAvatarHeaderPhoto { get; set; } = true; @@ -107,6 +108,39 @@ namespace OF_DL.Entities // When enabled, post/message text is stored as-is without XML stripping. [ToggleableConfig] public bool DisableTextSanitization { get; set; } = false; + + public IFileNameFormatConfig GetCreatorFileNameFormatConfig(string username) + { + FileNameFormatConfig createFileNameFormatConfig = new FileNameFormatConfig(); + + Func func = (val1, val2) => + { + if (string.IsNullOrEmpty(val1)) + return val2; + else + return val1; + }; + + if(CreatorConfigs.TryGetValue(username, out CreatorConfig? creatorConfig)) + { + createFileNameFormatConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat; + createFileNameFormatConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat; + createFileNameFormatConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat; + createFileNameFormatConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat; + } + + createFileNameFormatConfig.PaidMessageFileNameFormat = func(createFileNameFormatConfig.PaidMessageFileNameFormat, PaidMessageFileNameFormat); + createFileNameFormatConfig.PostFileNameFormat = func(createFileNameFormatConfig.PostFileNameFormat, PostFileNameFormat); + createFileNameFormatConfig.MessageFileNameFormat = func(createFileNameFormatConfig.MessageFileNameFormat, MessageFileNameFormat); + createFileNameFormatConfig.PaidPostFileNameFormat = func(createFileNameFormatConfig.PaidPostFileNameFormat, PaidPostFileNameFormat); + + Log.Debug("PaidMessageFilenameFormat: {CombinedConfigPaidMessageFileNameFormat}", createFileNameFormatConfig.PaidMessageFileNameFormat); + Log.Debug("PostFileNameFormat: {CombinedConfigPostFileNameFormat}", createFileNameFormatConfig.PostFileNameFormat); + Log.Debug("MessageFileNameFormat: {CombinedConfigMessageFileNameFormat}", createFileNameFormatConfig.MessageFileNameFormat); + Log.Debug("PaidPostFileNameFormat: {CombinedConfigPaidPostFileNameFormat}", createFileNameFormatConfig.PaidPostFileNameFormat); + + return createFileNameFormatConfig; + } } public class CreatorConfig : IFileNameFormatConfig diff --git a/OF DL/Entities/DownloadResult.cs b/OF DL/Entities/DownloadResult.cs new file mode 100644 index 0000000..f44d26d --- /dev/null +++ b/OF DL/Entities/DownloadResult.cs @@ -0,0 +1,37 @@ +namespace OF_DL.Entities; + +/// +/// Represents the result of a download operation. +/// +public class DownloadResult +{ + /// + /// Total number of media items processed. + /// + public int TotalCount { get; set; } + + /// + /// Number of newly downloaded media items. + /// + public int NewDownloads { get; set; } + + /// + /// Number of media items that were already downloaded. + /// + public int ExistingDownloads { get; set; } + + /// + /// The type of media downloaded (e.g., "Posts", "Messages", "Stories", etc.). + /// + public string MediaType { get; set; } = string.Empty; + + /// + /// Indicates whether the download operation was successful. + /// + public bool Success { get; set; } = true; + + /// + /// Optional error message if the download failed. + /// + public string? ErrorMessage { get; set; } +} diff --git a/OF DL/Entities/IDownloadConfig.cs b/OF DL/Entities/IDownloadConfig.cs deleted file mode 100644 index 21565d0..0000000 --- a/OF DL/Entities/IDownloadConfig.cs +++ /dev/null @@ -1,56 +0,0 @@ -using OF_DL.Enumerations; - -namespace OF_DL.Entities -{ - public interface IDownloadConfig - { - bool DownloadAvatarHeaderPhoto { get; set; } - bool DownloadPaidPosts { get; set; } - bool DownloadPosts { get; set; } - bool DownloadArchived { get; set; } - bool DownloadStreams { get; set; } - bool DownloadStories { get; set; } - bool DownloadHighlights { get; set; } - bool DownloadMessages { get; set; } - bool DownloadPaidMessages { get; set; } - bool DownloadImages { get; set; } - bool DownloadVideos { get; set; } - bool DownloadAudios { get; set; } - - VideoResolution DownloadVideoResolution { get; set; } - - int? Timeout { get; set; } - bool FolderPerPaidPost { get; set; } - bool FolderPerPost { get; set; } - bool FolderPerPaidMessage { get; set; } - bool FolderPerMessage { get; set; } - - bool RenameExistingFilesWhenCustomFormatIsSelected { get; set; } - bool ShowScrapeSize { get; set; } - bool LimitDownloadRate { get; set; } - int DownloadLimitInMbPerSec { get; set; } - string? FFmpegPath { get; set; } - - bool SkipAds { get; set; } - bool BypassContentForCreatorsWhoNoLongerExist { get; set; } - - #region Download Date Configurations - - bool DownloadOnlySpecificDates { get; set; } - - // This enum will define if we want data from before or after the CustomDate. - DownloadDateSelection DownloadDateSelection { get; set; } - - // This is the specific date used in combination with the above enum. - DateTime? CustomDate { get; set; } - #endregion - - bool DownloadPostsIncrementally { get; set; } - - bool DownloadDuplicatedMedia { get; set; } - public LoggingLevel LoggingLevel { get; set; } - - bool IgnoreOwnMessages { get; set; } - } - -} diff --git a/OF DL/Helpers/AuthHelper.cs b/OF DL/Helpers/AuthHelper.cs deleted file mode 100644 index 33d522c..0000000 --- a/OF DL/Helpers/AuthHelper.cs +++ /dev/null @@ -1,167 +0,0 @@ -using OF_DL.Entities; -using OF_DL.Helpers.Interfaces; -using PuppeteerSharp; -using PuppeteerSharp.BrowserData; -using Serilog; - -namespace OF_DL.Helpers; - -public class AuthHelper : IAuthHelper -{ - private readonly LaunchOptions _options = new() - { - Headless = false, - Channel = ChromeReleaseChannel.Stable, - DefaultViewport = null, - Args = ["--no-sandbox", "--disable-setuid-sandbox"], - UserDataDir = Path.GetFullPath("chrome-data") - }; - - private readonly string[] _desiredCookies = - [ - "auth_id", - "sess" - ]; - - private const int LoginTimeout = 600000; // 10 minutes - private const int FeedLoadTimeout = 60000; // 1 minute - - public async Task SetupBrowser(bool runningInDocker) - { - string? executablePath = Environment.GetEnvironmentVariable("OFDL_PUPPETEER_EXECUTABLE_PATH"); - if (executablePath != null) - { - Log.Information("OFDL_PUPPETEER_EXECUTABLE_PATH environment variable found. Using browser executable path: {executablePath}", executablePath); - _options.ExecutablePath = executablePath; - } - else - { - var browserFetcher = new BrowserFetcher(); - var installedBrowsers = browserFetcher.GetInstalledBrowsers().ToList(); - if (installedBrowsers.Count == 0) - { - Log.Information("Downloading browser."); - var downloadedBrowser = await browserFetcher.DownloadAsync(); - Log.Information("Browser downloaded. Path: {executablePath}", - downloadedBrowser.GetExecutablePath()); - _options.ExecutablePath = downloadedBrowser.GetExecutablePath(); - } - else - { - _options.ExecutablePath = installedBrowsers.First().GetExecutablePath(); - } - } - - if (runningInDocker) - { - Log.Information("Running in Docker. Disabling sandbox and GPU."); - _options.Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"]; - } - } - - private async Task GetBcToken(IPage page) - { - return await page.EvaluateExpressionAsync("window.localStorage.getItem('bcTokenSha') || ''"); - } - - public async Task GetAuthFromBrowser(bool isDocker = false) - { - try - { - IBrowser? browser; - try - { - browser = await Puppeteer.LaunchAsync(_options); - } - catch (ProcessException e) - { - if (e.Message.Contains("Failed to launch browser") && Directory.Exists(_options.UserDataDir)) - { - Log.Error("Failed to launch browser. Deleting chrome-data directory and trying again."); - Directory.Delete(_options.UserDataDir, true); - browser = await Puppeteer.LaunchAsync(_options); - } - else - { - throw; - } - } - - if (browser == null) - { - throw new Exception("Could not get browser"); - } - - IPage[]? pages = await browser.PagesAsync(); - IPage? page = pages.First(); - - if (page == null) - { - throw new Exception("Could not get page"); - } - - Log.Debug("Navigating to OnlyFans."); - await page.GoToAsync("https://onlyfans.com"); - - Log.Debug("Waiting for user to login"); - await page.WaitForSelectorAsync(".b-feed", new WaitForSelectorOptions { Timeout = LoginTimeout }); - Log.Debug("Feed element detected (user logged in)"); - - await page.ReloadAsync(); - - await page.WaitForNavigationAsync(new NavigationOptions { - WaitUntil = [WaitUntilNavigation.Networkidle2], - Timeout = FeedLoadTimeout - }); - Log.Debug("DOM loaded. Getting BC token and cookies ..."); - - string xBc; - try - { - xBc = await GetBcToken(page); - } - catch (Exception e) - { - throw new Exception("Error getting bcToken"); - } - - Dictionary mappedCookies = (await page.GetCookiesAsync()) - .Where(cookie => cookie.Domain.Contains("onlyfans.com")) - .ToDictionary(cookie => cookie.Name, cookie => cookie.Value); - - mappedCookies.TryGetValue("auth_id", out string? userId); - if (userId == null) - { - throw new Exception("Could not find 'auth_id' cookie"); - } - - mappedCookies.TryGetValue("sess", out string? sess); - if (sess == null) - { - throw new Exception("Could not find 'sess' cookie"); - } - - string? userAgent = await browser.GetUserAgentAsync(); - if (userAgent == null) - { - throw new Exception("Could not get user agent"); - } - - string cookies = string.Join(" ", mappedCookies.Keys.Where(key => _desiredCookies.Contains(key)) - .Select(key => $"${key}={mappedCookies[key]};")); - - return new Auth() - { - COOKIE = cookies, - USER_AGENT = userAgent, - USER_ID = userId, - X_BC = xBc - }; - } - catch (Exception e) - { - Log.Error(e, "Error getting auth from browser"); - return null; - } - } -} diff --git a/OF DL/Helpers/DownloadContext.cs b/OF DL/Helpers/DownloadContext.cs index a3146d7..cae7598 100644 --- a/OF DL/Helpers/DownloadContext.cs +++ b/OF DL/Helpers/DownloadContext.cs @@ -1,28 +1,26 @@ -using OF_DL.Entities; +using OF_DL.Entities; +using OF_DL.Services; namespace OF_DL.Helpers { internal interface IDownloadContext { - public IDownloadConfig DownloadConfig { get; } public IFileNameFormatConfig FileNameFormatConfig { get; } - public IAPIHelper ApiHelper { get; } - public IDBHelper DBHelper { get; } - public IDownloadHelper DownloadHelper { get; } + public IAPIService ApiService { get; } + public IDBService DBService { get; } + public IDownloadService DownloadService { get; } } internal class DownloadContext( - IDownloadConfig downloadConfig, IFileNameFormatConfig fileNameFormatConfig, - IAPIHelper apiHelper, - IDBHelper dBHelper, - IDownloadHelper downloadHelper) + IAPIService apiService, + IDBService dBService, + IDownloadService downloadService) : IDownloadContext { - public IAPIHelper ApiHelper { get; } = apiHelper; - public IDBHelper DBHelper { get; } = dBHelper; - public IDownloadHelper DownloadHelper { get; } = downloadHelper; - public IDownloadConfig DownloadConfig { get; } = downloadConfig; + public IAPIService ApiService { get; } = apiService; + public IDBService DBService { get; } = dBService; + public IDownloadService DownloadService { get; } = downloadService; public IFileNameFormatConfig FileNameFormatConfig { get; } = fileNameFormatConfig; } } diff --git a/OF DL/Helpers/DownloadHelper.cs b/OF DL/Helpers/DownloadHelper.cs deleted file mode 100644 index 8454bfc..0000000 --- a/OF DL/Helpers/DownloadHelper.cs +++ /dev/null @@ -1,2040 +0,0 @@ -using FFmpeg.NET; -using FFmpeg.NET.Events; -using FFmpeg.NET.Services; -using OF_DL.Entities; -using OF_DL.Entities.Archived; -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.Utils; -using Org.BouncyCastle.Asn1.Tsp; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Tsp; -using Serilog; -using Spectre.Console; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml.Linq; -using static OF_DL.Entities.Lists.UserList; -using static OF_DL.Entities.Messages.Messages; -using FromUser = OF_DL.Entities.Messages.FromUser; - -namespace OF_DL.Helpers; - -public class DownloadHelper : IDownloadHelper -{ - private readonly Auth auth; - private readonly IDBHelper dbHelper; - private readonly IFileNameHelper fileNameHelper; - private readonly IDownloadConfig downloadConfig; - private readonly IFileNameFormatConfig fileNameFormatConfig; - private TaskCompletionSource _completionSource; - - public DownloadHelper(Auth auth, IDownloadConfig downloadConfig, IFileNameFormatConfig fileNameFormatConfig, IDBHelper dbHelper, IFileNameHelper fileNameHelper) - { - this.auth = auth; - this.dbHelper = dbHelper; - this.fileNameHelper = fileNameHelper; - this.downloadConfig = downloadConfig; - this.fileNameFormatConfig = fileNameFormatConfig; - } - - #region common - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - protected async Task CreateDirectoriesAndDownloadMedia(string path, - string url, - string folder, - long media_id, - string api_type, - ProgressTask task, - string serverFileName, - string resolvedFileName) - { - try - { - string customFileName = string.Empty; - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - string extension = Path.GetExtension(url.Split("?")[0]); - - path = UpdatePathBasedOnExtension(folder, path, extension); - - return await ProcessMediaDownload(folder, media_id, api_type, url, path, serverFileName, resolvedFileName, extension, task); - } - catch (Exception ex) - { - Console.WriteLine("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); - } - } - return false; - } - - - /// - /// Updates the given path based on the file extension. - /// - /// The parent folder. - /// The initial relative path. - /// The file extension. - /// A string that represents the updated path based on the file extension. - private string UpdatePathBasedOnExtension(string folder, string path, string extension) - { - string subdirectory = string.Empty; - - switch (extension.ToLower()) - { - case ".jpg": - case ".jpeg": - case ".png": - subdirectory = "/Images"; - break; - case ".mp4": - case ".avi": - case ".wmv": - case ".gif": - case ".mov": - subdirectory = "/Videos"; - break; - case ".mp3": - case ".wav": - case ".ogg": - subdirectory = "/Audios"; - break; - } - - if (!string.IsNullOrEmpty(subdirectory)) - { - path += subdirectory; - string fullPath = folder + path; - - if (!Directory.Exists(fullPath)) - { - Directory.CreateDirectory(fullPath); - } - } - - return path; - } - - - /// - /// Generates a custom filename based on the given format and properties. - /// - /// The format string for the filename. - /// General information about the post. - /// Media associated with the post. - /// Author of the post. - /// Dictionary containing user-related data. - /// Helper class for filename operations. - /// A Task resulting in a string that represents the custom filename. - private async Task GenerateCustomFileName(string filename, - string? filenameFormat, - object? postInfo, - object? postMedia, - object? author, - string username, - Dictionary users, - IFileNameHelper fileNameHelper, - CustomFileNameOption option) - { - if (string.IsNullOrEmpty(filenameFormat) || postInfo == null || postMedia == null || author == null) - { - return option switch - { - CustomFileNameOption.ReturnOriginal => filename, - CustomFileNameOption.ReturnEmpty => string.Empty, - _ => filename, - }; - } - - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - properties.AddRange(matches.Select(match => match.Groups[1].Value)); - - Dictionary values = await fileNameHelper.GetFilename(postInfo, postMedia, author, properties, username, users); - return await fileNameHelper.BuildFilename(filenameFormat, values); - } - - - private async Task GetFileSizeAsync(string url, Auth auth) - { - long fileSize = 0; - - try - { - Uri uri = new(url); - - if (uri.Host == "cdn3.onlyfans.com" && uri.LocalPath.Contains("/dash/files")) - { - string[] messageUrlParsed = url.Split(','); - string mpdURL = messageUrlParsed[0]; - string policy = messageUrlParsed[1]; - string signature = messageUrlParsed[2]; - string kvp = messageUrlParsed[3]; - - mpdURL = mpdURL.Replace(".mpd", "_source.mp4"); - - using HttpClient client = new(); - client.DefaultRequestHeaders.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE}"); - client.DefaultRequestHeaders.Add("User-Agent", auth.USER_AGENT); - - using HttpResponseMessage response = await client.GetAsync(mpdURL, HttpCompletionOption.ResponseHeadersRead); - if (response.IsSuccessStatusCode) - { - fileSize = response.Content.Headers.ContentLength ?? 0; - } - } - else - { - using HttpClient client = new(); - client.DefaultRequestHeaders.Add("User-Agent", auth.USER_AGENT); - using HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); - if (response.IsSuccessStatusCode) - { - fileSize = response.Content.Headers.ContentLength ?? 0; - } - } - } - catch (Exception ex) - { - Console.WriteLine($"Error getting file size for URL '{url}': {ex.Message}"); - } - - return fileSize; - } - - public static async Task GetDRMVideoLastModified(string url, Auth auth) - { - Uri uri = new(url); - - string[] messageUrlParsed = url.Split(','); - string mpdURL = messageUrlParsed[0]; - string policy = messageUrlParsed[1]; - string signature = messageUrlParsed[2]; - string kvp = messageUrlParsed[3]; - - mpdURL = mpdURL.Replace(".mpd", "_source.mp4"); - - using HttpClient client = new(); - client.DefaultRequestHeaders.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE}"); - client.DefaultRequestHeaders.Add("User-Agent", auth.USER_AGENT); - - using HttpResponseMessage response = await client.GetAsync(mpdURL, HttpCompletionOption.ResponseHeadersRead); - if (response.IsSuccessStatusCode) - { - return response.Content.Headers.LastModified.Value.DateTime; - } - return DateTime.Now; - } - public static async Task GetMediaLastModified(string url) - { - using HttpClient client = new(); - - using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); - if (response.IsSuccessStatusCode) - { - return response.Content.Headers.LastModified.Value.DateTime; - } - return DateTime.Now; - } - - /// - /// Processes the download and database update of media. - /// - /// The folder where the media is stored. - /// The ID of the media. - /// The full path to the media. - /// The URL from where to download the media. - /// The relative path to the media. - /// The filename after any required manipulations. - /// The file extension. - /// The task object for tracking progress. - /// A Task resulting in a boolean indicating whether the media is newly downloaded or not. - public async Task ProcessMediaDownload(string folder, - long media_id, - string api_type, - string url, - string path, - string serverFilename, - string resolvedFilename, - string extension, - ProgressTask task) - { - - try - { - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - return await HandleNewMedia(folder: folder, - media_id: media_id, - api_type: api_type, - url: url, - path: path, - serverFilename: serverFilename, - resolvedFilename: resolvedFilename, - extension: extension, - task: task); - } - else - { - bool status = await HandlePreviouslyDownloadedMediaAsync(folder, media_id, api_type, task); - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (serverFilename != resolvedFilename)) - { - await HandleRenamingOfExistingFilesAsync(folder, media_id, api_type, path, serverFilename, resolvedFilename, extension); - } - return status; - } - } - catch (Exception ex) - { - // Handle exception (e.g., log it) - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - } - - - private async Task HandleRenamingOfExistingFilesAsync(string folder, - long media_id, - string api_type, - string path, - string serverFilename, - string resolvedFilename, - string extension) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{serverFilename}{extension}"; - string fullPathWithTheNewFileName = $"{folder}{path}/{resolvedFilename}{extension}"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - var lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, resolvedFilename + extension, size, true, lastModified); - return true; - } - - - /// - /// Handles new media by downloading and updating the database. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// A Task resulting in a boolean indicating whether the media is newly downloaded or not. - private async Task HandleNewMedia(string folder, - long media_id, - string api_type, - string url, - string path, - string serverFilename, - string resolvedFilename, - string extension, - ProgressTask task) - { - long fileSizeInBytes; - DateTime lastModified; - bool status; - - string fullPathWithTheServerFileName = $"{folder}{path}/{serverFilename}{extension}"; - string fullPathWithTheNewFileName = $"{folder}{path}/{resolvedFilename}{extension}"; - - //there are a few possibilities here. - //1.file has been downloaded in the past but it has the server filename - // in that case it should be set as existing and it should be renamed - //2.file has been downloaded in the past but it has custom filename. - // it should be set as existing and nothing else. - // of coures 1 and 2 depends in the fact that there may be a difference in the resolved file name - // (ie user has selected a custom format. If he doesn't then the resolved name will be the same as the server filename - //3.file doesn't exist and it should be downloaded. - - // Handle the case where the file has been downloaded in the past with the server filename - //but it has downloaded outsite of this application so it doesn't exist in the database - if (File.Exists(fullPathWithTheServerFileName)) - { - string finalPath; - if (fullPathWithTheServerFileName != fullPathWithTheNewFileName) - { - finalPath = fullPathWithTheNewFileName; - //rename. - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - } - } - else - { - finalPath = fullPathWithTheServerFileName; - } - - fileSizeInBytes = GetLocalFileSize(finalPath); - lastModified = File.GetLastWriteTime(finalPath); - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - status = false; - } - // Handle the case where the file has been downloaded in the past with a custom filename. - //but it has downloaded outsite of this application so it doesn't exist in the database - // this is a bit improbable but we should check for that. - else if (File.Exists(fullPathWithTheNewFileName)) - { - fileSizeInBytes = GetLocalFileSize(fullPathWithTheNewFileName); - lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName); - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - status = false; - } - else //file doesn't exist and we should download it. - { - lastModified = await DownloadFile(url, fullPathWithTheNewFileName, task); - fileSizeInBytes = GetLocalFileSize(fullPathWithTheNewFileName); - status = true; - } - - //finaly check which filename we should use. Custom or the server one. - //if a custom is used, then the servefilename will be different from the resolved filename. - string finalName = serverFilename == resolvedFilename ? serverFilename : resolvedFilename; - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, finalName + extension, fileSizeInBytes, true, lastModified); - return status; - } - - - /// - /// Handles media that has been previously downloaded and updates the task accordingly. - /// - /// - /// - /// - /// - /// A boolean indicating whether the media is newly downloaded or not. - private async Task HandlePreviouslyDownloadedMediaAsync(string folder, long media_id, string api_type, ProgressTask task) - { - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - return false; - } - - - /// - /// Gets the file size of the media. - /// - /// The path to the file. - /// The file size in bytes. - private long GetLocalFileSize(string filePath) - { - return new FileInfo(filePath).Length; - } - - - /// - /// Downloads a file from the given URL and saves it to the specified destination path. - /// - /// The URL to download the file from. - /// The path where the downloaded file will be saved. - /// Progress tracking object. - /// A Task resulting in a DateTime indicating the last modified date of the downloaded file. - - private async Task DownloadFile(string url, string destinationPath, ProgressTask task) - { - using var client = new HttpClient(); - var request = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri(url) - }; - - using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); - response.EnsureSuccessStatusCode(); - var body = await response.Content.ReadAsStreamAsync(); - - // Wrap the body stream with the ThrottledStream to limit read rate. - using (ThrottledStream throttledStream = new(body, downloadConfig.DownloadLimitInMbPerSec * 1_000_000, downloadConfig.LimitDownloadRate)) - { - using FileStream fileStream = new(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 16384, true); - var buffer = new byte[16384]; - int read; - while ((read = await throttledStream.ReadAsync(buffer, CancellationToken.None)) > 0) - { - if (downloadConfig.ShowScrapeSize) - { - task.Increment(read); - } - await fileStream.WriteAsync(buffer.AsMemory(0, read), CancellationToken.None); - } - } - - File.SetLastWriteTime(destinationPath, response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now); - if (!downloadConfig.ShowScrapeSize) - { - task.Increment(1); - } - return response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now; - } - - public async Task CalculateTotalFileSize(List urls) - { - long totalFileSize = 0; - if (urls.Count > 250) - { - int batchSize = 250; - - var tasks = new List>(); - - for (int i = 0; i < urls.Count; i += batchSize) - { - var batchUrls = urls.Skip(i).Take(batchSize).ToList(); - - var batchTasks = batchUrls.Select(url => GetFileSizeAsync(url, auth)); - tasks.AddRange(batchTasks); - - await Task.WhenAll(batchTasks); - - await Task.Delay(5000); - } - - long[] fileSizes = await Task.WhenAll(tasks); - foreach (long fileSize in fileSizes) - { - totalFileSize += fileSize; - } - } - else - { - var tasks = new List>(); - - foreach (string url in urls) - { - tasks.Add(GetFileSizeAsync(url, auth)); - } - - long[] fileSizes = await Task.WhenAll(tasks); - foreach (long fileSize in fileSizes) - { - totalFileSize += fileSize; - } - } - - return totalFileSize; - } - #endregion - - #region drm common - - private async Task DownloadDrmMedia(string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string customFileName, string filename, string path) - { - try - { - _completionSource = new TaskCompletionSource(); - - int pos1 = decryptionKey.IndexOf(':'); - string decKey = ""; - if (pos1 >= 0) - { - decKey = decryptionKey.Substring(pos1 + 1); - } - - int streamIndex = 0; - string tempFilename = $"{folder}{path}/{filename}_source.mp4"; - - //int? streamIndex = await GetVideoStreamIndexFromMpd(url, policy, signature, kvp, downloadConfig.DownloadVideoResolution); - - //if (streamIndex == null) - // throw new Exception($"Could not find video stream for resolution {downloadConfig.DownloadVideoResolution}"); - - //string tempFilename; - - //switch (downloadConfig.DownloadVideoResolution) - //{ - // case VideoResolution.source: - // tempFilename = $"{folder}{path}/{filename}_source.mp4"; - // break; - // case VideoResolution._240: - // tempFilename = $"{folder}{path}/{filename}_240.mp4"; - // break; - // case VideoResolution._720: - // tempFilename = $"{folder}{path}/{filename}_720.mp4"; - // break; - // default: - // tempFilename = $"{folder}{path}/{filename}_source.mp4"; - // break; - //} - - // Configure ffmpeg log level and optional report file location - bool ffmpegDebugLogging = Log.IsEnabled(Serilog.Events.LogEventLevel.Debug); - - string logLevelArgs = ffmpegDebugLogging || downloadConfig.LoggingLevel is LoggingLevel.Verbose or LoggingLevel.Debug - ? "-loglevel debug -report" - : downloadConfig.LoggingLevel switch - { - LoggingLevel.Information => "-loglevel info", - LoggingLevel.Warning => "-loglevel warning", - LoggingLevel.Error => "-loglevel error", - LoggingLevel.Fatal => "-loglevel fatal", - _ => string.Empty - }; - - if (logLevelArgs.Contains("-report", StringComparison.OrdinalIgnoreCase)) - { - // Direct ffmpeg report files into the same logs directory Serilog uses (relative to current working directory) - string logDir = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs")); - Directory.CreateDirectory(logDir); - string ffReportPath = Path.Combine(logDir, "ffmpeg-%p-%t.log"); // ffmpeg will replace %p/%t - Environment.SetEnvironmentVariable("FFREPORT", $"file={ffReportPath}:level=32"); - Log.Debug("FFREPORT enabled at: {FFREPORT} (cwd: {Cwd})", Environment.GetEnvironmentVariable("FFREPORT"), Environment.CurrentDirectory); - } - else - { - Environment.SetEnvironmentVariable("FFREPORT", null); - Log.Debug("FFREPORT disabled (cwd: {Cwd})", Environment.CurrentDirectory); - } - - string cookieHeader = - "Cookie: " + - $"CloudFront-Policy={policy}; " + - $"CloudFront-Signature={signature}; " + - $"CloudFront-Key-Pair-Id={kvp}; " + - $"{sess}"; - - string parameters = - $"{logLevelArgs} " + - $"-cenc_decryption_key {decKey} " + - $"-headers \"{cookieHeader}\" " + - $"-user_agent \"{user_agent}\" " + - "-referer \"https://onlyfans.com\" " + - "-rw_timeout 20000000 " + - "-reconnect 1 -reconnect_streamed 1 -reconnect_on_network_error 1 -reconnect_delay_max 10 " + - "-y " + - $"-i \"{url}\" " + - $"-map 0:v:{streamIndex} -map 0:a? " + - "-c copy " + - $"\"{tempFilename}\""; - - Log.Debug($"Calling FFMPEG with Parameters: {parameters}"); - - Engine ffmpeg = new Engine(downloadConfig.FFmpegPath); - ffmpeg.Error += OnError; - ffmpeg.Complete += async (sender, args) => - { - _completionSource.TrySetResult(true); - await OnFFMPEGDownloadComplete(tempFilename, lastModified, folder, path, customFileName, filename, media_id, api_type, task); - }; - await ffmpeg.ExecuteAsync(parameters, CancellationToken.None); - - return await _completionSource.Task; - } - 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 false; - } - #endregion - - #region normal posts - public async Task DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary users) - { - string path; - if (downloadConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && postInfo?.postedAt is not null) - { - path = $"/Posts/Free/{postInfo.id} {postInfo.postedAt:yyyy-MM-dd HH-mm-ss}"; - } - else - { - path = "/Posts/Free"; - } - - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, postInfo, postMedia, author, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - public async Task DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary users) - { - string path; - if (downloadConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && postInfo?.postedAt is not null) - { - path = $"/Posts/Free/{postInfo.id} {postInfo.postedAt:yyyy-MM-dd HH-mm-ss}"; - } - else - { - path = "/Posts/Free"; - } - - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, postInfo, postMedia, author, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - public async Task DownloadStreamMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary users) - { - string path; - if (downloadConfig.FolderPerPost && streamInfo != null && streamInfo?.id is not null && streamInfo?.postedAt is not null) - { - path = $"/Posts/Free/{streamInfo.id} {streamInfo.postedAt:yyyy-MM-dd HH-mm-ss}"; - } - else - { - path = "/Posts/Free"; - } - - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, streamInfo, streamMedia, author, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - - - public async Task DownloadMessageMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary users) - { - string path; - if (downloadConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null) - { - path = $"/Messages/Free/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}"; - } - else - { - path = "/Messages/Free"; - } - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, fromUser, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - - public async Task DownloadMessagePreviewMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Messages.Medium? messageMedia, FromUser? fromUser, Dictionary users) - { - string path; - if (downloadConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null) - { - path = $"/Messages/Free/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}"; - } - else - { - path = "/Messages/Free"; - } - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, fromUser, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - - - public async Task DownloadArchivedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Archived.List? messageInfo, Archived.Medium? messageMedia, Archived.Author? author, Dictionary users) - { - string path = "/Archived/Posts/Free"; - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, author, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - - - - public async Task DownloadStoryMedia(string url, string folder, long media_id, string api_type, ProgressTask task) - { - string path = "/Stories/Free"; - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, filename); - } - - public async Task DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary users) - { - string path; - if (downloadConfig.FolderPerPaidMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null) - { - path = $"/Messages/Paid/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}"; - } - else - { - path = "/Messages/Paid"; - } - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, fromUser, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - - public async Task DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary users) - { - string path; - if (downloadConfig.FolderPerPaidMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null) - { - path = $"/Messages/Paid/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}"; - } - else - { - path = "/Messages/Paid"; - } - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, fromUser, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - - public async Task DownloadPurchasedPostMedia(string url, - string folder, - long media_id, - string api_type, - ProgressTask task, - string? filenameFormat, - Purchased.List? messageInfo, - Medium? messageMedia, - Purchased.FromUser? fromUser, - Dictionary users) - { - string path; - if (downloadConfig.FolderPerPaidPost && messageInfo != null && messageInfo?.id is not null && messageInfo?.postedAt is not null) - { - path = $"/Posts/Paid/{messageInfo.id} {messageInfo.postedAt.Value:yyyy-MM-dd HH-mm-ss}"; - } - else - { - path = "/Posts/Paid"; - } - Uri uri = new(url); - string filename = System.IO.Path.GetFileNameWithoutExtension(uri.LocalPath); - string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, fromUser, folder.Split("/")[^1], users, fileNameHelper, CustomFileNameOption.ReturnOriginal); - return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename); - } - - #endregion - public async Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username) - { - try - { - string path = $"/Profile"; - - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - if (!string.IsNullOrEmpty(avatarUrl)) - { - string avatarpath = $"{path}/Avatars"; - if (!Directory.Exists(folder + avatarpath)) - { - Directory.CreateDirectory(folder + avatarpath); - } - - List avatarMD5Hashes = WidevineClient.Utils.CalculateFolderMD5(folder + avatarpath); - - Uri uri = new(avatarUrl); - string destinationPath = $"{folder}{avatarpath}/"; - - var client = new HttpClient(); - - var request = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = uri - - }; - using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); - response.EnsureSuccessStatusCode(); - - using var memoryStream = new MemoryStream(); - await response.Content.CopyToAsync(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - - MD5 md5 = MD5.Create(); - byte[] hash = md5.ComputeHash(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - if (!avatarMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant())) - { - destinationPath = destinationPath + string.Format("{0} {1}.jpg", username, response.Content.Headers.LastModified.HasValue ? response.Content.Headers.LastModified.Value.LocalDateTime.ToString("dd-MM-yyyy") : DateTime.Now.ToString("dd-MM-yyyy")); - - using (FileStream fileStream = File.Create(destinationPath)) - { - await memoryStream.CopyToAsync(fileStream); - } - File.SetLastWriteTime(destinationPath, response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now); - } - } - - if (!string.IsNullOrEmpty(headerUrl)) - { - string headerpath = $"{path}/Headers"; - if (!Directory.Exists(folder + headerpath)) - { - Directory.CreateDirectory(folder + headerpath); - } - - List headerMD5Hashes = WidevineClient.Utils.CalculateFolderMD5(folder + headerpath); - - Uri uri = new(headerUrl); - string destinationPath = $"{folder}{headerpath}/"; - - var client = new HttpClient(); - - var request = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = uri - - }; - using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); - response.EnsureSuccessStatusCode(); - - using var memoryStream = new MemoryStream(); - await response.Content.CopyToAsync(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - - MD5 md5 = MD5.Create(); - byte[] hash = md5.ComputeHash(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - if (!headerMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant())) - { - destinationPath = destinationPath + string.Format("{0} {1}.jpg", username, response.Content.Headers.LastModified.HasValue ? response.Content.Headers.LastModified.Value.LocalDateTime.ToString("dd-MM-yyyy") : DateTime.Now.ToString("dd-MM-yyyy")); - - using (FileStream fileStream = File.Create(destinationPath)) - { - await memoryStream.CopyToAsync(fileStream); - } - File.SetLastWriteTime(destinationPath, response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now); - } - } - } - catch (Exception ex) - { - Console.WriteLine("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); - } - } - } - - private async Task OnFFMPEGDownloadComplete(string tempFilename, DateTime lastModified, string folder, string path, string customFileName, string filename, long media_id, string api_type, ProgressTask task) - { - try - { - if (File.Exists(tempFilename)) - { - File.SetLastWriteTime(tempFilename, lastModified); - } - if (!string.IsNullOrEmpty(customFileName)) - { - File.Move(tempFilename, $"{folder + path + "/" + customFileName + ".mp4"}"); - } - - // Cleanup Files - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : tempFilename).Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + ".mp4" : filename + "_source.mp4", fileSizeInBytes, true, 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); - } - } - } - - private void OnError(object sender, ConversionErrorEventArgs e) - { - // Guard all fields to avoid NullReference exceptions from FFmpeg.NET - var input = e?.Input?.Name ?? ""; - var output = e?.Output?.Name ?? ""; - var exitCode = e?.Exception?.ExitCode.ToString() ?? ""; - var message = e?.Exception?.Message ?? ""; - var inner = e?.Exception?.InnerException?.Message ?? ""; - - Log.Error("FFmpeg failed. Input={Input} Output={Output} ExitCode={ExitCode} Message={Message} Inner={Inner}", - input, output, exitCode, message, inner); - - _completionSource?.TrySetResult(false); - } - - - #region drm posts - public async Task DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary users) - { - try - { - string customFileName = string.Empty; - string path; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - if (downloadConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null) - { - path = $"/Messages/Free/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; - } - else - { - path = "/Messages/Free/Videos"; - } - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - - if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(messageInfo, messageMedia, fromUser, properties, folder.Split("/")[^1],users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - - public async Task DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Messages.Medium? messageMedia, FromUser? fromUser, Dictionary users) - { - try - { - string customFileName = string.Empty; - string path; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - if (downloadConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null) - { - path = $"/Messages/Free/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; - } - else - { - path = "/Messages/Free/Videos"; - } - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - - if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(messageInfo, messageMedia, fromUser, properties, folder.Split("/")[^1],users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - - - public async Task DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary users) - { - try - { - string customFileName = string.Empty; - string path; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - if (downloadConfig.FolderPerPaidMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null) - { - path = $"/Messages/Paid/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; - } - else - { - path = "/Messages/Paid/Videos"; - } - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(messageInfo, messageMedia, fromUser, properties, folder.Split("/")[^1], users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - - public async Task DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary users) - { - try - { - string customFileName = string.Empty; - string path; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - if (downloadConfig.FolderPerPaidMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null) - { - path = $"/Messages/Paid/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; - } - else - { - path = "/Messages/Paid/Videos"; - } - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(messageInfo, messageMedia, fromUser, properties, folder.Split("/")[^1], users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - - - public async Task DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary users) - { - try - { - string customFileName = string.Empty; - string path; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - if (downloadConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && postInfo?.postedAt is not null) - { - path = $"/Posts/Free/{postInfo.id} {postInfo.postedAt:yyyy-MM-dd HH-mm-ss}/Videos"; - } - else - { - path = "/Posts/Free/Videos"; - } - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(postInfo, postMedia, author, properties, folder.Split("/")[^1], users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - public async Task DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary users) - { - try - { - string customFileName = string.Empty; - string path; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - if (downloadConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && postInfo?.postedAt is not null) - { - path = $"/Posts/Free/{postInfo.id} {postInfo.postedAt:yyyy-MM-dd HH-mm-ss}/Videos"; - } - else - { - path = "/Posts/Free/Videos"; - } - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(postInfo, postMedia, author, properties, folder.Split("/")[^1], users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - public async Task DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary users) - { - try - { - string customFileName = string.Empty; - string path; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - if (downloadConfig.FolderPerPost && streamInfo != null && streamInfo?.id is not null && streamInfo?.postedAt is not null) - { - path = $"/Posts/Free/{streamInfo.id} {streamInfo.postedAt:yyyy-MM-dd HH-mm-ss}/Videos"; - } - else - { - path = "/Posts/Free/Videos"; - } - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - if (!string.IsNullOrEmpty(filenameFormat) && streamInfo != null && streamMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(streamInfo, streamMedia, author, properties, folder.Split("/")[^1], users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - - public async Task DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? postInfo, Medium? postMedia, Purchased.FromUser? fromUser, Dictionary users) - { - try - { - string customFileName = string.Empty; - string path; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - if (downloadConfig.FolderPerPaidPost && postInfo != null && postInfo?.id is not null && postInfo?.postedAt is not null) - { - path = $"/Posts/Paid/{postInfo.id} {postInfo.postedAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; - } - else - { - path = "/Posts/Paid/Videos"; - } - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - - if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(postInfo, postMedia, fromUser, properties, folder.Split("/")[^1], users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - - - public async Task DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Archived.List? postInfo, Archived.Medium? postMedia, Archived.Author? author, Dictionary users) - { - try - { - string customFileName = string.Empty; - Uri uri = new(url); - string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; - string path = "/Archived/Posts/Free/Videos"; - if (!Directory.Exists(folder + path)) - { - Directory.CreateDirectory(folder + path); - } - - if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null) - { - List properties = new(); - string pattern = @"\{(.*?)\}"; - MatchCollection matches = Regex.Matches(filenameFormat, pattern); - foreach (Match match in matches) - { - properties.Add(match.Groups[1].Value); - } - Dictionary values = await fileNameHelper.GetFilename(postInfo, postMedia, author, properties, folder.Split("/")[^1], users); - customFileName = await fileNameHelper.BuildFilename(filenameFormat, values); - } - - if (!await dbHelper.CheckDownloaded(folder, media_id, api_type)) - { - if (!string.IsNullOrEmpty(customFileName) ? !File.Exists(folder + path + "/" + customFileName + ".mp4") : !File.Exists(folder + path + "/" + filename + "_source.mp4")) - { - return await DownloadDrmMedia(auth.USER_AGENT, policy, signature, kvp, auth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, task, customFileName, filename, path); - } - else - { - long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) ? folder + path + "/" + customFileName + ".mp4" : folder + path + "/" + filename + "_source.mp4").Length; - if (downloadConfig.ShowScrapeSize) - { - task.Increment(fileSizeInBytes); - } - else - { - task.Increment(1); - } - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", fileSizeInBytes, true, lastModified); - } - } - else - { - if (!string.IsNullOrEmpty(customFileName)) - { - if (downloadConfig.RenameExistingFilesWhenCustomFormatIsSelected && (filename + "_source" != customFileName)) - { - string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; - string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; - if (!File.Exists(fullPathWithTheServerFileName)) - { - return false; - } - try - { - File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); - } - catch (Exception ex) - { - Console.WriteLine($"An error occurred: {ex.Message}"); - return false; - } - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - await dbHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified); - } - } - - if (downloadConfig.ShowScrapeSize) - { - long size = await dbHelper.GetStoredFileSize(folder, media_id, api_type); - task.Increment(size); - } - else - { - task.Increment(1); - } - } - return false; - } - 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 false; - } - #endregion - - private async Task GetVideoStreamIndexFromMpd(string mpdUrl, string policy, string signature, string kvp, VideoResolution resolution) - { - 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(); - XDocument doc = XDocument.Parse(body); - XNamespace ns = "urn:mpeg:dash:schema:mpd:2011"; - XNamespace cenc = "urn:mpeg:cenc:2013"; - var videoAdaptationSet = doc - .Descendants(ns + "AdaptationSet") - .FirstOrDefault(e => (string)e.Attribute("mimeType") == "video/mp4"); - - if (videoAdaptationSet == null) - return null; - - string targetHeight = resolution switch - { - VideoResolution._240 => "240", - VideoResolution._720 => "720", - VideoResolution.source => "1280", - _ => throw new ArgumentOutOfRangeException(nameof(resolution)) - }; - - var representations = videoAdaptationSet.Elements(ns + "Representation").ToList(); - - for (int i = 0; i < representations.Count; i++) - { - if ((string)representations[i].Attribute("height") == targetHeight) - return i; // this is the index FFmpeg will use for `-map 0:v:{i}` - } - } - - return null; - } -} diff --git a/OF DL/Helpers/Interfaces/IAuthHelper.cs b/OF DL/Helpers/Interfaces/IAuthHelper.cs deleted file mode 100644 index e1d0726..0000000 --- a/OF DL/Helpers/Interfaces/IAuthHelper.cs +++ /dev/null @@ -1,10 +0,0 @@ -using OF_DL.Entities; - -namespace OF_DL.Helpers.Interfaces -{ - public interface IAuthHelper - { - Task SetupBrowser(bool runningInDocker); - Task GetAuthFromBrowser(bool isDocker = false); - } -} diff --git a/OF DL/Helpers/Interfaces/IDownloadHelper.cs b/OF DL/Helpers/Interfaces/IDownloadHelper.cs deleted file mode 100644 index 62672ae..0000000 --- a/OF DL/Helpers/Interfaces/IDownloadHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using OF_DL.Entities; -using OF_DL.Entities.Archived; -using OF_DL.Entities.Messages; -using OF_DL.Entities.Post; -using OF_DL.Entities.Purchased; -using OF_DL.Entities.Streams; -using Spectre.Console; -using static OF_DL.Entities.Messages.Messages; -using FromUser = OF_DL.Entities.Messages.FromUser; - -namespace OF_DL.Helpers -{ - public interface IDownloadHelper - { - Task CalculateTotalFileSize(List urls); - Task DownloadArchivedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Archived.List messageInfo, Archived.Medium messageMedia, Archived.Author author, Dictionary users); - Task DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Archived.List postInfo, Archived.Medium postMedia, Archived.Author author, Dictionary users); - Task DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary users); - Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username); - Task DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Messages.List messageInfo, Messages.Medium messageMedia, Messages.FromUser fromUser, Dictionary users); - Task DownloadMessageMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Messages.List messageInfo, Messages.Medium messageMedia, Messages.FromUser fromUser, Dictionary users); - Task DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Post.List postInfo, Post.Medium postMedia, Post.Author author, Dictionary users); - Task DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary users); - Task DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary users); - Task DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary users); - - Task DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary users); - Task DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary users); - - Task DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary users); - Task DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List postInfo, Medium postMedia, Purchased.FromUser fromUser, Dictionary users); - Task DownloadPurchasedPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary users); - Task DownloadStoryMedia(string url, string folder, long media_id, string api_type, ProgressTask task); - Task DownloadStreamMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary users); - Task DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Streams.List streamInfo, Streams.Medium streamMedia, Streams.Author author, Dictionary users); - Task DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url, - string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, - ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, - FromUser? fromUser, Dictionary users); - - Task DownloadMessagePreviewMedia(string url, string folder, long media_id, string api_type, - ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, - FromUser? fromUser, Dictionary users); - } -} diff --git a/OF DL/Program.cs b/OF DL/Program.cs index d79378b..041900c 100644 --- a/OF DL/Program.cs +++ b/OF DL/Program.cs @@ -8,6 +8,7 @@ using OF_DL.Entities.Purchased; using OF_DL.Entities.Streams; using OF_DL.Enumerations; using OF_DL.Enumurations; +using OF_DL.CLI; using OF_DL.Helpers; using OF_DL.Services; using Serilog; @@ -17,13 +18,11 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; using static OF_DL.Entities.Messages.Messages; using Microsoft.Extensions.DependencyInjection; -using OF_DL.Helpers.Interfaces; namespace OF_DL; public class Program(IServiceProvider serviceProvider) { - public int MAX_AGE = 0; public static List paid_post_ids = new(); private static bool clientIdBlobMissing = false; @@ -79,68 +78,59 @@ public class Program(IServiceProvider serviceProvider) AnsiConsole.Markup("Documentation: [link]https://docs.ofdl.tools/[/]\n"); AnsiConsole.Markup("Discord server: [link]https://discord.com/invite/6bUW8EJ53j[/]\n\n"); - // Set up dependency injection with LoggingService and ConfigService - ServiceCollection services = new(); - services.AddSingleton(); - services.AddSingleton(); - ServiceProvider tempServiceProvider = services.BuildServiceProvider(); - // Load configuration using ConfigService - IConfigService configService = tempServiceProvider.GetRequiredService(); - - if (!await configService.LoadConfigurationAsync(args)) - { - AnsiConsole.MarkupLine($"\n[red]config.conf is not valid, check your syntax![/]\n"); - AnsiConsole.MarkupLine($"[red]Press any key to exit.[/]"); - if (!configService.IsCliNonInteractive) - { - Console.ReadKey(); - } - Environment.Exit(3); - return; - } - - if (configService.CurrentConfig == null) - { - AnsiConsole.MarkupLine($"\n[red]Failed to load configuration.[/]\n"); - Environment.Exit(3); - return; - } - - AnsiConsole.Markup("[green]config.conf located successfully!\n[/]"); - - // Set up full dependency injection with loaded config - services = new ServiceCollection(); - ConfigureServices(services, configService.CurrentConfig, tempServiceProvider.GetRequiredService()); + ServiceCollection services = await ConfigureServices(args); var serviceProvider = services.BuildServiceProvider(); // Get the Program instance and run var program = serviceProvider.GetRequiredService(); - await program.RunAsync(args, configService.IsCliNonInteractive); + await program.RunAsync(); } - private static void ConfigureServices(IServiceCollection services, Entities.Config config, ILoggingService loggingService) + private static async Task ConfigureServices(string[] args) { - services.AddSingleton(config); - services.AddSingleton(config); - services.AddSingleton(config); + // Set up dependency injection with LoggingService and ConfigService + ServiceCollection services = new(); + services.AddSingleton(); + services.AddSingleton(); + ServiceProvider tempServiceProvider = services.BuildServiceProvider(); + + ILoggingService loggingService = tempServiceProvider.GetRequiredService(); + IConfigService configService = tempServiceProvider.GetRequiredService(); + + + if (!await configService.LoadConfigurationAsync(args)) + { + AnsiConsole.MarkupLine($"\n[red]config.conf is not valid, check your syntax![/]\n"); + AnsiConsole.MarkupLine($"[red]Press any key to exit.[/]"); + if (!configService.IsCliNonInteractive) + { + Console.ReadKey(); + } + Environment.Exit(3); + } + + AnsiConsole.Markup("[green]config.conf located successfully!\n[/]"); + + // Set up full dependency injection with loaded config + services = []; services.AddSingleton(loggingService); - - services.AddSingleton(); + services.AddSingleton(configService); services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); - } - private async Task RunAsync(string[] args, bool cliNonInteractive) + return services; + } + + private async Task RunAsync() { - var config = serviceProvider.GetRequiredService(); - var authService = serviceProvider.GetRequiredService(); + IConfigService configService = serviceProvider.GetRequiredService(); + IAuthService authService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); try { @@ -158,7 +148,7 @@ public class Program(IServiceProvider serviceProvider) Console.Write("Press any key to continue.\n"); Log.Error("Windows version prior to 10.x: {0}", os.VersionString); - if (!cliNonInteractive) + if (!configService.CurrentConfig.NonInteractiveMode) { Console.ReadKey(); } @@ -241,13 +231,13 @@ public class Program(IServiceProvider serviceProvider) { // File exists but failed to load Log.Information("Auth file found but could not be deserialized"); - if (!config!.DisableBrowserAuth) + if (!configService.CurrentConfig!.DisableBrowserAuth) { Log.Debug("Deleting auth.json"); File.Delete("auth.json"); } - if (cliNonInteractive) + if (configService.CurrentConfig.NonInteractiveMode) { AnsiConsole.MarkupLine($"\n[red]auth.json has invalid JSON syntax. The file can be generated automatically when OF-DL is run in the standard, interactive mode.[/]\n"); AnsiConsole.MarkupLine($"[red]You may also want to try using the browser extension which is documented here:[/]\n"); @@ -259,7 +249,7 @@ public class Program(IServiceProvider serviceProvider) } - if (!config!.DisableBrowserAuth) + if (!configService.CurrentConfig!.DisableBrowserAuth) { await LoadAuthFromBrowser(); } @@ -276,7 +266,7 @@ public class Program(IServiceProvider serviceProvider) } else { - if (cliNonInteractive) + if (configService.CurrentConfig.NonInteractiveMode) { AnsiConsole.MarkupLine($"\n[red]auth.json is missing. The file can be generated automatically when OF-DL is run in the standard, interactive mode.[/]\n"); AnsiConsole.MarkupLine($"[red]You may also want to try using the browser extension which is documented here:[/]\n"); @@ -287,7 +277,7 @@ public class Program(IServiceProvider serviceProvider) Environment.Exit(2); } - if (!config!.DisableBrowserAuth) + if (!configService.CurrentConfig!.DisableBrowserAuth) { await LoadAuthFromBrowser(); } @@ -323,7 +313,7 @@ public class Program(IServiceProvider serviceProvider) AnsiConsole.MarkupLine($"[red]Press any key to exit.[/]"); Log.Error("rules.json processing failed.", e.Message); - if (!cliNonInteractive) + if (!configService.CurrentConfig.NonInteractiveMode) { Console.ReadKey(); } @@ -331,37 +321,37 @@ public class Program(IServiceProvider serviceProvider) } } - if(cliNonInteractive) + if(configService.CurrentConfig.NonInteractiveMode) { // CLI argument overrides configuration - config!.NonInteractiveMode = true; + configService.CurrentConfig!.NonInteractiveMode = true; Log.Debug("NonInteractiveMode = true"); } - if(config!.NonInteractiveMode) + if(configService.CurrentConfig!.NonInteractiveMode) { - cliNonInteractive = true; // If it was set in the config, reset the cli value so exception handling works + configService.CurrentConfig.NonInteractiveMode = true; // If it was set in the config, reset the cli value so exception handling works Log.Debug("NonInteractiveMode = true (set via config)"); } var ffmpegFound = false; var pathAutoDetected = false; - if (!string.IsNullOrEmpty(config!.FFmpegPath) && ValidateFilePath(config.FFmpegPath)) + if (!string.IsNullOrEmpty(configService.CurrentConfig!.FFmpegPath) && ValidateFilePath(configService.CurrentConfig.FFmpegPath)) { // FFmpeg path is set in config.json and is valid ffmpegFound = true; - Log.Debug($"FFMPEG found: {config.FFmpegPath}"); + Log.Debug($"FFMPEG found: {configService.CurrentConfig.FFmpegPath}"); Log.Debug("FFMPEG path set in config.conf"); } else if (!string.IsNullOrEmpty(authService.CurrentAuth!.FFMPEG_PATH) && ValidateFilePath(authService.CurrentAuth.FFMPEG_PATH)) { // FFmpeg path is set in auth.json and is valid (config.conf takes precedence and auth.json is only available for backward compatibility) ffmpegFound = true; - config.FFmpegPath = authService.CurrentAuth.FFMPEG_PATH; - Log.Debug($"FFMPEG found: {config.FFmpegPath}"); + configService.CurrentConfig.FFmpegPath = authService.CurrentAuth.FFMPEG_PATH; + Log.Debug($"FFMPEG found: {configService.CurrentConfig.FFmpegPath}"); Log.Debug("FFMPEG path set in auth.json"); } - else if (string.IsNullOrEmpty(config.FFmpegPath)) + else if (string.IsNullOrEmpty(configService.CurrentConfig.FFmpegPath)) { // FFmpeg path is not set in config.conf, so we will try to locate it in the PATH or current directory var ffmpegPath = GetFullPath("ffmpeg"); @@ -370,7 +360,7 @@ public class Program(IServiceProvider serviceProvider) // FFmpeg is found in the PATH or current directory ffmpegFound = true; pathAutoDetected = true; - config.FFmpegPath = ffmpegPath; + configService.CurrentConfig.FFmpegPath = ffmpegPath; Log.Debug($"FFMPEG found: {ffmpegPath}"); Log.Debug("FFMPEG path found via PATH or current directory"); } @@ -383,7 +373,7 @@ public class Program(IServiceProvider serviceProvider) // FFmpeg windows executable is found in the PATH or current directory ffmpegFound = true; pathAutoDetected = true; - config.FFmpegPath = ffmpegPath; + configService.CurrentConfig.FFmpegPath = ffmpegPath; Log.Debug($"FFMPEG found: {ffmpegPath}"); Log.Debug("FFMPEG path found in windows excutable directory"); } @@ -394,7 +384,7 @@ public class Program(IServiceProvider serviceProvider) { if (pathAutoDetected) { - AnsiConsole.Markup($"[green]FFmpeg located successfully. Path auto-detected: {config.FFmpegPath}\n[/]"); + AnsiConsole.Markup($"[green]FFmpeg located successfully. Path auto-detected: {configService.CurrentConfig.FFmpegPath}\n[/]"); } else { @@ -402,9 +392,9 @@ public class Program(IServiceProvider serviceProvider) } // Escape backslashes in the path for Windows - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && config.FFmpegPath!.Contains(@":\") && !config.FFmpegPath.Contains(@":\\")) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && configService.CurrentConfig.FFmpegPath!.Contains(@":\") && !configService.CurrentConfig.FFmpegPath.Contains(@":\\")) { - config.FFmpegPath = config.FFmpegPath.Replace(@"\", @"\\"); + configService.CurrentConfig.FFmpegPath = configService.CurrentConfig.FFmpegPath.Replace(@"\", @"\\"); } // Get FFmpeg version @@ -412,7 +402,7 @@ public class Program(IServiceProvider serviceProvider) { var processStartInfo = new System.Diagnostics.ProcessStartInfo { - FileName = config.FFmpegPath, + FileName = configService.CurrentConfig.FFmpegPath, Arguments = "-version", RedirectStandardOutput = true, RedirectStandardError = true, @@ -465,8 +455,8 @@ public class Program(IServiceProvider serviceProvider) else { AnsiConsole.Markup("[red]Cannot locate FFmpeg; please modify config.conf with the correct path. Press any key to exit.[/]"); - Log.Error($"Cannot locate FFmpeg with path: {config.FFmpegPath}"); - if (!config.NonInteractiveMode) + Log.Error($"Cannot locate FFmpeg with path: {configService.CurrentConfig.FFmpegPath}"); + if (!configService.CurrentConfig.NonInteractiveMode) { Console.ReadKey(); } @@ -500,16 +490,13 @@ public class Program(IServiceProvider serviceProvider) AnsiConsole.Markup("[yellow]device_client_id_blob and/or device_private_key missing, https://ofdl.tools/ or https://cdrm-project.com/ will be used instead for DRM protected videos\n[/]"); } - //Check if auth is valid - var apiHelper = ActivatorUtilities.CreateInstance(serviceProvider, authService.CurrentAuth); - - Entities.User? validate = await apiHelper.GetUserInfo($"/users/me"); + Entities.User? validate = await apiService.GetUserInfo($"/users/me"); if (validate == null || (validate?.name == null && validate?.username == null)) { Log.Error("Auth failed"); authService.CurrentAuth = null; - if (!config!.DisableBrowserAuth) + if (!configService.CurrentConfig!.DisableBrowserAuth) { if (File.Exists("auth.json")) { @@ -517,7 +504,7 @@ public class Program(IServiceProvider serviceProvider) } } - if (!cliNonInteractive && !config!.DisableBrowserAuth) + if (!configService.CurrentConfig.NonInteractiveMode && !configService.CurrentConfig!.DisableBrowserAuth) { await LoadAuthFromBrowser(); } @@ -532,7 +519,7 @@ public class Program(IServiceProvider serviceProvider) } AnsiConsole.Markup($"[green]Logged In successfully as {validate.name} {validate.username}\n[/]"); - await DownloadAllData(apiHelper, authService.CurrentAuth, config); + await DownloadAllData(); } catch (Exception ex) { @@ -545,7 +532,7 @@ public class Program(IServiceProvider serviceProvider) Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); } Console.WriteLine("\nPress any key to exit."); - if (!cliNonInteractive) + if (!configService.CurrentConfig.NonInteractiveMode) { Console.ReadKey(); } @@ -553,10 +540,15 @@ public class Program(IServiceProvider serviceProvider) } } - private async Task DownloadAllData(IAPIHelper m_ApiHelper, Auth Auth, Entities.Config Config) + private async Task DownloadAllData() { - IDBHelper dBHelper = serviceProvider.GetRequiredService(); + IDBService dbService = serviceProvider.GetRequiredService(); IConfigService configService = serviceProvider.GetRequiredService(); + IAuthService authService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); + + Entities.Config Config = configService.CurrentConfig!; Log.Debug("Calling DownloadAllData"); @@ -564,7 +556,7 @@ public class Program(IServiceProvider serviceProvider) { DateTime startTime = DateTime.Now; Dictionary users = new(); - Dictionary activeSubs = await m_ApiHelper.GetActiveSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config); + Dictionary activeSubs = await apiService.GetActiveSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions); Log.Debug("Subscriptions: "); @@ -580,7 +572,7 @@ public class Program(IServiceProvider serviceProvider) { Log.Debug("Inactive Subscriptions: "); - Dictionary expiredSubs = await m_ApiHelper.GetExpiredSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config); + Dictionary expiredSubs = await apiService.GetExpiredSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions); foreach (KeyValuePair expiredSub in expiredSubs) { if (!users.ContainsKey(expiredSub.Key)) @@ -591,7 +583,7 @@ public class Program(IServiceProvider serviceProvider) } } - Dictionary lists = await m_ApiHelper.GetLists("/lists", Config); + Dictionary lists = await apiService.GetLists("/lists"); // Remove users from the list if they are in the ignored list if (!string.IsNullOrEmpty(Config.IgnoredUsersListName)) @@ -603,12 +595,12 @@ public class Program(IServiceProvider serviceProvider) } else { - var ignoredUsernames = await m_ApiHelper.GetListUsers($"/lists/{ignoredUsersListId}/users", Config) ?? []; + var ignoredUsernames = await apiService.GetListUsers($"/lists/{ignoredUsersListId}/users") ?? []; users = users.Where(x => !ignoredUsernames.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value); } } - await dBHelper.CreateUsersDB(users); + await dbService.CreateUsersDB(users); KeyValuePair> hasSelectedUsersKVP; if(Config.NonInteractiveMode && Config.NonInteractiveModePurchasedTab) { @@ -621,16 +613,16 @@ public class Program(IServiceProvider serviceProvider) else if (Config.NonInteractiveMode && !string.IsNullOrEmpty(Config.NonInteractiveModeListName)) { var listId = lists[Config.NonInteractiveModeListName]; - var listUsernames = await m_ApiHelper.GetListUsers($"/lists/{listId}/users", Config) ?? []; + var listUsernames = await apiService.GetListUsers($"/lists/{listId}/users") ?? []; var selectedUsers = users.Where(x => listUsernames.Contains(x.Key)).Distinct().ToDictionary(x => x.Key, x => x.Value); hasSelectedUsersKVP = new KeyValuePair>(true, selectedUsers); } else { var loggingService = serviceProvider.GetRequiredService(); - var userSelectionResult = await HandleUserSelection(m_ApiHelper, Config, users, lists, configService, loggingService); + var userSelectionResult = await HandleUserSelection(users, lists); - Config = userSelectionResult.updatedConfig; + Config = configService.CurrentConfig!; hasSelectedUsersKVP = new KeyValuePair>(userSelectionResult.IsExit, userSelectionResult.selectedUsers); } @@ -687,22 +679,15 @@ public class Program(IServiceProvider serviceProvider) AnsiConsole.Markup($"[red]Folder for {username} already created\n[/]"); } - await dBHelper.CreateDB(path); + await dbService.CreateDB(path); - var downloadContext = ActivatorUtilities.CreateInstance( - serviceProvider, - Config, - GetCreatorFileNameFormatConfig(Config, username), - m_ApiHelper, - dBHelper); - - await DownloadSinglePost(downloadContext, post_id, path, users); + await DownloadSinglePost(username, post_id, path, users); } } } else if (hasSelectedUsersKVP.Key && hasSelectedUsersKVP.Value != null && hasSelectedUsersKVP.Value.ContainsKey("PurchasedTab")) { - Dictionary purchasedTabUsers = await m_ApiHelper.GetPurchasedTabUsers("/posts/paid/all", Config, users); + Dictionary purchasedTabUsers = await apiService.GetPurchasedTabUsers("/posts/paid/all", users); AnsiConsole.Markup($"[red]Checking folders for Users in Purchased Tab\n[/]"); foreach (KeyValuePair user in purchasedTabUsers) { @@ -718,7 +703,7 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Download path: {path}"); - await dBHelper.CheckUsername(user, path); + await dbService.CheckUsername(user, path); if (!Directory.Exists(path)) { @@ -732,9 +717,9 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Folder for {user.Key} already created"); } - Entities.User user_info = await m_ApiHelper.GetUserInfo($"/users/{user.Key}"); + Entities.User user_info = await apiService.GetUserInfo($"/users/{user.Key}"); - await dBHelper.CreateDB(path); + await dbService.CreateDB(path); } string p = ""; @@ -749,7 +734,7 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Download path: {p}"); - List purchasedTabCollections = await m_ApiHelper.GetPurchasedTab("/posts/paid/all", p, Config, users); + List purchasedTabCollections = await apiService.GetPurchasedTab("/posts/paid/all", p, users); foreach(PurchasedTabCollection purchasedTabCollection in purchasedTabCollections) { AnsiConsole.Markup($"[red]\nScraping Data for {purchasedTabCollection.Username}\n[/]"); @@ -766,17 +751,10 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Download path: {path}"); - var downloadContext = ActivatorUtilities.CreateInstance( - serviceProvider, - Config, - GetCreatorFileNameFormatConfig(Config, purchasedTabCollection.Username), - m_ApiHelper, - dBHelper); - int paidPostCount = 0; int paidMessagesCount = 0; - paidPostCount = await DownloadPaidPostsPurchasedTab(downloadContext, purchasedTabCollection.PaidPosts, users.FirstOrDefault(u => u.Value == purchasedTabCollection.UserId), paidPostCount, path, users); - paidMessagesCount = await DownloadPaidMessagesPurchasedTab(downloadContext, purchasedTabCollection.PaidMessages, users.FirstOrDefault(u => u.Value == purchasedTabCollection.UserId), paidMessagesCount, path, users); + paidPostCount = await DownloadPaidPostsPurchasedTab(purchasedTabCollection.Username, purchasedTabCollection.PaidPosts, users.FirstOrDefault(u => u.Value == purchasedTabCollection.UserId), paidPostCount, path, users); + paidMessagesCount = await DownloadPaidMessagesPurchasedTab(purchasedTabCollection.Username, purchasedTabCollection.PaidMessages, users.FirstOrDefault(u => u.Value == purchasedTabCollection.UserId), paidMessagesCount, path, users); AnsiConsole.Markup("\n"); AnsiConsole.Write(new BreakdownChart() @@ -816,7 +794,7 @@ public class Program(IServiceProvider serviceProvider) { long message_id = Convert.ToInt64(messageUrl.Split("?firstId=")[1]); long user_id = Convert.ToInt64(messageUrl.Split("/")[6]); - JObject user = await m_ApiHelper.GetUserInfoById($"/users/list?x[]={user_id.ToString()}"); + JObject user = await apiService.GetUserInfoById($"/users/list?x[]={user_id.ToString()}"); string username = string.Empty; Log.Debug($"Message ID: {message_id}"); @@ -857,16 +835,9 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Folder for {username} already created"); } - await dBHelper.CreateDB(path); + await dbService.CreateDB(path); - var downloadContext = ActivatorUtilities.CreateInstance( - serviceProvider, - Config, - GetCreatorFileNameFormatConfig(Config, username), - m_ApiHelper, - dBHelper); - - await DownloadPaidMessage(downloadContext, hasSelectedUsersKVP, username, 1, path, message_id); + await DownloadPaidMessage(username, hasSelectedUsersKVP, 1, path, message_id); } } else if (hasSelectedUsersKVP.Key && !hasSelectedUsersKVP.Value.ContainsKey("ConfigChanged")) @@ -898,7 +869,7 @@ public class Program(IServiceProvider serviceProvider) Log.Debug("Download path: ", path); - await dBHelper.CheckUsername(user, path); + await dbService.CheckUsername(user, path); if (!Directory.Exists(path)) { @@ -912,62 +883,55 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Folder for {user.Key} already created"); } - await dBHelper.CreateDB(path); - - var downloadContext = ActivatorUtilities.CreateInstance( - serviceProvider, - Config, - GetCreatorFileNameFormatConfig(Config, user.Key), - m_ApiHelper, - dBHelper); + await dbService.CreateDB(path); if (Config.DownloadAvatarHeaderPhoto) { - Entities.User? user_info = await m_ApiHelper.GetUserInfo($"/users/{user.Key}"); + Entities.User? user_info = await apiService.GetUserInfo($"/users/{user.Key}"); if (user_info != null) { - await downloadContext.DownloadHelper.DownloadAvatarHeader(user_info.avatar, user_info.header, path, user.Key); + await downloadService.DownloadAvatarHeader(user_info.avatar, user_info.header, path, user.Key); } } if (Config.DownloadPaidPosts) { - paidPostCount = await DownloadPaidPosts(downloadContext, hasSelectedUsersKVP, user, paidPostCount, path); + paidPostCount = await DownloadPaidPosts(user.Key, hasSelectedUsersKVP, user, paidPostCount, path); } if (Config.DownloadPosts) { - postCount = await DownloadFreePosts(downloadContext, hasSelectedUsersKVP, user, postCount, path); + postCount = await DownloadFreePosts(user.Key, hasSelectedUsersKVP, user, postCount, path); } if (Config.DownloadArchived) { - archivedCount = await DownloadArchived(downloadContext, hasSelectedUsersKVP, user, archivedCount, path); + archivedCount = await DownloadArchived(user.Key, hasSelectedUsersKVP, user, archivedCount, path); } if (Config.DownloadStreams) { - streamsCount = await DownloadStreams(downloadContext, hasSelectedUsersKVP, user, streamsCount, path); + streamsCount = await DownloadStreams(user.Key, hasSelectedUsersKVP, user, streamsCount, path); } if (Config.DownloadStories) { - storiesCount = await DownloadStories(downloadContext, user, storiesCount, path); + storiesCount = await DownloadStories(user.Key, user, storiesCount, path); } if (Config.DownloadHighlights) { - highlightsCount = await DownloadHighlights(downloadContext, user, highlightsCount, path); + highlightsCount = await DownloadHighlights(user.Key, user, highlightsCount, path); } if (Config.DownloadMessages) { - messagesCount = await DownloadMessages(downloadContext, hasSelectedUsersKVP, user, messagesCount, path); + messagesCount = await DownloadMessages(user.Key, hasSelectedUsersKVP, user, messagesCount, path); } if (Config.DownloadPaidMessages) { - paidMessagesCount = await DownloadPaidMessages(downloadContext, hasSelectedUsersKVP, user, paidMessagesCount, path); + paidMessagesCount = await DownloadPaidMessages(user.Key, hasSelectedUsersKVP, user, paidMessagesCount, path); } AnsiConsole.Markup("\n"); @@ -998,570 +962,276 @@ public class Program(IServiceProvider serviceProvider) } while (!Config.NonInteractiveMode); } - private static IFileNameFormatConfig GetCreatorFileNameFormatConfig(Entities.Config config, string userName) + private async Task DownloadPaidMessages(string username, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int paidMessagesCount, string path) { - FileNameFormatConfig combinedConfig = new FileNameFormatConfig(); - - Func func = (val1, val2) => - { - if (string.IsNullOrEmpty(val1)) - return val2; - else - return val1; - }; - - if(config.CreatorConfigs.ContainsKey(userName)) - { - CreatorConfig creatorConfig = config.CreatorConfigs[userName]; - if(creatorConfig != null) - { - combinedConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat; - combinedConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat; - combinedConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat; - combinedConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat; - } - } - - combinedConfig.PaidMessageFileNameFormat = func(combinedConfig.PaidMessageFileNameFormat, config.PaidMessageFileNameFormat); - combinedConfig.PostFileNameFormat = func(combinedConfig.PostFileNameFormat, config.PostFileNameFormat); - combinedConfig.MessageFileNameFormat = func(combinedConfig.MessageFileNameFormat, config.MessageFileNameFormat); - combinedConfig.PaidPostFileNameFormat = func(combinedConfig.PaidPostFileNameFormat, config.PaidPostFileNameFormat); - - Log.Debug($"PaidMessageFilenameFormat: {combinedConfig.PaidMessageFileNameFormat}"); - Log.Debug($"PostFileNameFormat: {combinedConfig.PostFileNameFormat}"); - Log.Debug($"MessageFileNameFormat: {combinedConfig.MessageFileNameFormat}"); - Log.Debug($"PaidPostFileNameFormatt: {combinedConfig.PaidPostFileNameFormat}"); - - return combinedConfig; - } - - private static async Task DownloadPaidMessages(IDownloadContext downloadContext, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int paidMessagesCount, string path) - { - Log.Debug($"Calling DownloadPaidMessages - {user.Key}"); + IConfigService configService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); PaidMessageCollection paidMessageCollection = new PaidMessageCollection(); await AnsiConsole.Status() .StartAsync("[red]Getting Paid Messages[/]", async ctx => { - paidMessageCollection = await downloadContext.ApiHelper.GetPaidMessages("/posts/paid/chat", path, user.Key, downloadContext.DownloadConfig!, ctx); + paidMessageCollection = await apiService.GetPaidMessages("/posts/paid/chat", path, user.Key, ctx); }); - int oldPaidMessagesCount = 0; - int newPaidMessagesCount = 0; + if (paidMessageCollection != null && paidMessageCollection.PaidMessages.Count > 0) { AnsiConsole.Markup($"[red]Found {paidMessageCollection.PaidMessages.Count} Media from {paidMessageCollection.PaidMessageObjects.Count} Paid Messages\n[/]"); - Log.Debug($"Found {paidMessageCollection.PaidMessages.Count} Media from {paidMessageCollection.PaidMessageObjects.Count} Paid Messages"); - paidMessagesCount = paidMessageCollection.PaidMessages.Count; + long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(paidMessageCollection.PaidMessages.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(paidMessageCollection.PaidMessages.Values.ToList()); } else { - totalSize = paidMessagesCount; + totalSize = paidMessageCollection.PaidMessages.Count; } + + DownloadResult result = null; await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { - // Define tasks var task = ctx.AddTask($"[red]Downloading {paidMessageCollection.PaidMessages.Count} Paid Messages[/]", autoStart: false); - Log.Debug($"Downloading {paidMessageCollection.PaidMessages.Count} Paid Messages"); task.MaxValue = totalSize; task.StartTask(); - foreach (KeyValuePair paidMessageKVP in paidMessageCollection.PaidMessages) - { - bool isNew; - if (paidMessageKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) - { - string[] messageUrlParsed = paidMessageKVP.Value.Split(','); - string mpdURL = messageUrlParsed[0]; - string policy = messageUrlParsed[1]; - string signature = messageUrlParsed[2]; - string kvp = messageUrlParsed[3]; - string mediaId = messageUrlParsed[4]; - string messageId = messageUrlParsed[5]; - string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); - if (pssh != null) - { - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", "?type=widevine"); - string decryptionKey; - if (clientIdBlobMissing || devicePrivateKeyMissing) - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); - } - else - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); - } + var progressReporter = new SpectreProgressReporter(task); + result = await downloadService.DownloadPaidMessages(username, path, hasSelectedUsersKVP.Value, clientIdBlobMissing, devicePrivateKeyMissing, paidMessageCollection, progressReporter); - Medium? mediaInfo = paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.id == paidMessageKVP.Key); - Purchased.List? messageInfo = paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - - isNew = await downloadContext.DownloadHelper.DownloadPurchasedMessageDRMVideo( - policy: policy, - signature: signature, - kvp: kvp, - url: mpdURL, - decryptionKey: decryptionKey, - folder: path, - lastModified: lastModified, - media_id: paidMessageKVP.Key, - api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidMessageFileNameFormat ?? string.Empty, - messageInfo: messageInfo, - messageMedia: mediaInfo, - fromUser: messageInfo?.fromUser, - users: hasSelectedUsersKVP.Value); - - if (isNew) - { - newPaidMessagesCount++; - } - else - { - oldPaidMessagesCount++; - } - } - } - else - { - Medium? mediaInfo = paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.id == paidMessageKVP.Key); - Purchased.List messageInfo = paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - - isNew = await downloadContext.DownloadHelper.DownloadPurchasedMedia( - url: paidMessageKVP.Value, - folder: path, - media_id: paidMessageKVP.Key, - api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidMessageFileNameFormat ?? string.Empty, - messageInfo: messageInfo, - messageMedia: mediaInfo, - fromUser: messageInfo?.fromUser, - users: hasSelectedUsersKVP.Value); - if (isNew) - { - newPaidMessagesCount++; - } - else - { - oldPaidMessagesCount++; - } - } - } task.StopTask(); }); - AnsiConsole.Markup($"[red]Paid Messages Already Downloaded: {oldPaidMessagesCount} New Paid Messages Downloaded: {newPaidMessagesCount}[/]\n"); + + AnsiConsole.Markup($"[red]Paid Messages Already Downloaded: {result.ExistingDownloads} New Paid Messages Downloaded: {result.NewDownloads}[/]\n"); + return result.TotalCount; } else { AnsiConsole.Markup($"[red]Found 0 Paid Messages\n[/]"); + return 0; } - - return paidMessagesCount; } - - private static async Task DownloadMessages(IDownloadContext downloadContext, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int messagesCount, string path) + private async Task DownloadMessages(string username, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int messagesCount, string path) { - Log.Debug($"Calling DownloadMessages - {user.Key}"); + IConfigService configService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); MessageCollection messages = new MessageCollection(); await AnsiConsole.Status() .StartAsync("[red]Getting Messages[/]", async ctx => { - messages = await downloadContext.ApiHelper.GetMessages($"/chats/{user.Value}/messages", path, downloadContext.DownloadConfig!, ctx); + messages = await apiService.GetMessages($"/chats/{user.Value}/messages", path, ctx); }); - int oldMessagesCount = 0; - int newMessagesCount = 0; + if (messages != null && messages.Messages.Count > 0) { AnsiConsole.Markup($"[red]Found {messages.Messages.Count} Media from {messages.MessageObjects.Count} Messages\n[/]"); - Log.Debug($"[red]Found {messages.Messages.Count} Media from {messages.MessageObjects.Count} Messages"); - messagesCount = messages.Messages.Count; + long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(messages.Messages.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(messages.Messages.Values.ToList()); } else { - totalSize = messagesCount; + totalSize = messages.Messages.Count; } + + DownloadResult result = null; await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { - // Define tasks var task = ctx.AddTask($"[red]Downloading {messages.Messages.Count} Messages[/]", autoStart: false); - Log.Debug($"Downloading {messages.Messages.Count} Messages"); task.MaxValue = totalSize; task.StartTask(); - foreach (KeyValuePair messageKVP in messages.Messages) - { - bool isNew; - if (messageKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) - { - string[] messageUrlParsed = messageKVP.Value.Split(','); - string mpdURL = messageUrlParsed[0]; - string policy = messageUrlParsed[1]; - string signature = messageUrlParsed[2]; - string kvp = messageUrlParsed[3]; - string mediaId = messageUrlParsed[4]; - string messageId = messageUrlParsed[5]; - string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); - if (pssh != null) - { - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", "?type=widevine"); - string decryptionKey; - if (clientIdBlobMissing || devicePrivateKeyMissing) - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); - } - else - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); - } - Messages.Medium? mediaInfo = messages.MessageMedia.FirstOrDefault(m => m.id == messageKVP.Key); - Messages.List? messageInfo = messages.MessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadMessageDRMVideo( - policy: policy, - signature: signature, - kvp: kvp, - url: mpdURL, - decryptionKey: decryptionKey, - folder: path, - lastModified: lastModified, - media_id: messageKVP.Key, - api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.MessageFileNameFormat ?? string.Empty, - messageInfo: messageInfo, - messageMedia: mediaInfo, - fromUser: messageInfo?.fromUser, - users: hasSelectedUsersKVP.Value); + var progressReporter = new SpectreProgressReporter(task); + result = await downloadService.DownloadMessages(username, user.Value, path, hasSelectedUsersKVP.Value, clientIdBlobMissing, devicePrivateKeyMissing, messages, progressReporter); - - if (isNew) - { - newMessagesCount++; - } - else - { - oldMessagesCount++; - } - } - } - else - { - Messages.Medium? mediaInfo = messages.MessageMedia.FirstOrDefault(m => m.id == messageKVP.Key); - Messages.List? messageInfo = messages.MessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - - isNew = await downloadContext.DownloadHelper.DownloadMessageMedia( - url: messageKVP.Value, - folder: path, - media_id: messageKVP.Key, - api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig!.MessageFileNameFormat ?? string.Empty, - messageInfo: messageInfo, - messageMedia: mediaInfo, - fromUser: messageInfo?.fromUser, - users: hasSelectedUsersKVP.Value); - - if (isNew) - { - newMessagesCount++; - } - else - { - oldMessagesCount++; - } - } - } task.StopTask(); }); - AnsiConsole.Markup($"[red]Messages Already Downloaded: {oldMessagesCount} New Messages Downloaded: {newMessagesCount}[/]\n"); + + AnsiConsole.Markup($"[red]Messages Already Downloaded: {result.ExistingDownloads} New Messages Downloaded: {result.NewDownloads}[/]\n"); + return result.TotalCount; } else { AnsiConsole.Markup($"[red]Found 0 Messages\n[/]"); + return 0; } - - return messagesCount; } - private static async Task DownloadHighlights(IDownloadContext downloadContext, KeyValuePair user, int highlightsCount, string path) + private async Task DownloadHighlights(string username, KeyValuePair user, int highlightsCount, string path) { - Log.Debug($"Calling DownloadHighlights - {user.Key}"); + IConfigService configService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); AnsiConsole.Markup($"[red]Getting Highlights\n[/]"); - Dictionary highlights = await downloadContext.ApiHelper.GetMedia(MediaType.Highlights, $"/users/{user.Value}/stories/highlights", null, path, downloadContext.DownloadConfig!, paid_post_ids); - int oldHighlightsCount = 0; - int newHighlightsCount = 0; - if (highlights != null && highlights.Count > 0) + + // Calculate total size for progress bar + long totalSize = 0; + var tempHighlights = await apiService.GetMedia(MediaType.Highlights, $"/users/{user.Value}/stories/highlights", null, path, paid_post_ids); + if (tempHighlights != null && tempHighlights.Count > 0) { - AnsiConsole.Markup($"[red]Found {highlights.Count} Highlights\n[/]"); - Log.Debug($"Found {highlights.Count} Highlights"); - highlightsCount = highlights.Count; - long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + AnsiConsole.Markup($"[red]Found {tempHighlights.Count} Highlights\n[/]"); + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(highlights.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(tempHighlights.Values.ToList()); } else { - totalSize = highlightsCount; + totalSize = tempHighlights.Count; } - await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) - .StartAsync(async ctx => - { - // Define tasks - var task = ctx.AddTask($"[red]Downloading {highlights.Count} Highlights[/]", autoStart: false); - Log.Debug($"Downloading {highlights.Count} Highlights"); - task.MaxValue = totalSize; - task.StartTask(); - foreach (KeyValuePair highlightKVP in highlights) - { - bool isNew = await downloadContext.DownloadHelper.DownloadStoryMedia(highlightKVP.Value, path, highlightKVP.Key, "Stories", task); - if (isNew) - { - newHighlightsCount++; - } - else - { - oldHighlightsCount++; - } - } - task.StopTask(); - }); - AnsiConsole.Markup($"[red]Highlights Already Downloaded: {oldHighlightsCount} New Highlights Downloaded: {newHighlightsCount}[/]\n"); - Log.Debug($"Highlights Already Downloaded: {oldHighlightsCount} New Highlights Downloaded: {newHighlightsCount}"); } else { AnsiConsole.Markup($"[red]Found 0 Highlights\n[/]"); - Log.Debug($"Found 0 Highlights"); + return 0; } - return highlightsCount; + DownloadResult result = null; + await AnsiConsole.Progress() + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) + .StartAsync(async ctx => + { + var task = ctx.AddTask($"[red]Downloading {tempHighlights.Count} Highlights[/]", autoStart: false); + task.MaxValue = totalSize; + task.StartTask(); + + var progressReporter = new SpectreProgressReporter(task); + result = await downloadService.DownloadHighlights(username, user.Value, path, paid_post_ids.ToHashSet(), progressReporter); + + task.StopTask(); + }); + + AnsiConsole.Markup($"[red]Highlights Already Downloaded: {result.ExistingDownloads} New Highlights Downloaded: {result.NewDownloads}[/]\n"); + return result.TotalCount; } - private static async Task DownloadStories(IDownloadContext downloadContext, KeyValuePair user, int storiesCount, string path) + private async Task DownloadStories(string username, KeyValuePair user, int storiesCount, string path) { - Log.Debug($"Calling DownloadStories - {user.Key}"); + IConfigService configService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); AnsiConsole.Markup($"[red]Getting Stories\n[/]"); - Dictionary stories = await downloadContext.ApiHelper.GetMedia(MediaType.Stories, $"/users/{user.Value}/stories", null, path, downloadContext.DownloadConfig!, paid_post_ids); - int oldStoriesCount = 0; - int newStoriesCount = 0; - if (stories != null && stories.Count > 0) + + // Calculate total size for progress bar + long totalSize = 0; + var tempStories = await serviceProvider.GetRequiredService().GetMedia(MediaType.Stories, $"/users/{user.Value}/stories", null, path, paid_post_ids); + if (tempStories != null && tempStories.Count > 0) { - AnsiConsole.Markup($"[red]Found {stories.Count} Stories\n[/]"); - Log.Debug($"Found {stories.Count} Stories"); - storiesCount = stories.Count; - long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + AnsiConsole.Markup($"[red]Found {tempStories.Count} Stories\n[/]"); + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(stories.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(tempStories.Values.ToList()); } else { - totalSize = storiesCount; + totalSize = tempStories.Count; } - await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) - .StartAsync(async ctx => - { - // Define tasks - var task = ctx.AddTask($"[red]Downloading {stories.Count} Stories[/]", autoStart: false); - Log.Debug($"Downloading {stories.Count} Stories"); - task.MaxValue = totalSize; - task.StartTask(); - foreach (KeyValuePair storyKVP in stories) - { - bool isNew = await downloadContext.DownloadHelper.DownloadStoryMedia(storyKVP.Value, path, storyKVP.Key, "Stories", task); - if (isNew) - { - newStoriesCount++; - } - else - { - oldStoriesCount++; - } - } - task.StopTask(); - }); - AnsiConsole.Markup($"[red]Stories Already Downloaded: {oldStoriesCount} New Stories Downloaded: {newStoriesCount}[/]\n"); - Log.Debug($"Stories Already Downloaded: {oldStoriesCount} New Stories Downloaded: {newStoriesCount}"); } else { AnsiConsole.Markup($"[red]Found 0 Stories\n[/]"); - Log.Debug($"Found 0 Stories"); + return 0; } - return storiesCount; + DownloadResult result = null; + await AnsiConsole.Progress() + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) + .StartAsync(async ctx => + { + var task = ctx.AddTask($"[red]Downloading {tempStories.Count} Stories[/]", autoStart: false); + task.MaxValue = totalSize; + task.StartTask(); + + var progressReporter = new SpectreProgressReporter(task); + result = await downloadService.DownloadStories(username, user.Value, path, paid_post_ids.ToHashSet(), progressReporter); + + task.StopTask(); + }); + + AnsiConsole.Markup($"[red]Stories Already Downloaded: {result.ExistingDownloads} New Stories Downloaded: {result.NewDownloads}[/]\n"); + return result.TotalCount; } - private static async Task DownloadArchived(IDownloadContext downloadContext, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int archivedCount, string path) + private async Task DownloadArchived(string username, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int archivedCount, string path) { - Log.Debug($"Calling DownloadArchived - {user.Key}"); + IConfigService configService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); ArchivedCollection archived = new ArchivedCollection(); await AnsiConsole.Status() .StartAsync("[red]Getting Archived Posts[/]", async ctx => { - archived = await downloadContext.ApiHelper.GetArchived($"/users/{user.Value}/posts", path, downloadContext.DownloadConfig!, ctx); + archived = await apiService.GetArchived($"/users/{user.Value}/posts", path, ctx); }); - int oldArchivedCount = 0; - int newArchivedCount = 0; if (archived != null && archived.ArchivedPosts.Count > 0) { AnsiConsole.Markup($"[red]Found {archived.ArchivedPosts.Count} Media from {archived.ArchivedPostObjects.Count} Archived Posts\n[/]"); - Log.Debug($"Found {archived.ArchivedPosts.Count} Media from {archived.ArchivedPostObjects.Count} Archived Posts"); - archivedCount = archived.ArchivedPosts.Count; + long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(archived.ArchivedPosts.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(archived.ArchivedPosts.Values.ToList()); } else { - totalSize = archivedCount; + totalSize = archived.ArchivedPosts.Count; } + + DownloadResult result = null; await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { - // Define tasks var task = ctx.AddTask($"[red]Downloading {archived.ArchivedPosts.Count} Archived Posts[/]", autoStart: false); - Log.Debug($"Downloading {archived.ArchivedPosts.Count} Archived Posts"); task.MaxValue = totalSize; task.StartTask(); - foreach (KeyValuePair archivedKVP in archived.ArchivedPosts) - { - bool isNew; - if (archivedKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) - { - string[] messageUrlParsed = archivedKVP.Value.Split(','); - string mpdURL = messageUrlParsed[0]; - string policy = messageUrlParsed[1]; - string signature = messageUrlParsed[2]; - string kvp = messageUrlParsed[3]; - string mediaId = messageUrlParsed[4]; - string postId = messageUrlParsed[5]; - string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); - if (pssh != null) - { - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", "?type=widevine"); - string decryptionKey; - if (clientIdBlobMissing || devicePrivateKeyMissing) - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); - } - else - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); - } - Archived.Medium? mediaInfo = archived.ArchivedPostMedia.FirstOrDefault(m => m.id == archivedKVP.Key); - Archived.List? postInfo = archived.ArchivedPostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadArchivedPostDRMVideo( - policy: policy, - signature: signature, - kvp: kvp, - url: mpdURL, - decryptionKey: decryptionKey, - folder: path, - lastModified: lastModified, - media_id: archivedKVP.Key, - api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PostFileNameFormat ?? string.Empty, - postInfo: postInfo, - postMedia: mediaInfo, - author: postInfo?.author, - users: hasSelectedUsersKVP.Value); + var progressReporter = new SpectreProgressReporter(task); + result = await downloadService.DownloadArchived(username, user.Value, path, hasSelectedUsersKVP.Value, clientIdBlobMissing, devicePrivateKeyMissing, archived, progressReporter); - if (isNew) - { - newArchivedCount++; - } - else - { - oldArchivedCount++; - } - } - } - else - { - Archived.Medium? mediaInfo = archived.ArchivedPostMedia.FirstOrDefault(m => m.id == archivedKVP.Key); - Archived.List? postInfo = archived.ArchivedPostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - - isNew = await downloadContext.DownloadHelper.DownloadArchivedMedia( - url: archivedKVP.Value, - folder: path, - media_id: archivedKVP.Key, - api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PostFileNameFormat ?? string.Empty, - messageInfo: postInfo, - messageMedia: mediaInfo, - author: postInfo?.author, - users: hasSelectedUsersKVP.Value); - - if (isNew) - { - newArchivedCount++; - } - else - { - oldArchivedCount++; - } - } - } task.StopTask(); }); - AnsiConsole.Markup($"[red]Archived Posts Already Downloaded: {oldArchivedCount} New Archived Posts Downloaded: {newArchivedCount}[/]\n"); + + AnsiConsole.Markup($"[red]Archived Posts Already Downloaded: {result.ExistingDownloads} New Archived Posts Downloaded: {result.NewDownloads}[/]\n"); + return result.TotalCount; } else { AnsiConsole.Markup($"[red]Found 0 Archived Posts\n[/]"); + return 0; } - - return archivedCount; } - private static async Task DownloadFreePosts(IDownloadContext downloadContext, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int postCount, string path) + private async Task DownloadFreePosts(string username, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int postCount, string path) { + IConfigService configService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); + + AnsiConsole.Markup($"[red]Getting Posts (this may take a long time, depending on the number of Posts the creator has)\n[/]"); Log.Debug($"Calling DownloadFreePosts - {user.Key}"); PostCollection posts = new PostCollection(); await AnsiConsole.Status() - .StartAsync("[red]Getting Posts (this may take a long time, depending on the number of Posts the creator has)[/]", async ctx => + .StartAsync("[red]Getting Posts[/]", async ctx => { - posts = await downloadContext.ApiHelper.GetPosts($"/users/{user.Value}/posts", path, downloadContext.DownloadConfig!, paid_post_ids, ctx); + posts = await apiService.GetPosts($"/users/{user.Value}/posts", path, paid_post_ids, ctx); }); - int oldPostCount = 0; - int newPostCount = 0; if (posts == null || posts.Posts.Count <= 0) { AnsiConsole.Markup($"[red]Found 0 Posts\n[/]"); @@ -1571,125 +1241,39 @@ public class Program(IServiceProvider serviceProvider) AnsiConsole.Markup($"[red]Found {posts.Posts.Count} Media from {posts.PostObjects.Count} Posts\n[/]"); Log.Debug($"Found {posts.Posts.Count} Media from {posts.PostObjects.Count} Posts"); - postCount = posts.Posts.Count; - long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) - { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(posts.Posts.Values.ToList()); - } - else - { - totalSize = postCount; - } + + long totalSize = configService.CurrentConfig.ShowScrapeSize + ? await downloadService.CalculateTotalFileSize(posts.Posts.Values.ToList()) + : posts.Posts.Count; + + DownloadResult result = null; await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { var task = ctx.AddTask($"[red]Downloading {posts.Posts.Count} Posts[/]", autoStart: false); - Log.Debug($"Downloading {posts.Posts.Count} Posts"); task.MaxValue = totalSize; task.StartTask(); - foreach (KeyValuePair postKVP in posts.Posts) - { - bool isNew; - if (postKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) - { - string[] messageUrlParsed = postKVP.Value.Split(','); - string mpdURL = messageUrlParsed[0]; - string policy = messageUrlParsed[1]; - string signature = messageUrlParsed[2]; - string kvp = messageUrlParsed[3]; - string mediaId = messageUrlParsed[4]; - string postId = messageUrlParsed[5]; - string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); - if (pssh == null) - { - continue; - } - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", "?type=widevine"); - string decryptionKey; - if (clientIdBlobMissing || devicePrivateKeyMissing) - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); - } - else - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); - } - Post.Medium mediaInfo = posts.PostMedia.FirstOrDefault(m => m.id == postKVP.Key); - Post.List postInfo = posts.PostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + var progressReporter = new SpectreProgressReporter(task); + result = await downloadService.DownloadFreePosts(username, user.Value, path, hasSelectedUsersKVP.Value, clientIdBlobMissing, devicePrivateKeyMissing, posts, progressReporter); - isNew = await downloadContext.DownloadHelper.DownloadPostDRMVideo( - policy: policy, - signature: signature, - kvp: kvp, - url: mpdURL, - decryptionKey: decryptionKey, - folder: path, - lastModified: lastModified, - media_id: postKVP.Key, - api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PostFileNameFormat ?? string.Empty, - postInfo: postInfo, - postMedia: mediaInfo, - author: postInfo?.author, - users: hasSelectedUsersKVP.Value); - if (isNew) - { - newPostCount++; - } - else - { - oldPostCount++; - } - } - else - { - try - { - Post.Medium? mediaInfo = posts.PostMedia.FirstOrDefault(m => (m?.id == postKVP.Key) == true); - Post.List? postInfo = posts.PostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - - isNew = await downloadContext.DownloadHelper.DownloadPostMedia( - url: postKVP.Value, - folder: path, - media_id: postKVP.Key, - api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PostFileNameFormat ?? string.Empty, - postInfo: postInfo, - postMedia: mediaInfo, - author: postInfo?.author, - users: hasSelectedUsersKVP.Value); - if (isNew) - { - newPostCount++; - } - else - { - oldPostCount++; - } - } - catch - { - Console.WriteLine("Media was null"); - } - } - } task.StopTask(); }); - AnsiConsole.Markup($"[red]Posts Already Downloaded: {oldPostCount} New Posts Downloaded: {newPostCount}[/]\n"); - Log.Debug("Posts Already Downloaded: {oldPostCount} New Posts Downloaded: {newPostCount}"); - return postCount; + AnsiConsole.Markup($"[red]Posts Already Downloaded: {result.ExistingDownloads} New Posts Downloaded: {result.NewDownloads}[/]\n"); + Log.Debug($"Posts Already Downloaded: {result.ExistingDownloads} New Posts Downloaded: {result.NewDownloads}"); + + return result.TotalCount; } - private static async Task DownloadPaidPosts(IDownloadContext downloadContext, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int paidPostCount, string path) + private async Task DownloadPaidPosts(string username, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int paidPostCount, string path) { + IConfigService configService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); + + AnsiConsole.Markup($"[red]Getting Paid Posts\n[/]"); Log.Debug($"Calling DownloadPaidPosts - {user.Key}"); PaidPostCollection purchasedPosts = new PaidPostCollection(); @@ -1697,11 +1281,9 @@ public class Program(IServiceProvider serviceProvider) await AnsiConsole.Status() .StartAsync("[red]Getting Paid Posts[/]", async ctx => { - purchasedPosts = await downloadContext.ApiHelper.GetPaidPosts("/posts/paid/post", path, user.Key, downloadContext.DownloadConfig!, paid_post_ids, ctx); + purchasedPosts = await apiService.GetPaidPosts("/posts/paid/post", path, user.Key, paid_post_ids, ctx); }); - int oldPaidPostCount = 0; - int newPaidPostCount = 0; if (purchasedPosts == null || purchasedPosts.PaidPosts.Count <= 0) { AnsiConsole.Markup($"[red]Found 0 Paid Posts\n[/]"); @@ -1711,118 +1293,40 @@ public class Program(IServiceProvider serviceProvider) AnsiConsole.Markup($"[red]Found {purchasedPosts.PaidPosts.Count} Media from {purchasedPosts.PaidPostObjects.Count} Paid Posts\n[/]"); Log.Debug($"Found {purchasedPosts.PaidPosts.Count} Media from {purchasedPosts.PaidPostObjects.Count} Paid Posts"); - paidPostCount = purchasedPosts.PaidPosts.Count; - long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) - { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(purchasedPosts.PaidPosts.Values.ToList()); - } - else - { - totalSize = paidPostCount; - } + + long totalSize = configService.CurrentConfig.ShowScrapeSize + ? await downloadService.CalculateTotalFileSize(purchasedPosts.PaidPosts.Values.ToList()) + : purchasedPosts.PaidPosts.Count; + + DownloadResult result = null; await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { - // Define tasks var task = ctx.AddTask($"[red]Downloading {purchasedPosts.PaidPosts.Count} Paid Posts[/]", autoStart: false); - Log.Debug($"Downloading {purchasedPosts.PaidPosts.Count} Paid Posts"); task.MaxValue = totalSize; task.StartTask(); - foreach (KeyValuePair purchasedPostKVP in purchasedPosts.PaidPosts) - { - bool isNew; - if (purchasedPostKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) - { - string[] messageUrlParsed = purchasedPostKVP.Value.Split(','); - string mpdURL = messageUrlParsed[0]; - string policy = messageUrlParsed[1]; - string signature = messageUrlParsed[2]; - string kvp = messageUrlParsed[3]; - string mediaId = messageUrlParsed[4]; - string postId = messageUrlParsed[5]; - string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); - if (pssh == null) - { - continue; - } - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", "?type=widevine"); - string decryptionKey; - if (clientIdBlobMissing || devicePrivateKeyMissing) - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); - } - else - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); - } - Medium? mediaInfo = purchasedPosts.PaidPostMedia.FirstOrDefault(m => m.id == purchasedPostKVP.Key); - Purchased.List? postInfo = purchasedPosts.PaidPostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + var progressReporter = new SpectreProgressReporter(task); + result = await downloadService.DownloadPaidPosts(username, user.Value, path, hasSelectedUsersKVP.Value, clientIdBlobMissing, devicePrivateKeyMissing, purchasedPosts, progressReporter); - isNew = await downloadContext.DownloadHelper.DownloadPurchasedPostDRMVideo( - policy: policy, - signature: signature, - kvp: kvp, - url: mpdURL, - decryptionKey: decryptionKey, - folder: path, - lastModified: lastModified, - media_id: purchasedPostKVP.Key, - api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidPostFileNameFormat ?? string.Empty, - postInfo: postInfo, - postMedia: mediaInfo, - fromUser: postInfo?.fromUser, - users: hasSelectedUsersKVP.Value); - if (isNew) - { - newPaidPostCount++; - } - else - { - oldPaidPostCount++; - } - } - else - { - Medium mediaInfo = purchasedPosts.PaidPostMedia.FirstOrDefault(m => m.id == purchasedPostKVP.Key); - Purchased.List postInfo = purchasedPosts.PaidPostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - - isNew = await downloadContext.DownloadHelper.DownloadPurchasedPostMedia( - url: purchasedPostKVP.Value, - folder: path, - media_id: purchasedPostKVP.Key, - api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidPostFileNameFormat ?? string.Empty, - messageInfo: postInfo, - messageMedia: mediaInfo, - fromUser: postInfo?.fromUser, - users: hasSelectedUsersKVP.Value); - if (isNew) - { - newPaidPostCount++; - } - else - { - oldPaidPostCount++; - } - } - } task.StopTask(); }); - AnsiConsole.Markup($"[red]Paid Posts Already Downloaded: {oldPaidPostCount} New Paid Posts Downloaded: {newPaidPostCount}[/]\n"); - Log.Debug($"Paid Posts Already Downloaded: {oldPaidPostCount} New Paid Posts Downloaded: {newPaidPostCount}"); - return paidPostCount; + + AnsiConsole.Markup($"[red]Paid Posts Already Downloaded: {result.ExistingDownloads} New Paid Posts Downloaded: {result.NewDownloads}[/]\n"); + Log.Debug($"Paid Posts Already Downloaded: {result.ExistingDownloads} New Paid Posts Downloaded: {result.NewDownloads}"); + + return result.TotalCount; } - private static async Task DownloadPaidPostsPurchasedTab(IDownloadContext downloadContext, PaidPostCollection purchasedPosts, KeyValuePair user, int paidPostCount, string path, Dictionary users) + private async Task DownloadPaidPostsPurchasedTab(string username, PaidPostCollection purchasedPosts, KeyValuePair user, int paidPostCount, string path, Dictionary users) { + IDBService dbService = serviceProvider.GetRequiredService(); + IConfigService configService = serviceProvider.GetRequiredService(); + IAuthService authService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); + int oldPaidPostCount = 0; int newPaidPostCount = 0; if (purchasedPosts == null || purchasedPosts.PaidPosts.Count <= 0) @@ -1837,16 +1341,16 @@ public class Program(IServiceProvider serviceProvider) paidPostCount = purchasedPosts.PaidPosts.Count; long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(purchasedPosts.PaidPosts.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(purchasedPosts.PaidPosts.Values.ToList()); } else { totalSize = paidPostCount; } await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { // Define tasks @@ -1867,27 +1371,27 @@ public class Program(IServiceProvider serviceProvider) string mediaId = messageUrlParsed[4]; string postId = messageUrlParsed[5]; string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); + string? pssh = await apiService.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); if (pssh == null) { continue; } - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", "?type=widevine"); + DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); + Dictionary drmHeaders = apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", "?type=widevine"); string decryptionKey; if (clientIdBlobMissing || devicePrivateKeyMissing) { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); } else { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); } Medium? mediaInfo = purchasedPosts?.PaidPostMedia?.FirstOrDefault(m => m.id == purchasedPostKVP.Key); Purchased.List? postInfo = mediaInfo != null ? purchasedPosts?.PaidPostObjects?.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true) : null; - isNew = await downloadContext.DownloadHelper.DownloadPurchasedPostDRMVideo( + isNew = await downloadService.DownloadPurchasedPostDRMVideo( policy: policy, signature: signature, kvp: kvp, @@ -1897,8 +1401,8 @@ public class Program(IServiceProvider serviceProvider) lastModified: lastModified, media_id: purchasedPostKVP.Key, api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidPostFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PaidPostFileNameFormat ?? string.Empty, postInfo: postInfo, postMedia: mediaInfo, fromUser: postInfo?.fromUser, @@ -1917,13 +1421,13 @@ public class Program(IServiceProvider serviceProvider) Medium? mediaInfo = purchasedPosts?.PaidPostMedia?.FirstOrDefault(m => m.id == purchasedPostKVP.Key); Purchased.List? postInfo = mediaInfo != null ? purchasedPosts?.PaidPostObjects?.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true) : null; - isNew = await downloadContext.DownloadHelper.DownloadPurchasedPostMedia( + isNew = await downloadService.DownloadPurchasedPostMedia( url: purchasedPostKVP.Value, folder: path, media_id: purchasedPostKVP.Key, api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidPostFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PaidPostFileNameFormat ?? string.Empty, messageInfo: postInfo, messageMedia: mediaInfo, fromUser: postInfo?.fromUser, @@ -1945,8 +1449,14 @@ public class Program(IServiceProvider serviceProvider) return paidPostCount; } - private static async Task DownloadPaidMessagesPurchasedTab(IDownloadContext downloadContext, PaidMessageCollection paidMessageCollection, KeyValuePair user, int paidMessagesCount, string path, Dictionary users) + private async Task DownloadPaidMessagesPurchasedTab(string username, PaidMessageCollection paidMessageCollection, KeyValuePair user, int paidMessagesCount, string path, Dictionary users) { + IDBService dbService = serviceProvider.GetRequiredService(); + IConfigService configService = serviceProvider.GetRequiredService(); + IAuthService authService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); + int oldPaidMessagesCount = 0; int newPaidMessagesCount = 0; if (paidMessageCollection != null && paidMessageCollection.PaidMessages.Count > 0) @@ -1955,16 +1465,16 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Found {paidMessageCollection.PaidMessages.Count} Media from {paidMessageCollection.PaidMessageObjects.Count} Paid Messages"); paidMessagesCount = paidMessageCollection.PaidMessages.Count; long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(paidMessageCollection.PaidMessages.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(paidMessageCollection.PaidMessages.Values.ToList()); } else { totalSize = paidMessagesCount; } await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { // Define tasks @@ -1985,25 +1495,25 @@ public class Program(IServiceProvider serviceProvider) string mediaId = messageUrlParsed[4]; string messageId = messageUrlParsed[5]; string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); + string? pssh = await apiService.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); if (pssh != null) { - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", "?type=widevine"); + DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); + Dictionary drmHeaders = apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", "?type=widevine"); string decryptionKey; if (clientIdBlobMissing || devicePrivateKeyMissing) { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); } else { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); } Medium? mediaInfo = paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.id == paidMessageKVP.Key); Purchased.List? messageInfo = paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadPurchasedMessageDRMVideo( + isNew = await downloadService.DownloadPurchasedMessageDRMVideo( policy: policy, signature: signature, kvp: kvp, @@ -2013,8 +1523,8 @@ public class Program(IServiceProvider serviceProvider) lastModified: lastModified, media_id: paidMessageKVP.Key, api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidMessageFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PaidMessageFileNameFormat ?? string.Empty, messageInfo: messageInfo, messageMedia: mediaInfo, fromUser: messageInfo?.fromUser, @@ -2035,13 +1545,13 @@ public class Program(IServiceProvider serviceProvider) Medium? mediaInfo = paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.id == paidMessageKVP.Key); Purchased.List messageInfo = paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadPurchasedMedia( + isNew = await downloadService.DownloadPurchasedMedia( url: paidMessageKVP.Value, folder: path, media_id: paidMessageKVP.Key, api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidMessageFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PaidMessageFileNameFormat ?? string.Empty, messageInfo: messageInfo, messageMedia: mediaInfo, fromUser: messageInfo?.fromUser, @@ -2070,152 +1580,71 @@ public class Program(IServiceProvider serviceProvider) return paidMessagesCount; } - private static async Task DownloadStreams(IDownloadContext downloadContext, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int streamsCount, string path) + private async Task DownloadStreams(string username, KeyValuePair> hasSelectedUsersKVP, KeyValuePair user, int streamsCount, string path) { - Log.Debug($"Calling DownloadStreams - {user.Key}"); + IConfigService configService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); StreamsCollection streams = new StreamsCollection(); await AnsiConsole.Status() .StartAsync("[red]Getting Streams[/]", async ctx => { - streams = await downloadContext.ApiHelper.GetStreams($"/users/{user.Value}/posts/streams", path, downloadContext.DownloadConfig!, paid_post_ids, ctx); + streams = await apiService.GetStreams($"/users/{user.Value}/posts/streams", path, paid_post_ids, ctx); }); - int oldStreamsCount = 0; - int newStreamsCount = 0; - if (streams == null || streams.Streams.Count <= 0) + if (streams != null && streams.Streams.Count > 0) { - AnsiConsole.Markup($"[red]Found 0 Streams\n[/]"); - Log.Debug($"Found 0 Streams"); - return 0; - } + AnsiConsole.Markup($"[red]Found {streams.Streams.Count} Media from {streams.StreamObjects.Count} Streams\n[/]"); - AnsiConsole.Markup($"[red]Found {streams.Streams.Count} Media from {streams.StreamObjects.Count} Streams\n[/]"); - Log.Debug($"Found {streams.Streams.Count} Media from {streams.StreamObjects.Count} Streams"); - streamsCount = streams.Streams.Count; - long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) - { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(streams.Streams.Values.ToList()); + long totalSize = 0; + if (configService.CurrentConfig.ShowScrapeSize) + { + totalSize = await downloadService.CalculateTotalFileSize(streams.Streams.Values.ToList()); + } + else + { + totalSize = streams.Streams.Count; + } + + DownloadResult result = null; + await AnsiConsole.Progress() + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) + .StartAsync(async ctx => + { + var task = ctx.AddTask($"[red]Downloading {streams.Streams.Count} Streams[/]", autoStart: false); + task.MaxValue = totalSize; + task.StartTask(); + + var progressReporter = new SpectreProgressReporter(task); + result = await downloadService.DownloadStreams(username, user.Value, path, hasSelectedUsersKVP.Value, clientIdBlobMissing, devicePrivateKeyMissing, streams, progressReporter); + + task.StopTask(); + }); + + AnsiConsole.Markup($"[red]Streams Already Downloaded: {result.ExistingDownloads} New Streams Downloaded: {result.NewDownloads}[/]\n"); + return result.TotalCount; } else { - totalSize = streamsCount; + AnsiConsole.Markup($"[red]Found 0 Streams\n[/]"); + return 0; } - await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) - .StartAsync(async ctx => - { - var task = ctx.AddTask($"[red]Downloading {streams.Streams.Count} Streams[/]", autoStart: false); - Log.Debug($"Downloading {streams.Streams.Count} Streams"); - task.MaxValue = totalSize; - task.StartTask(); - foreach (KeyValuePair streamKVP in streams.Streams) - { - bool isNew; - if (streamKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) - { - string[] messageUrlParsed = streamKVP.Value.Split(','); - string mpdURL = messageUrlParsed[0]; - string policy = messageUrlParsed[1]; - string signature = messageUrlParsed[2]; - string kvp = messageUrlParsed[3]; - string mediaId = messageUrlParsed[4]; - string postId = messageUrlParsed[5]; - string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); - if (pssh == null) - { - continue; - } - - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", "?type=widevine"); - string decryptionKey; - if (clientIdBlobMissing || devicePrivateKeyMissing) - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); - } - else - { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); - } - Streams.Medium mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.id == streamKVP.Key); - Streams.List streamInfo = streams.StreamObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - - isNew = await downloadContext.DownloadHelper.DownloadStreamsDRMVideo( - policy: policy, - signature: signature, - kvp: kvp, - url: mpdURL, - decryptionKey: decryptionKey, - folder: path, - lastModified: lastModified, - media_id: streamKVP.Key, - api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PostFileNameFormat ?? string.Empty, - streamInfo: streamInfo, - streamMedia: mediaInfo, - author: streamInfo?.author, - users: hasSelectedUsersKVP.Value); - if (isNew) - { - newStreamsCount++; - } - else - { - oldStreamsCount++; - } - } - else - { - try - { - Streams.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => (m?.id == streamKVP.Key) == true); - Streams.List? streamInfo = streams.StreamObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - - isNew = await downloadContext.DownloadHelper.DownloadStreamMedia( - url: streamKVP.Value, - folder: path, - media_id: streamKVP.Key, - api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PostFileNameFormat ?? string.Empty, - streamInfo: streamInfo, - streamMedia: mediaInfo, - author: streamInfo?.author, - users: hasSelectedUsersKVP.Value); - if (isNew) - { - newStreamsCount++; - } - else - { - oldStreamsCount++; - } - } - catch - { - Console.WriteLine("Media was null"); - } - } - } - task.StopTask(); - }); - AnsiConsole.Markup($"[red]Streams Already Downloaded: {oldStreamsCount} New Streams Downloaded: {newStreamsCount}[/]\n"); - Log.Debug($"Streams Already Downloaded: {oldStreamsCount} New Streams Downloaded: {newStreamsCount}"); - return streamsCount; } - - private static async Task DownloadPaidMessage(IDownloadContext downloadContext, KeyValuePair> hasSelectedUsersKVP, string username, int paidMessagesCount, string path, long message_id) + private async Task DownloadPaidMessage(string username, KeyValuePair> hasSelectedUsersKVP, int paidMessagesCount, string path, long message_id) { + IDBService dbService = serviceProvider.GetRequiredService(); + IConfigService configService = serviceProvider.GetRequiredService(); + IAuthService authService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); + Log.Debug($"Calling DownloadPaidMessage - {username}"); AnsiConsole.Markup($"[red]Getting Paid Message\n[/]"); - SinglePaidMessageCollection singlePaidMessageCollection = await downloadContext.ApiHelper.GetPaidMessage($"/messages/{message_id.ToString()}", path, downloadContext.DownloadConfig!); + SinglePaidMessageCollection singlePaidMessageCollection = await apiService.GetPaidMessage($"/messages/{message_id.ToString()}", path); int oldPreviewPaidMessagesCount = 0; int newPreviewPaidMessagesCount = 0; int oldPaidMessagesCount = 0; @@ -2226,16 +1655,16 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Found {singlePaidMessageCollection.PreviewSingleMessages.Count} Preview Media from {singlePaidMessageCollection.SingleMessageObjects.Count} Paid Messages"); paidMessagesCount = singlePaidMessageCollection.SingleMessages.Count; long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(singlePaidMessageCollection.PreviewSingleMessages.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(singlePaidMessageCollection.PreviewSingleMessages.Values.ToList()); } else { totalSize = paidMessagesCount; } await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { // Define tasks @@ -2256,25 +1685,25 @@ public class Program(IServiceProvider serviceProvider) string mediaId = messageUrlParsed[4]; string messageId = messageUrlParsed[5]; string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); + string? pssh = await apiService.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); if (pssh != null) { - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", "?type=widevine"); + DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); + Dictionary drmHeaders = apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", "?type=widevine"); string decryptionKey; if (clientIdBlobMissing || devicePrivateKeyMissing) { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); } else { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); } Medium? mediaInfo = singlePaidMessageCollection.PreviewSingleMessageMedia.FirstOrDefault(m => m.id == paidMessageKVP.Key); SingleMessage? messageInfo = singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadSingleMessagePreviewDRMVideo( + isNew = await downloadService.DownloadSingleMessagePreviewDRMVideo( policy: policy, signature: signature, kvp: kvp, @@ -2284,8 +1713,8 @@ public class Program(IServiceProvider serviceProvider) lastModified: lastModified, media_id: paidMessageKVP.Key, api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidMessageFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PaidMessageFileNameFormat ?? string.Empty, messageInfo: messageInfo, messageMedia: mediaInfo, fromUser: messageInfo?.fromUser, @@ -2306,13 +1735,13 @@ public class Program(IServiceProvider serviceProvider) Medium? mediaInfo = singlePaidMessageCollection.PreviewSingleMessageMedia.FirstOrDefault(m => m.id == paidMessageKVP.Key); SingleMessage? messageInfo = singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadMessagePreviewMedia( + isNew = await downloadService.DownloadMessagePreviewMedia( url: paidMessageKVP.Value, folder: path, media_id: paidMessageKVP.Key, api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidMessageFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PaidMessageFileNameFormat ?? string.Empty, messageInfo: messageInfo, messageMedia: mediaInfo, fromUser: messageInfo?.fromUser, @@ -2338,16 +1767,16 @@ public class Program(IServiceProvider serviceProvider) Log.Debug($"Found {singlePaidMessageCollection.SingleMessages.Count} Media from {singlePaidMessageCollection.SingleMessageObjects.Count} Paid Messages"); paidMessagesCount = singlePaidMessageCollection.SingleMessages.Count; long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(singlePaidMessageCollection.SingleMessages.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(singlePaidMessageCollection.SingleMessages.Values.ToList()); } else { totalSize = paidMessagesCount; } await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { // Define tasks @@ -2368,25 +1797,25 @@ public class Program(IServiceProvider serviceProvider) string mediaId = messageUrlParsed[4]; string messageId = messageUrlParsed[5]; string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); + string? pssh = await apiService.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); if (pssh != null) { - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", "?type=widevine"); + DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); + Dictionary drmHeaders = apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", "?type=widevine"); string decryptionKey; if (clientIdBlobMissing || devicePrivateKeyMissing) { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); } else { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", pssh); } Medium? mediaInfo = singlePaidMessageCollection.SingleMessageMedia.FirstOrDefault(m => m.id == paidMessageKVP.Key); SingleMessage? messageInfo = singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadSinglePurchasedMessageDRMVideo( + isNew = await downloadService.DownloadSinglePurchasedMessageDRMVideo( policy: policy, signature: signature, kvp: kvp, @@ -2396,8 +1825,8 @@ public class Program(IServiceProvider serviceProvider) lastModified: lastModified, media_id: paidMessageKVP.Key, api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidMessageFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PaidMessageFileNameFormat ?? string.Empty, messageInfo: messageInfo, messageMedia: mediaInfo, fromUser: messageInfo?.fromUser, @@ -2418,13 +1847,13 @@ public class Program(IServiceProvider serviceProvider) Medium? mediaInfo = singlePaidMessageCollection.SingleMessageMedia.FirstOrDefault(m => m.id == paidMessageKVP.Key); SingleMessage? messageInfo = singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadSinglePurchasedMedia( + isNew = await downloadService.DownloadSinglePurchasedMedia( url: paidMessageKVP.Value, folder: path, media_id: paidMessageKVP.Key, api_type: "Messages", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PaidMessageFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PaidMessageFileNameFormat ?? string.Empty, messageInfo: messageInfo, messageMedia: mediaInfo, fromUser: messageInfo?.fromUser, @@ -2453,12 +1882,16 @@ public class Program(IServiceProvider serviceProvider) return paidMessagesCount; } - private static async Task DownloadSinglePost(IDownloadContext downloadContext, long post_id, string path, Dictionary users) + private async Task DownloadSinglePost(string username, long post_id, string path, Dictionary users) { + IConfigService configService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + IDownloadService downloadService = serviceProvider.GetRequiredService(); + Log.Debug($"Calling DownloadSinglePost - {post_id.ToString()}"); AnsiConsole.Markup($"[red]Getting Post\n[/]"); - SinglePostCollection post = await downloadContext.ApiHelper.GetPost($"/posts/{post_id.ToString()}", path, downloadContext.DownloadConfig!); + SinglePostCollection post = await apiService.GetPost($"/posts/{post_id.ToString()}", path); if (post == null) { AnsiConsole.Markup($"[red]Couldn't find post\n[/]"); @@ -2467,9 +1900,9 @@ public class Program(IServiceProvider serviceProvider) } long totalSize = 0; - if (downloadContext.DownloadConfig.ShowScrapeSize) + if (configService.CurrentConfig.ShowScrapeSize) { - totalSize = await downloadContext.DownloadHelper.CalculateTotalFileSize(post.SinglePosts.Values.ToList()); + totalSize = await downloadService.CalculateTotalFileSize(post.SinglePosts.Values.ToList()); } else { @@ -2477,7 +1910,7 @@ public class Program(IServiceProvider serviceProvider) } bool isNew = false; await AnsiConsole.Progress() - .Columns(GetProgressColumns(downloadContext.DownloadConfig.ShowScrapeSize)) + .Columns(GetProgressColumns(configService.CurrentConfig.ShowScrapeSize)) .StartAsync(async ctx => { var task = ctx.AddTask($"[red]Downloading Post[/]", autoStart: false); @@ -2495,27 +1928,27 @@ public class Program(IServiceProvider serviceProvider) string mediaId = messageUrlParsed[4]; string postId = messageUrlParsed[5]; string? licenseURL = null; - string? pssh = await downloadContext.ApiHelper.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); + string? pssh = await apiService.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); if (pssh == null) { continue; } - DateTime lastModified = await downloadContext.ApiHelper.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); - Dictionary drmHeaders = downloadContext.ApiHelper.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", "?type=widevine"); + DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); + Dictionary drmHeaders = apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", "?type=widevine"); string decryptionKey; if (clientIdBlobMissing || devicePrivateKeyMissing) { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyOFDL(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); } else { - decryptionKey = await downloadContext.ApiHelper.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); + decryptionKey = await apiService.GetDecryptionKeyCDM(drmHeaders, $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", pssh); } SinglePost.Medium mediaInfo = post.SinglePostMedia.FirstOrDefault(m => m.id == postKVP.Key); SinglePost postInfo = post.SinglePostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadPostDRMVideo( + isNew = await downloadService.DownloadPostDRMVideo( policy: policy, signature: signature, kvp: kvp, @@ -2525,8 +1958,8 @@ public class Program(IServiceProvider serviceProvider) lastModified: lastModified, media_id: postKVP.Key, api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PostFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? string.Empty, postInfo: postInfo, postMedia: mediaInfo, author: postInfo?.author, @@ -2539,13 +1972,13 @@ public class Program(IServiceProvider serviceProvider) SinglePost.Medium? mediaInfo = post.SinglePostMedia.FirstOrDefault(m => (m?.id == postKVP.Key) == true); SinglePost? postInfo = post.SinglePostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); - isNew = await downloadContext.DownloadHelper.DownloadPostMedia( + isNew = await downloadService.DownloadPostMedia( url: postKVP.Value, folder: path, media_id: postKVP.Key, api_type: "Posts", - task: task, - filenameFormat: downloadContext.FileNameFormatConfig.PostFileNameFormat ?? string.Empty, + progressReporter: new SpectreProgressReporter(task), + filenameFormat: configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? string.Empty, postInfo: postInfo, postMedia: mediaInfo, author: postInfo?.author, @@ -2571,10 +2004,16 @@ public class Program(IServiceProvider serviceProvider) } } - public static async Task<(bool IsExit, Dictionary? selectedUsers, Entities.Config? updatedConfig)> HandleUserSelection(IAPIHelper apiHelper, Entities.Config currentConfig, Dictionary users, Dictionary lists, IConfigService configService, ILoggingService loggingService) + public async Task<(bool IsExit, Dictionary? selectedUsers)> HandleUserSelection(Dictionary users, Dictionary lists) { + IConfigService configService = serviceProvider.GetRequiredService(); + IAuthService authService = serviceProvider.GetRequiredService(); + IAPIService apiService = serviceProvider.GetRequiredService(); + ILoggingService loggingService = serviceProvider.GetRequiredService(); + bool hasSelectedUsers = false; Dictionary selectedUsers = new Dictionary(); + Entities.Config currentConfig = configService.CurrentConfig!; while (!hasSelectedUsers) { @@ -2616,7 +2055,7 @@ public class Program(IServiceProvider serviceProvider) foreach (var item in listSelection) { long listId = lists[item.Replace("[red]", "").Replace("[/]", "")]; - List usernames = await apiHelper.GetListUsers($"/lists/{listId}/users", currentConfig); + List usernames = await apiService.GetListUsers($"/lists/{listId}/users"); foreach (string user in usernames) { listUsernames.Add(user); @@ -2655,11 +2094,11 @@ public class Program(IServiceProvider serviceProvider) } break; case "[red]Download Single Post[/]": - return (true, new Dictionary { { "SinglePost", 0 } }, currentConfig); + return (true, new Dictionary { { "SinglePost", 0 } }); case "[red]Download Single Paid Message[/]": - return (true, new Dictionary { { "SingleMessage", 0 } }, currentConfig); + return (true, new Dictionary { { "SingleMessage", 0 } }); case "[red]Download Purchased Tab[/]": - return (true, new Dictionary { { "PurchasedTab", 0 } }, currentConfig); + return (true, new Dictionary { { "PurchasedTab", 0 } }); case "[red]Edit config.conf[/]": while (true) { @@ -2731,7 +2170,7 @@ public class Program(IServiceProvider serviceProvider) currentConfig = newConfig; if (configChanged) { - return (true, new Dictionary { { "ConfigChanged", 0 } }, currentConfig); + return (true, new Dictionary { { "ConfigChanged", 0 } }); } break; } @@ -2786,7 +2225,7 @@ public class Program(IServiceProvider serviceProvider) if (configChanged) { - return (true, new Dictionary { { "ConfigChanged", 0 } }, currentConfig); + return (true, new Dictionary { { "ConfigChanged", 0 } }); } break; @@ -2803,13 +2242,13 @@ public class Program(IServiceProvider serviceProvider) Log.Information("Deleting auth.json"); File.Delete("auth.json"); } - return (false, null, currentConfig); // Return false to indicate exit + return (false, null); // Return false to indicate exit case "[red]Exit[/]": - return (false, null, currentConfig); // Return false to indicate exit + return (false, null); // Return false to indicate exit } } - return (true, selectedUsers, currentConfig); // Return true to indicate selected users + return (true, selectedUsers); // Return true to indicate selected users } public static List GetMainMenuOptions(Dictionary users, Dictionary lists) @@ -2873,7 +2312,8 @@ public class Program(IServiceProvider serviceProvider) return true; } - static ProgressColumn[] GetProgressColumns(bool showScrapeSize) + + static ProgressColumn[] GetProgressColumns(bool showScrapeSize) { List progressColumns; if (showScrapeSize) @@ -2931,5 +2371,4 @@ public class Program(IServiceProvider serviceProvider) File.WriteAllText("auth.json", newAuthString); } } - } diff --git a/OF DL/Helpers/APIHelper.cs b/OF DL/Services/APIService.cs similarity index 84% rename from OF DL/Helpers/APIHelper.cs rename to OF DL/Services/APIService.cs index ae9c645..3d9517e 100644 --- a/OF DL/Helpers/APIHelper.cs +++ b/OF DL/Services/APIService.cs @@ -18,23 +18,23 @@ using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Xml.Linq; +using OF_DL.Widevine; using WidevineClient.Widevine; using static WidevineClient.HttpUtil; +using Constants = OF_DL.Helpers.Constants; -namespace OF_DL.Helpers; +namespace OF_DL.Services; -public class APIHelper : IAPIHelper +public class APIService(IAuthService authService, IConfigService configService, IDBService dbService) + : IAPIService { private static readonly JsonSerializerSettings m_JsonSerializerSettings; - private readonly IDBHelper dbHelper; - private readonly IDownloadConfig downloadConfig; - private readonly Auth auth; private static DateTime? cachedDynamicRulesExpiration; private static DynamicRules? cachedDynamicRules; private const int MaxAttempts = 30; private const int DelayBetweenAttempts = 3000; - static APIHelper() + static APIService() { m_JsonSerializerSettings = new() { @@ -42,13 +42,6 @@ public class APIHelper : IAPIHelper }; } - public APIHelper(Auth auth, IDownloadConfig downloadConfig, IDBHelper dbHelper) - { - this.auth = auth; - this.downloadConfig = downloadConfig; - this.dbHelper = dbHelper; - } - public Dictionary GetDynamicHeaders(string path, string queryParams) { @@ -95,7 +88,7 @@ public class APIHelper : IAPIHelper DateTimeOffset dto = (DateTimeOffset)DateTime.UtcNow; long timestamp = dto.ToUnixTimeMilliseconds(); - string input = $"{root!.StaticParam}\n{timestamp}\n{path + queryParams}\n{auth.USER_ID}"; + string input = $"{root!.StaticParam}\n{timestamp}\n{path + queryParams}\n{authService.CurrentAuth.USER_ID}"; byte[] inputBytes = Encoding.UTF8.GetBytes(input); byte[] hashBytes = SHA1.HashData(inputBytes); string hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); @@ -107,12 +100,12 @@ public class APIHelper : IAPIHelper { { "accept", "application/json, text/plain" }, { "app-token", root.AppToken! }, - { "cookie", auth!.COOKIE! }, + { "cookie", authService.CurrentAuth!.COOKIE! }, { "sign", sign }, { "time", timestamp.ToString() }, - { "user-id", auth!.USER_ID! }, - { "user-agent", auth!.USER_AGENT! }, - { "x-bc", auth!.X_BC! } + { "user-id", authService.CurrentAuth!.USER_ID! }, + { "user-agent", authService.CurrentAuth!.USER_AGENT! }, + { "x-bc", authService.CurrentAuth!.X_BC! } }; return headers; } @@ -167,12 +160,12 @@ public class APIHelper : IAPIHelper } - private static HttpClient GetHttpClient(IDownloadConfig? config = null) + private HttpClient GetHttpClient() { var client = new HttpClient(); - if (config?.Timeout != null && config.Timeout > 0) + if (configService.CurrentConfig?.Timeout != null && configService.CurrentConfig.Timeout > 0) { - client.Timeout = TimeSpan.FromSeconds(config.Timeout.Value); + client.Timeout = TimeSpan.FromSeconds(configService.CurrentConfig.Timeout.Value); } return client; } @@ -295,7 +288,7 @@ public class APIHelper : IAPIHelper } - public async Task?> GetAllSubscriptions(Dictionary getParams, string endpoint, bool includeRestricted, IDownloadConfig config) + public async Task?> GetAllSubscriptions(Dictionary getParams, string endpoint, bool includeRestricted) { try { @@ -359,7 +352,7 @@ public class APIHelper : IAPIHelper return null; } - public async Task?> GetActiveSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config) + public async Task?> GetActiveSubscriptions(string endpoint, bool includeRestricted) { Dictionary getParams = new() { @@ -369,11 +362,11 @@ public class APIHelper : IAPIHelper { "format", "infinite"} }; - return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config); + return await GetAllSubscriptions(getParams, endpoint, includeRestricted); } - public async Task?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config) + public async Task?> GetExpiredSubscriptions(string endpoint, bool includeRestricted) { Dictionary getParams = new() @@ -386,11 +379,11 @@ public class APIHelper : IAPIHelper Log.Debug("Calling GetExpiredSubscriptions"); - return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config); + return await GetAllSubscriptions(getParams, endpoint, includeRestricted); } - public async Task> GetLists(string endpoint, IDownloadConfig config) + public async Task> GetLists(string endpoint) { Log.Debug("Calling GetLists"); @@ -456,7 +449,7 @@ public class APIHelper : IAPIHelper } - public async Task?> GetListUsers(string endpoint, IDownloadConfig config) + public async Task?> GetListUsers(string endpoint) { Log.Debug($"Calling GetListUsers - {endpoint}"); @@ -520,7 +513,6 @@ public class APIHelper : IAPIHelper string endpoint, string? username, string folder, - IDownloadConfig config, List paid_post_ids) { @@ -570,34 +562,34 @@ public class APIHelper : IAPIHelper { if (story.media[0].createdAt.HasValue) { - await dbHelper.AddStory(folder, story.id, string.Empty, "0", false, false, story.media[0].createdAt.Value); + await dbService.AddStory(folder, story.id, string.Empty, "0", false, false, story.media[0].createdAt.Value); } else if (story.createdAt.HasValue) { - await dbHelper.AddStory(folder, story.id, string.Empty, "0", false, false, story.createdAt.Value); + await dbService.AddStory(folder, story.id, string.Empty, "0", false, false, story.createdAt.Value); } else { - await dbHelper.AddStory(folder, story.id, string.Empty, "0", false, false, DateTime.Now); + await dbService.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 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) + await dbService.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" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } @@ -627,7 +619,7 @@ public class APIHelper : IAPIHelper Log.Debug("Media Highlights - " + endpoint); - var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); newhighlights = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); highlights.list.AddRange(newhighlights.list); @@ -652,7 +644,7 @@ public class APIHelper : IAPIHelper HighlightMedia highlightMedia = new(); Dictionary highlight_headers = GetDynamicHeaders("/api2/v2/stories/highlights/" + highlight_id, string.Empty); - HttpClient highlight_client = GetHttpClient(config); + HttpClient highlight_client = GetHttpClient(); HttpRequestMessage highlight_request = new(HttpMethod.Get, $"https://onlyfans.com/api2/v2/stories/highlights/{highlight_id}"); @@ -671,34 +663,34 @@ public class APIHelper : IAPIHelper { if (item.media[0].createdAt.HasValue) { - await dbHelper.AddStory(folder, item.id, string.Empty, "0", false, false, item.media[0].createdAt.Value); + await dbService.AddStory(folder, item.id, string.Empty, "0", false, false, item.media[0].createdAt.Value); } else if (item.createdAt.HasValue) { - await dbHelper.AddStory(folder, item.id, string.Empty, "0", false, false, item.createdAt.Value); + await dbService.AddStory(folder, item.id, string.Empty, "0", false, false, item.createdAt.Value); } else { - await dbHelper.AddStory(folder, item.id, string.Empty, "0", false, false, DateTime.Now); + await dbService.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 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) + await dbService.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" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } @@ -730,7 +722,7 @@ public class APIHelper : IAPIHelper } - public async Task GetPaidPosts(string endpoint, string folder, string username, IDownloadConfig config, List paid_post_ids, StatusContext ctx) + public async Task GetPaidPosts(string endpoint, string folder, string username, List paid_post_ids, StatusContext ctx) { Log.Debug($"Calling GetPaidPosts - {username}"); @@ -748,7 +740,7 @@ public class APIHelper : IAPIHelper { "author", username } }; - var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); paidPosts = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Paid Posts\n[/] [red]Found {paidPosts.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); @@ -761,7 +753,7 @@ public class APIHelper : IAPIHelper Purchased newPaidPosts = new(); - var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); newPaidPosts = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); paidPosts.list.AddRange(newPaidPosts.list); @@ -808,7 +800,7 @@ public class APIHelper : IAPIHelper } } } - await 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); + await dbService.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) { @@ -817,19 +809,19 @@ public class APIHelper : IAPIHelper paid_post_ids.Add(medium.id); } - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } @@ -840,7 +832,7 @@ public class APIHelper : IAPIHelper { if (!paidPostCollection.PaidPosts.ContainsKey(medium.id)) { - await 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); + await dbService.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); } @@ -850,7 +842,7 @@ public class APIHelper : IAPIHelper if (!paidPostCollection.PaidPosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, purchase.id, medium.files.drm.manifest.dash, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? 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); } @@ -863,7 +855,7 @@ public class APIHelper : IAPIHelper { if (!paidPostCollection.PaidPosts.ContainsKey(medium.id)) { - await 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); + await dbService.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); } @@ -872,7 +864,7 @@ public class APIHelper : IAPIHelper { if (!paidPostCollection.PaidPosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, purchase.id, medium.files.drm.manifest.dash, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? 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); } @@ -898,7 +890,7 @@ public class APIHelper : IAPIHelper } - public async Task GetPosts(string endpoint, string folder, IDownloadConfig config, List paid_post_ids, StatusContext ctx) + public async Task GetPosts(string endpoint, string folder, List paid_post_ids, StatusContext ctx) { Log.Debug($"Calling GetPosts - {endpoint}"); @@ -918,14 +910,14 @@ public class APIHelper : IAPIHelper Enumerations.DownloadDateSelection downloadDateSelection = Enumerations.DownloadDateSelection.before; DateTime? downloadAsOf = null; - if (config.DownloadOnlySpecificDates && config.CustomDate.HasValue) + if (configService.CurrentConfig.DownloadOnlySpecificDates && configService.CurrentConfig.CustomDate.HasValue) { - downloadDateSelection = config.DownloadDateSelection; - downloadAsOf = config.CustomDate; + downloadDateSelection = configService.CurrentConfig.DownloadDateSelection; + downloadAsOf = configService.CurrentConfig.CustomDate; } - else if (config.DownloadPostsIncrementally) + else if (configService.CurrentConfig.DownloadPostsIncrementally) { - var mostRecentPostDate = await dbHelper.GetMostRecentPostDate(folder); + var mostRecentPostDate = await dbService.GetMostRecentPostDate(folder); if (mostRecentPostDate.HasValue) { downloadDateSelection = Enumerations.DownloadDateSelection.after; @@ -954,7 +946,7 @@ public class APIHelper : IAPIHelper { Post newposts = new(); - var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); newposts = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); posts.list.AddRange(newposts.list); @@ -975,7 +967,7 @@ public class APIHelper : IAPIHelper foreach (Post.List post in posts.list) { - if (config.SkipAds) + if (configService.CurrentConfig.SkipAds) { if (post.rawText != null && (post.rawText.Contains("#ad") || post.rawText.Contains("/trial/") || post.rawText.Contains("#announcement"))) { @@ -1001,25 +993,25 @@ public class APIHelper : IAPIHelper } } } - await 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); + await dbService.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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } @@ -1032,7 +1024,7 @@ public class APIHelper : IAPIHelper { if (!postCollection.Posts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, post.id, medium.files!.full.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); postCollection.Posts.Add(medium.id, medium.files!.full.url); postCollection.PostMedia.Add(medium); } @@ -1044,7 +1036,7 @@ public class APIHelper : IAPIHelper { if (!postCollection.Posts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, post.id, medium.files.preview.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); postCollection.Posts.Add(medium.id, medium.files.preview.url); postCollection.PostMedia.Add(medium); } @@ -1058,7 +1050,7 @@ public class APIHelper : IAPIHelper { if (!postCollection.Posts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, post.id, medium.files.drm.manifest.dash, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((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); } @@ -1083,7 +1075,7 @@ public class APIHelper : IAPIHelper } return null; } - public async Task GetPost(string endpoint, string folder, IDownloadConfig config) + public async Task GetPost(string endpoint, string folder) { Log.Debug($"Calling GetPost - {endpoint}"); @@ -1115,38 +1107,38 @@ public class APIHelper : IAPIHelper } } } - await 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); + await dbService.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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (medium.canView && medium.files?.drm == null) { - switch (downloadConfig.DownloadVideoResolution) + switch (configService.CurrentConfig.DownloadVideoResolution) { case VideoResolution.source: if (medium.files!.full != null && !string.IsNullOrEmpty(medium.files!.full.url)) { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, singlePost.id, medium.files!.full.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); singlePostCollection.SinglePosts.Add(medium.id, medium.files!.full.url); singlePostCollection.SinglePostMedia.Add(medium); } @@ -1159,7 +1151,7 @@ public class APIHelper : IAPIHelper { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, singlePost.id, medium.videoSources._240, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); singlePostCollection.SinglePosts.Add(medium.id, medium.videoSources._240); singlePostCollection.SinglePostMedia.Add(medium); } @@ -1173,7 +1165,7 @@ public class APIHelper : IAPIHelper { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, singlePost.id, medium.videoSources._720, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); singlePostCollection.SinglePosts.Add(medium.id, medium.videoSources._720); singlePostCollection.SinglePostMedia.Add(medium); } @@ -1189,7 +1181,7 @@ public class APIHelper : IAPIHelper { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, singlePost.id, medium.files.drm.manifest.dash, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((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); } @@ -1199,7 +1191,7 @@ public class APIHelper : IAPIHelper { if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, singlePost.id, medium.files.preview.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); singlePostCollection.SinglePosts.Add(medium.id, medium.files.preview.url); singlePostCollection.SinglePostMedia.Add(medium); } @@ -1224,7 +1216,7 @@ public class APIHelper : IAPIHelper return null; } - public async Task GetStreams(string endpoint, string folder, IDownloadConfig config, List paid_post_ids, StatusContext ctx) + public async Task GetStreams(string endpoint, string folder, List paid_post_ids, StatusContext ctx) { Log.Debug($"Calling GetStreams - {endpoint}"); @@ -1242,15 +1234,15 @@ public class APIHelper : IAPIHelper }; Enumerations.DownloadDateSelection downloadDateSelection = Enumerations.DownloadDateSelection.before; - if (config.DownloadOnlySpecificDates && config.CustomDate.HasValue) + if (configService.CurrentConfig.DownloadOnlySpecificDates && configService.CurrentConfig.CustomDate.HasValue) { - downloadDateSelection = config.DownloadDateSelection; + downloadDateSelection = configService.CurrentConfig.DownloadDateSelection; } UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, - config.CustomDate); + configService.CurrentConfig.CustomDate); var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); streams = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); @@ -1269,7 +1261,7 @@ public class APIHelper : IAPIHelper { Streams newstreams = new(); - var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); newstreams = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); streams.list.AddRange(newstreams.list); @@ -1304,25 +1296,25 @@ public class APIHelper : IAPIHelper } } } - await 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); + await dbService.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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } @@ -1333,7 +1325,7 @@ public class APIHelper : IAPIHelper { if (!streamsCollection.Streams.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, stream.id, medium.files.full.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), streamPreviewIds.Contains((long)medium.id) ? true : false, false, null); streamsCollection.Streams.Add(medium.id, medium.files.full.url); streamsCollection.StreamMedia.Add(medium); } @@ -1346,7 +1338,7 @@ public class APIHelper : IAPIHelper { if (!streamsCollection.Streams.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, stream.id, medium.files.drm.manifest.dash, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), streamPreviewIds.Contains((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); } @@ -1373,7 +1365,7 @@ public class APIHelper : IAPIHelper } - public async Task GetArchived(string endpoint, string folder, IDownloadConfig config, StatusContext ctx) + public async Task GetArchived(string endpoint, string folder, StatusContext ctx) { Log.Debug($"Calling GetArchived - {endpoint}"); @@ -1393,17 +1385,17 @@ public class APIHelper : IAPIHelper }; Enumerations.DownloadDateSelection downloadDateSelection = Enumerations.DownloadDateSelection.before; - if (config.DownloadOnlySpecificDates && config.CustomDate.HasValue) + if (configService.CurrentConfig.DownloadOnlySpecificDates && configService.CurrentConfig.CustomDate.HasValue) { - downloadDateSelection = config.DownloadDateSelection; + downloadDateSelection = configService.CurrentConfig.DownloadDateSelection; } UpdateGetParamsForDateSelection( downloadDateSelection, ref getParams, - config.CustomDate); + configService.CurrentConfig.CustomDate); - var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); archived = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Archived Posts\n[/] [red]Found {archived.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); @@ -1418,7 +1410,7 @@ public class APIHelper : IAPIHelper { Archived newarchived = new(); - var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); newarchived = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); archived.list.AddRange(newarchived.list); @@ -1452,25 +1444,25 @@ public class APIHelper : IAPIHelper } } } - await 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); + await dbService.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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } @@ -1478,7 +1470,7 @@ public class APIHelper : IAPIHelper { if (!archivedCollection.ArchivedPosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, archive.id, medium.files.full.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? true : false, false, null); archivedCollection.ArchivedPosts.Add(medium.id, medium.files.full.url); archivedCollection.ArchivedPostMedia.Add(medium); } @@ -1487,7 +1479,7 @@ public class APIHelper : IAPIHelper { if (!archivedCollection.ArchivedPosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, archive.id, medium.files.drm.manifest.dash, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? true : false, false, null); 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); } @@ -1513,7 +1505,7 @@ public class APIHelper : IAPIHelper } - public async Task GetMessages(string endpoint, string folder, IDownloadConfig config, StatusContext ctx) + public async Task GetMessages(string endpoint, string folder, StatusContext ctx) { Log.Debug($"Calling GetMessages - {endpoint}"); @@ -1529,7 +1521,7 @@ public class APIHelper : IAPIHelper { "skip_users", "all" } }; - var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); messages = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Messages\n[/] [red]Found {messages.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); @@ -1541,7 +1533,7 @@ public class APIHelper : IAPIHelper { Messages newmessages = new(); - var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); newmessages = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); messages.list.AddRange(newmessages.list); @@ -1558,7 +1550,7 @@ public class APIHelper : IAPIHelper foreach (Messages.List list in messages.list) { - if (config.SkipAds) + if (configService.CurrentConfig.SkipAds) { if (list.text != null && (list.text.Contains("#ad") || list.text.Contains("/trial/"))) { @@ -1579,9 +1571,9 @@ public class APIHelper : IAPIHelper } } } - if (!config.IgnoreOwnMessages || list.fromUser.id != Convert.ToInt32(auth.USER_ID)) + if (!configService.CurrentConfig.IgnoreOwnMessages || list.fromUser.id != Convert.ToInt32(authService.CurrentAuth.USER_ID)) { - await 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); + await dbService.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) { @@ -1589,50 +1581,50 @@ public class APIHelper : IAPIHelper { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url)) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!messageCollection.Messages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, list.id, medium.files.full.url, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), messagePreviewIds.Contains(medium.id) ? 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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!messageCollection.Messages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, list.id, medium.files.drm.manifest.dash, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), messagePreviewIds.Contains(medium.id) ? 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); } @@ -1645,50 +1637,50 @@ public class APIHelper : IAPIHelper { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url) && messagePreviewIds.Contains(medium.id)) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!messageCollection.Messages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, list.id, medium.files.full.url, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), messagePreviewIds.Contains(medium.id) ? 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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!messageCollection.Messages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, list.id, medium.files.drm.manifest.dash, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), messagePreviewIds.Contains(medium.id) ? 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); } @@ -1714,7 +1706,7 @@ public class APIHelper : IAPIHelper return null; } - public async Task GetPaidMessage(string endpoint, string folder, IDownloadConfig config) + public async Task GetPaidMessage(string endpoint, string folder) { Log.Debug($"Calling GetPaidMessage - {endpoint}"); @@ -1729,12 +1721,12 @@ public class APIHelper : IAPIHelper { "order", "desc" } }; - var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); message = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); - if (!config.IgnoreOwnMessages || message.fromUser.id != Convert.ToInt32(auth.USER_ID)) + if (!configService.CurrentConfig.IgnoreOwnMessages || message.fromUser.id != Convert.ToInt32(authService.CurrentAuth.USER_ID)) { - await 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); + await dbService.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) @@ -1757,104 +1749,104 @@ public class APIHelper : IAPIHelper { if (!messagePreviewIds.Contains(medium.id) && medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url)) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!singlePaidMessageCollection.SingleMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, message.id, medium.files.full.url, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), messagePreviewIds.Contains(medium.id) ? 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.full != null && !string.IsNullOrEmpty(medium.files.full.url)) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!singlePaidMessageCollection.PreviewSingleMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, message.id, medium.files.full.url, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), messagePreviewIds.Contains(medium.id) ? true : false, false, null); singlePaidMessageCollection.PreviewSingleMessages.Add(medium.id, medium.files.full.url.ToString()); singlePaidMessageCollection.PreviewSingleMessageMedia.Add(medium); } } else if (!messagePreviewIds.Contains(medium.id) && medium.canView && medium.files != null && medium.files.drm != null) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!singlePaidMessageCollection.SingleMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, message.id, medium.files.drm.manifest.dash, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), messagePreviewIds.Contains(medium.id) ? 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); } } else if (messagePreviewIds.Contains(medium.id) && medium.canView && medium.files != null && medium.files.drm != null) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!singlePaidMessageCollection.PreviewSingleMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, message.id, medium.files.drm.manifest.dash, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), messagePreviewIds.Contains(medium.id) ? true : false, false, null); singlePaidMessageCollection.PreviewSingleMessages.Add(medium.id, $"{medium.files.drm.manifest.dash},{medium.files.drm.signature.dash.CloudFrontPolicy},{medium.files.drm.signature.dash.CloudFrontSignature},{medium.files.drm.signature.dash.CloudFrontKeyPairId},{medium.id},{message.id}"); singlePaidMessageCollection.PreviewSingleMessageMedia.Add(medium); } @@ -1880,7 +1872,7 @@ public class APIHelper : IAPIHelper } - public async Task GetPaidMessages(string endpoint, string folder, string username, IDownloadConfig config, StatusContext ctx) + public async Task GetPaidMessages(string endpoint, string folder, string username, StatusContext ctx) { Log.Debug($"Calling GetPaidMessages - {username}"); @@ -1898,7 +1890,7 @@ public class APIHelper : IAPIHelper { "skip_users", "all" } }; - var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); paidMessages = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); ctx.Status($"[red]Getting Paid Messages\n[/] [red]Found {paidMessages.list.Count}[/]"); ctx.Spinner(Spinner.Known.Dots); @@ -1911,7 +1903,7 @@ public class APIHelper : IAPIHelper 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); + HttpClient loopclient = GetHttpClient(); HttpRequestMessage looprequest = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{loopqueryParams}"); @@ -1941,15 +1933,15 @@ public class APIHelper : IAPIHelper { 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 (!configService.CurrentConfig.IgnoreOwnMessages || purchase.fromUser.id != Convert.ToInt32(authService.CurrentAuth.USER_ID)) { if (purchase.postedAt != null) { - await 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); + await dbService.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 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); + await dbService.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) @@ -1989,50 +1981,50 @@ public class APIHelper : IAPIHelper bool has = previewids.Any(cus => cus.Equals(medium.id)); if (!has && medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url)) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!paidMessageCollection.PaidMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, purchase.id, medium.files.full.url, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? 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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!paidMessageCollection.PaidMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, purchase.id, medium.files.drm.manifest.dash, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? 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); } @@ -2042,50 +2034,50 @@ public class APIHelper : IAPIHelper { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url)) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!paidMessageCollection.PaidMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, purchase.id, medium.files.full.url, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? 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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!paidMessageCollection.PaidMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(folder, medium.id, purchase.id, medium.files.drm.manifest.dash, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? 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); } @@ -2113,7 +2105,7 @@ public class APIHelper : IAPIHelper return null; } - public async Task> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary users) + public async Task> GetPurchasedTabUsers(string endpoint, Dictionary users) { Log.Debug($"Calling GetPurchasedTabUsers - {endpoint}"); @@ -2130,7 +2122,7 @@ public class APIHelper : IAPIHelper { "skip_users", "all" } }; - var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); purchased = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); if (purchased != null && purchased.hasMore) { @@ -2140,7 +2132,7 @@ public class APIHelper : IAPIHelper 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); + HttpClient loopclient = GetHttpClient(); HttpRequestMessage looprequest = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{loopqueryParams}"); @@ -2192,7 +2184,7 @@ public class APIHelper : IAPIHelper if(user is null) { - if (!config.BypassContentForCreatorsWhoNoLongerExist) + if (!configService.CurrentConfig.BypassContentForCreatorsWhoNoLongerExist) { if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}")) { @@ -2242,7 +2234,7 @@ public class APIHelper : IAPIHelper if (user is null) { - if (!config.BypassContentForCreatorsWhoNoLongerExist) + if (!configService.CurrentConfig.BypassContentForCreatorsWhoNoLongerExist) { if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}")) { @@ -2286,7 +2278,7 @@ public class APIHelper : IAPIHelper return null; } - public async Task> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary users) + public async Task> GetPurchasedTab(string endpoint, string folder, Dictionary users) { Log.Debug($"Calling GetPurchasedTab - {endpoint}"); @@ -2304,7 +2296,7 @@ public class APIHelper : IAPIHelper { "skip_users", "all" } }; - var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); purchased = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); if (purchased != null && purchased.hasMore) { @@ -2314,7 +2306,7 @@ public class APIHelper : IAPIHelper 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); + HttpClient loopclient = GetHttpClient(); HttpRequestMessage looprequest = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{loopqueryParams}"); @@ -2406,23 +2398,23 @@ public class APIHelper : IAPIHelper } } } - await 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); + await dbService.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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } @@ -2434,7 +2426,7 @@ public class APIHelper : IAPIHelper if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(path, medium.id, purchase.id, medium.files.full.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? true : false, false, null); purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.id, medium.files.full.url); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } @@ -2444,7 +2436,7 @@ public class APIHelper : IAPIHelper if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(path, medium.id, purchase.id, medium.files.drm.manifest.dash, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? 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); } @@ -2457,7 +2449,7 @@ public class APIHelper : IAPIHelper { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(path, medium.id, purchase.id, medium.files.full.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? true : false, false, null); purchasedTabCollection.PaidPosts.PaidPosts.Add(medium.id, medium.files.full.url); purchasedTabCollection.PaidPosts.PaidPostMedia.Add(medium); } @@ -2466,7 +2458,7 @@ public class APIHelper : IAPIHelper { if (!purchasedTabCollection.PaidPosts.PaidPosts.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(path, medium.id, purchase.id, medium.files.drm.manifest.dash, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), previewids.Contains(medium.id) ? 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); } @@ -2477,11 +2469,11 @@ public class APIHelper : IAPIHelper case "message": if (purchase.postedAt != null) { - await 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); + await dbService.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 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); + await dbService.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) @@ -2521,50 +2513,50 @@ public class APIHelper : IAPIHelper bool has = paidMessagePreviewids.Any(cus => cus.Equals(medium.id)); if (!has && medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url)) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!purchasedTabCollection.PaidMessages.PaidMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(path, medium.id, purchase.id, medium.files.full.url, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), paidMessagePreviewids.Contains(medium.id) ? 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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!purchasedTabCollection.PaidMessages.PaidMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(path, medium.id, purchase.id, medium.files.drm.manifest.dash, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), paidMessagePreviewids.Contains(medium.id) ? 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); } @@ -2574,50 +2566,50 @@ public class APIHelper : IAPIHelper { if (medium.canView && medium.files != null && medium.files.full != null && !string.IsNullOrEmpty(medium.files.full.url)) { - if (medium.type == "photo" && !config.DownloadImages) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!purchasedTabCollection.PaidMessages.PaidMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(path, medium.id, purchase.id, medium.files.full.url, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), paidMessagePreviewids.Contains(medium.id) ? 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) + if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) { continue; } - if (medium.type == "video" && !config.DownloadVideos) + if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "gif" && !config.DownloadVideos) + if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) { continue; } - if (medium.type == "audio" && !config.DownloadAudios) + if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) { continue; } if (!purchasedTabCollection.PaidMessages.PaidMessages.ContainsKey(medium.id)) { - await 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); + await dbService.AddMedia(path, medium.id, purchase.id, medium.files.drm.manifest.dash, null, null, null, "Messages", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), paidMessagePreviewids.Contains(medium.id) ? 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); } @@ -2656,9 +2648,9 @@ public class APIHelper : IAPIHelper HttpClient client = new(); HttpRequestMessage request = new(HttpMethod.Get, mpdUrl); - request.Headers.Add("user-agent", auth.USER_AGENT); + request.Headers.Add("user-agent", authService.CurrentAuth.USER_AGENT); request.Headers.Add("Accept", "*/*"); - request.Headers.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE};"); + request.Headers.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {authService.CurrentAuth.COOKIE};"); using (var response = await client.SendAsync(request)) { response.EnsureSuccessStatusCode(); @@ -2701,9 +2693,9 @@ public class APIHelper : IAPIHelper HttpClient client = new(); HttpRequestMessage request = new(HttpMethod.Get, mpdUrl); - request.Headers.Add("user-agent", auth.USER_AGENT); + request.Headers.Add("user-agent", authService.CurrentAuth.USER_AGENT); request.Headers.Add("Accept", "*/*"); - request.Headers.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE};"); + request.Headers.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {authService.CurrentAuth.COOKIE};"); using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); diff --git a/OF DL/Services/AuthService.cs b/OF DL/Services/AuthService.cs index bf93ddd..c705b2f 100644 --- a/OF DL/Services/AuthService.cs +++ b/OF DL/Services/AuthService.cs @@ -1,12 +1,31 @@ using Newtonsoft.Json; using OF_DL.Entities; -using OF_DL.Helpers.Interfaces; +using PuppeteerSharp; +using PuppeteerSharp.BrowserData; using Serilog; namespace OF_DL.Services { - public class AuthService(IAuthHelper authHelper) : IAuthService + public class AuthService : IAuthService { + private readonly LaunchOptions _options = new() + { + Headless = false, + Channel = ChromeReleaseChannel.Stable, + DefaultViewport = null, + Args = ["--no-sandbox", "--disable-setuid-sandbox"], + UserDataDir = Path.GetFullPath("chrome-data") + }; + + private readonly string[] _desiredCookies = + [ + "auth_id", + "sess" + ]; + + private const int LoginTimeout = 600000; // 10 minutes + private const int FeedLoadTimeout = 60000; // 1 minute + public Auth? CurrentAuth { get; set; } public async Task LoadFromFileAsync(string filePath = "auth.json") @@ -37,8 +56,8 @@ namespace OF_DL.Services { bool runningInDocker = Environment.GetEnvironmentVariable("OFDL_DOCKER") != null; - await authHelper.SetupBrowser(runningInDocker); - CurrentAuth = await authHelper.GetAuthFromBrowser(); + await SetupBrowser(runningInDocker); + CurrentAuth = await GetAuthFromBrowser(); return CurrentAuth != null; } @@ -68,5 +87,144 @@ namespace OF_DL.Services Log.Error(ex, "Failed to save auth to file"); } } + + private async Task SetupBrowser(bool runningInDocker) + { + string? executablePath = Environment.GetEnvironmentVariable("OFDL_PUPPETEER_EXECUTABLE_PATH"); + if (executablePath != null) + { + Log.Information("OFDL_PUPPETEER_EXECUTABLE_PATH environment variable found. Using browser executable path: {executablePath}", executablePath); + _options.ExecutablePath = executablePath; + } + else + { + var browserFetcher = new BrowserFetcher(); + var installedBrowsers = browserFetcher.GetInstalledBrowsers().ToList(); + if (installedBrowsers.Count == 0) + { + Log.Information("Downloading browser."); + var downloadedBrowser = await browserFetcher.DownloadAsync(); + Log.Information("Browser downloaded. Path: {executablePath}", + downloadedBrowser.GetExecutablePath()); + _options.ExecutablePath = downloadedBrowser.GetExecutablePath(); + } + else + { + _options.ExecutablePath = installedBrowsers.First().GetExecutablePath(); + } + } + + if (runningInDocker) + { + Log.Information("Running in Docker. Disabling sandbox and GPU."); + _options.Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"]; + } + } + + private async Task GetBcToken(IPage page) + { + return await page.EvaluateExpressionAsync("window.localStorage.getItem('bcTokenSha') || ''"); + } + + private async Task GetAuthFromBrowser(bool isDocker = false) + { + try + { + IBrowser? browser; + try + { + browser = await Puppeteer.LaunchAsync(_options); + } + catch (ProcessException e) + { + if (e.Message.Contains("Failed to launch browser") && Directory.Exists(_options.UserDataDir)) + { + Log.Error("Failed to launch browser. Deleting chrome-data directory and trying again."); + Directory.Delete(_options.UserDataDir, true); + browser = await Puppeteer.LaunchAsync(_options); + } + else + { + throw; + } + } + + if (browser == null) + { + throw new Exception("Could not get browser"); + } + + IPage[]? pages = await browser.PagesAsync(); + IPage? page = pages.First(); + + if (page == null) + { + throw new Exception("Could not get page"); + } + + Log.Debug("Navigating to OnlyFans."); + await page.GoToAsync("https://onlyfans.com"); + + Log.Debug("Waiting for user to login"); + await page.WaitForSelectorAsync(".b-feed", new WaitForSelectorOptions { Timeout = LoginTimeout }); + Log.Debug("Feed element detected (user logged in)"); + + await page.ReloadAsync(); + + await page.WaitForNavigationAsync(new NavigationOptions { + WaitUntil = [WaitUntilNavigation.Networkidle2], + Timeout = FeedLoadTimeout + }); + Log.Debug("DOM loaded. Getting BC token and cookies ..."); + + string xBc; + try + { + xBc = await GetBcToken(page); + } + catch (Exception e) + { + throw new Exception("Error getting bcToken"); + } + + Dictionary mappedCookies = (await page.GetCookiesAsync()) + .Where(cookie => cookie.Domain.Contains("onlyfans.com")) + .ToDictionary(cookie => cookie.Name, cookie => cookie.Value); + + mappedCookies.TryGetValue("auth_id", out string? userId); + if (userId == null) + { + throw new Exception("Could not find 'auth_id' cookie"); + } + + mappedCookies.TryGetValue("sess", out string? sess); + if (sess == null) + { + throw new Exception("Could not find 'sess' cookie"); + } + + string? userAgent = await browser.GetUserAgentAsync(); + if (userAgent == null) + { + throw new Exception("Could not get user agent"); + } + + string cookies = string.Join(" ", mappedCookies.Keys.Where(key => _desiredCookies.Contains(key)) + .Select(key => $"${key}={mappedCookies[key]};")); + + return new Auth() + { + COOKIE = cookies, + USER_AGENT = userAgent, + USER_ID = userId, + X_BC = xBc + }; + } + catch (Exception e) + { + Log.Error(e, "Error getting auth from browser"); + return null; + } + } } } diff --git a/OF DL/Services/ConfigService.cs b/OF DL/Services/ConfigService.cs index 67a418a..b2c6a28 100644 --- a/OF DL/Services/ConfigService.cs +++ b/OF DL/Services/ConfigService.cs @@ -10,7 +10,7 @@ namespace OF_DL.Services { public class ConfigService(ILoggingService loggingService) : IConfigService { - public Config? CurrentConfig { get; private set; } + public Config CurrentConfig { get; private set; } = new(); public bool IsCliNonInteractive { get; private set; } public async Task LoadConfigurationAsync(string[] args) diff --git a/OF DL/Helpers/DBHelper.cs b/OF DL/Services/DBService.cs similarity index 97% rename from OF DL/Helpers/DBHelper.cs rename to OF DL/Services/DBService.cs index 697fa71..70efd80 100644 --- a/OF DL/Helpers/DBHelper.cs +++ b/OF DL/Services/DBService.cs @@ -1,25 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; -using OF_DL.Enumurations; -using System.IO; using Microsoft.Data.Sqlite; using Serilog; -using OF_DL.Entities; -namespace OF_DL.Helpers +namespace OF_DL.Services { - public class DBHelper : IDBHelper + public class DBService(IConfigService configService) : IDBService { - private readonly IDownloadConfig downloadConfig; - - public DBHelper(IDownloadConfig downloadConfig) - { - this.downloadConfig = downloadConfig; - } - public async Task CreateDB(string folder) { try @@ -366,7 +352,7 @@ namespace OF_DL.Helpers connection.Open(); await EnsureCreatedAtColumnExists(connection, "medias"); StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id"); - if (downloadConfig.DownloadDuplicatedMedia) + if (configService.CurrentConfig.DownloadDuplicatedMedia) { sql.Append(" and api_type=@api_type"); } @@ -405,7 +391,7 @@ namespace OF_DL.Helpers using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db")) { StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id"); - if(downloadConfig.DownloadDuplicatedMedia) + if(configService.CurrentConfig.DownloadDuplicatedMedia) { sql.Append(" and api_type=@api_type"); } @@ -440,7 +426,7 @@ namespace OF_DL.Helpers // Construct the update command StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id"); - if (downloadConfig.DownloadDuplicatedMedia) + if (configService.CurrentConfig.DownloadDuplicatedMedia) { sql.Append(" and api_type=@api_type"); } diff --git a/OF DL/Services/DownloadService.cs b/OF DL/Services/DownloadService.cs new file mode 100644 index 0000000..9fb0403 --- /dev/null +++ b/OF DL/Services/DownloadService.cs @@ -0,0 +1,2630 @@ +using System.Security.Cryptography; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using FFmpeg.NET; +using FFmpeg.NET.Events; +using OF_DL.Entities; +using OF_DL.Entities.Archived; +using OF_DL.Entities.Messages; +using OF_DL.Entities.Post; +using OF_DL.Entities.Purchased; +using OF_DL.Entities.Streams; +using OF_DL.Enumerations; +using OF_DL.Enumurations; +using OF_DL.Utils; +using Serilog; +using Serilog.Events; +using static OF_DL.Entities.Messages.Messages; +using FromUser = OF_DL.Entities.Messages.FromUser; + +namespace OF_DL.Services; + +public class DownloadService( + IAuthService authService, + IConfigService configService, + IDBService dbService, + IFileNameService fileNameService, + IAPIService apiService) + : IDownloadService +{ + private TaskCompletionSource _completionSource; + + public async Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username) + { + try + { + string path = "/Profile"; + + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + if (!string.IsNullOrEmpty(avatarUrl)) + { + string avatarpath = $"{path}/Avatars"; + if (!Directory.Exists(folder + avatarpath)) Directory.CreateDirectory(folder + avatarpath); + + List avatarMD5Hashes = WidevineClient.Utils.CalculateFolderMD5(folder + avatarpath); + + Uri uri = new(avatarUrl); + string destinationPath = $"{folder}{avatarpath}/"; + + HttpClient client = new(); + + HttpRequestMessage request = new() + { + Method = HttpMethod.Get, + RequestUri = uri + }; + using HttpResponseMessage response = + await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); + + using MemoryStream memoryStream = new(); + await response.Content.CopyToAsync(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + + MD5 md5 = MD5.Create(); + byte[] hash = md5.ComputeHash(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + if (!avatarMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant())) + { + destinationPath = destinationPath + string.Format("{0} {1}.jpg", username, + response.Content.Headers.LastModified.HasValue + ? response.Content.Headers.LastModified.Value.LocalDateTime.ToString("dd-MM-yyyy") + : DateTime.Now.ToString("dd-MM-yyyy")); + + using (FileStream fileStream = File.Create(destinationPath)) + { + await memoryStream.CopyToAsync(fileStream); + } + + File.SetLastWriteTime(destinationPath, + response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now); + } + } + + if (!string.IsNullOrEmpty(headerUrl)) + { + string headerpath = $"{path}/Headers"; + if (!Directory.Exists(folder + headerpath)) Directory.CreateDirectory(folder + headerpath); + + List headerMD5Hashes = WidevineClient.Utils.CalculateFolderMD5(folder + headerpath); + + Uri uri = new(headerUrl); + string destinationPath = $"{folder}{headerpath}/"; + + HttpClient client = new(); + + HttpRequestMessage request = new() + { + Method = HttpMethod.Get, + RequestUri = uri + }; + using HttpResponseMessage response = + await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); + + using MemoryStream memoryStream = new(); + await response.Content.CopyToAsync(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + + MD5 md5 = MD5.Create(); + byte[] hash = md5.ComputeHash(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + if (!headerMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant())) + { + destinationPath = destinationPath + string.Format("{0} {1}.jpg", username, + response.Content.Headers.LastModified.HasValue + ? response.Content.Headers.LastModified.Value.LocalDateTime.ToString("dd-MM-yyyy") + : DateTime.Now.ToString("dd-MM-yyyy")); + + using (FileStream fileStream = File.Create(destinationPath)) + { + await memoryStream.CopyToAsync(fileStream); + } + + File.SetLastWriteTime(destinationPath, + response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now); + } + } + } + catch (Exception ex) + { + Console.WriteLine("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); + } + } + } + + #region drm common + + private async Task DownloadDrmMedia(string user_agent, string policy, string signature, string kvp, + string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, + string api_type, IProgressReporter progressReporter, string customFileName, string filename, string path) + { + try + { + _completionSource = new TaskCompletionSource(); + + int pos1 = decryptionKey.IndexOf(':'); + string decKey = ""; + if (pos1 >= 0) decKey = decryptionKey.Substring(pos1 + 1); + + int streamIndex = 0; + string tempFilename = $"{folder}{path}/{filename}_source.mp4"; + + //int? streamIndex = await GetVideoStreamIndexFromMpd(url, policy, signature, kvp, downloadConfig.DownloadVideoResolution); + + //if (streamIndex == null) + // throw new Exception($"Could not find video stream for resolution {downloadConfig.DownloadVideoResolution}"); + + //string tempFilename; + + //switch (downloadConfig.DownloadVideoResolution) + //{ + // case VideoResolution.source: + // tempFilename = $"{folder}{path}/{filename}_source.mp4"; + // break; + // case VideoResolution._240: + // tempFilename = $"{folder}{path}/{filename}_240.mp4"; + // break; + // case VideoResolution._720: + // tempFilename = $"{folder}{path}/{filename}_720.mp4"; + // break; + // default: + // tempFilename = $"{folder}{path}/{filename}_source.mp4"; + // break; + //} + + // Configure ffmpeg log level and optional report file location + bool ffmpegDebugLogging = Log.IsEnabled(LogEventLevel.Debug); + + string logLevelArgs = ffmpegDebugLogging || + configService.CurrentConfig.LoggingLevel is LoggingLevel.Verbose or LoggingLevel.Debug + ? "-loglevel debug -report" + : configService.CurrentConfig.LoggingLevel switch + { + LoggingLevel.Information => "-loglevel info", + LoggingLevel.Warning => "-loglevel warning", + LoggingLevel.Error => "-loglevel error", + LoggingLevel.Fatal => "-loglevel fatal", + _ => string.Empty + }; + + if (logLevelArgs.Contains("-report", StringComparison.OrdinalIgnoreCase)) + { + // Direct ffmpeg report files into the same logs directory Serilog uses (relative to current working directory) + string logDir = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs")); + Directory.CreateDirectory(logDir); + string ffReportPath = Path.Combine(logDir, "ffmpeg-%p-%t.log"); // ffmpeg will replace %p/%t + Environment.SetEnvironmentVariable("FFREPORT", $"file={ffReportPath}:level=32"); + Log.Debug("FFREPORT enabled at: {FFREPORT} (cwd: {Cwd})", + Environment.GetEnvironmentVariable("FFREPORT"), Environment.CurrentDirectory); + } + else + { + Environment.SetEnvironmentVariable("FFREPORT", null); + Log.Debug("FFREPORT disabled (cwd: {Cwd})", Environment.CurrentDirectory); + } + + string cookieHeader = + "Cookie: " + + $"CloudFront-Policy={policy}; " + + $"CloudFront-Signature={signature}; " + + $"CloudFront-Key-Pair-Id={kvp}; " + + $"{sess}"; + + string parameters = + $"{logLevelArgs} " + + $"-cenc_decryption_key {decKey} " + + $"-headers \"{cookieHeader}\" " + + $"-user_agent \"{user_agent}\" " + + "-referer \"https://onlyfans.com\" " + + "-rw_timeout 20000000 " + + "-reconnect 1 -reconnect_streamed 1 -reconnect_on_network_error 1 -reconnect_delay_max 10 " + + "-y " + + $"-i \"{url}\" " + + $"-map 0:v:{streamIndex} -map 0:a? " + + "-c copy " + + $"\"{tempFilename}\""; + + Log.Debug($"Calling FFMPEG with Parameters: {parameters}"); + + Engine ffmpeg = new(configService.CurrentConfig.FFmpegPath); + ffmpeg.Error += OnError; + ffmpeg.Complete += async (sender, args) => + { + _completionSource.TrySetResult(true); + await OnFFMPEGDownloadComplete(tempFilename, lastModified, folder, path, customFileName, filename, + media_id, api_type, progressReporter); + }; + await ffmpeg.ExecuteAsync(parameters, CancellationToken.None); + + return await _completionSource.Task; + } + 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 false; + } + + #endregion + + private async Task OnFFMPEGDownloadComplete(string tempFilename, DateTime lastModified, string folder, string path, + string customFileName, string filename, long media_id, string api_type, IProgressReporter progressReporter) + { + try + { + if (File.Exists(tempFilename)) File.SetLastWriteTime(tempFilename, lastModified); + if (!string.IsNullOrEmpty(customFileName)) + File.Move(tempFilename, $"{folder + path + "/" + customFileName + ".mp4"}"); + + // Cleanup Files + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : tempFilename).Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + ".mp4" : filename + "_source.mp4", + fileSizeInBytes, true, 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); + } + } + } + + private void OnError(object sender, ConversionErrorEventArgs e) + { + // Guard all fields to avoid NullReference exceptions from FFmpeg.NET + string input = e?.Input?.Name ?? ""; + string output = e?.Output?.Name ?? ""; + string exitCode = e?.Exception?.ExitCode.ToString() ?? ""; + string message = e?.Exception?.Message ?? ""; + string inner = e?.Exception?.InnerException?.Message ?? ""; + + Log.Error("FFmpeg failed. Input={Input} Output={Output} ExitCode={ExitCode} Message={Message} Inner={Inner}", + input, output, exitCode, message, inner); + + _completionSource?.TrySetResult(false); + } + + private async Task GetVideoStreamIndexFromMpd(string mpdUrl, string policy, string signature, string kvp, + VideoResolution resolution) + { + HttpClient client = new(); + HttpRequestMessage request = new(HttpMethod.Get, mpdUrl); + request.Headers.Add("user-agent", authService.CurrentAuth.USER_AGENT); + request.Headers.Add("Accept", "*/*"); + request.Headers.Add("Cookie", + $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {authService.CurrentAuth.COOKIE};"); + using (HttpResponseMessage response = await client.SendAsync(request)) + { + response.EnsureSuccessStatusCode(); + string body = await response.Content.ReadAsStringAsync(); + XDocument doc = XDocument.Parse(body); + XNamespace ns = "urn:mpeg:dash:schema:mpd:2011"; + XNamespace cenc = "urn:mpeg:cenc:2013"; + XElement? videoAdaptationSet = doc + .Descendants(ns + "AdaptationSet") + .FirstOrDefault(e => (string)e.Attribute("mimeType") == "video/mp4"); + + if (videoAdaptationSet == null) + return null; + + string targetHeight = resolution switch + { + VideoResolution._240 => "240", + VideoResolution._720 => "720", + VideoResolution.source => "1280", + _ => throw new ArgumentOutOfRangeException(nameof(resolution)) + }; + + List representations = videoAdaptationSet.Elements(ns + "Representation").ToList(); + + for (int i = 0; i < representations.Count; i++) + if ((string)representations[i].Attribute("height") == targetHeight) + return i; // this is the index FFmpeg will use for `-map 0:v:{i}` + } + + return null; + } + + #region common + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + protected async Task CreateDirectoriesAndDownloadMedia(string path, + string url, + string folder, + long media_id, + string api_type, + IProgressReporter progressReporter, + string serverFileName, + string resolvedFileName) + { + try + { + string customFileName = string.Empty; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + string extension = Path.GetExtension(url.Split("?")[0]); + + path = UpdatePathBasedOnExtension(folder, path, extension); + + return await ProcessMediaDownload(folder, media_id, api_type, url, path, serverFileName, resolvedFileName, + extension, progressReporter); + } + catch (Exception ex) + { + Console.WriteLine("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); + } + } + + return false; + } + + + /// + /// Updates the given path based on the file extension. + /// + /// The parent folder. + /// The initial relative path. + /// The file extension. + /// A string that represents the updated path based on the file extension. + private string UpdatePathBasedOnExtension(string folder, string path, string extension) + { + string subdirectory = string.Empty; + + switch (extension.ToLower()) + { + case ".jpg": + case ".jpeg": + case ".png": + subdirectory = "/Images"; + break; + case ".mp4": + case ".avi": + case ".wmv": + case ".gif": + case ".mov": + subdirectory = "/Videos"; + break; + case ".mp3": + case ".wav": + case ".ogg": + subdirectory = "/Audios"; + break; + } + + if (!string.IsNullOrEmpty(subdirectory)) + { + path += subdirectory; + string fullPath = folder + path; + + if (!Directory.Exists(fullPath)) Directory.CreateDirectory(fullPath); + } + + return path; + } + + + /// + /// Generates a custom filename based on the given format and properties. + /// + /// The format string for the filename. + /// General information about the post. + /// Media associated with the post. + /// Author of the post. + /// Dictionary containing user-related data. + /// Helper class for filename operations. + /// A Task resulting in a string that represents the custom filename. + private async Task GenerateCustomFileName(string filename, + string? filenameFormat, + object? postInfo, + object? postMedia, + object? author, + string username, + Dictionary users, + IFileNameService fileNameService, + CustomFileNameOption option) + { + if (string.IsNullOrEmpty(filenameFormat) || postInfo == null || postMedia == null || author == null) + return option switch + { + CustomFileNameOption.ReturnOriginal => filename, + CustomFileNameOption.ReturnEmpty => string.Empty, + _ => filename + }; + + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + properties.AddRange(matches.Select(match => match.Groups[1].Value)); + + Dictionary values = + await fileNameService.GetFilename(postInfo, postMedia, author, properties, username, users); + return await fileNameService.BuildFilename(filenameFormat, values); + } + + + private async Task GetFileSizeAsync(string url) + { + long fileSize = 0; + + try + { + Uri uri = new(url); + + if (uri.Host == "cdn3.onlyfans.com" && uri.LocalPath.Contains("/dash/files")) + { + string[] messageUrlParsed = url.Split(','); + string mpdURL = messageUrlParsed[0]; + string policy = messageUrlParsed[1]; + string signature = messageUrlParsed[2]; + string kvp = messageUrlParsed[3]; + + mpdURL = mpdURL.Replace(".mpd", "_source.mp4"); + + using HttpClient client = new(); + client.DefaultRequestHeaders.Add("Cookie", + $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {authService.CurrentAuth.COOKIE}"); + client.DefaultRequestHeaders.Add("User-Agent", authService.CurrentAuth.USER_AGENT); + + using HttpResponseMessage response = + await client.GetAsync(mpdURL, HttpCompletionOption.ResponseHeadersRead); + if (response.IsSuccessStatusCode) fileSize = response.Content.Headers.ContentLength ?? 0; + } + else + { + using HttpClient client = new(); + client.DefaultRequestHeaders.Add("User-Agent", authService.CurrentAuth.USER_AGENT); + using HttpResponseMessage response = + await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + if (response.IsSuccessStatusCode) fileSize = response.Content.Headers.ContentLength ?? 0; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error getting file size for URL '{url}': {ex.Message}"); + } + + return fileSize; + } + + public static async Task GetDRMVideoLastModified(string url, Auth auth) + { + Uri uri = new(url); + + string[] messageUrlParsed = url.Split(','); + string mpdURL = messageUrlParsed[0]; + string policy = messageUrlParsed[1]; + string signature = messageUrlParsed[2]; + string kvp = messageUrlParsed[3]; + + mpdURL = mpdURL.Replace(".mpd", "_source.mp4"); + + using HttpClient client = new(); + client.DefaultRequestHeaders.Add("Cookie", + $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE}"); + client.DefaultRequestHeaders.Add("User-Agent", auth.USER_AGENT); + + using HttpResponseMessage response = await client.GetAsync(mpdURL, HttpCompletionOption.ResponseHeadersRead); + if (response.IsSuccessStatusCode) return response.Content.Headers.LastModified.Value.DateTime; + return DateTime.Now; + } + + public static async Task GetMediaLastModified(string url) + { + using HttpClient client = new(); + + using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + if (response.IsSuccessStatusCode) return response.Content.Headers.LastModified.Value.DateTime; + return DateTime.Now; + } + + /// + /// Processes the download and database update of media. + /// + /// The folder where the media is stored. + /// The ID of the media. + /// The full path to the media. + /// The URL from where to download the media. + /// The relative path to the media. + /// The filename after any required manipulations. + /// The file extension. + /// The task object for tracking progress. + /// A Task resulting in a boolean indicating whether the media is newly downloaded or not. + public async Task ProcessMediaDownload(string folder, + long media_id, + string api_type, + string url, + string path, + string serverFilename, + string resolvedFilename, + string extension, + IProgressReporter progressReporter) + { + try + { + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + return await HandleNewMedia(folder, + media_id, + api_type, + url, + path, + serverFilename, + resolvedFilename, + extension, + progressReporter); + + bool status = await HandlePreviouslyDownloadedMediaAsync(folder, media_id, api_type, progressReporter); + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + serverFilename != resolvedFilename) + await HandleRenamingOfExistingFilesAsync(folder, media_id, api_type, path, serverFilename, + resolvedFilename, extension); + return status; + } + catch (Exception ex) + { + // Handle exception (e.g., log it) + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + } + + + private async Task HandleRenamingOfExistingFilesAsync(string folder, + long media_id, + string api_type, + string path, + string serverFilename, + string resolvedFilename, + string extension) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{serverFilename}{extension}"; + string fullPathWithTheNewFileName = $"{folder}{path}/{resolvedFilename}{extension}"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + DateTime lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, resolvedFilename + extension, size, true, + lastModified); + return true; + } + + + /// + /// Handles new media by downloading and updating the database. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// A Task resulting in a boolean indicating whether the media is newly downloaded or not. + private async Task HandleNewMedia(string folder, + long media_id, + string api_type, + string url, + string path, + string serverFilename, + string resolvedFilename, + string extension, + IProgressReporter progressReporter) + { + long fileSizeInBytes; + DateTime lastModified; + bool status; + + string fullPathWithTheServerFileName = $"{folder}{path}/{serverFilename}{extension}"; + string fullPathWithTheNewFileName = $"{folder}{path}/{resolvedFilename}{extension}"; + + //there are a few possibilities here. + //1.file has been downloaded in the past but it has the server filename + // in that case it should be set as existing and it should be renamed + //2.file has been downloaded in the past but it has custom filename. + // it should be set as existing and nothing else. + // of coures 1 and 2 depends in the fact that there may be a difference in the resolved file name + // (ie user has selected a custom format. If he doesn't then the resolved name will be the same as the server filename + //3.file doesn't exist and it should be downloaded. + + // Handle the case where the file has been downloaded in the past with the server filename + //but it has downloaded outsite of this application so it doesn't exist in the database + if (File.Exists(fullPathWithTheServerFileName)) + { + string finalPath; + if (fullPathWithTheServerFileName != fullPathWithTheNewFileName) + { + finalPath = fullPathWithTheNewFileName; + //rename. + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + } + } + else + { + finalPath = fullPathWithTheServerFileName; + } + + fileSizeInBytes = GetLocalFileSize(finalPath); + lastModified = File.GetLastWriteTime(finalPath); + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + status = false; + } + // Handle the case where the file has been downloaded in the past with a custom filename. + //but it has downloaded outsite of this application so it doesn't exist in the database + // this is a bit improbable but we should check for that. + else if (File.Exists(fullPathWithTheNewFileName)) + { + fileSizeInBytes = GetLocalFileSize(fullPathWithTheNewFileName); + lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName); + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + status = false; + } + else //file doesn't exist and we should download it. + { + lastModified = await DownloadFile(url, fullPathWithTheNewFileName, progressReporter); + fileSizeInBytes = GetLocalFileSize(fullPathWithTheNewFileName); + status = true; + } + + //finaly check which filename we should use. Custom or the server one. + //if a custom is used, then the servefilename will be different from the resolved filename. + string finalName = serverFilename == resolvedFilename ? serverFilename : resolvedFilename; + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, finalName + extension, fileSizeInBytes, + true, lastModified); + return status; + } + + + /// + /// Handles media that has been previously downloaded and updates the task accordingly. + /// + /// + /// + /// + /// + /// A boolean indicating whether the media is newly downloaded or not. + private async Task HandlePreviouslyDownloadedMediaAsync(string folder, long media_id, string api_type, + IProgressReporter progressReporter) + { + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + + return false; + } + + + /// + /// Gets the file size of the media. + /// + /// The path to the file. + /// The file size in bytes. + private long GetLocalFileSize(string filePath) + { + return new FileInfo(filePath).Length; + } + + + /// + /// Downloads a file from the given URL and saves it to the specified destination path. + /// + /// The URL to download the file from. + /// The path where the downloaded file will be saved. + /// Progress tracking object. + /// A Task resulting in a DateTime indicating the last modified date of the downloaded file. + private async Task DownloadFile(string url, string destinationPath, IProgressReporter progressReporter) + { + using HttpClient client = new(); + HttpRequestMessage request = new() + { + Method = HttpMethod.Get, + RequestUri = new Uri(url) + }; + + using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); + Stream body = await response.Content.ReadAsStreamAsync(); + + // Wrap the body stream with the ThrottledStream to limit read rate. + using (ThrottledStream throttledStream = new(body, + configService.CurrentConfig.DownloadLimitInMbPerSec * 1_000_000, + configService.CurrentConfig.LimitDownloadRate)) + { + using FileStream fileStream = new(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 16384, + true); + byte[] buffer = new byte[16384]; + int read; + while ((read = await throttledStream.ReadAsync(buffer, CancellationToken.None)) > 0) + { + if (configService.CurrentConfig.ShowScrapeSize) progressReporter.ReportProgress(read); + await fileStream.WriteAsync(buffer.AsMemory(0, read), CancellationToken.None); + } + } + + File.SetLastWriteTime(destinationPath, response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now); + if (!configService.CurrentConfig.ShowScrapeSize) progressReporter.ReportProgress(1); + return response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now; + } + + public async Task CalculateTotalFileSize(List urls) + { + long totalFileSize = 0; + if (urls.Count > 250) + { + int batchSize = 250; + + List> tasks = new(); + + for (int i = 0; i < urls.Count; i += batchSize) + { + List batchUrls = urls.Skip(i).Take(batchSize).ToList(); + + IEnumerable> batchTasks = batchUrls.Select(GetFileSizeAsync); + tasks.AddRange(batchTasks); + + await Task.WhenAll(batchTasks); + + await Task.Delay(5000); + } + + long[] fileSizes = await Task.WhenAll(tasks); + foreach (long fileSize in fileSizes) totalFileSize += fileSize; + } + else + { + List> tasks = new(); + + foreach (string url in urls) tasks.Add(GetFileSizeAsync(url)); + + long[] fileSizes = await Task.WhenAll(tasks); + foreach (long fileSize in fileSizes) totalFileSize += fileSize; + } + + return totalFileSize; + } + + #endregion + + #region normal posts + + public async Task DownloadPostMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, + Post.Author? author, Dictionary users) + { + string path; + if (configService.CurrentConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && + postInfo?.postedAt is not null) + path = $"/Posts/Free/{postInfo.id} {postInfo.postedAt:yyyy-MM-dd HH-mm-ss}"; + else + path = "/Posts/Free"; + + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, postInfo, postMedia, author, + folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + public async Task DownloadPostMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, + SinglePost.Author? author, Dictionary users) + { + string path; + if (configService.CurrentConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && + postInfo?.postedAt is not null) + path = $"/Posts/Free/{postInfo.id} {postInfo.postedAt:yyyy-MM-dd HH-mm-ss}"; + else + path = "/Posts/Free"; + + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, postInfo, postMedia, author, + folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + public async Task DownloadStreamMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, + Streams.Medium? streamMedia, Streams.Author? author, Dictionary users) + { + string path; + if (configService.CurrentConfig.FolderPerPost && streamInfo != null && streamInfo?.id is not null && + streamInfo?.postedAt is not null) + path = $"/Posts/Free/{streamInfo.id} {streamInfo.postedAt:yyyy-MM-dd HH-mm-ss}"; + else + path = "/Posts/Free"; + + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, streamInfo, streamMedia, + author, folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + + public async Task DownloadMessageMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, List? messageInfo, Medium? messageMedia, + Messages.FromUser? fromUser, Dictionary users) + { + string path; + if (configService.CurrentConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && + messageInfo?.createdAt is not null) + path = $"/Messages/Free/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}"; + else + path = "/Messages/Free"; + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, + fromUser, folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + public async Task DownloadMessagePreviewMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, + FromUser? fromUser, Dictionary users) + { + string path; + if (configService.CurrentConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && + messageInfo?.createdAt is not null) + path = $"/Messages/Free/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}"; + else + path = "/Messages/Free"; + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, + fromUser, folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + + public async Task DownloadArchivedMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Archived.List? messageInfo, + Archived.Medium? messageMedia, Archived.Author? author, Dictionary users) + { + string path = "/Archived/Posts/Free"; + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, + author, folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + + public async Task DownloadStoryMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter) + { + string path = "/Stories/Free"; + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, filename); + } + + public async Task DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, + Purchased.FromUser? fromUser, Dictionary users) + { + string path; + if (configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null && messageInfo?.id is not null && + messageInfo?.createdAt is not null) + path = $"/Messages/Paid/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}"; + else + path = "/Messages/Paid"; + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, + fromUser, folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + public async Task DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, + FromUser? fromUser, Dictionary users) + { + string path; + if (configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null && messageInfo?.id is not null && + messageInfo?.createdAt is not null) + path = $"/Messages/Paid/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}"; + else + path = "/Messages/Paid"; + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, + fromUser, folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + public async Task DownloadPurchasedPostMedia(string url, + string folder, + long media_id, + string api_type, + IProgressReporter progressReporter, + string? filenameFormat, + Purchased.List? messageInfo, + Medium? messageMedia, + Purchased.FromUser? fromUser, + Dictionary users) + { + string path; + if (configService.CurrentConfig.FolderPerPaidPost && messageInfo != null && messageInfo?.id is not null && + messageInfo?.postedAt is not null) + path = $"/Posts/Paid/{messageInfo.id} {messageInfo.postedAt.Value:yyyy-MM-dd HH-mm-ss}"; + else + path = "/Posts/Paid"; + Uri uri = new(url); + string filename = Path.GetFileNameWithoutExtension(uri.LocalPath); + string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, messageInfo, messageMedia, + fromUser, folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); + return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, + filename, resolvedFilename); + } + + #endregion + + + #region drm posts + + public async Task DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, + string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, List? messageInfo, Medium? messageMedia, + Messages.FromUser? fromUser, Dictionary users) + { + try + { + string customFileName = string.Empty; + string path; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + if (configService.CurrentConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && + messageInfo?.createdAt is not null) + path = $"/Messages/Free/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; + else + path = "/Messages/Free/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + + if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(messageInfo, messageMedia, + fromUser, properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + public async Task DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, + string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, + FromUser? fromUser, Dictionary users) + { + try + { + string customFileName = string.Empty; + string path; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + if (configService.CurrentConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && + messageInfo?.createdAt is not null) + path = $"/Messages/Free/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; + else + path = "/Messages/Free/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + + if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(messageInfo, messageMedia, + fromUser, properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + + public async Task DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, + string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, + Purchased.FromUser? fromUser, Dictionary users) + { + try + { + string customFileName = string.Empty; + string path; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + if (configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null && + messageInfo?.id is not null && messageInfo?.createdAt is not null) + path = $"/Messages/Paid/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; + else + path = "/Messages/Paid/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(messageInfo, messageMedia, + fromUser, properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + public async Task DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, + string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, + FromUser? fromUser, Dictionary users) + { + try + { + string customFileName = string.Empty; + string path; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + if (configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null && + messageInfo?.id is not null && messageInfo?.createdAt is not null) + path = $"/Messages/Paid/{messageInfo.id} {messageInfo.createdAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; + else + path = "/Messages/Paid/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(messageInfo, messageMedia, + fromUser, properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + + public async Task DownloadPostDRMVideo(string policy, string signature, string kvp, string url, + string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, + Post.Author? author, Dictionary users) + { + try + { + string customFileName = string.Empty; + string path; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + if (configService.CurrentConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && + postInfo?.postedAt is not null) + path = $"/Posts/Free/{postInfo.id} {postInfo.postedAt:yyyy-MM-dd HH-mm-ss}/Videos"; + else + path = "/Posts/Free/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(postInfo, postMedia, author, + properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + public async Task DownloadPostDRMVideo(string policy, string signature, string kvp, string url, + string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, + SinglePost.Author author, Dictionary users) + { + try + { + string customFileName = string.Empty; + string path; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + if (configService.CurrentConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && + postInfo?.postedAt is not null) + path = $"/Posts/Free/{postInfo.id} {postInfo.postedAt:yyyy-MM-dd HH-mm-ss}/Videos"; + else + path = "/Posts/Free/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(postInfo, postMedia, author, + properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + public async Task DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, + string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, + Streams.Medium? streamMedia, Streams.Author? author, Dictionary users) + { + try + { + string customFileName = string.Empty; + string path; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + if (configService.CurrentConfig.FolderPerPost && streamInfo != null && streamInfo?.id is not null && + streamInfo?.postedAt is not null) + path = $"/Posts/Free/{streamInfo.id} {streamInfo.postedAt:yyyy-MM-dd HH-mm-ss}/Videos"; + else + path = "/Posts/Free/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + if (!string.IsNullOrEmpty(filenameFormat) && streamInfo != null && streamMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(streamInfo, streamMedia, author, + properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + public async Task DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, + string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Purchased.List? postInfo, Medium? postMedia, + Purchased.FromUser? fromUser, Dictionary users) + { + try + { + string customFileName = string.Empty; + string path; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + if (configService.CurrentConfig.FolderPerPaidPost && postInfo != null && postInfo?.id is not null && + postInfo?.postedAt is not null) + path = $"/Posts/Paid/{postInfo.id} {postInfo.postedAt.Value:yyyy-MM-dd HH-mm-ss}/Videos"; + else + path = "/Posts/Paid/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + + if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(postInfo, postMedia, fromUser, + properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + + public async Task DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, + string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, Archived.List? postInfo, Archived.Medium? postMedia, + Archived.Author? author, Dictionary users) + { + try + { + string customFileName = string.Empty; + Uri uri = new(url); + string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; + string path = "/Archived/Posts/Free/Videos"; + if (!Directory.Exists(folder + path)) Directory.CreateDirectory(folder + path); + + if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null) + { + List properties = new(); + string pattern = @"\{(.*?)\}"; + MatchCollection matches = Regex.Matches(filenameFormat, pattern); + foreach (Match match in matches) properties.Add(match.Groups[1].Value); + Dictionary values = await fileNameService.GetFilename(postInfo, postMedia, author, + properties, folder.Split("/")[^1], users); + customFileName = await fileNameService.BuildFilename(filenameFormat, values); + } + + if (!await dbService.CheckDownloaded(folder, media_id, api_type)) + { + if (!string.IsNullOrEmpty(customFileName) + ? !File.Exists(folder + path + "/" + customFileName + ".mp4") + : !File.Exists(folder + path + "/" + filename + "_source.mp4")) + return await DownloadDrmMedia(authService.CurrentAuth.USER_AGENT, policy, signature, kvp, + authService.CurrentAuth.COOKIE, url, decryptionKey, folder, lastModified, media_id, api_type, + progressReporter, customFileName, filename, path); + + long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) + ? folder + path + "/" + customFileName + ".mp4" + : folder + path + "/" + filename + "_source.mp4").Length; + if (configService.CurrentConfig.ShowScrapeSize) + progressReporter.ReportProgress(fileSizeInBytes); + else + progressReporter.ReportProgress(1); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, + !string.IsNullOrEmpty(customFileName) ? customFileName + "mp4" : filename + "_source.mp4", + fileSizeInBytes, true, lastModified); + } + else + { + if (!string.IsNullOrEmpty(customFileName)) + if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && + filename + "_source" != customFileName) + { + string fullPathWithTheServerFileName = $"{folder}{path}/{filename}_source.mp4"; + string fullPathWithTheNewFileName = $"{folder}{path}/{customFileName}.mp4"; + if (!File.Exists(fullPathWithTheServerFileName)) return false; + try + { + File.Move(fullPathWithTheServerFileName, fullPathWithTheNewFileName); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred: {ex.Message}"); + return false; + } + + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", + size, true, lastModified); + } + + if (configService.CurrentConfig.ShowScrapeSize) + { + long size = await dbService.GetStoredFileSize(folder, media_id, api_type); + progressReporter.ReportProgress(size); + } + else + { + progressReporter.ReportProgress(1); + } + } + + return false; + } + 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 false; + } + + #endregion + + #region Collection Download Methods + + public async Task DownloadHighlights(string username, long userId, string path, + HashSet paidPostIds, IProgressReporter progressReporter) + { + Log.Debug($"Calling DownloadHighlights - {username}"); + + Dictionary highlights = await apiService.GetMedia(MediaType.Highlights, + $"/users/{userId}/stories/highlights", null, path, paidPostIds.ToList()); + + if (highlights == null || highlights.Count == 0) + { + Log.Debug("Found 0 Highlights"); + return new DownloadResult + { + TotalCount = 0, + NewDownloads = 0, + ExistingDownloads = 0, + MediaType = "Highlights", + Success = true + }; + } + + Log.Debug($"Found {highlights.Count} Highlights"); + + int oldHighlightsCount = 0; + int newHighlightsCount = 0; + + foreach (KeyValuePair highlightKVP in highlights) + { + bool isNew = + await DownloadStoryMedia(highlightKVP.Value, path, highlightKVP.Key, "Stories", progressReporter); + if (isNew) + newHighlightsCount++; + else + oldHighlightsCount++; + } + + Log.Debug( + $"Highlights Already Downloaded: {oldHighlightsCount} New Highlights Downloaded: {newHighlightsCount}"); + + return new DownloadResult + { + TotalCount = highlights.Count, + NewDownloads = newHighlightsCount, + ExistingDownloads = oldHighlightsCount, + MediaType = "Highlights", + Success = true + }; + } + + public async Task DownloadStories(string username, long userId, string path, + HashSet paidPostIds, IProgressReporter progressReporter) + { + Log.Debug($"Calling DownloadStories - {username}"); + + Dictionary stories = await apiService.GetMedia(MediaType.Stories, $"/users/{userId}/stories", + null, path, paidPostIds.ToList()); + + if (stories == null || stories.Count == 0) + { + Log.Debug("Found 0 Stories"); + return new DownloadResult + { + TotalCount = 0, + NewDownloads = 0, + ExistingDownloads = 0, + MediaType = "Stories", + Success = true + }; + } + + Log.Debug($"Found {stories.Count} Stories"); + + int oldStoriesCount = 0; + int newStoriesCount = 0; + + foreach (KeyValuePair storyKVP in stories) + { + bool isNew = await DownloadStoryMedia(storyKVP.Value, path, storyKVP.Key, "Stories", progressReporter); + if (isNew) + newStoriesCount++; + else + oldStoriesCount++; + } + + Log.Debug($"Stories Already Downloaded: {oldStoriesCount} New Stories Downloaded: {newStoriesCount}"); + + return new DownloadResult + { + TotalCount = stories.Count, + NewDownloads = newStoriesCount, + ExistingDownloads = oldStoriesCount, + MediaType = "Stories", + Success = true + }; + } + + public async Task DownloadArchived(string username, long userId, string path, + Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, + ArchivedCollection archived, IProgressReporter progressReporter) + { + Log.Debug($"Calling DownloadArchived - {username}"); + + if (archived == null || archived.ArchivedPosts.Count == 0) + { + Log.Debug("Found 0 Archived Posts"); + return new DownloadResult + { + TotalCount = 0, + NewDownloads = 0, + ExistingDownloads = 0, + MediaType = "Archived Posts", + Success = true + }; + } + + Log.Debug( + $"Found {archived.ArchivedPosts.Count} Media from {archived.ArchivedPostObjects.Count} Archived Posts"); + + int oldArchivedCount = 0; + int newArchivedCount = 0; + + foreach (KeyValuePair archivedKVP in archived.ArchivedPosts) + { + bool isNew; + if (archivedKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) + { + string[] messageUrlParsed = archivedKVP.Value.Split(','); + string mpdURL = messageUrlParsed[0]; + string policy = messageUrlParsed[1]; + string signature = messageUrlParsed[2]; + string kvp = messageUrlParsed[3]; + string mediaId = messageUrlParsed[4]; + string postId = messageUrlParsed[5]; + string? pssh = await apiService.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); + + if (pssh != null) + { + DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); + Dictionary drmHeaders = + apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/post/{postId}", + "?type=widevine"); + string decryptionKey; + if (clientIdBlobMissing || devicePrivateKeyMissing) + decryptionKey = await apiService.GetDecryptionKeyOFDL(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", + pssh); + else + decryptionKey = await apiService.GetDecryptionKeyCDM(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/post/{postId}?type=widevine", + pssh); + Archived.Medium? mediaInfo = + archived.ArchivedPostMedia.FirstOrDefault(m => m.id == archivedKVP.Key); + Archived.List? postInfo = + archived.ArchivedPostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + + isNew = await DownloadArchivedPostDRMVideo( + policy, + signature, + kvp, + mpdURL, + decryptionKey, + path, + lastModified, + archivedKVP.Key, + "Posts", + progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? + string.Empty, + postInfo, + mediaInfo, + postInfo?.author, + users); + } + else + { + continue; + } + } + else + { + Archived.Medium? mediaInfo = archived.ArchivedPostMedia.FirstOrDefault(m => m.id == archivedKVP.Key); + Archived.List? postInfo = + archived.ArchivedPostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + + isNew = await DownloadArchivedMedia( + archivedKVP.Value, + path, + archivedKVP.Key, + "Posts", + progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? + string.Empty, + postInfo, + mediaInfo, + postInfo?.author, + users); + } + + if (isNew) + newArchivedCount++; + else + oldArchivedCount++; + } + + Log.Debug( + $"Archived Posts Already Downloaded: {oldArchivedCount} New Archived Posts Downloaded: {newArchivedCount}"); + + return new DownloadResult + { + TotalCount = archived.ArchivedPosts.Count, + NewDownloads = newArchivedCount, + ExistingDownloads = oldArchivedCount, + MediaType = "Archived Posts", + Success = true + }; + } + + public async Task DownloadMessages(string username, long userId, string path, + Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, + MessageCollection messages, IProgressReporter progressReporter) + { + Log.Debug($"Calling DownloadMessages - {username}"); + + if (messages == null || messages.Messages.Count == 0) + { + Log.Debug("Found 0 Messages"); + return new DownloadResult + { + TotalCount = 0, + NewDownloads = 0, + ExistingDownloads = 0, + MediaType = "Messages", + Success = true + }; + } + + Log.Debug($"Found {messages.Messages.Count} Media from {messages.MessageObjects.Count} Messages"); + + int oldMessagesCount = 0; + int newMessagesCount = 0; + + foreach (KeyValuePair messageKVP in messages.Messages) + { + bool isNew; + if (messageKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) + { + string[] messageUrlParsed = messageKVP.Value.Split(','); + string mpdURL = messageUrlParsed[0]; + string policy = messageUrlParsed[1]; + string signature = messageUrlParsed[2]; + string kvp = messageUrlParsed[3]; + string mediaId = messageUrlParsed[4]; + string messageId = messageUrlParsed[5]; + string? pssh = await apiService.GetDRMMPDPSSH(mpdURL, policy, signature, kvp); + + if (pssh != null) + { + DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdURL, policy, signature, kvp); + Dictionary drmHeaders = + apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/message/{messageId}", + "?type=widevine"); + string decryptionKey; + if (clientIdBlobMissing || devicePrivateKeyMissing) + decryptionKey = await apiService.GetDecryptionKeyOFDL(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", + pssh); + else + decryptionKey = await apiService.GetDecryptionKeyCDM(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/message/{messageId}?type=widevine", + pssh); + Medium? mediaInfo = messages.MessageMedia.FirstOrDefault(m => m.id == messageKVP.Key); + List? messageInfo = + messages.MessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + + isNew = await DownloadMessageDRMVideo( + policy, + signature, + kvp, + mpdURL, + decryptionKey, + path, + lastModified, + messageKVP.Key, + "Messages", + progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? + string.Empty, + messageInfo, + mediaInfo, + messageInfo?.fromUser, + users); + } + else + { + continue; + } + } + else + { + Medium? mediaInfo = messages.MessageMedia.FirstOrDefault(m => m.id == messageKVP.Key); + List? messageInfo = messages.MessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + + isNew = await DownloadMessageMedia( + messageKVP.Value, + path, + messageKVP.Key, + "Messages", + progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? + string.Empty, + messageInfo, + mediaInfo, + messageInfo?.fromUser, + users); + } + + if (isNew) + newMessagesCount++; + else + oldMessagesCount++; + } + + Log.Debug($"Messages Already Downloaded: {oldMessagesCount} New Messages Downloaded: {newMessagesCount}"); + + return new DownloadResult + { + TotalCount = messages.Messages.Count, + NewDownloads = newMessagesCount, + ExistingDownloads = oldMessagesCount, + MediaType = "Messages", + Success = true + }; + } + + + public async Task DownloadPaidMessages(string username, string path, Dictionary users, + bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidMessageCollection paidMessageCollection, + IProgressReporter progressReporter) + { + Log.Debug($"Calling DownloadPaidMessages - {username}"); + + if (paidMessageCollection == null || paidMessageCollection.PaidMessages.Count == 0) + { + Log.Debug("Found 0 Paid Messages"); + return new DownloadResult + { + TotalCount = 0, NewDownloads = 0, ExistingDownloads = 0, MediaType = "Paid Messages", Success = true + }; + } + + Log.Debug( + $"Found {paidMessageCollection.PaidMessages.Count} Media from {paidMessageCollection.PaidMessageObjects.Count} Paid Messages"); + + int oldCount = 0; + int newCount = 0; + + foreach (KeyValuePair kvp in paidMessageCollection.PaidMessages) + { + bool isNew; + if (kvp.Value.Contains("cdn3.onlyfans.com/dash/files")) + { + string[] parsed = kvp.Value.Split(','); + string? pssh = await apiService.GetDRMMPDPSSH(parsed[0], parsed[1], parsed[2], parsed[3]); + if (pssh != null) + { + DateTime lastModified = + await apiService.GetDRMMPDLastModified(parsed[0], parsed[1], parsed[2], parsed[3]); + Dictionary drmHeaders = + apiService.GetDynamicHeaders($"/api2/v2/users/media/{parsed[4]}/drm/message/{parsed[5]}", + "?type=widevine"); + string decryptionKey = clientIdBlobMissing || devicePrivateKeyMissing + ? await apiService.GetDecryptionKeyOFDL(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/message/{parsed[5]}?type=widevine", + pssh) + : await apiService.GetDecryptionKeyCDM(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/message/{parsed[5]}?type=widevine", + pssh); + + Medium? mediaInfo = paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.id == kvp.Key); + Purchased.List? messageInfo = + paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => + p?.media?.Contains(mediaInfo) == true); + + isNew = await DownloadPurchasedMessageDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], + decryptionKey, path, lastModified, kvp.Key, "Messages", progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? + string.Empty, messageInfo, mediaInfo, messageInfo?.fromUser, users); + } + else + { + continue; + } + } + else + { + Medium? mediaInfo = paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.id == kvp.Key); + Purchased.List? messageInfo = + paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + isNew = await DownloadPurchasedMedia(kvp.Value, path, kvp.Key, "Messages", progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? + string.Empty, messageInfo, mediaInfo, messageInfo?.fromUser, users); + } + + if (isNew) newCount++; + else oldCount++; + } + + Log.Debug($"Paid Messages Already Downloaded: {oldCount} New Paid Messages Downloaded: {newCount}"); + return new DownloadResult + { + TotalCount = paidMessageCollection.PaidMessages.Count, NewDownloads = newCount, + ExistingDownloads = oldCount, MediaType = "Paid Messages", Success = true + }; + } + + public async Task DownloadStreams(string username, long userId, string path, + Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, + StreamsCollection streams, IProgressReporter progressReporter) + { + Log.Debug($"Calling DownloadStreams - {username}"); + + if (streams == null || streams.Streams.Count == 0) + { + Log.Debug("Found 0 Streams"); + return new DownloadResult + { TotalCount = 0, NewDownloads = 0, ExistingDownloads = 0, MediaType = "Streams", Success = true }; + } + + Log.Debug($"Found {streams.Streams.Count} Media from {streams.StreamObjects.Count} Streams"); + + int oldCount = 0; + int newCount = 0; + + foreach (KeyValuePair kvp in streams.Streams) + { + bool isNew; + if (kvp.Value.Contains("cdn3.onlyfans.com/dash/files")) + { + string[] parsed = kvp.Value.Split(','); + string? pssh = await apiService.GetDRMMPDPSSH(parsed[0], parsed[1], parsed[2], parsed[3]); + if (pssh != null) + { + DateTime lastModified = + await apiService.GetDRMMPDLastModified(parsed[0], parsed[1], parsed[2], parsed[3]); + Dictionary drmHeaders = + apiService.GetDynamicHeaders($"/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}", + "?type=widevine"); + string decryptionKey = clientIdBlobMissing || devicePrivateKeyMissing + ? await apiService.GetDecryptionKeyOFDL(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}?type=widevine", + pssh) + : await apiService.GetDecryptionKeyCDM(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}?type=widevine", + pssh); + + Streams.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.id == kvp.Key); + Streams.List? streamInfo = + streams.StreamObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + + isNew = await DownloadStreamsDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], decryptionKey, + path, lastModified, kvp.Key, "Streams", progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? + string.Empty, streamInfo, mediaInfo, streamInfo?.author, users); + } + else + { + continue; + } + } + else + { + Streams.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.id == kvp.Key); + Streams.List? streamInfo = + streams.StreamObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + isNew = await DownloadStreamMedia(kvp.Value, path, kvp.Key, "Streams", progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? + string.Empty, streamInfo, mediaInfo, streamInfo?.author, users); + } + + if (isNew) newCount++; + else oldCount++; + } + + Log.Debug($"Streams Already Downloaded: {oldCount} New Streams Downloaded: {newCount}"); + return new DownloadResult + { + TotalCount = streams.Streams.Count, NewDownloads = newCount, ExistingDownloads = oldCount, + MediaType = "Streams", Success = true + }; + } + +// Add these methods to DownloadService.cs before the #endregion line + + public async Task DownloadFreePosts(string username, long userId, string path, + Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostCollection posts, + IProgressReporter progressReporter) + { + Log.Debug($"Calling DownloadFreePosts - {username}"); + + if (posts == null || posts.Posts.Count == 0) + { + Log.Debug("Found 0 Posts"); + return new DownloadResult + { TotalCount = 0, NewDownloads = 0, ExistingDownloads = 0, MediaType = "Posts", Success = true }; + } + + Log.Debug($"Found {posts.Posts.Count} Media from {posts.PostObjects.Count} Posts"); + + int oldCount = 0, newCount = 0; + + foreach (KeyValuePair postKVP in posts.Posts) + { + bool isNew; + if (postKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) + { + string[] parsed = postKVP.Value.Split(','); + string? pssh = await apiService.GetDRMMPDPSSH(parsed[0], parsed[1], parsed[2], parsed[3]); + if (pssh != null) + { + DateTime lastModified = + await apiService.GetDRMMPDLastModified(parsed[0], parsed[1], parsed[2], parsed[3]); + Dictionary drmHeaders = + apiService.GetDynamicHeaders($"/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}", + "?type=widevine"); + string decryptionKey = clientIdBlobMissing || devicePrivateKeyMissing + ? await apiService.GetDecryptionKeyOFDL(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}?type=widevine", + pssh) + : await apiService.GetDecryptionKeyCDM(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}?type=widevine", + pssh); + + Post.Medium mediaInfo = posts.PostMedia.FirstOrDefault(m => m.id == postKVP.Key); + Post.List postInfo = posts.PostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + + isNew = await DownloadPostDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], decryptionKey, path, + lastModified, postKVP.Key, "Posts", progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? + string.Empty, postInfo, mediaInfo, postInfo?.author, users); + } + else + { + continue; + } + } + else + { + Post.Medium? mediaInfo = posts.PostMedia.FirstOrDefault(m => m?.id == postKVP.Key); + Post.List? postInfo = posts.PostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + isNew = await DownloadPostMedia(postKVP.Value, path, postKVP.Key, "Posts", progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? + string.Empty, postInfo, mediaInfo, postInfo?.author, users); + } + + if (isNew) newCount++; + else oldCount++; + } + + Log.Debug($"Posts Already Downloaded: {oldCount} New Posts Downloaded: {newCount}"); + return new DownloadResult + { + TotalCount = posts.Posts.Count, NewDownloads = newCount, ExistingDownloads = oldCount, MediaType = "Posts", + Success = true + }; + } + + public async Task DownloadPaidPosts(string username, long userId, string path, + Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, + PaidPostCollection purchasedPosts, IProgressReporter progressReporter) + { + Log.Debug($"Calling DownloadPaidPosts - {username}"); + + if (purchasedPosts == null || purchasedPosts.PaidPosts.Count == 0) + { + Log.Debug("Found 0 Paid Posts"); + return new DownloadResult + { TotalCount = 0, NewDownloads = 0, ExistingDownloads = 0, MediaType = "Paid Posts", Success = true }; + } + + Log.Debug( + $"Found {purchasedPosts.PaidPosts.Count} Media from {purchasedPosts.PaidPostObjects.Count} Paid Posts"); + + int oldCount = 0, newCount = 0; + + foreach (KeyValuePair postKVP in purchasedPosts.PaidPosts) + { + bool isNew; + if (postKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) + { + string[] parsed = postKVP.Value.Split(','); + string? pssh = await apiService.GetDRMMPDPSSH(parsed[0], parsed[1], parsed[2], parsed[3]); + if (pssh != null) + { + DateTime lastModified = + await apiService.GetDRMMPDLastModified(parsed[0], parsed[1], parsed[2], parsed[3]); + Dictionary drmHeaders = + apiService.GetDynamicHeaders($"/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}", + "?type=widevine"); + string decryptionKey = clientIdBlobMissing || devicePrivateKeyMissing + ? await apiService.GetDecryptionKeyOFDL(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}?type=widevine", + pssh) + : await apiService.GetDecryptionKeyCDM(drmHeaders, + $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}?type=widevine", + pssh); + + Medium? mediaInfo = purchasedPosts.PaidPostMedia.FirstOrDefault(m => m.id == postKVP.Key); + Purchased.List? postInfo = + purchasedPosts.PaidPostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + + isNew = await DownloadPurchasedPostDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], + decryptionKey, path, lastModified, postKVP.Key, "Posts", progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? + string.Empty, postInfo, mediaInfo, postInfo?.fromUser, users); + } + else + { + continue; + } + } + else + { + Medium? mediaInfo = purchasedPosts.PaidPostMedia.FirstOrDefault(m => m.id == postKVP.Key); + Purchased.List? postInfo = + purchasedPosts.PaidPostObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); + isNew = await DownloadPurchasedPostMedia(postKVP.Value, path, postKVP.Key, "Posts", progressReporter, + configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? + string.Empty, postInfo, mediaInfo, postInfo?.fromUser, users); + } + + if (isNew) newCount++; + else oldCount++; + } + + Log.Debug($"Paid Posts Already Downloaded: {oldCount} New Paid Posts Downloaded: {newCount}"); + return new DownloadResult + { + TotalCount = purchasedPosts.PaidPosts.Count, NewDownloads = newCount, ExistingDownloads = oldCount, + MediaType = "Paid Posts", Success = true + }; + } + + #endregion +} diff --git a/OF DL/Helpers/FileNameHelper.cs b/OF DL/Services/FileNameService.cs similarity index 93% rename from OF DL/Helpers/FileNameHelper.cs rename to OF DL/Services/FileNameService.cs index 178a4e8..584df74 100644 --- a/OF DL/Helpers/FileNameHelper.cs +++ b/OF DL/Services/FileNameService.cs @@ -1,23 +1,10 @@ using HtmlAgilityPack; -using OF_DL.Entities; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace OF_DL.Helpers +namespace OF_DL.Services { - public class FileNameHelper : IFileNameHelper + public class FileNameService(IAuthService authService) : IFileNameService { - private readonly Auth auth; - - public FileNameHelper(Auth auth) - { - this.auth = auth; - } - public async Task> GetFilename(object obj1, object obj2, object obj3, List selectedProperties, string username, Dictionary users = null) { Dictionary values = new(); @@ -43,7 +30,7 @@ namespace OF_DL.Helpers object policy = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontPolicy"); object signature = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontSignature"); object kvp = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontKeyPairId"); - DateTime lastModified = await DownloadHelper.GetDRMVideoLastModified(string.Join(",", mpdurl, policy, signature, kvp), auth); + DateTime lastModified = await DownloadService.GetDRMVideoLastModified(string.Join(",", mpdurl, policy, signature, kvp), authService.CurrentAuth); values.Add(propertyName, lastModified.ToString("yyyy-MM-dd")); continue; } @@ -52,7 +39,7 @@ namespace OF_DL.Helpers object source = GetNestedPropertyValue(obj2, "files.full.url"); if(source != null) { - DateTime lastModified = await DownloadHelper.GetMediaLastModified(source.ToString()); + DateTime lastModified = await DownloadService.GetMediaLastModified(source.ToString()); values.Add(propertyName, lastModified.ToString("yyyy-MM-dd")); continue; } @@ -61,7 +48,7 @@ namespace OF_DL.Helpers object preview = GetNestedPropertyValue(obj2, "preview"); if(preview != null) { - DateTime lastModified = await DownloadHelper.GetMediaLastModified(preview.ToString()); + DateTime lastModified = await DownloadService.GetMediaLastModified(preview.ToString()); values.Add(propertyName, lastModified.ToString("yyyy-MM-dd")); continue; } diff --git a/OF DL/Helpers/Interfaces/IAPIHelper.cs b/OF DL/Services/IAPIService.cs similarity index 60% rename from OF DL/Helpers/Interfaces/IAPIHelper.cs rename to OF DL/Services/IAPIService.cs index a917fff..e3959ea 100644 --- a/OF DL/Helpers/Interfaces/IAPIHelper.cs +++ b/OF DL/Services/IAPIService.cs @@ -8,32 +8,32 @@ using OF_DL.Entities.Streams; using OF_DL.Enumurations; using Spectre.Console; -namespace OF_DL.Helpers +namespace OF_DL.Services { - public interface IAPIHelper + public interface IAPIService { Task GetDecryptionKeyCDRMProject(Dictionary drmHeaders, string licenceURL, string pssh); Task GetDecryptionKeyCDM(Dictionary drmHeaders, string licenceURL, string pssh); Task GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp); Task GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp); - Task> GetLists(string endpoint, IDownloadConfig config); - Task> GetListUsers(string endpoint, IDownloadConfig config); - Task> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, IDownloadConfig config, List paid_post_ids); - Task GetPaidPosts(string endpoint, string folder, string username, IDownloadConfig config, List paid_post_ids, StatusContext ctx); - Task GetPosts(string endpoint, string folder, IDownloadConfig config, List paid_post_ids, StatusContext ctx); - Task GetPost(string endpoint, string folder, IDownloadConfig config); - Task GetStreams(string endpoint, string folder, IDownloadConfig config, List paid_post_ids, StatusContext ctx); - Task GetArchived(string endpoint, string folder, IDownloadConfig config, StatusContext ctx); - Task GetMessages(string endpoint, string folder, IDownloadConfig config, StatusContext ctx); - Task GetPaidMessages(string endpoint, string folder, string username, IDownloadConfig config, StatusContext ctx); - Task GetPaidMessage(string endpoint, string folder, IDownloadConfig config); - Task> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary users); - Task> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary users); + Task> GetLists(string endpoint); + Task> GetListUsers(string endpoint); + Task> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, List paid_post_ids); + Task GetPaidPosts(string endpoint, string folder, string username, List paid_post_ids, StatusContext ctx); + Task GetPosts(string endpoint, string folder, List paid_post_ids, StatusContext ctx); + Task GetPost(string endpoint, string folder); + Task GetStreams(string endpoint, string folder, List paid_post_ids, StatusContext ctx); + Task GetArchived(string endpoint, string folder, StatusContext ctx); + Task GetMessages(string endpoint, string folder, StatusContext ctx); + Task GetPaidMessages(string endpoint, string folder, string username, StatusContext ctx); + Task GetPaidMessage(string endpoint, string folder); + Task> GetPurchasedTabUsers(string endpoint, Dictionary users); + Task> GetPurchasedTab(string endpoint, string folder, Dictionary users); Task GetUserInfo(string endpoint); Task GetUserInfoById(string endpoint); Dictionary GetDynamicHeaders(string path, string queryParam); - Task> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config); - Task> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config); + Task> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions); + Task> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions); Task GetDecryptionKeyOFDL(Dictionary drmHeaders, string licenceURL, string pssh); } } diff --git a/OF DL/Services/IConfigService.cs b/OF DL/Services/IConfigService.cs index 773e82c..57c639a 100644 --- a/OF DL/Services/IConfigService.cs +++ b/OF DL/Services/IConfigService.cs @@ -4,7 +4,7 @@ namespace OF_DL.Services { public interface IConfigService { - Config? CurrentConfig { get; } + Config CurrentConfig { get; } bool IsCliNonInteractive { get; } Task LoadConfigurationAsync(string[] args); Task SaveConfigurationAsync(string filePath = "config.conf"); diff --git a/OF DL/Helpers/Interfaces/IDBHelper.cs b/OF DL/Services/IDBService.cs similarity index 95% rename from OF DL/Helpers/Interfaces/IDBHelper.cs rename to OF DL/Services/IDBService.cs index ab55249..5a32c03 100644 --- a/OF DL/Helpers/Interfaces/IDBHelper.cs +++ b/OF DL/Services/IDBService.cs @@ -1,6 +1,6 @@ -namespace OF_DL.Helpers +namespace OF_DL.Services { - public interface IDBHelper + public interface IDBService { Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, long user_id); Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at); diff --git a/OF DL/Services/IDownloadService.cs b/OF DL/Services/IDownloadService.cs new file mode 100644 index 0000000..696eaee --- /dev/null +++ b/OF DL/Services/IDownloadService.cs @@ -0,0 +1,50 @@ +using OF_DL.Entities; +using OF_DL.Entities.Archived; +using OF_DL.Entities.Messages; +using OF_DL.Entities.Post; +using OF_DL.Entities.Purchased; +using OF_DL.Entities.Streams; +using static OF_DL.Entities.Messages.Messages; +using FromUser = OF_DL.Entities.Messages.FromUser; + +namespace OF_DL.Services +{ + public interface IDownloadService + { + Task CalculateTotalFileSize(List urls); + Task ProcessMediaDownload(string folder, long media_id, string api_type, string url, string path, string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter); + Task DownloadArchivedMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Archived.List? messageInfo, Archived.Medium? messageMedia, Archived.Author? author, Dictionary users); + Task DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Archived.List? postInfo, Archived.Medium? postMedia, Archived.Author? author, Dictionary users); + Task DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary users); + Task DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary users); + Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username); + Task DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary users); + Task DownloadMessageMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary users); + Task DownloadPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary users); + Task DownloadPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary users); + Task DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary users); + Task DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary users); + Task DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary users); + Task DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary users); + Task DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? postInfo, Medium? postMedia, Purchased.FromUser? fromUser, Dictionary users); + Task DownloadPurchasedPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary users); + Task DownloadStoryMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter); + Task DownloadStreamMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary users); + Task DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary users); + Task DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url, + string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, + FromUser? fromUser, Dictionary users); + Task DownloadMessagePreviewMedia(string url, string folder, long media_id, string api_type, + IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, + FromUser? fromUser, Dictionary users); + Task DownloadHighlights(string username, long userId, string path, HashSet paidPostIds, IProgressReporter progressReporter); + Task DownloadStories(string username, long userId, string path, HashSet paidPostIds, IProgressReporter progressReporter); + Task DownloadArchived(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, ArchivedCollection archived, IProgressReporter progressReporter); + Task DownloadMessages(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, MessageCollection messages, IProgressReporter progressReporter); + Task DownloadPaidMessages(string username, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter); + Task DownloadStreams(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamsCollection streams, IProgressReporter progressReporter); + Task DownloadFreePosts(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostCollection posts, IProgressReporter progressReporter); + Task DownloadPaidPosts(string username, long userId, string path, Dictionary users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidPostCollection purchasedPosts, IProgressReporter progressReporter); + } +} diff --git a/OF DL/Services/IFileNameService.cs b/OF DL/Services/IFileNameService.cs new file mode 100644 index 0000000..d692f30 --- /dev/null +++ b/OF DL/Services/IFileNameService.cs @@ -0,0 +1,8 @@ +namespace OF_DL.Services +{ + public interface IFileNameService + { + Task BuildFilename(string fileFormat, Dictionary values); + Task> GetFilename(object obj1, object obj2, object obj3, List selectedProperties, string username, Dictionary users = null); + } +} diff --git a/OF DL/Services/IProgressReporter.cs b/OF DL/Services/IProgressReporter.cs new file mode 100644 index 0000000..d312059 --- /dev/null +++ b/OF DL/Services/IProgressReporter.cs @@ -0,0 +1,20 @@ +namespace OF_DL.Services; + +/// +/// Interface for reporting download progress in a UI-agnostic way. +/// This allows the download service to report progress without being coupled to any specific UI framework. +/// +public interface IProgressReporter +{ + /// + /// Reports progress increment. The value represents either bytes downloaded or file count depending on configuration. + /// + /// The amount to increment progress by + void ReportProgress(long increment); + + /// + /// Reports a status message (optional for implementations). + /// + /// The status message to report + void ReportStatus(string message); +} diff --git a/OF DL/CDMApi.cs b/OF DL/Widevine/CDMApi.cs similarity index 89% rename from OF DL/CDMApi.cs rename to OF DL/Widevine/CDMApi.cs index e01eae2..662377b 100644 --- a/OF DL/CDMApi.cs +++ b/OF DL/Widevine/CDMApi.cs @@ -1,7 +1,6 @@ -using System; -using System.Collections.Generic; +using WidevineClient.Widevine; -namespace WidevineClient.Widevine +namespace OF_DL.Widevine { public class CDMApi {