2
0
forked from sim0n00ps/OF-DL
OF-DL/OF DL/Services/ConfigService.cs

459 lines
22 KiB
C#

using System.Reflection;
using System.Text;
using Akka.Configuration;
using Akka.Configuration.Hocon;
using Newtonsoft.Json;
using OF_DL.Models;
using OF_DL.Enumerations;
using OF_DL.Utils;
using Serilog;
using Config = OF_DL.Models.Config;
namespace OF_DL.Services;
public class ConfigService(ILoggingService loggingService) : IConfigService
{
public Config CurrentConfig { get; private set; } = new();
public bool IsCliNonInteractive { get; private set; }
public async Task<bool> LoadConfigurationAsync(string[] args)
{
try
{
IsCliNonInteractive = false;
// Migrate from config.json to config.conf if needed
await MigrateFromJsonToConfAsync();
// Load config.conf or create default
if (File.Exists("config.conf"))
{
Log.Debug("config.conf located successfully");
if (!await LoadConfigFromFileAsync("config.conf"))
{
return false;
}
}
else
{
Log.Debug("config.conf not found, creating default");
await CreateDefaultConfigFileAsync();
if (!await LoadConfigFromFileAsync("config.conf"))
{
return false;
}
}
// Check for command-line arguments
if (args != null && args.Length > 0)
{
const string NON_INTERACTIVE_ARG = "--non-interactive";
if (args.Any(a => a.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase)))
{
IsCliNonInteractive = true;
Log.Debug("NonInteractiveMode set via command line");
}
Log.Debug("Additional arguments:");
foreach (string argument in args)
{
Log.Debug(argument);
}
}
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Configuration loading failed");
return false;
}
}
public async Task SaveConfigurationAsync(string filePath = "config.conf")
{
if (CurrentConfig == null)
{
Log.Warning("Attempted to save null config to file");
return;
}
try
{
string hoconConfig = BuildHoconFromConfig(CurrentConfig);
await File.WriteAllTextAsync(filePath, hoconConfig);
Log.Debug($"Config saved to file: {filePath}");
}
catch (Exception ex)
{
Log.Error(ex, "Failed to save config to file");
}
}
public void UpdateConfig(Config newConfig)
{
CurrentConfig = newConfig;
// Update logging level
loggingService.UpdateLoggingLevel(newConfig.LoggingLevel);
// Apply text sanitization preference globally
XmlUtils.Passthrough = newConfig.DisableTextSanitization;
Log.Debug("Configuration updated");
string configString = JsonConvert.SerializeObject(newConfig, Formatting.Indented);
Log.Debug(configString);
}
private async Task MigrateFromJsonToConfAsync()
{
if (!File.Exists("config.json"))
{
return;
}
try
{
Log.Debug("config.json found, migrating to config.conf");
string jsonText = await File.ReadAllTextAsync("config.json");
Config? jsonConfig = JsonConvert.DeserializeObject<Config>(jsonText);
if (jsonConfig != null)
{
string hoconConfig = BuildHoconFromConfig(jsonConfig);
await File.WriteAllTextAsync("config.conf", hoconConfig);
File.Delete("config.json");
Log.Information("config.conf created successfully from config.json");
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to migrate config.json to config.conf");
throw;
}
}
private async Task<bool> LoadConfigFromFileAsync(string filePath)
{
try
{
string hoconText = await File.ReadAllTextAsync(filePath);
Akka.Configuration.Config? hoconConfig = ConfigurationFactory.ParseString(hoconText);
CurrentConfig = new Config
{
// Auth
DisableBrowserAuth = hoconConfig.GetBoolean("Auth.DisableBrowserAuth"),
// FFmpeg Settings
FFmpegPath = hoconConfig.GetString("External.FFmpegPath"),
// Download Settings
DownloadAvatarHeaderPhoto = hoconConfig.GetBoolean("Download.Media.DownloadAvatarHeaderPhoto"),
DownloadPaidPosts = hoconConfig.GetBoolean("Download.Media.DownloadPaidPosts"),
DownloadPosts = hoconConfig.GetBoolean("Download.Media.DownloadPosts"),
DownloadArchived = hoconConfig.GetBoolean("Download.Media.DownloadArchived"),
DownloadStreams = hoconConfig.GetBoolean("Download.Media.DownloadStreams"),
DownloadStories = hoconConfig.GetBoolean("Download.Media.DownloadStories"),
DownloadHighlights = hoconConfig.GetBoolean("Download.Media.DownloadHighlights"),
DownloadMessages = hoconConfig.GetBoolean("Download.Media.DownloadMessages"),
DownloadPaidMessages = hoconConfig.GetBoolean("Download.Media.DownloadPaidMessages"),
DownloadImages = hoconConfig.GetBoolean("Download.Media.DownloadImages"),
DownloadVideos = hoconConfig.GetBoolean("Download.Media.DownloadVideos"),
DownloadAudios = hoconConfig.GetBoolean("Download.Media.DownloadAudios"),
IgnoreOwnMessages = hoconConfig.GetBoolean("Download.IgnoreOwnMessages"),
DownloadPostsIncrementally = hoconConfig.GetBoolean("Download.DownloadPostsIncrementally"),
BypassContentForCreatorsWhoNoLongerExist =
hoconConfig.GetBoolean("Download.BypassContentForCreatorsWhoNoLongerExist"),
DownloadDuplicatedMedia = hoconConfig.GetBoolean("Download.DownloadDuplicatedMedia"),
SkipAds = hoconConfig.GetBoolean("Download.SkipAds"),
DownloadPath = hoconConfig.GetString("Download.DownloadPath"),
DownloadOnlySpecificDates = hoconConfig.GetBoolean("Download.DownloadOnlySpecificDates"),
DownloadDateSelection =
Enum.Parse<DownloadDateSelection>(hoconConfig.GetString("Download.DownloadDateSelection"), true),
CustomDate =
!string.IsNullOrWhiteSpace(hoconConfig.GetString("Download.CustomDate"))
? DateTime.Parse(hoconConfig.GetString("Download.CustomDate"))
: null,
ShowScrapeSize = hoconConfig.GetBoolean("Download.ShowScrapeSize"),
DisableTextSanitization =
bool.TryParse(hoconConfig.GetString("Download.DisableTextSanitization", "false"), out bool dts)
? dts
: false,
DownloadVideoResolution =
ParseVideoResolution(hoconConfig.GetString("Download.DownloadVideoResolution", "source")),
// File Settings
PaidPostFileNameFormat = hoconConfig.GetString("File.PaidPostFileNameFormat"),
PostFileNameFormat = hoconConfig.GetString("File.PostFileNameFormat"),
PaidMessageFileNameFormat = hoconConfig.GetString("File.PaidMessageFileNameFormat"),
MessageFileNameFormat = hoconConfig.GetString("File.MessageFileNameFormat"),
RenameExistingFilesWhenCustomFormatIsSelected =
hoconConfig.GetBoolean("File.RenameExistingFilesWhenCustomFormatIsSelected"),
// Folder Settings
FolderPerPaidPost = hoconConfig.GetBoolean("Folder.FolderPerPaidPost"),
FolderPerPost = hoconConfig.GetBoolean("Folder.FolderPerPost"),
FolderPerPaidMessage = hoconConfig.GetBoolean("Folder.FolderPerPaidMessage"),
FolderPerMessage = hoconConfig.GetBoolean("Folder.FolderPerMessage"),
// Subscription Settings
IncludeExpiredSubscriptions = hoconConfig.GetBoolean("Subscriptions.IncludeExpiredSubscriptions"),
IncludeRestrictedSubscriptions = hoconConfig.GetBoolean("Subscriptions.IncludeRestrictedSubscriptions"),
IgnoredUsersListName = hoconConfig.GetString("Subscriptions.IgnoredUsersListName"),
// Interaction Settings
NonInteractiveMode = hoconConfig.GetBoolean("Interaction.NonInteractiveMode"),
NonInteractiveModeListName = hoconConfig.GetString("Interaction.NonInteractiveModeListName"),
NonInteractiveModePurchasedTab = hoconConfig.GetBoolean("Interaction.NonInteractiveModePurchasedTab"),
// Performance Settings
Timeout =
string.IsNullOrWhiteSpace(hoconConfig.GetString("Performance.Timeout"))
? -1
: hoconConfig.GetInt("Performance.Timeout"),
LimitDownloadRate = hoconConfig.GetBoolean("Performance.LimitDownloadRate"),
DownloadLimitInMbPerSec = hoconConfig.GetInt("Performance.DownloadLimitInMbPerSec"),
// Logging/Debug Settings
LoggingLevel = Enum.Parse<LoggingLevel>(hoconConfig.GetString("Logging.LoggingLevel"), true)
};
// Validate file name formats
ValidateFileNameFormat(CurrentConfig.PaidPostFileNameFormat, "PaidPostFileNameFormat");
ValidateFileNameFormat(CurrentConfig.PostFileNameFormat, "PostFileNameFormat");
ValidateFileNameFormat(CurrentConfig.PaidMessageFileNameFormat, "PaidMessageFileNameFormat");
ValidateFileNameFormat(CurrentConfig.MessageFileNameFormat, "MessageFileNameFormat");
// Load creator-specific configs
Akka.Configuration.Config? creatorConfigsSection = hoconConfig.GetConfig("CreatorConfigs");
if (creatorConfigsSection != null)
{
foreach ((string? creatorKey, _) in creatorConfigsSection.AsEnumerable())
{
Akka.Configuration.Config? creatorHocon = creatorConfigsSection.GetConfig(creatorKey);
if (!CurrentConfig.CreatorConfigs.ContainsKey(creatorKey) && creatorHocon != null)
{
CurrentConfig.CreatorConfigs.Add(creatorKey,
new CreatorConfig
{
PaidPostFileNameFormat = creatorHocon.GetString("PaidPostFileNameFormat"),
PostFileNameFormat = creatorHocon.GetString("PostFileNameFormat"),
PaidMessageFileNameFormat = creatorHocon.GetString("PaidMessageFileNameFormat"),
MessageFileNameFormat = creatorHocon.GetString("MessageFileNameFormat")
});
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[creatorKey].PaidPostFileNameFormat,
$"{creatorKey}.PaidPostFileNameFormat");
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[creatorKey].PostFileNameFormat,
$"{creatorKey}.PostFileNameFormat");
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[creatorKey].PaidMessageFileNameFormat,
$"{creatorKey}.PaidMessageFileNameFormat");
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[creatorKey].MessageFileNameFormat,
$"{creatorKey}.MessageFileNameFormat");
}
}
}
// Update logging level
loggingService.UpdateLoggingLevel(CurrentConfig.LoggingLevel);
// Apply text sanitization preference globally
XmlUtils.Passthrough = CurrentConfig.DisableTextSanitization;
Log.Debug("Configuration loaded successfully");
string configString = JsonConvert.SerializeObject(CurrentConfig, Formatting.Indented);
Log.Debug(configString);
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Failed to parse config file");
return false;
}
}
private async Task CreateDefaultConfigFileAsync()
{
Config defaultConfig = new();
string hoconConfig = BuildHoconFromConfig(defaultConfig);
await File.WriteAllTextAsync("config.conf", hoconConfig);
Log.Information("Created default config.conf file");
}
private string BuildHoconFromConfig(Config config)
{
StringBuilder hocon = new();
hocon.AppendLine("# Auth");
hocon.AppendLine("Auth {");
hocon.AppendLine($" DisableBrowserAuth = {config.DisableBrowserAuth.ToString().ToLower()}");
hocon.AppendLine("}");
hocon.AppendLine("# External Tools");
hocon.AppendLine("External {");
hocon.AppendLine($" FFmpegPath = \"{config.FFmpegPath}\"");
hocon.AppendLine("}");
hocon.AppendLine("# Download Settings");
hocon.AppendLine("Download {");
hocon.AppendLine(" Media {");
hocon.AppendLine($" DownloadAvatarHeaderPhoto = {config.DownloadAvatarHeaderPhoto.ToString().ToLower()}");
hocon.AppendLine($" DownloadPaidPosts = {config.DownloadPaidPosts.ToString().ToLower()}");
hocon.AppendLine($" DownloadPosts = {config.DownloadPosts.ToString().ToLower()}");
hocon.AppendLine($" DownloadArchived = {config.DownloadArchived.ToString().ToLower()}");
hocon.AppendLine($" DownloadStreams = {config.DownloadStreams.ToString().ToLower()}");
hocon.AppendLine($" DownloadStories = {config.DownloadStories.ToString().ToLower()}");
hocon.AppendLine($" DownloadHighlights = {config.DownloadHighlights.ToString().ToLower()}");
hocon.AppendLine($" DownloadMessages = {config.DownloadMessages.ToString().ToLower()}");
hocon.AppendLine($" DownloadPaidMessages = {config.DownloadPaidMessages.ToString().ToLower()}");
hocon.AppendLine($" DownloadImages = {config.DownloadImages.ToString().ToLower()}");
hocon.AppendLine($" DownloadVideos = {config.DownloadVideos.ToString().ToLower()}");
hocon.AppendLine($" DownloadAudios = {config.DownloadAudios.ToString().ToLower()}");
hocon.AppendLine(" }");
hocon.AppendLine($" IgnoreOwnMessages = {config.IgnoreOwnMessages.ToString().ToLower()}");
hocon.AppendLine($" DownloadPostsIncrementally = {config.DownloadPostsIncrementally.ToString().ToLower()}");
hocon.AppendLine(
$" BypassContentForCreatorsWhoNoLongerExist = {config.BypassContentForCreatorsWhoNoLongerExist.ToString().ToLower()}");
hocon.AppendLine($" DownloadDuplicatedMedia = {config.DownloadDuplicatedMedia.ToString().ToLower()}");
hocon.AppendLine($" SkipAds = {config.SkipAds.ToString().ToLower()}");
hocon.AppendLine($" DownloadPath = \"{config.DownloadPath}\"");
hocon.AppendLine($" DownloadOnlySpecificDates = {config.DownloadOnlySpecificDates.ToString().ToLower()}");
hocon.AppendLine($" DownloadDateSelection = \"{config.DownloadDateSelection.ToString().ToLower()}\"");
hocon.AppendLine($" CustomDate = \"{config.CustomDate?.ToString("yyyy-MM-dd")}\"");
hocon.AppendLine($" ShowScrapeSize = {config.ShowScrapeSize.ToString().ToLower()}");
hocon.AppendLine($" DisableTextSanitization = {config.DisableTextSanitization.ToString().ToLower()}");
hocon.AppendLine(
$" DownloadVideoResolution = \"{(config.DownloadVideoResolution == VideoResolution.source ? "source" : config.DownloadVideoResolution.ToString().TrimStart('_'))}\"");
hocon.AppendLine("}");
hocon.AppendLine("# File Settings");
hocon.AppendLine("File {");
hocon.AppendLine($" PaidPostFileNameFormat = \"{config.PaidPostFileNameFormat}\"");
hocon.AppendLine($" PostFileNameFormat = \"{config.PostFileNameFormat}\"");
hocon.AppendLine($" PaidMessageFileNameFormat = \"{config.PaidMessageFileNameFormat}\"");
hocon.AppendLine($" MessageFileNameFormat = \"{config.MessageFileNameFormat}\"");
hocon.AppendLine(
$" RenameExistingFilesWhenCustomFormatIsSelected = {config.RenameExistingFilesWhenCustomFormatIsSelected.ToString().ToLower()}");
hocon.AppendLine("}");
hocon.AppendLine("# Creator-Specific Configurations");
hocon.AppendLine("CreatorConfigs {");
foreach (KeyValuePair<string, CreatorConfig> creatorConfig in config.CreatorConfigs)
{
hocon.AppendLine($" \"{creatorConfig.Key}\" {{");
hocon.AppendLine($" PaidPostFileNameFormat = \"{creatorConfig.Value.PaidPostFileNameFormat}\"");
hocon.AppendLine($" PostFileNameFormat = \"{creatorConfig.Value.PostFileNameFormat}\"");
hocon.AppendLine($" PaidMessageFileNameFormat = \"{creatorConfig.Value.PaidMessageFileNameFormat}\"");
hocon.AppendLine($" MessageFileNameFormat = \"{creatorConfig.Value.MessageFileNameFormat}\"");
hocon.AppendLine(" }");
}
hocon.AppendLine("}");
hocon.AppendLine("# Folder Settings");
hocon.AppendLine("Folder {");
hocon.AppendLine($" FolderPerPaidPost = {config.FolderPerPaidPost.ToString().ToLower()}");
hocon.AppendLine($" FolderPerPost = {config.FolderPerPost.ToString().ToLower()}");
hocon.AppendLine($" FolderPerPaidMessage = {config.FolderPerPaidMessage.ToString().ToLower()}");
hocon.AppendLine($" FolderPerMessage = {config.FolderPerMessage.ToString().ToLower()}");
hocon.AppendLine("}");
hocon.AppendLine("# Subscription Settings");
hocon.AppendLine("Subscriptions {");
hocon.AppendLine($" IncludeExpiredSubscriptions = {config.IncludeExpiredSubscriptions.ToString().ToLower()}");
hocon.AppendLine(
$" IncludeRestrictedSubscriptions = {config.IncludeRestrictedSubscriptions.ToString().ToLower()}");
hocon.AppendLine($" IgnoredUsersListName = \"{config.IgnoredUsersListName}\"");
hocon.AppendLine("}");
hocon.AppendLine("# Interaction Settings");
hocon.AppendLine("Interaction {");
hocon.AppendLine($" NonInteractiveMode = {config.NonInteractiveMode.ToString().ToLower()}");
hocon.AppendLine($" NonInteractiveModeListName = \"{config.NonInteractiveModeListName}\"");
hocon.AppendLine(
$" NonInteractiveModePurchasedTab = {config.NonInteractiveModePurchasedTab.ToString().ToLower()}");
hocon.AppendLine("}");
hocon.AppendLine("# Performance Settings");
hocon.AppendLine("Performance {");
hocon.AppendLine($" Timeout = {(config.Timeout.HasValue ? config.Timeout.Value : -1)}");
hocon.AppendLine($" LimitDownloadRate = {config.LimitDownloadRate.ToString().ToLower()}");
hocon.AppendLine($" DownloadLimitInMbPerSec = {config.DownloadLimitInMbPerSec}");
hocon.AppendLine("}");
hocon.AppendLine("# Logging/Debug Settings");
hocon.AppendLine("Logging {");
hocon.AppendLine($" LoggingLevel = \"{config.LoggingLevel.ToString().ToLower()}\"");
hocon.AppendLine("}");
return hocon.ToString();
}
private void ValidateFileNameFormat(string? format, string settingName)
{
if (!string.IsNullOrEmpty(format) &&
!format.Contains("{mediaId}", StringComparison.OrdinalIgnoreCase) &&
!format.Contains("{filename}", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
$"{settingName} is not unique enough. Please include either '{{mediaId}}' or '{{filename}}' to ensure files are not overwritten.");
}
}
public List<(string Name, bool Value)> GetToggleableProperties()
{
List<(string Name, bool Value)> result = [];
foreach (PropertyInfo propInfo in typeof(Config).GetProperties())
{
ToggleableConfigAttribute? attr = propInfo.GetCustomAttribute<ToggleableConfigAttribute>();
if (attr != null)
{
result.Add((propInfo.Name, (bool)propInfo.GetValue(CurrentConfig)!));
}
}
return result;
}
public bool ApplyToggleableSelections(List<string> selectedNames)
{
bool configChanged = false;
Config newConfig = new();
foreach (PropertyInfo propInfo in typeof(Config).GetProperties())
{
ToggleableConfigAttribute? attr = propInfo.GetCustomAttribute<ToggleableConfigAttribute>();
if (attr != null)
{
bool newValue = selectedNames.Contains(propInfo.Name);
bool oldValue = (bool)propInfo.GetValue(CurrentConfig)!;
propInfo.SetValue(newConfig, newValue);
if (newValue != oldValue)
{
configChanged = true;
}
}
else
{
propInfo.SetValue(newConfig, propInfo.GetValue(CurrentConfig));
}
}
UpdateConfig(newConfig);
return configChanged;
}
private VideoResolution ParseVideoResolution(string value)
{
if (value.Equals("source", StringComparison.OrdinalIgnoreCase))
{
return VideoResolution.source;
}
return Enum.Parse<VideoResolution>("_" + value, true);
}
}