From 9fc5a7d834bc43e9934e455e7b1d50c1256ddd42 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 24 Nov 2025 11:30:56 -0700 Subject: [PATCH] Add liberate option `--license` to use license file. Added instructions for liberating using a license file. --- Documentation/Advanced.md | 7 +- Source/LibationCli/Options/LiberateOptions.cs | 120 +++++++++++++----- 2 files changed, 93 insertions(+), 34 deletions(-) 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/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);