Add header comments and extract duplicated exception logging to a helper function
This commit is contained in:
parent
4889be1890
commit
e184df906f
26
OF DL.Core/Helpers/ExceptionLoggerHelper.cs
Normal file
26
OF DL.Core/Helpers/ExceptionLoggerHelper.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace OF_DL.Helpers;
|
||||||
|
|
||||||
|
internal static class ExceptionLoggerHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logs an exception to the console and Serilog with inner exception details.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ex">The exception to log.</param>
|
||||||
|
public static void LogException(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)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -29,8 +29,16 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
|
|||||||
UserDataDir = Path.GetFullPath("chrome-data")
|
UserDataDir = Path.GetFullPath("chrome-data")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current authentication state.
|
||||||
|
/// </summary>
|
||||||
public Auth? CurrentAuth { get; set; }
|
public Auth? CurrentAuth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads authentication data from disk.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The auth file path.</param>
|
||||||
|
/// <returns>True when auth data is loaded successfully.</returns>
|
||||||
public async Task<bool> LoadFromFileAsync(string filePath = "auth.json")
|
public async Task<bool> LoadFromFileAsync(string filePath = "auth.json")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -53,6 +61,10 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Launches a browser session and extracts auth data after login.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True when auth data is captured successfully.</returns>
|
||||||
public async Task<bool> LoadFromBrowserAsync()
|
public async Task<bool> LoadFromBrowserAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -71,6 +83,10 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Persists the current auth data to disk.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The auth file path.</param>
|
||||||
public async Task SaveToFileAsync(string filePath = "auth.json")
|
public async Task SaveToFileAsync(string filePath = "auth.json")
|
||||||
{
|
{
|
||||||
if (CurrentAuth == null)
|
if (CurrentAuth == null)
|
||||||
@ -129,6 +145,9 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
|
|||||||
private async Task<string> GetBcToken(IPage page) =>
|
private async Task<string> GetBcToken(IPage page) =>
|
||||||
await page.EvaluateExpressionAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
|
await page.EvaluateExpressionAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes the stored cookie string to only include required cookie values.
|
||||||
|
/// </summary>
|
||||||
public void ValidateCookieString()
|
public void ValidateCookieString()
|
||||||
{
|
{
|
||||||
if (CurrentAuth == null)
|
if (CurrentAuth == null)
|
||||||
@ -159,6 +178,10 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates auth by requesting the current user profile.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The authenticated user or null when validation fails.</returns>
|
||||||
public async Task<UserEntities.User?> ValidateAuthAsync()
|
public async Task<UserEntities.User?> ValidateAuthAsync()
|
||||||
{
|
{
|
||||||
// Resolve IApiService lazily to avoid circular dependency
|
// Resolve IApiService lazily to avoid circular dependency
|
||||||
@ -166,6 +189,9 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
|
|||||||
return await apiService.GetUserInfo("/users/me");
|
return await apiService.GetUserInfo("/users/me");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears persisted auth data and browser profile state.
|
||||||
|
/// </summary>
|
||||||
public void Logout()
|
public void Logout()
|
||||||
{
|
{
|
||||||
if (Directory.Exists("chrome-data"))
|
if (Directory.Exists("chrome-data"))
|
||||||
|
|||||||
@ -12,9 +12,21 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public class ConfigService(ILoggingService loggingService) : IConfigService
|
public class ConfigService(ILoggingService loggingService) : IConfigService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the active configuration in memory.
|
||||||
|
/// </summary>
|
||||||
public Config CurrentConfig { get; private set; } = new();
|
public Config CurrentConfig { get; private set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the CLI requested non-interactive mode.
|
||||||
|
/// </summary>
|
||||||
public bool IsCliNonInteractive { get; private set; }
|
public bool IsCliNonInteractive { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads configuration from disk and applies runtime settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">CLI arguments used to influence configuration.</param>
|
||||||
|
/// <returns>True when configuration is loaded successfully.</returns>
|
||||||
public async Task<bool> LoadConfigurationAsync(string[] args)
|
public async Task<bool> LoadConfigurationAsync(string[] args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -73,6 +85,10 @@ public class ConfigService(ILoggingService loggingService) : IConfigService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the current configuration to disk.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The destination config file path.</param>
|
||||||
public async Task SaveConfigurationAsync(string filePath = "config.conf")
|
public async Task SaveConfigurationAsync(string filePath = "config.conf")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -87,6 +103,10 @@ public class ConfigService(ILoggingService loggingService) : IConfigService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the current configuration and applies runtime settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newConfig">The new configuration instance.</param>
|
||||||
public void UpdateConfig(Config newConfig)
|
public void UpdateConfig(Config newConfig)
|
||||||
{
|
{
|
||||||
CurrentConfig = newConfig;
|
CurrentConfig = newConfig;
|
||||||
@ -399,6 +419,9 @@ public class ConfigService(ILoggingService loggingService) : IConfigService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns toggleable config properties and their current values.
|
||||||
|
/// </summary>
|
||||||
public List<(string Name, bool Value)> GetToggleableProperties()
|
public List<(string Name, bool Value)> GetToggleableProperties()
|
||||||
{
|
{
|
||||||
List<(string Name, bool Value)> result = [];
|
List<(string Name, bool Value)> result = [];
|
||||||
@ -420,6 +443,11 @@ public class ConfigService(ILoggingService loggingService) : IConfigService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a set of toggle selections to the configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="selectedNames">The names of toggles that should be enabled.</param>
|
||||||
|
/// <returns>True when any values were changed.</returns>
|
||||||
public bool ApplyToggleableSelections(List<string> selectedNames)
|
public bool ApplyToggleableSelections(List<string> selectedNames)
|
||||||
{
|
{
|
||||||
bool configChanged = false;
|
bool configChanged = false;
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
|
using OF_DL.Helpers;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace OF_DL.Services;
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
public class DbService(IConfigService configService) : IDbService
|
public class DbService(IConfigService configService) : IDbService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates or updates the per-user metadata database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
public async Task CreateDb(string folder)
|
public async Task CreateDb(string folder)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -131,19 +136,14 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates or updates the global users database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="users">The users to seed or update.</param>
|
||||||
public async Task CreateUsersDb(Dictionary<string, long> users)
|
public async Task CreateUsersDb(Dictionary<string, long> users)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -188,19 +188,15 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures a username matches the stored user ID and migrates folders if needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The user pair to validate.</param>
|
||||||
|
/// <param name="path">The expected user folder path.</param>
|
||||||
public async Task CheckUsername(KeyValuePair<string, long> user, string path)
|
public async Task CheckUsername(KeyValuePair<string, long> user, string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -245,19 +241,21 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a message record when it does not already exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
|
/// <param name="postId">The message or post ID.</param>
|
||||||
|
/// <param name="messageText">The message text.</param>
|
||||||
|
/// <param name="price">The price string.</param>
|
||||||
|
/// <param name="isPaid">Whether the message is paid.</param>
|
||||||
|
/// <param name="isArchived">Whether the message is archived.</param>
|
||||||
|
/// <param name="createdAt">The creation timestamp.</param>
|
||||||
|
/// <param name="userId">The sender user ID.</param>
|
||||||
public async Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid,
|
public async Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid,
|
||||||
bool isArchived, DateTime createdAt, long userId)
|
bool isArchived, DateTime createdAt, long userId)
|
||||||
{
|
{
|
||||||
@ -289,20 +287,21 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a post record when it does not already exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
|
/// <param name="postId">The post ID.</param>
|
||||||
|
/// <param name="messageText">The post text.</param>
|
||||||
|
/// <param name="price">The price string.</param>
|
||||||
|
/// <param name="isPaid">Whether the post is paid.</param>
|
||||||
|
/// <param name="isArchived">Whether the post is archived.</param>
|
||||||
|
/// <param name="createdAt">The creation timestamp.</param>
|
||||||
public async Task AddPost(string folder, long postId, string messageText, string price, bool isPaid,
|
public async Task AddPost(string folder, long postId, string messageText, string price, bool isPaid,
|
||||||
bool isArchived, DateTime createdAt)
|
bool isArchived, DateTime createdAt)
|
||||||
{
|
{
|
||||||
@ -333,20 +332,21 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a story record when it does not already exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
|
/// <param name="postId">The story ID.</param>
|
||||||
|
/// <param name="messageText">The story text.</param>
|
||||||
|
/// <param name="price">The price string.</param>
|
||||||
|
/// <param name="isPaid">Whether the story is paid.</param>
|
||||||
|
/// <param name="isArchived">Whether the story is archived.</param>
|
||||||
|
/// <param name="createdAt">The creation timestamp.</param>
|
||||||
public async Task AddStory(string folder, long postId, string messageText, string price, bool isPaid,
|
public async Task AddStory(string folder, long postId, string messageText, string price, bool isPaid,
|
||||||
bool isArchived, DateTime createdAt)
|
bool isArchived, DateTime createdAt)
|
||||||
{
|
{
|
||||||
@ -377,20 +377,26 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a media record when it does not already exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
|
/// <param name="mediaId">The media ID.</param>
|
||||||
|
/// <param name="postId">The parent post ID.</param>
|
||||||
|
/// <param name="link">The media URL.</param>
|
||||||
|
/// <param name="directory">The local directory path.</param>
|
||||||
|
/// <param name="filename">The local filename.</param>
|
||||||
|
/// <param name="size">The media size in bytes.</param>
|
||||||
|
/// <param name="apiType">The API type label.</param>
|
||||||
|
/// <param name="mediaType">The media type label.</param>
|
||||||
|
/// <param name="preview">Whether the media is a preview.</param>
|
||||||
|
/// <param name="downloaded">Whether the media is downloaded.</param>
|
||||||
|
/// <param name="createdAt">The creation timestamp.</param>
|
||||||
public async Task AddMedia(string folder, long mediaId, long postId, string link, string? directory,
|
public async Task AddMedia(string folder, long mediaId, long postId, string link, string? directory,
|
||||||
string? filename, long? size, string apiType, string mediaType, bool preview, bool downloaded,
|
string? filename, long? size, string apiType, string mediaType, bool preview, bool downloaded,
|
||||||
DateTime? createdAt)
|
DateTime? createdAt)
|
||||||
@ -421,20 +427,18 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the media has been marked as downloaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
|
/// <param name="mediaId">The media ID.</param>
|
||||||
|
/// <param name="apiType">The API type label.</param>
|
||||||
|
/// <returns>True when the media is marked as downloaded.</returns>
|
||||||
public async Task<bool> CheckDownloaded(string folder, long mediaId, string apiType)
|
public async Task<bool> CheckDownloaded(string folder, long mediaId, string apiType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -456,22 +460,24 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the media record with local file details.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
|
/// <param name="mediaId">The media ID.</param>
|
||||||
|
/// <param name="apiType">The API type label.</param>
|
||||||
|
/// <param name="directory">The local directory path.</param>
|
||||||
|
/// <param name="filename">The local filename.</param>
|
||||||
|
/// <param name="size">The file size in bytes.</param>
|
||||||
|
/// <param name="downloaded">Whether the media is downloaded.</param>
|
||||||
|
/// <param name="createdAt">The creation timestamp.</param>
|
||||||
public async Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename,
|
public async Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename,
|
||||||
long size, bool downloaded, DateTime createdAt)
|
long size, bool downloaded, DateTime createdAt)
|
||||||
{
|
{
|
||||||
@ -503,6 +509,13 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the stored size for a media record.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
|
/// <param name="mediaId">The media ID.</param>
|
||||||
|
/// <param name="apiType">The API type label.</param>
|
||||||
|
/// <returns>The stored file size.</returns>
|
||||||
public async Task<long> GetStoredFileSize(string folder, long mediaId, string apiType)
|
public async Task<long> GetStoredFileSize(string folder, long mediaId, string apiType)
|
||||||
{
|
{
|
||||||
await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||||
@ -516,6 +529,11 @@ public class DbService(IConfigService configService) : IDbService
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the most recent post date based on downloaded and pending media.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">The user folder path.</param>
|
||||||
|
/// <returns>The most recent post date if available.</returns>
|
||||||
public async Task<DateTime?> GetMostRecentPostDate(string folder)
|
public async Task<DateTime?> GetMostRecentPostDate(string folder)
|
||||||
{
|
{
|
||||||
DateTime? mostRecentDate = null;
|
DateTime? mostRecentDate = null;
|
||||||
|
|||||||
@ -15,8 +15,15 @@ public class DownloadOrchestrationService(
|
|||||||
IDownloadService downloadService,
|
IDownloadService downloadService,
|
||||||
IDbService dbService) : IDownloadOrchestrationService
|
IDbService dbService) : IDownloadOrchestrationService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of paid post media IDs to avoid duplicates.
|
||||||
|
/// </summary>
|
||||||
public List<long> PaidPostIds { get; } = new();
|
public List<long> PaidPostIds { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the available users and lists based on current configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A result containing users, lists, and any errors.</returns>
|
||||||
public async Task<UserListResult> GetAvailableUsersAsync()
|
public async Task<UserListResult> GetAvailableUsersAsync()
|
||||||
{
|
{
|
||||||
UserListResult result = new();
|
UserListResult result = new();
|
||||||
@ -89,6 +96,13 @@ public class DownloadOrchestrationService(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the users that belong to a specific list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listName">The list name.</param>
|
||||||
|
/// <param name="allUsers">All available users.</param>
|
||||||
|
/// <param name="lists">Known lists keyed by name.</param>
|
||||||
|
/// <returns>The users that belong to the list.</returns>
|
||||||
public async Task<Dictionary<string, long>> GetUsersForListAsync(
|
public async Task<Dictionary<string, long>> GetUsersForListAsync(
|
||||||
string listName, Dictionary<string, long> allUsers, Dictionary<string, long> lists)
|
string listName, Dictionary<string, long> allUsers, Dictionary<string, long> lists)
|
||||||
{
|
{
|
||||||
@ -98,11 +112,22 @@ public class DownloadOrchestrationService(
|
|||||||
.ToDictionary(x => x.Key, x => x.Value);
|
.ToDictionary(x => x.Key, x => x.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the download path for a username based on configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <returns>The resolved download path.</returns>
|
||||||
public string ResolveDownloadPath(string username) =>
|
public string ResolveDownloadPath(string username) =>
|
||||||
!string.IsNullOrEmpty(configService.CurrentConfig.DownloadPath)
|
!string.IsNullOrEmpty(configService.CurrentConfig.DownloadPath)
|
||||||
? Path.Combine(configService.CurrentConfig.DownloadPath, username)
|
? Path.Combine(configService.CurrentConfig.DownloadPath, username)
|
||||||
: $"__user_data__/sites/OnlyFans/{username}";
|
: $"__user_data__/sites/OnlyFans/{username}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures the user folder and metadata database exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
public async Task PrepareUserFolderAsync(string username, long userId, string path)
|
public async Task PrepareUserFolderAsync(string username, long userId, string path)
|
||||||
{
|
{
|
||||||
await dbService.CheckUsername(new KeyValuePair<string, long>(username, userId), path);
|
await dbService.CheckUsername(new KeyValuePair<string, long>(username, userId), path);
|
||||||
@ -116,6 +141,17 @@ public class DownloadOrchestrationService(
|
|||||||
await dbService.CreateDb(path);
|
await dbService.CreateDb(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads all configured content types for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="eventHandler">Download event handler.</param>
|
||||||
|
/// <returns>Counts of downloaded items per content type.</returns>
|
||||||
public async Task<CreatorDownloadResult> DownloadCreatorContentAsync(
|
public async Task<CreatorDownloadResult> DownloadCreatorContentAsync(
|
||||||
string username, long userId, string path,
|
string username, long userId, string path,
|
||||||
Dictionary<string, long> users,
|
Dictionary<string, long> users,
|
||||||
@ -281,6 +317,16 @@ public class DownloadOrchestrationService(
|
|||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a single post by ID for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="postId">The post ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="eventHandler">Download event handler.</param>
|
||||||
public async Task DownloadSinglePostAsync(
|
public async Task DownloadSinglePostAsync(
|
||||||
string username, long postId, string path,
|
string username, long postId, string path,
|
||||||
Dictionary<string, long> users,
|
Dictionary<string, long> users,
|
||||||
@ -320,6 +366,13 @@ public class DownloadOrchestrationService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads content from the Purchased tab across creators.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="eventHandler">Download event handler.</param>
|
||||||
public async Task DownloadPurchasedTabAsync(
|
public async Task DownloadPurchasedTabAsync(
|
||||||
Dictionary<string, long> users,
|
Dictionary<string, long> users,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
@ -427,6 +480,16 @@ public class DownloadOrchestrationService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a single paid message by ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="messageId">The message ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="eventHandler">Download event handler.</param>
|
||||||
public async Task DownloadSinglePaidMessageAsync(
|
public async Task DownloadSinglePaidMessageAsync(
|
||||||
string username, long messageId, string path,
|
string username, long messageId, string path,
|
||||||
Dictionary<string, long> users,
|
Dictionary<string, long> users,
|
||||||
@ -493,6 +556,11 @@ public class DownloadOrchestrationService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves a username for a user ID, including deleted users.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user ID.</param>
|
||||||
|
/// <returns>The resolved username or a deleted user placeholder.</returns>
|
||||||
public async Task<string?> ResolveUsernameAsync(long userId)
|
public async Task<string?> ResolveUsernameAsync(long userId)
|
||||||
{
|
{
|
||||||
JObject? user = await apiService.GetUserInfoById($"/users/list?x[]={userId}");
|
JObject? user = await apiService.GetUserInfoById($"/users/list?x[]={userId}");
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using FFmpeg.NET;
|
|||||||
using FFmpeg.NET.Events;
|
using FFmpeg.NET.Events;
|
||||||
using OF_DL.Models;
|
using OF_DL.Models;
|
||||||
using OF_DL.Enumerations;
|
using OF_DL.Enumerations;
|
||||||
|
using OF_DL.Helpers;
|
||||||
using OF_DL.Models.Downloads;
|
using OF_DL.Models.Downloads;
|
||||||
using ArchivedEntities = OF_DL.Models.Entities.Archived;
|
using ArchivedEntities = OF_DL.Models.Entities.Archived;
|
||||||
using MessageEntities = OF_DL.Models.Entities.Messages;
|
using MessageEntities = OF_DL.Models.Entities.Messages;
|
||||||
@ -26,6 +27,13 @@ public class DownloadService(
|
|||||||
{
|
{
|
||||||
private TaskCompletionSource<bool> _completionSource = new();
|
private TaskCompletionSource<bool> _completionSource = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads profile avatar and header images for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="avatarUrl">The avatar URL.</param>
|
||||||
|
/// <param name="headerUrl">The header URL.</param>
|
||||||
|
/// <param name="folder">The creator folder path.</param>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
public async Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username)
|
public async Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -189,16 +197,7 @@ public class DownloadService(
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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;
|
return false;
|
||||||
@ -223,7 +222,7 @@ public class DownloadService(
|
|||||||
long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName)
|
long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName)
|
||||||
? folder + path + "/" + customFileName + ".mp4"
|
? folder + path + "/" + customFileName + ".mp4"
|
||||||
: tempFilename).Length;
|
: tempFilename).Length;
|
||||||
progressReporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? fileSizeInBytes : 1);
|
ReportProgress(progressReporter, fileSizeInBytes);
|
||||||
|
|
||||||
await dbService.UpdateMedia(folder, mediaId, apiType, folder + path,
|
await dbService.UpdateMedia(folder, mediaId, apiType, folder + path,
|
||||||
!string.IsNullOrEmpty(customFileName) ? customFileName + ".mp4" : filename + "_source.mp4",
|
!string.IsNullOrEmpty(customFileName) ? customFileName + ".mp4" : filename + "_source.mp4",
|
||||||
@ -231,16 +230,7 @@ public class DownloadService(
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,6 +478,12 @@ public class DownloadService(
|
|||||||
return fileSize;
|
return fileSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the last modified timestamp for a DRM media URL.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The DRM media URL (including CloudFront tokens).</param>
|
||||||
|
/// <param name="auth">The current auth context.</param>
|
||||||
|
/// <returns>The last modified timestamp if available.</returns>
|
||||||
public static async Task<DateTime> GetDrmVideoLastModified(string url, Auth auth)
|
public static async Task<DateTime> GetDrmVideoLastModified(string url, Auth auth)
|
||||||
{
|
{
|
||||||
string[] messageUrlParsed = url.Split(',');
|
string[] messageUrlParsed = url.Split(',');
|
||||||
@ -515,6 +511,11 @@ public class DownloadService(
|
|||||||
return DateTime.Now;
|
return DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the last modified timestamp for a media URL.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The media URL.</param>
|
||||||
|
/// <returns>The last modified timestamp if available.</returns>
|
||||||
public static async Task<DateTime> GetMediaLastModified(string url)
|
public static async Task<DateTime> GetMediaLastModified(string url)
|
||||||
{
|
{
|
||||||
using HttpClient client = new();
|
using HttpClient client = new();
|
||||||
@ -681,7 +682,7 @@ public class DownloadService(
|
|||||||
|
|
||||||
fileSizeInBytes = GetLocalFileSize(finalPath);
|
fileSizeInBytes = GetLocalFileSize(finalPath);
|
||||||
lastModified = File.GetLastWriteTime(finalPath);
|
lastModified = File.GetLastWriteTime(finalPath);
|
||||||
progressReporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? fileSizeInBytes : 1);
|
ReportProgress(progressReporter, fileSizeInBytes);
|
||||||
|
|
||||||
status = false;
|
status = false;
|
||||||
}
|
}
|
||||||
@ -692,7 +693,7 @@ public class DownloadService(
|
|||||||
{
|
{
|
||||||
fileSizeInBytes = GetLocalFileSize(fullPathWithTheNewFileName);
|
fileSizeInBytes = GetLocalFileSize(fullPathWithTheNewFileName);
|
||||||
lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName);
|
lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName);
|
||||||
progressReporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? fileSizeInBytes : 1);
|
ReportProgress(progressReporter, fileSizeInBytes);
|
||||||
|
|
||||||
status = false;
|
status = false;
|
||||||
}
|
}
|
||||||
@ -723,15 +724,10 @@ public class DownloadService(
|
|||||||
private async Task<bool> HandlePreviouslyDownloadedMediaAsync(string folder, long mediaId, string apiType,
|
private async Task<bool> HandlePreviouslyDownloadedMediaAsync(string folder, long mediaId, string apiType,
|
||||||
IProgressReporter progressReporter)
|
IProgressReporter progressReporter)
|
||||||
{
|
{
|
||||||
if (configService.CurrentConfig.ShowScrapeSize)
|
long size = configService.CurrentConfig.ShowScrapeSize
|
||||||
{
|
? await dbService.GetStoredFileSize(folder, mediaId, apiType)
|
||||||
long size = await dbService.GetStoredFileSize(folder, mediaId, apiType);
|
: 1;
|
||||||
progressReporter.ReportProgress(size);
|
ReportProgress(progressReporter, size);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
progressReporter.ReportProgress(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -791,6 +787,11 @@ public class DownloadService(
|
|||||||
return response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now;
|
return response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the total size of a set of URLs by fetching their metadata.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="urls">The media URLs.</param>
|
||||||
|
/// <returns>The total size in bytes.</returns>
|
||||||
public async Task<long> CalculateTotalFileSize(List<string> urls)
|
public async Task<long> CalculateTotalFileSize(List<string> urls)
|
||||||
{
|
{
|
||||||
long totalFileSize = 0;
|
long totalFileSize = 0;
|
||||||
@ -826,6 +827,21 @@ public class DownloadService(
|
|||||||
return totalFileSize;
|
return totalFileSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a single media item, applying filename formatting and folder rules.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The media URL.</param>
|
||||||
|
/// <param name="folder">The creator folder path.</param>
|
||||||
|
/// <param name="mediaId">The media ID.</param>
|
||||||
|
/// <param name="apiType">The API type label.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <param name="path">The relative folder path.</param>
|
||||||
|
/// <param name="filenameFormat">Optional filename format.</param>
|
||||||
|
/// <param name="postInfo">Post or message info.</param>
|
||||||
|
/// <param name="postMedia">Media info.</param>
|
||||||
|
/// <param name="author">Author info.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <returns>True when the media is newly downloaded.</returns>
|
||||||
public async Task<bool> DownloadMedia(string url, string folder, long mediaId, string apiType,
|
public async Task<bool> DownloadMedia(string url, string folder, long mediaId, string apiType,
|
||||||
IProgressReporter progressReporter, string path,
|
IProgressReporter progressReporter, string path,
|
||||||
string? filenameFormat, object? postInfo, object? postMedia,
|
string? filenameFormat, object? postInfo, object? postMedia,
|
||||||
@ -840,6 +856,26 @@ public class DownloadService(
|
|||||||
filename, resolvedFilename);
|
filename, resolvedFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a DRM-protected video using the provided decryption key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="policy">CloudFront policy token.</param>
|
||||||
|
/// <param name="signature">CloudFront signature token.</param>
|
||||||
|
/// <param name="kvp">CloudFront key pair ID.</param>
|
||||||
|
/// <param name="url">The MPD URL.</param>
|
||||||
|
/// <param name="decryptionKey">The decryption key.</param>
|
||||||
|
/// <param name="folder">The creator folder path.</param>
|
||||||
|
/// <param name="lastModified">The source last modified timestamp.</param>
|
||||||
|
/// <param name="mediaId">The media ID.</param>
|
||||||
|
/// <param name="apiType">The API type label.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <param name="path">The relative folder path.</param>
|
||||||
|
/// <param name="filenameFormat">Optional filename format.</param>
|
||||||
|
/// <param name="postInfo">Post or message info.</param>
|
||||||
|
/// <param name="postMedia">Media info.</param>
|
||||||
|
/// <param name="author">Author info.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <returns>True when the media is newly downloaded.</returns>
|
||||||
public async Task<bool> DownloadDrmVideo(string policy, string signature, string kvp, string url,
|
public async Task<bool> DownloadDrmVideo(string policy, string signature, string kvp, string url,
|
||||||
string decryptionKey, string folder, DateTime lastModified, long mediaId, string apiType,
|
string decryptionKey, string folder, DateTime lastModified, long mediaId, string apiType,
|
||||||
IProgressReporter progressReporter, string path,
|
IProgressReporter progressReporter, string path,
|
||||||
@ -918,37 +954,23 @@ public class DownloadService(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
long size = await dbService.GetStoredFileSize(folder, mediaId, apiType);
|
long storedFileSize = await dbService.GetStoredFileSize(folder, mediaId, apiType);
|
||||||
await dbService.UpdateMedia(folder, mediaId, apiType, folder + path, customFileName + ".mp4",
|
await dbService.UpdateMedia(folder, mediaId, apiType, folder + path, customFileName + ".mp4",
|
||||||
size, true, lastModified);
|
storedFileSize, true, lastModified);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configService.CurrentConfig.ShowScrapeSize)
|
long progressSize = configService.CurrentConfig.ShowScrapeSize
|
||||||
{
|
? await dbService.GetStoredFileSize(folder, mediaId, apiType)
|
||||||
long size = await dbService.GetStoredFileSize(folder, mediaId, apiType);
|
: 1;
|
||||||
progressReporter.ReportProgress(size);
|
ReportProgress(progressReporter, progressSize);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
progressReporter.ReportProgress(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
ExceptionLoggerHelper.LogException(ex);
|
||||||
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;
|
return false;
|
||||||
@ -957,6 +979,19 @@ public class DownloadService(
|
|||||||
private void ReportProgress(IProgressReporter reporter, long sizeOrCount) =>
|
private void ReportProgress(IProgressReporter reporter, long sizeOrCount) =>
|
||||||
reporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? sizeOrCount : 1);
|
reporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? sizeOrCount : 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves decryption information for a DRM media item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mpdUrl">The MPD URL.</param>
|
||||||
|
/// <param name="policy">CloudFront policy token.</param>
|
||||||
|
/// <param name="signature">CloudFront signature token.</param>
|
||||||
|
/// <param name="kvp">CloudFront key pair ID.</param>
|
||||||
|
/// <param name="mediaId">The media ID.</param>
|
||||||
|
/// <param name="contentId">The content ID.</param>
|
||||||
|
/// <param name="drmType">The DRM type.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <returns>The decryption key and last modified timestamp.</returns>
|
||||||
public async Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo(
|
public async Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo(
|
||||||
string mpdUrl, string policy, string signature, string kvp,
|
string mpdUrl, string policy, string signature, string kvp,
|
||||||
string mediaId, string contentId, string drmType,
|
string mediaId, string contentId, string drmType,
|
||||||
@ -978,6 +1013,15 @@ public class DownloadService(
|
|||||||
return (decryptionKey, lastModified);
|
return (decryptionKey, lastModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads highlight media for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="paidPostIds">Paid post media IDs.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadHighlights(string username, long userId, string path,
|
public async Task<DownloadResult> DownloadHighlights(string username, long userId, string path,
|
||||||
HashSet<long> paidPostIds, IProgressReporter progressReporter)
|
HashSet<long> paidPostIds, IProgressReporter progressReporter)
|
||||||
{
|
{
|
||||||
@ -1032,6 +1076,15 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads story media for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="paidPostIds">Paid post media IDs.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadStories(string username, long userId, string path,
|
public async Task<DownloadResult> DownloadStories(string username, long userId, string path,
|
||||||
HashSet<long> paidPostIds, IProgressReporter progressReporter)
|
HashSet<long> paidPostIds, IProgressReporter progressReporter)
|
||||||
{
|
{
|
||||||
@ -1084,6 +1137,18 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads archived posts for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="archived">The archived posts collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadArchived(string username, long userId, string path,
|
public async Task<DownloadResult> DownloadArchived(string username, long userId, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
ArchivedEntities.ArchivedCollection archived, IProgressReporter progressReporter)
|
ArchivedEntities.ArchivedCollection archived, IProgressReporter progressReporter)
|
||||||
@ -1165,6 +1230,18 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads free messages for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="messages">The messages collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadMessages(string username, long userId, string path,
|
public async Task<DownloadResult> DownloadMessages(string username, long userId, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
MessageEntities.MessageCollection messages, IProgressReporter progressReporter)
|
MessageEntities.MessageCollection messages, IProgressReporter progressReporter)
|
||||||
@ -1247,6 +1324,17 @@ public class DownloadService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads paid messages for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="paidMessageCollection">The paid message collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadPaidMessages(string username, string path, Dictionary<string, long> users,
|
public async Task<DownloadResult> DownloadPaidMessages(string username, string path, Dictionary<string, long> users,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.PaidMessageCollection paidMessageCollection,
|
PurchasedEntities.PaidMessageCollection paidMessageCollection,
|
||||||
@ -1330,6 +1418,18 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads stream posts for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="streams">The streams collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadStreams(string username, long userId, string path,
|
public async Task<DownloadResult> DownloadStreams(string username, long userId, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
StreamEntities.StreamsCollection streams, IProgressReporter progressReporter)
|
StreamEntities.StreamsCollection streams, IProgressReporter progressReporter)
|
||||||
@ -1410,6 +1510,18 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads free posts for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="posts">The posts collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadFreePosts(string username, long userId, string path,
|
public async Task<DownloadResult> DownloadFreePosts(string username, long userId, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PostEntities.PostCollection posts,
|
PostEntities.PostCollection posts,
|
||||||
@ -1490,6 +1602,18 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads paid posts for a creator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="userId">The creator user ID.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="purchasedPosts">The paid post collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadPaidPosts(string username, long userId, string path,
|
public async Task<DownloadResult> DownloadPaidPosts(string username, long userId, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter)
|
PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter)
|
||||||
@ -1571,6 +1695,17 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads paid posts sourced from the Purchased tab.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="purchasedPosts">The paid post collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadPaidPostsPurchasedTab(string username, string path,
|
public async Task<DownloadResult> DownloadPaidPostsPurchasedTab(string username, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter)
|
PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter)
|
||||||
@ -1645,6 +1780,17 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads paid messages sourced from the Purchased tab.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="paidMessageCollection">The paid message collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadPaidMessagesPurchasedTab(string username, string path,
|
public async Task<DownloadResult> DownloadPaidMessagesPurchasedTab(string username, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter)
|
PurchasedEntities.PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter)
|
||||||
@ -1718,6 +1864,17 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a single post collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="post">The single post collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadSinglePost(string username, string path,
|
public async Task<DownloadResult> DownloadSinglePost(string username, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PostEntities.SinglePostCollection post, IProgressReporter progressReporter)
|
PostEntities.SinglePostCollection post, IProgressReporter progressReporter)
|
||||||
@ -1796,6 +1953,17 @@ public class DownloadService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a single paid message collection (including previews).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The creator username.</param>
|
||||||
|
/// <param name="path">The creator folder path.</param>
|
||||||
|
/// <param name="users">Known users map.</param>
|
||||||
|
/// <param name="clientIdBlobMissing">Whether the CDM client ID blob is missing.</param>
|
||||||
|
/// <param name="devicePrivateKeyMissing">Whether the CDM private key is missing.</param>
|
||||||
|
/// <param name="singlePaidMessageCollection">The single paid message collection.</param>
|
||||||
|
/// <param name="progressReporter">Progress reporter.</param>
|
||||||
|
/// <returns>The download result.</returns>
|
||||||
public async Task<DownloadResult> DownloadSinglePaidMessage(string username, string path,
|
public async Task<DownloadResult> DownloadSinglePaidMessage(string username, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection,
|
PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection,
|
||||||
|
|||||||
@ -5,6 +5,16 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public class FileNameService(IAuthService authService) : IFileNameService
|
public class FileNameService(IAuthService authService) : IFileNameService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a map of filename token values from post, media, and author data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The post or message object.</param>
|
||||||
|
/// <param name="media">The media object.</param>
|
||||||
|
/// <param name="author">The author object.</param>
|
||||||
|
/// <param name="selectedProperties">The tokens requested by the filename format.</param>
|
||||||
|
/// <param name="username">The resolved username when available.</param>
|
||||||
|
/// <param name="users">Optional lookup of user IDs to usernames.</param>
|
||||||
|
/// <returns>A dictionary of token values keyed by token name.</returns>
|
||||||
public async Task<Dictionary<string, string>> GetFilename(object info, object media, object author,
|
public async Task<Dictionary<string, string>> GetFilename(object info, object media, object author,
|
||||||
List<string> selectedProperties, string username, Dictionary<string, long>? users = null)
|
List<string> selectedProperties, string username, Dictionary<string, long>? users = null)
|
||||||
{
|
{
|
||||||
@ -168,6 +178,12 @@ public class FileNameService(IAuthService authService) : IFileNameService
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies token values to a filename format and removes invalid file name characters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileFormat">The filename format string.</param>
|
||||||
|
/// <param name="values">Token values to substitute.</param>
|
||||||
|
/// <returns>The resolved filename.</returns>
|
||||||
public Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values)
|
public Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<string, string> kvp in values)
|
foreach (KeyValuePair<string, string> kvp in values)
|
||||||
|
|||||||
@ -11,55 +11,121 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public interface IApiService
|
public interface IApiService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a decryption key using the local CDM integration.
|
||||||
|
/// </summary>
|
||||||
Task<string> GetDecryptionKeyCdm(Dictionary<string, string> drmHeaders, string licenceUrl, string pssh);
|
Task<string> GetDecryptionKeyCdm(Dictionary<string, string> drmHeaders, string licenceUrl, string pssh);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the last modified timestamp for a DRM MPD manifest.
|
||||||
|
/// </summary>
|
||||||
Task<DateTime> GetDrmMpdLastModified(string mpdUrl, string policy, string signature, string kvp);
|
Task<DateTime> GetDrmMpdLastModified(string mpdUrl, string policy, string signature, string kvp);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the Widevine PSSH from an MPD manifest.
|
||||||
|
/// </summary>
|
||||||
Task<string> GetDrmMpdPssh(string mpdUrl, string policy, string signature, string kvp);
|
Task<string> GetDrmMpdPssh(string mpdUrl, string policy, string signature, string kvp);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the user's lists.
|
||||||
|
/// </summary>
|
||||||
Task<Dictionary<string, long>?> GetLists(string endpoint);
|
Task<Dictionary<string, long>?> GetLists(string endpoint);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves usernames for a specific list.
|
||||||
|
/// </summary>
|
||||||
Task<List<string>?> GetListUsers(string endpoint);
|
Task<List<string>?> GetListUsers(string endpoint);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves media URLs for stories or highlights.
|
||||||
|
/// </summary>
|
||||||
Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username, string folder,
|
Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username, string folder,
|
||||||
List<long> paidPostIds);
|
List<long> paidPostIds);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves paid posts and their media.
|
||||||
|
/// </summary>
|
||||||
Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username,
|
Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username,
|
||||||
List<long> paidPostIds,
|
List<long> paidPostIds,
|
||||||
IStatusReporter statusReporter);
|
IStatusReporter statusReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves posts and their media.
|
||||||
|
/// </summary>
|
||||||
Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paidPostIds,
|
Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paidPostIds,
|
||||||
IStatusReporter statusReporter);
|
IStatusReporter statusReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a single post and its media.
|
||||||
|
/// </summary>
|
||||||
Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder);
|
Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves streams and their media.
|
||||||
|
/// </summary>
|
||||||
Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder, List<long> paidPostIds,
|
Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder, List<long> paidPostIds,
|
||||||
IStatusReporter statusReporter);
|
IStatusReporter statusReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves archived posts and their media.
|
||||||
|
/// </summary>
|
||||||
Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder,
|
Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder,
|
||||||
IStatusReporter statusReporter);
|
IStatusReporter statusReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves messages and their media.
|
||||||
|
/// </summary>
|
||||||
Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder, IStatusReporter statusReporter);
|
Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder, IStatusReporter statusReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves paid messages and their media.
|
||||||
|
/// </summary>
|
||||||
Task<PurchasedEntities.PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username,
|
Task<PurchasedEntities.PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username,
|
||||||
IStatusReporter statusReporter);
|
IStatusReporter statusReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a single paid message and its media.
|
||||||
|
/// </summary>
|
||||||
Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder);
|
Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves users that appear in the Purchased tab.
|
||||||
|
/// </summary>
|
||||||
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users);
|
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves Purchased tab content grouped by user.
|
||||||
|
/// </summary>
|
||||||
Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder,
|
Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder,
|
||||||
Dictionary<string, long> users);
|
Dictionary<string, long> users);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves user information.
|
||||||
|
/// </summary>
|
||||||
Task<UserEntities.User?> GetUserInfo(string endpoint);
|
Task<UserEntities.User?> GetUserInfo(string endpoint);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves user information by ID.
|
||||||
|
/// </summary>
|
||||||
Task<JObject?> GetUserInfoById(string endpoint);
|
Task<JObject?> GetUserInfoById(string endpoint);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds signed headers for API requests.
|
||||||
|
/// </summary>
|
||||||
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);
|
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves active subscriptions.
|
||||||
|
/// </summary>
|
||||||
Task<Dictionary<string, long>?> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
Task<Dictionary<string, long>?> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves expired subscriptions.
|
||||||
|
/// </summary>
|
||||||
Task<Dictionary<string, long>?> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
Task<Dictionary<string, long>?> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a decryption key via the OFDL fallback service.
|
||||||
|
/// </summary>
|
||||||
Task<string> GetDecryptionKeyOfdl(Dictionary<string, string> drmHeaders, string licenceUrl, string pssh);
|
Task<string> GetDecryptionKeyOfdl(Dictionary<string, string> drmHeaders, string licenceUrl, string pssh);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,24 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public interface IAuthService
|
public interface IAuthService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current authentication state.
|
||||||
|
/// </summary>
|
||||||
Auth? CurrentAuth { get; set; }
|
Auth? CurrentAuth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads authentication data from disk.
|
||||||
|
/// </summary>
|
||||||
Task<bool> LoadFromFileAsync(string filePath = "auth.json");
|
Task<bool> LoadFromFileAsync(string filePath = "auth.json");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Launches a browser session and extracts auth data after login.
|
||||||
|
/// </summary>
|
||||||
Task<bool> LoadFromBrowserAsync();
|
Task<bool> LoadFromBrowserAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Persists the current auth data to disk.
|
||||||
|
/// </summary>
|
||||||
Task SaveToFileAsync(string filePath = "auth.json");
|
Task SaveToFileAsync(string filePath = "auth.json");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -4,14 +4,29 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public interface IConfigService
|
public interface IConfigService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the active configuration in memory.
|
||||||
|
/// </summary>
|
||||||
Config CurrentConfig { get; }
|
Config CurrentConfig { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the CLI requested non-interactive mode.
|
||||||
|
/// </summary>
|
||||||
bool IsCliNonInteractive { get; }
|
bool IsCliNonInteractive { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads configuration from disk and applies runtime settings.
|
||||||
|
/// </summary>
|
||||||
Task<bool> LoadConfigurationAsync(string[] args);
|
Task<bool> LoadConfigurationAsync(string[] args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the current configuration to disk.
|
||||||
|
/// </summary>
|
||||||
Task SaveConfigurationAsync(string filePath = "config.conf");
|
Task SaveConfigurationAsync(string filePath = "config.conf");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the current configuration and applies runtime settings.
|
||||||
|
/// </summary>
|
||||||
void UpdateConfig(Config newConfig);
|
void UpdateConfig(Config newConfig);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -2,30 +2,63 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public interface IDbService
|
public interface IDbService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a message record when it does not already exist.
|
||||||
|
/// </summary>
|
||||||
Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
|
Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
|
||||||
DateTime createdAt, long userId);
|
DateTime createdAt, long userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a post record when it does not already exist.
|
||||||
|
/// </summary>
|
||||||
Task AddPost(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
|
Task AddPost(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
|
||||||
DateTime createdAt);
|
DateTime createdAt);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a story record when it does not already exist.
|
||||||
|
/// </summary>
|
||||||
Task AddStory(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
|
Task AddStory(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
|
||||||
DateTime createdAt);
|
DateTime createdAt);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates or updates the per-user metadata database.
|
||||||
|
/// </summary>
|
||||||
Task CreateDb(string folder);
|
Task CreateDb(string folder);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates or updates the global users database.
|
||||||
|
/// </summary>
|
||||||
Task CreateUsersDb(Dictionary<string, long> users);
|
Task CreateUsersDb(Dictionary<string, long> users);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures a username matches the stored user ID and migrates folders if needed.
|
||||||
|
/// </summary>
|
||||||
Task CheckUsername(KeyValuePair<string, long> user, string path);
|
Task CheckUsername(KeyValuePair<string, long> user, string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a media record when it does not already exist.
|
||||||
|
/// </summary>
|
||||||
Task AddMedia(string folder, long mediaId, long postId, string link, string? directory, string? filename,
|
Task AddMedia(string folder, long mediaId, long postId, string link, string? directory, string? filename,
|
||||||
long? size, string apiType, string mediaType, bool preview, bool downloaded, DateTime? createdAt);
|
long? size, string apiType, string mediaType, bool preview, bool downloaded, DateTime? createdAt);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the media record with local file details.
|
||||||
|
/// </summary>
|
||||||
Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename, long size,
|
Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename, long size,
|
||||||
bool downloaded, DateTime createdAt);
|
bool downloaded, DateTime createdAt);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the stored size for a media record.
|
||||||
|
/// </summary>
|
||||||
Task<long> GetStoredFileSize(string folder, long mediaId, string apiType);
|
Task<long> GetStoredFileSize(string folder, long mediaId, string apiType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the media has been marked as downloaded.
|
||||||
|
/// </summary>
|
||||||
Task<bool> CheckDownloaded(string folder, long mediaId, string apiType);
|
Task<bool> CheckDownloaded(string folder, long mediaId, string apiType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the most recent post date based on downloaded and pending media.
|
||||||
|
/// </summary>
|
||||||
Task<DateTime?> GetMostRecentPostDate(string folder);
|
Task<DateTime?> GetMostRecentPostDate(string folder);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,72 +9,126 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public interface IDownloadService
|
public interface IDownloadService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the total size of a set of URLs by fetching their metadata.
|
||||||
|
/// </summary>
|
||||||
Task<long> CalculateTotalFileSize(List<string> urls);
|
Task<long> CalculateTotalFileSize(List<string> urls);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads media and updates metadata storage.
|
||||||
|
/// </summary>
|
||||||
Task<bool> ProcessMediaDownload(string folder, long mediaId, string apiType, string url, string path,
|
Task<bool> ProcessMediaDownload(string folder, long mediaId, string apiType, string url, string path,
|
||||||
string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter);
|
string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a single media item.
|
||||||
|
/// </summary>
|
||||||
Task<bool> DownloadMedia(string url, string folder, long mediaId, string apiType,
|
Task<bool> DownloadMedia(string url, string folder, long mediaId, string apiType,
|
||||||
IProgressReporter progressReporter, string path,
|
IProgressReporter progressReporter, string path,
|
||||||
string? filenameFormat, object? postInfo, object? postMedia,
|
string? filenameFormat, object? postInfo, object? postMedia,
|
||||||
object? author, Dictionary<string, long> users);
|
object? author, Dictionary<string, long> users);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a DRM-protected video.
|
||||||
|
/// </summary>
|
||||||
Task<bool> DownloadDrmVideo(string policy, string signature, string kvp, string url,
|
Task<bool> DownloadDrmVideo(string policy, string signature, string kvp, string url,
|
||||||
string decryptionKey, string folder, DateTime lastModified, long mediaId, string apiType,
|
string decryptionKey, string folder, DateTime lastModified, long mediaId, string apiType,
|
||||||
IProgressReporter progressReporter, string path,
|
IProgressReporter progressReporter, string path,
|
||||||
string? filenameFormat, object? postInfo, object? postMedia,
|
string? filenameFormat, object? postInfo, object? postMedia,
|
||||||
object? author, Dictionary<string, long> users);
|
object? author, Dictionary<string, long> users);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves decryption information for a DRM media item.
|
||||||
|
/// </summary>
|
||||||
Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo(
|
Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo(
|
||||||
string mpdUrl, string policy, string signature, string kvp,
|
string mpdUrl, string policy, string signature, string kvp,
|
||||||
string mediaId, string contentId, string drmType,
|
string mediaId, string contentId, string drmType,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing);
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads profile avatar and header images for a creator.
|
||||||
|
/// </summary>
|
||||||
Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username);
|
Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads highlight media for a creator.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadHighlights(string username, long userId, string path, HashSet<long> paidPostIds,
|
Task<DownloadResult> DownloadHighlights(string username, long userId, string path, HashSet<long> paidPostIds,
|
||||||
IProgressReporter progressReporter);
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads story media for a creator.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadStories(string username, long userId, string path, HashSet<long> paidPostIds,
|
Task<DownloadResult> DownloadStories(string username, long userId, string path, HashSet<long> paidPostIds,
|
||||||
IProgressReporter progressReporter);
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads archived posts for a creator.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadArchived(string username, long userId, string path, Dictionary<string, long> users,
|
Task<DownloadResult> DownloadArchived(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing, ArchivedEntities.ArchivedCollection archived,
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, ArchivedEntities.ArchivedCollection archived,
|
||||||
IProgressReporter progressReporter);
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads free messages for a creator.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadMessages(string username, long userId, string path, Dictionary<string, long> users,
|
Task<DownloadResult> DownloadMessages(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing, MessageEntities.MessageCollection messages,
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, MessageEntities.MessageCollection messages,
|
||||||
IProgressReporter progressReporter);
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads paid messages for a creator.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadPaidMessages(string username, string path, Dictionary<string, long> users,
|
Task<DownloadResult> DownloadPaidMessages(string username, string path, Dictionary<string, long> users,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.PaidMessageCollection paidMessageCollection,
|
PurchasedEntities.PaidMessageCollection paidMessageCollection,
|
||||||
IProgressReporter progressReporter);
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads stream posts for a creator.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadStreams(string username, long userId, string path, Dictionary<string, long> users,
|
Task<DownloadResult> DownloadStreams(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamEntities.StreamsCollection streams,
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamEntities.StreamsCollection streams,
|
||||||
IProgressReporter progressReporter);
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads free posts for a creator.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadFreePosts(string username, long userId, string path, Dictionary<string, long> users,
|
Task<DownloadResult> DownloadFreePosts(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostEntities.PostCollection posts,
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostEntities.PostCollection posts,
|
||||||
IProgressReporter progressReporter);
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads paid posts for a creator.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadPaidPosts(string username, long userId, string path, Dictionary<string, long> users,
|
Task<DownloadResult> DownloadPaidPosts(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
bool clientIdBlobMissing, bool devicePrivateKeyMissing, PurchasedEntities.PaidPostCollection purchasedPosts,
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, PurchasedEntities.PaidPostCollection purchasedPosts,
|
||||||
IProgressReporter progressReporter);
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads paid posts sourced from the Purchased tab.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadPaidPostsPurchasedTab(string username, string path,
|
Task<DownloadResult> DownloadPaidPostsPurchasedTab(string username, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter);
|
PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads paid messages sourced from the Purchased tab.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadPaidMessagesPurchasedTab(string username, string path,
|
Task<DownloadResult> DownloadPaidMessagesPurchasedTab(string username, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter);
|
PurchasedEntities.PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a single post collection.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadSinglePost(string username, string path,
|
Task<DownloadResult> DownloadSinglePost(string username, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PostEntities.SinglePostCollection post, IProgressReporter progressReporter);
|
PostEntities.SinglePostCollection post, IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a single paid message collection.
|
||||||
|
/// </summary>
|
||||||
Task<DownloadResult> DownloadSinglePaidMessage(string username, string path,
|
Task<DownloadResult> DownloadSinglePaidMessage(string username, string path,
|
||||||
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
|
||||||
PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection,
|
PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection,
|
||||||
|
|||||||
@ -2,8 +2,14 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public interface IFileNameService
|
public interface IFileNameService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Applies token values to a filename format and removes invalid characters.
|
||||||
|
/// </summary>
|
||||||
Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values);
|
Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a map of filename token values from post, media, and author data.
|
||||||
|
/// </summary>
|
||||||
Task<Dictionary<string, string>> GetFilename(object info, object media, object author,
|
Task<Dictionary<string, string>> GetFilename(object info, object media, object author,
|
||||||
List<string> selectedProperties,
|
List<string> selectedProperties,
|
||||||
string username, Dictionary<string, long>? users = null);
|
string username, Dictionary<string, long>? users = null);
|
||||||
|
|||||||
@ -5,9 +5,18 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public interface ILoggingService
|
public interface ILoggingService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the level switch that controls runtime logging verbosity.
|
||||||
|
/// </summary>
|
||||||
LoggingLevelSwitch LevelSwitch { get; }
|
LoggingLevelSwitch LevelSwitch { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the minimum logging level at runtime.
|
||||||
|
/// </summary>
|
||||||
void UpdateLoggingLevel(LoggingLevel newLevel);
|
void UpdateLoggingLevel(LoggingLevel newLevel);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the current minimum logging level.
|
||||||
|
/// </summary>
|
||||||
LoggingLevel GetCurrentLoggingLevel();
|
LoggingLevel GetCurrentLoggingLevel();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,13 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public interface IStartupService
|
public interface IStartupService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the runtime environment and returns a structured result.
|
||||||
|
/// </summary>
|
||||||
Task<StartupResult> ValidateEnvironmentAsync();
|
Task<StartupResult> ValidateEnvironmentAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks the current application version against the latest release tag.
|
||||||
|
/// </summary>
|
||||||
Task<VersionCheckResult> CheckVersionAsync();
|
Task<VersionCheckResult> CheckVersionAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,14 +13,24 @@ public class LoggingService : ILoggingService
|
|||||||
InitializeLogger();
|
InitializeLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the level switch that controls runtime logging verbosity.
|
||||||
|
/// </summary>
|
||||||
public LoggingLevelSwitch LevelSwitch { get; }
|
public LoggingLevelSwitch LevelSwitch { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the minimum logging level at runtime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newLevel">The new minimum log level.</param>
|
||||||
public void UpdateLoggingLevel(LoggingLevel newLevel)
|
public void UpdateLoggingLevel(LoggingLevel newLevel)
|
||||||
{
|
{
|
||||||
LevelSwitch.MinimumLevel = (LogEventLevel)newLevel;
|
LevelSwitch.MinimumLevel = (LogEventLevel)newLevel;
|
||||||
Log.Debug("Logging level updated to: {LoggingLevel}", newLevel);
|
Log.Debug("Logging level updated to: {LoggingLevel}", newLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the current minimum logging level.
|
||||||
|
/// </summary>
|
||||||
public LoggingLevel GetCurrentLoggingLevel() => (LoggingLevel)LevelSwitch.MinimumLevel;
|
public LoggingLevel GetCurrentLoggingLevel() => (LoggingLevel)LevelSwitch.MinimumLevel;
|
||||||
|
|
||||||
private void InitializeLogger()
|
private void InitializeLogger()
|
||||||
|
|||||||
@ -13,6 +13,10 @@ namespace OF_DL.Services;
|
|||||||
|
|
||||||
public class StartupService(IConfigService configService, IAuthService authService) : IStartupService
|
public class StartupService(IConfigService configService, IAuthService authService) : IStartupService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the runtime environment and returns a structured result.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A result describing environment checks and detected tools.</returns>
|
||||||
public async Task<StartupResult> ValidateEnvironmentAsync()
|
public async Task<StartupResult> ValidateEnvironmentAsync()
|
||||||
{
|
{
|
||||||
StartupResult result = new();
|
StartupResult result = new();
|
||||||
@ -78,6 +82,10 @@ public class StartupService(IConfigService configService, IAuthService authServi
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks the current application version against the latest release tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A result describing the version check status.</returns>
|
||||||
public async Task<VersionCheckResult> CheckVersionAsync()
|
public async Task<VersionCheckResult> CheckVersionAsync()
|
||||||
{
|
{
|
||||||
VersionCheckResult result = new();
|
VersionCheckResult result = new();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user