using System.Reflection; using System.Text; using Akka.Configuration; using Newtonsoft.Json; using OF_DL.Enumerations; using OF_DL.Models.Config; using OF_DL.Utils; using Serilog; using Config = OF_DL.Models.Config.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 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.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") { 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(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 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(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(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 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(); if (attr != null) { bool? value = (bool?)propInfo.GetValue(CurrentConfig); if (value == null) { continue; } result.Add((propInfo.Name, value.Value)); } } return result; } public bool ApplyToggleableSelections(List selectedNames) { bool configChanged = false; Config newConfig = new(); foreach (PropertyInfo propInfo in typeof(Config).GetProperties()) { ToggleableConfigAttribute? attr = propInfo.GetCustomAttribute(); if (attr != null) { bool newValue = selectedNames.Contains(propInfo.Name); bool? oldValue = (bool?)propInfo.GetValue(CurrentConfig); if (oldValue == null) { continue; } propInfo.SetValue(newConfig, newValue); if (newValue != oldValue.Value) { 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("_" + value, true); } }