From 35bde51e7d0f28304348dff010ca3a3d6c8b29b4 Mon Sep 17 00:00:00 2001 From: whimsical-c4lic0 Date: Wed, 18 Feb 2026 03:14:24 -0600 Subject: [PATCH] Add a warning about missing CDM keys before starting downloads --- OF DL.Core/Models/Config/Config.cs | 2 + OF DL.Core/Services/ConfigService.cs | 4 + OF DL.Gui/ViewModels/ConfigFieldViewModel.cs | 3 +- OF DL.Gui/ViewModels/MainWindowViewModel.cs | 124 ++++++++++++++++--- OF DL.Gui/Views/MainWindow.axaml | 38 ++++++ OF DL.Gui/Views/MainWindow.axaml.cs | 1 + OF DL.Tests/Services/ConfigServiceTests.cs | 20 +++ docs/config/all-configuration-options.md | 12 ++ docs/config/configuration.md | 1 + 9 files changed, 190 insertions(+), 15 deletions(-) diff --git a/OF DL.Core/Models/Config/Config.cs b/OF DL.Core/Models/Config/Config.cs index c83e71b..78dcf3a 100644 --- a/OF DL.Core/Models/Config/Config.cs +++ b/OF DL.Core/Models/Config/Config.cs @@ -92,6 +92,8 @@ public class Config : IFileNameFormatConfig [JsonConverter(typeof(StringEnumConverter))] public Theme Theme { get; set; } = Theme.light; + [ToggleableConfig] public bool HideMissingCdmKeysWarning { get; set; } + [ToggleableConfig] public bool IgnoreOwnMessages { get; set; } [ToggleableConfig] public bool DisableBrowserAuth { get; set; } diff --git a/OF DL.Core/Services/ConfigService.cs b/OF DL.Core/Services/ConfigService.cs index d892b88..f46ff4b 100644 --- a/OF DL.Core/Services/ConfigService.cs +++ b/OF DL.Core/Services/ConfigService.cs @@ -238,6 +238,9 @@ public class ConfigService(ILoggingService loggingService) : IConfigService // Appearance Settings Theme = ParseTheme(hoconConfig.GetString("Appearance.Theme", "light")), + HideMissingCdmKeysWarning = + bool.TryParse(hoconConfig.GetString("Appearance.HideMissingCdmKeysWarning", "false"), + out bool hideMissingCdmKeysWarning) && hideMissingCdmKeysWarning, // Logging/Debug Settings LoggingLevel = Enum.Parse(hoconConfig.GetString("Logging.LoggingLevel"), true) @@ -413,6 +416,7 @@ public class ConfigService(ILoggingService loggingService) : IConfigService hocon.AppendLine("# Appearance Settings"); hocon.AppendLine("Appearance {"); hocon.AppendLine($" Theme = \"{config.Theme.ToString().ToLower()}\""); + hocon.AppendLine($" HideMissingCdmKeysWarning = {config.HideMissingCdmKeysWarning.ToString().ToLower()}"); hocon.AppendLine("}"); hocon.AppendLine("# Logging/Debug Settings"); diff --git a/OF DL.Gui/ViewModels/ConfigFieldViewModel.cs b/OF DL.Gui/ViewModels/ConfigFieldViewModel.cs index 59d5179..fb277f6 100644 --- a/OF DL.Gui/ViewModels/ConfigFieldViewModel.cs +++ b/OF DL.Gui/ViewModels/ConfigFieldViewModel.cs @@ -57,7 +57,8 @@ public partial class ConfigFieldViewModel : ViewModelBase [nameof(Config.IgnoredUsersListName)] = "Ignored Users List", [nameof(Config.RenameExistingFilesWhenCustomFormatIsSelected)] = "Rename Existing Files with Custom Formats", - [nameof(Config.DownloadPath)] = "Download Folder" + [nameof(Config.DownloadPath)] = "Download Folder", + [nameof(Config.HideMissingCdmKeysWarning)] = "Hide Missing CDM Keys Warning" }; public ConfigFieldViewModel( diff --git a/OF DL.Gui/ViewModels/MainWindowViewModel.cs b/OF DL.Gui/ViewModels/MainWindowViewModel.cs index af9b84d..8f89f70 100644 --- a/OF DL.Gui/ViewModels/MainWindowViewModel.cs +++ b/OF DL.Gui/ViewModels/MainWindowViewModel.cs @@ -40,6 +40,7 @@ public partial class MainWindowViewModel( long? UserId); private const string UnknownToolVersion = "Not detected"; + private static readonly Regex s_singlePostUrlRegex = new( @"^https://onlyfans\.com/(?\d+)/(?[A-Za-z0-9_.-]+)/?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); @@ -154,6 +155,8 @@ public partial class MainWindowViewModel( "Download rate limit in MB/s when rate limiting is enabled.", [nameof(Config.Theme)] = "GUI theme for the configuration and download screens.", + [nameof(Config.HideMissingCdmKeysWarning)] = + "Hide the missing CDM keys warning before downloads start.", [nameof(Config.LoggingLevel)] = "Log verbosity written to logs/OFDL.txt." }; @@ -319,14 +322,23 @@ public partial class MainWindowViewModel( [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(OpenSinglePostOrMessageModalCommand))] [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))] + [NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))] + [NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))] private bool _isSinglePostOrMessageModalOpen; [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(OpenSinglePostOrMessageModalCommand))] [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))] + [NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))] + [NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))] + private bool _isMissingCdmWarningModalOpen; + + [ObservableProperty] private string _missingCdmWarningMessage = string.Empty; + + [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))] private string _singlePostOrMessageUrl = string.Empty; - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(HasSinglePostOrMessageUrlError))] + [ObservableProperty] [NotifyPropertyChangedFor(nameof(HasSinglePostOrMessageUrlError))] private string _singlePostOrMessageUrlError = string.Empty; public bool IsLoadingScreen => CurrentScreen == AppScreen.Loading; @@ -432,6 +444,7 @@ public partial class MainWindowViewModel( } private bool _isUpdatingAllUsersSelected; + private TaskCompletionSource? _missingCdmWarningCompletionSource; public async Task InitializeAsync() { @@ -752,6 +765,20 @@ public partial class MainWindowViewModel( IsSinglePostOrMessageModalOpen = false; } + [RelayCommand] + private void ConfirmMissingCdmWarning() + { + IsMissingCdmWarningModalOpen = false; + _missingCdmWarningCompletionSource?.TrySetResult(true); + } + + [RelayCommand] + private void CancelMissingCdmWarning() + { + IsMissingCdmWarningModalOpen = false; + _missingCdmWarningCompletionSource?.TrySetResult(false); + } + [RelayCommand(CanExecute = nameof(CanStopWork))] private void StopWork() { @@ -779,6 +806,11 @@ public partial class MainWindowViewModel( return; } + if (!await EnsureMissingCdmWarningConfirmedAsync()) + { + return; + } + IsDownloading = true; _workCancellationSource?.Dispose(); _workCancellationSource = new CancellationTokenSource(); @@ -871,20 +903,24 @@ public partial class MainWindowViewModel( private bool CanDownloadSelected() => CurrentScreen == AppScreen.UserSelection && AvailableUsers.Any(user => user.IsSelected) && + !IsMissingCdmWarningModalOpen && !IsDownloading; private bool CanDownloadPurchasedTab() => CurrentScreen == AppScreen.UserSelection && _allUsers.Count > 0 && + !IsMissingCdmWarningModalOpen && !IsDownloading; private bool CanOpenSinglePostOrMessageModal() => CurrentScreen == AppScreen.UserSelection && !IsDownloading && + !IsMissingCdmWarningModalOpen && !IsSinglePostOrMessageModalOpen; private bool CanSubmitSinglePostOrMessage() => IsSinglePostOrMessageModalOpen && + !IsMissingCdmWarningModalOpen && !IsDownloading && !string.IsNullOrWhiteSpace(SinglePostOrMessageUrl); @@ -951,10 +987,7 @@ public partial class MainWindowViewModel( _ = SelectUsersFromListAsync(); } - partial void OnSinglePostOrMessageUrlChanged(string value) - { - SinglePostOrMessageUrlError = string.Empty; - } + partial void OnSinglePostOrMessageUrlChanged(string value) => SinglePostOrMessageUrlError = string.Empty; partial void OnFfmpegPathChanged(string value) { @@ -1024,6 +1057,11 @@ public partial class MainWindowViewModel( return; } + if (!await EnsureMissingCdmWarningConfirmedAsync()) + { + return; + } + IsDownloading = true; _workCancellationSource?.Dispose(); _workCancellationSource = new CancellationTokenSource(); @@ -1134,6 +1172,55 @@ public partial class MainWindowViewModel( } } + private async Task EnsureMissingCdmWarningConfirmedAsync() + { + bool hasMissingCdmKeys = _startupResult.ClientIdBlobMissing || _startupResult.DevicePrivateKeyMissing; + if (!hasMissingCdmKeys || configService.CurrentConfig.HideMissingCdmKeysWarning) + { + return true; + } + + List missingFiles = []; + if (_startupResult.ClientIdBlobMissing) + { + missingFiles.Add("device_client_id_blob"); + } + + if (_startupResult.DevicePrivateKeyMissing) + { + missingFiles.Add("device_private_key"); + } + + string missingSummary = string.Join(" and ", missingFiles); + MissingCdmWarningMessage = + $"Missing CDM key file(s): {missingSummary}\n\n" + + "CDM keys are recommended to decrypt DRM-protected videos. Without these keys, the application will use a fallback online decryption service, which may be slower and less reliable.\n\n" + + "You can hide this warning in the future by enabling \"Hide Missing CDM Keys Warning\" in the configuration settings.\n\n"; + + _missingCdmWarningCompletionSource = + new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + IsMissingCdmWarningModalOpen = true; + + bool confirmed; + try + { + confirmed = await _missingCdmWarningCompletionSource.Task; + } + finally + { + _missingCdmWarningCompletionSource = null; + IsMissingCdmWarningModalOpen = false; + } + + if (!confirmed) + { + StatusMessage = "Download canceled."; + AppendLog("Download canceled after missing CDM keys warning."); + } + + return confirmed; + } + private static bool TryParseSinglePostOrMessageUrl( string url, out SingleDownloadRequest request, @@ -1254,7 +1341,7 @@ public partial class MainWindowViewModel( if (_startupResult.ClientIdBlobMissing || _startupResult.DevicePrivateKeyMissing) { AppendLog( - "Widevine device files are missing. Fallback decrypt services will be used for DRM protected videos."); + "CDM key files are missing. Fallback decrypt services will be used for DRM protected videos."); } return true; @@ -1472,9 +1559,10 @@ public partial class MainWindowViewModel( List orderedCategories = []; foreach (IGrouping group in grouped) { - IEnumerable orderedFields = group.Key is "File Naming" or "Download Behavior" - ? group.OrderBy(field => GetFieldOrder(group.Key, field.PropertyName)) - : group.OrderBy(field => field.DisplayName); + IEnumerable orderedFields = + group.Key is "File Naming" or "Download Behavior" or "Appearance" + ? group.OrderBy(field => GetFieldOrder(group.Key, field.PropertyName)) + : group.OrderBy(field => field.DisplayName); ConfigCategoryViewModel category = new(group.Key, orderedFields); orderedCategories.Add(category); @@ -1534,6 +1622,16 @@ public partial class MainWindowViewModel( }; } + if (categoryName == "Appearance") + { + return propertyName switch + { + nameof(Config.Theme) => 0, + nameof(Config.HideMissingCdmKeysWarning) => 1, + _ => 100 + }; + } + return 0; } @@ -1882,10 +1980,7 @@ public partial class MainWindowViewModel( return JsonConvert.DeserializeObject(json) ?? new Config(); } - private static void EnforceGuiOnlyConfigValues(Config config) - { - config.ShowScrapeSize = false; - } + private static void EnforceGuiOnlyConfigValues(Config config) => config.ShowScrapeSize = false; private static bool IsHiddenConfigField(string propertyName) => propertyName is nameof(Config.NonInteractiveMode) @@ -1962,6 +2057,7 @@ public partial class MainWindowViewModel( nameof(Config.DownloadLimitInMbPerSec) => "Performance", nameof(Config.Theme) => "Appearance", + nameof(Config.HideMissingCdmKeysWarning) => "Appearance", nameof(Config.LoggingLevel) => "Logging", diff --git a/OF DL.Gui/Views/MainWindow.axaml b/OF DL.Gui/Views/MainWindow.axaml index 1d269d5..6d3c4e1 100644 --- a/OF DL.Gui/Views/MainWindow.axaml +++ b/OF DL.Gui/Views/MainWindow.axaml @@ -1442,5 +1442,43 @@ + + + + + + + + + +