diff --git a/Source/ApplicationServices/LibraryCommands.cs b/Source/ApplicationServices/LibraryCommands.cs
index e567224a..3a85d63a 100644
--- a/Source/ApplicationServices/LibraryCommands.cs
+++ b/Source/ApplicationServices/LibraryCommands.cs
@@ -82,25 +82,12 @@ public static class LibraryCommands
return inactive;
}
- catch (AudibleApi.Authentication.LoginFailedException lfEx)
- {
- lfEx.SaveFiles(Configuration.Instance.LibationFiles.Location);
-
- // nuget Serilog.Exceptions would automatically log custom properties
- // However, it comes with a scary warning when used with EntityFrameworkCore which I'm not yet ready to implement:
- // https://github.com/RehanSaeed/Serilog.Exceptions
- // work-around: use 3rd param. don't just put exception object in 3rd param -- info overload: stack trace, etc
- Log.Logger.Error(lfEx, "Error scanning library. Login failed. {@DebugInfo}", new
- {
- lfEx.RequestUrl,
- ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode,
- ResponseStatusCodeDesc = lfEx.ResponseStatusCode,
- lfEx.ResponseInputFields,
- lfEx.ResponseBodyFilePaths
- });
- throw;
- }
- catch (Exception ex)
+ catch (Exception lfEx) when (AuthenticationExceptionHelper.IsAuthenticationFailure(lfEx))
+ {
+ Log.Logger.Error(lfEx, "Error scanning library. Authentication failed.");
+ throw;
+ }
+ catch (Exception ex)
{
Log.Logger.Error(ex, "Error scanning library");
throw;
@@ -188,25 +175,12 @@ public static class LibraryCommands
return (totalCount, newCount);
}
- catch (AudibleApi.Authentication.LoginFailedException lfEx)
- {
- lfEx.SaveFiles(Configuration.Instance.LibationFiles.Location);
-
- // nuget Serilog.Exceptions would automatically log custom properties
- // However, it comes with a scary warning when used with EntityFrameworkCore which I'm not yet ready to implement:
- // https://github.com/RehanSaeed/Serilog.Exceptions
- // work-around: use 3rd param. don't just put exception object in 3rd param -- info overload: stack trace, etc
- Log.Logger.Error(lfEx, "Error importing library. Login failed. {@DebugInfo}", new
- {
- lfEx.RequestUrl,
- ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode,
- ResponseStatusCodeDesc = lfEx.ResponseStatusCode,
- lfEx.ResponseInputFields,
- lfEx.ResponseBodyFilePaths
- });
- throw;
- }
- catch (Exception ex)
+ catch (Exception lfEx) when (AuthenticationExceptionHelper.IsAuthenticationFailure(lfEx))
+ {
+ Log.Logger.Error(lfEx, "Error scanning library. Authentication failed.");
+ throw;
+ }
+ catch (Exception ex)
{
Log.Logger.Error(ex, "Error importing library");
throw;
diff --git a/Source/AudibleUtilities/ApiExtended.cs b/Source/AudibleUtilities/ApiExtended.cs
index 90a89d0f..398f7ebe 100644
--- a/Source/AudibleUtilities/ApiExtended.cs
+++ b/Source/AudibleUtilities/ApiExtended.cs
@@ -5,13 +5,8 @@ using LibationFileManager;
using Newtonsoft.Json.Linq;
using Polly;
using Polly.Retry;
-using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
-using System.Threading;
using System.Threading.Channels;
-using System.Threading.Tasks;
namespace AudibleUtilities;
diff --git a/Source/AudibleUtilities/AuthenticationExceptionHelper.cs b/Source/AudibleUtilities/AuthenticationExceptionHelper.cs
index a86dcdfe..af9d66ea 100644
--- a/Source/AudibleUtilities/AuthenticationExceptionHelper.cs
+++ b/Source/AudibleUtilities/AuthenticationExceptionHelper.cs
@@ -1,5 +1,3 @@
-using AudibleApi.Authentication;
-
namespace AudibleUtilities;
public static class AuthenticationExceptionHelper
@@ -14,7 +12,7 @@ public static class AuthenticationExceptionHelper
for (var current = ex; current is not null; current = current.InnerException)
{
- if (current is AuthenticationRequiredException or LoginFailedException)
+ if (current is AuthenticationRequiredException)
return true;
if (current is InvalidOperationException { Message: var message }
diff --git a/Source/AudibleUtilities/HtmlText.cs b/Source/AudibleUtilities/HtmlText.cs
index 007b65c0..aa21d5e6 100644
--- a/Source/AudibleUtilities/HtmlText.cs
+++ b/Source/AudibleUtilities/HtmlText.cs
@@ -1,5 +1,4 @@
-using HtmlAgilityPack;
-using System.Linq;
+using System.Net;
using System.Text.RegularExpressions;
namespace AudibleUtilities;
@@ -11,35 +10,39 @@ namespace AudibleUtilities;
///
public static partial class HtmlText
{
- /// Joins the block-level runs. A single newline matches what
- /// Audible itself embeds in the description tags of its .aaxc files.
- public static string ToPlainText(string? html, string paragraphSeparator = "\n")
- {
- if (string.IsNullOrWhiteSpace(html))
- return "";
+ /// Joins the block-level runs. A single newline matches what
+ /// Audible itself embeds in the description tags of its .aaxc files.
+ public static string ToPlainText(string? html, string paragraphSeparator = "\n")
+ {
+ if (string.IsNullOrWhiteSpace(html))
+ return "";
- // Not every summary is marked up. Running the unmarked ones through the parser risks a stray
- // '<' swallowing the rest of the sentence, and buys nothing.
- if (!html.Contains('<'))
- return (HtmlEntity.DeEntitize(html) ?? html).Trim();
+ // Not every summary is marked up. Running the unmarked ones through the parser risks a stray
+ // '<' swallowing the rest of the sentence, and buys nothing.
+ if (!html.Contains('<'))
+ return (WebUtility.HtmlDecode(html) ?? html).Trim();
- var doc = new HtmlDocument();
- doc.LoadHtml(BlockBoundary().Replace(html, "\n"));
+ // Replace block-level boundaries with newlines, then strip all remaining tags.
+ var stripped = StripTags().Replace(BlockBoundary().Replace(html, "\n"), "");
- var text = HtmlEntity.DeEntitize(doc.DocumentNode.InnerText) ?? doc.DocumentNode.InnerText;
+ var text = WebUtility.HtmlDecode(stripped) ?? stripped;
- var paragraphs = text
- .Replace("\r\n", "\n")
- .Replace('\r', '\n')
- .Split('\n')
- .Select(line => line.Trim())
- .Where(line => line.Length > 0);
+ var paragraphs = text
+ .Replace("\r\n", "\n")
+ .Replace('\r', '\n')
+ .Split('\n')
+ .Select(line => line.Trim())
+ .Where(line => line.Length > 0);
- return string.Join(paragraphSeparator, paragraphs);
- }
+ return string.Join(paragraphSeparator, paragraphs);
+ }
- /// Every tag that ends a run of text. A line break counts as a paragraph break because
- /// Audible's summaries mark up paragraphs and nothing finer.
- [GeneratedRegex(@"<\s*br\s*/?\s*>|<\s*/\s*(?:p|div|li|tr|blockquote|h[1-6])\s*>", RegexOptions.IgnoreCase)]
- private static partial Regex BlockBoundary();
+ /// Every tag that ends a run of text. A line break counts as a paragraph break because
+ /// Audible's summaries mark up paragraphs and nothing finer.
+ [GeneratedRegex(@"<\s*br\s*/?\s*>|<\s*/\s*(?:p|div|li|tr|blockquote|h[1-6])\s*>", RegexOptions.IgnoreCase)]
+ private static partial Regex BlockBoundary();
+
+ /// Matches any HTML/XML tag.
+ [GeneratedRegex(@"<[^>]+>")]
+ private static partial Regex StripTags();
}
diff --git a/Source/HangoverAvalonia/HangoverAvalonia.csproj b/Source/HangoverAvalonia/HangoverAvalonia.csproj
index 075ee45a..dd9e794a 100644
--- a/Source/HangoverAvalonia/HangoverAvalonia.csproj
+++ b/Source/HangoverAvalonia/HangoverAvalonia.csproj
@@ -79,6 +79,9 @@
+
+
+
diff --git a/Source/HangoverAvalonia/ViewModels/MainVM.cs b/Source/HangoverAvalonia/ViewModels/MainVM.cs
index 4df87d05..bc83863c 100644
--- a/Source/HangoverAvalonia/ViewModels/MainVM.cs
+++ b/Source/HangoverAvalonia/ViewModels/MainVM.cs
@@ -5,7 +5,7 @@ namespace HangoverAvalonia.ViewModels;
public partial class MainVM : ViewModelBase
{
- public Func>? ConfirmDbMutationAsync { get; set; }
+ public Func> ConfirmDbMutationAsync { get; set; }
public MainVM()
{
diff --git a/Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs b/Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs
index 9cc5adcc..75dba7e2 100644
--- a/Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs
+++ b/Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs
@@ -77,7 +77,7 @@ public class TrashBinViewModel : ViewModelBase, IDisposable
item.IsChecked = false;
}
- public Func>? ConfirmDbMutationAsync { get; set; }
+ public Func> ConfirmDbMutationAsync { get; set; }
public async Task RestoreCheckedAsync()
{
diff --git a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs
deleted file mode 100644
index 8772f1e5..00000000
--- a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using AudibleApi;
-using System.Threading.Tasks;
-
-namespace LibationAvalonia.Dialogs.Login;
-
-public class AvaloniaLoginCallback : ILoginCallback
-{
- public string DeviceName { get; } = "Libation";
-
- public Task Get2faCodeAsync(string prompt) => throw new System.NotSupportedException();
- public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
- => throw new System.NotSupportedException();
- public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
- => throw new System.NotSupportedException();
- public Task<(string email, string password)> GetLoginAsync()
- => throw new System.NotSupportedException();
- public Task ShowApprovalNeededAsync() => throw new System.NotSupportedException();
-}
\ No newline at end of file
diff --git a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs
index 8e9289a4..d436621c 100644
--- a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs
+++ b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs
@@ -14,19 +14,17 @@ namespace LibationAvalonia.Dialogs.Login;
public class AvaloniaLoginChoiceEager : ILoginChoiceEager
{
- public ILoginCallback LoginCallback { get; } = new AvaloniaLoginCallback();
-
private readonly Account _account;
public AvaloniaLoginChoiceEager(Account account)
{
- _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
+ _account = ArgumentValidator.EnsureNotNull(account, nameof(account));
}
- public async Task StartAsync(ChoiceIn choiceIn)
+ public async Task StartAsync(ChoiceIn choiceIn)
=> await Dispatcher.UIThread.InvokeAsync(() => StartAsyncInternal(choiceIn));
- private async Task StartAsyncInternal(ChoiceIn choiceIn)
+ private async Task StartAsyncInternal(ChoiceIn choiceIn)
{
try
{
@@ -35,7 +33,7 @@ public class AvaloniaLoginChoiceEager : ILoginChoiceEager
{
try
{
- if (await BrowserLoginAsync(choiceIn) is ChoiceOut external)
+ if (await BrowserLoginAsync(choiceIn) is string external)
return external;
}
catch (Exception ex) when (WebView2LoginErrorMessage.IsWebView2SignInInfrastructureFailure(ex))
@@ -65,17 +63,17 @@ public class AvaloniaLoginChoiceEager : ILoginChoiceEager
var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
return await externalDialog.ShowDialogAsync() is DialogResult.OK
- ? ChoiceOut.External(externalDialog.ResponseUrl)
+ ? externalDialog.ResponseUrl
: null;
}
- private async Task BrowserLoginAsync(ChoiceIn shoiceIn)
+ private async Task BrowserLoginAsync(ChoiceIn shoiceIn)
{
// Time-of-use: setting can change before Show (e.g. settings saved) — never open NativeWebView when disabled.
if (!Configuration.Instance.UseWebView)
return null;
- TaskCompletionSource tcs = new();
+ TaskCompletionSource tcs = new();
NativeWebDialog dialog = new()
{
@@ -89,7 +87,7 @@ public class AvaloniaLoginChoiceEager : ILoginChoiceEager
{
if (e.Request?.AbsolutePath.StartsWith("/ap/maplanding") is true)
{
- tcs.TrySetResult(ChoiceOut.External(e.Request.ToString()));
+ tcs.TrySetResult(e.Request.ToString());
dialog.Close();
}
};
diff --git a/Source/LibationCli/Options/LoginExternalOptions.cs b/Source/LibationCli/Options/LoginExternalOptions.cs
index b865b581..42d7ee30 100644
--- a/Source/LibationCli/Options/LoginExternalOptions.cs
+++ b/Source/LibationCli/Options/LoginExternalOptions.cs
@@ -103,8 +103,7 @@ internal class LoginExternalOptions : OptionsBase
?? AudibleApi.Locale.Empty;
}
- internal static bool IsEmptyLocale(AudibleApi.Locale locale)
- => string.IsNullOrEmpty(locale.CountryCode);
+ internal static bool IsEmptyLocale(Locale locale) => string.IsNullOrEmpty(locale.CountryCode);
private sealed class CliLoginExternal : ILoginExternal
{
@@ -112,8 +111,6 @@ internal class LoginExternalOptions : OptionsBase
public CliLoginExternal(string? presetResponseUrl) => _presetResponseUrl = presetResponseUrl;
- public string DeviceName => "Libation";
-
public string GetResponseUrl(string loginUrl, CookieCollection signInCookies)
{
if (!string.IsNullOrEmpty(_presetResponseUrl))
diff --git a/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs b/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs
deleted file mode 100644
index 23d588a1..00000000
--- a/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using AudibleApi;
-using System.Threading.Tasks;
-
-namespace LibationWinForms.Login;
-
-public class WinformLoginCallback : ILoginCallback
-{
- public string DeviceName { get; } = "Libation";
-
- public Task Get2faCodeAsync(string prompt) => throw new System.NotSupportedException();
- public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
- => throw new System.NotSupportedException();
- public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
- => throw new System.NotSupportedException();
- public Task<(string email, string password)> GetLoginAsync()
- => throw new System.NotSupportedException();
- public Task ShowApprovalNeededAsync() => throw new System.NotSupportedException();
-}
\ No newline at end of file
diff --git a/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs b/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs
index 58321754..686e0503 100644
--- a/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs
+++ b/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs
@@ -1,5 +1,6 @@
using AudibleApi;
using AudibleUtilities;
+using Dinah.Core;
using LibationFileManager;
using LibationUiBase;
using LibationWinForms.Dialogs.Login;
@@ -11,20 +12,18 @@ namespace LibationWinForms.Login;
public class WinformLoginChoiceEager : ILoginChoiceEager
{
- public ILoginCallback LoginCallback { get; } = new WinformLoginCallback();
-
private Account _account { get; }
private Control Owner { get; }
public WinformLoginChoiceEager(Account account, Control owner)
{
- _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
- Owner = Dinah.Core.ArgumentValidator.EnsureNotNull(owner, nameof(owner));
+ _account = ArgumentValidator.EnsureNotNull(account, nameof(account));
+ Owner = ArgumentValidator.EnsureNotNull(owner, nameof(owner));
}
- public Task StartAsync(ChoiceIn choiceIn)
+ public Task StartAsync(ChoiceIn choiceIn)
=> Owner.Invoke(() => StartAsyncInternal(choiceIn));
- private Task StartAsyncInternal(ChoiceIn choiceIn)
+ private Task StartAsyncInternal(ChoiceIn choiceIn)
{
if (Configuration.Instance.UseWebView && Environment.OSVersion.Version.Major >= 10)
{
@@ -35,7 +34,7 @@ public class WinformLoginChoiceEager : ILoginChoiceEager
{
using var weblogin = new WebLoginDialog(_account.AccountId, choiceIn);
if (ShowDialog(weblogin))
- return Task.FromResult(ChoiceOut.External(weblogin.ResponseUrl));
+ return Task.FromResult(weblogin.ResponseUrl);
}
}
catch (Exception ex)
@@ -49,7 +48,7 @@ public class WinformLoginChoiceEager : ILoginChoiceEager
using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
return Task.FromResult(
ShowDialog(externalDialog)
- ? ChoiceOut.External(externalDialog.ResponseUrl)
+ ? externalDialog.ResponseUrl
: null);
}
diff --git a/Source/_Tests/AudibleUtilities.Tests/AuthenticationRequiredExceptionLogSafetyTests.cs b/Source/_Tests/AudibleUtilities.Tests/AuthenticationRequiredExceptionLogSafetyTests.cs
index 3fc921b9..7771ad49 100644
--- a/Source/_Tests/AudibleUtilities.Tests/AuthenticationRequiredExceptionLogSafetyTests.cs
+++ b/Source/_Tests/AudibleUtilities.Tests/AuthenticationRequiredExceptionLogSafetyTests.cs
@@ -1,7 +1,6 @@
using AudibleApi;
using AudibleApi.Authorization;
using AudibleApi.Cryptography;
-using AudibleUtilities;
using Dinah.Core.Security;
using LibationFileManager;
using Microsoft.VisualStudio.TestTools.UnitTesting;
diff --git a/Source/_Tests/LibationUiBase.Tests/TokenStorageSettingsUiTests.cs b/Source/_Tests/LibationUiBase.Tests/TokenStorageSettingsUiTests.cs
index 2753c86e..d9a18cfd 100644
--- a/Source/_Tests/LibationUiBase.Tests/TokenStorageSettingsUiTests.cs
+++ b/Source/_Tests/LibationUiBase.Tests/TokenStorageSettingsUiTests.cs
@@ -59,6 +59,7 @@ public class TokenStorageSettingsUiTests
{
StringAssert.Contains(TokenStorageSettingsUi.ExportConfirmBody, "password");
StringAssert.Contains(TokenStorageSettingsUi.ExportButtonToolTip, "Docker");
- Assert.AreEqual("Export encryption key...", TokenStorageSettingsUi.ExportButtonText);
+ var temp = TokenStorageSettingsUi.ExportButtonText;
+ Assert.AreEqual("Export encryption key...", temp);
}
}