diff --git a/Documentation/Advanced.md b/Documentation/Advanced.md index b4463f2b..7a7b9ec7 100644 --- a/Documentation/Advanced.md +++ b/Documentation/Advanced.md @@ -101,6 +101,11 @@ libationcli liberate -p libationcli liberate --force libationcli liberate -f ``` +#### Liberate using a license file from the `get-license` command +```console +libationcli liberate --license /path/to/license.lic +libationcli liberate --license - < /path/to/license.lic +``` #### List Libation Settings ```console libationcli get-setting @@ -153,7 +158,7 @@ foreach($q in $Qualities){ foreach($x in $xHE_AAC){ $license = ./libationcli get-license $asin --override FileDownloadQuality=$q --override Request_xHE_AAC=$x echo $($license | ConvertFrom-Json).ContentMetadata.content_reference - echo $license | ./libationcli liberate --force + echo $license | ./libationcli liberate --force --license - } } ``` diff --git a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs index 86e7d4fb..8ec087bd 100644 --- a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs +++ b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs @@ -3,6 +3,7 @@ using AudibleUtilities; using Avalonia.Controls; using Avalonia.Platform; using Avalonia.Threading; +using Dinah.Core; using LibationFileManager; using LibationUiBase.Forms; using System; @@ -67,8 +68,17 @@ namespace LibationAvalonia.Dialogs.Login { if (dialog.TryGetCookieManager() is NativeWebViewCookieManager cookieManager) { - foreach (System.Net.Cookie c in shoiceIn.SignInCookies) - cookieManager.AddOrUpdateCookie(c); + foreach (System.Net.Cookie c in shoiceIn.SignInCookies ?? []) + { + try + { + cookieManager.AddOrUpdateCookie(c); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, $"Failed to set cookie {c.Name} for domain {c.Domain}"); + } + } } //Set the source only after loading cookies dialog.Source = new Uri(shoiceIn.LoginUrl); diff --git a/Source/LibationCli/Options/LiberateOptions.cs b/Source/LibationCli/Options/LiberateOptions.cs index fa327eee..3a1ef9db 100644 --- a/Source/LibationCli/Options/LiberateOptions.cs +++ b/Source/LibationCli/Options/LiberateOptions.cs @@ -23,6 +23,10 @@ namespace LibationCli [Option(shortName: 'f', longName: "force", Required = false, Default = false, HelpText = "Force the book to re-download")] public bool Force { get; set; } + + + [Option(shortName: 'l', longName: "license", Required = false, Default = null, HelpText = "A license file from the get-license command. Either a file path or dash ('-') to read from standard input.")] + public string? LicenseInput { get; set; } protected override async Task ProcessAsync() { @@ -32,40 +36,9 @@ namespace LibationCli return; } - if (Console.IsInputRedirected) + if (LicenseInput is string licenseInput) { - Console.WriteLine("Reading license file from standard input."); - using var reader = new StreamReader(Console.OpenStandardInput()); - var stdIn = await reader.ReadToEndAsync(); - try - { - - var jsonSettings = new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore, - Converters = [new StringEnumConverter(), new ByteArrayHexConverter()] - }; - var licenseInfo = JsonConvert.DeserializeObject(stdIn, jsonSettings); - - if (licenseInfo?.ContentMetadata?.ContentReference?.Asin is not string asin) - { - Console.Error.WriteLine("Error: License file is missing ASIN information."); - return; - } - - if (DbContexts.GetLibraryBook_Flat_NoTracking(asin) is not LibraryBook libraryBook) - { - Console.Error.WriteLine($"Book not found with asin={asin}"); - return; - } - - SetDownloadedStatus(libraryBook); - await ProcessOneAsync(GetProcessable(licenseInfo), libraryBook, true); - } - catch - { - Console.Error.WriteLine("Error: Failed to read license file from standard input. Please ensure the input is a valid license file in JSON format."); - } + await LiberateFromLicense(licenseInput); } else { @@ -73,6 +46,87 @@ namespace LibationCli } } + private async Task LiberateFromLicense(string licPath) + { + var licenseInfo = licPath is "-" ? ReadLicenseFromStdIn() + : ReadLicenseFromFile(licPath); + + if (licenseInfo is null) + return; + + if (licenseInfo?.ContentMetadata?.ContentReference?.Asin is not string asin) + { + Console.Error.WriteLine("Error: License file is missing ASIN information."); + return; + } + + if (DbContexts.GetLibraryBook_Flat_NoTracking(asin) is not LibraryBook libraryBook) + { + Console.Error.WriteLine($"Book not found with asin={asin}"); + return; + } + + SetDownloadedStatus(libraryBook); + await ProcessOneAsync(GetProcessable(licenseInfo), libraryBook, true); + } + + private static DownloadOptions.LicenseInfo? ReadLicenseFromFile(string licFile) + { + if (!File.Exists(licFile)) + { + Console.Error.WriteLine("File does not exist: " + licFile); + return null; + } + + Console.WriteLine("Reading license from file."); + try + { + var serializer = CreateLicenseInfoSerializer(); + using var reader = new JsonTextReader(new StreamReader(licFile)); + return serializer.Deserialize(reader); + } + catch (Exception ex) + { + Serilog.Log.Error(ex, "Failed to read license file: {@LicenseFile}", licFile); + Console.Error.WriteLine("Error: Failed to read license file. Please ensure the file is a valid license file in JSON format."); + } + return null; + } + + private static DownloadOptions.LicenseInfo? ReadLicenseFromStdIn() + { + if (!Console.IsInputRedirected) + { + Console.Error.WriteLine("Ther is nothing in standard input to read."); + return null; + } + + Console.WriteLine("Reading license from standard input."); + try + { + var serializer = CreateLicenseInfoSerializer(); + using var reader = new JsonTextReader(new StreamReader(Console.OpenStandardInput())); + return serializer.Deserialize(reader); + } + catch (Exception ex) + { + Serilog.Log.Error(ex, "Failed to read license from standard input"); + Console.Error.WriteLine("Error: Failed to read license file from standard input. Please ensure the input is a valid license file in JSON format."); + } + return null; + } + + private static JsonSerializer CreateLicenseInfoSerializer() + { + var jsonSettings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + Converters = [new StringEnumConverter(), new ByteArrayHexConverter()] + }; + + return JsonSerializer.Create(jsonSettings); + } + private Processable GetProcessable(DownloadOptions.LicenseInfo? licenseInfo = null) => PdfOnly ? CreateProcessable() : CreateBackupBook(licenseInfo); diff --git a/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs b/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs index b54b3d5c..c5b45c7c 100644 --- a/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs +++ b/Source/LibationWinForms/Dialogs/Login/WebLoginDialog.cs @@ -41,7 +41,14 @@ namespace LibationWinForms.Login //Load init cookies foreach (System.Net.Cookie cookie in choiceIn.SignInCookies ?? []) { - webView.CoreWebView2.CookieManager.AddOrUpdateCookie(webView.CoreWebView2.CookieManager.CreateCookieWithSystemNetCookie(cookie)); + try + { + webView.CoreWebView2.CookieManager.AddOrUpdateCookie(webView.CoreWebView2.CookieManager.CreateCookieWithSystemNetCookie(cookie)); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, $"Failed to set cookie {cookie.Name} for domain {cookie.Domain}"); + } } webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;