Files
Libation/Source/AudibleUtilities/Extensions.cs
T
Cursor Agentandrmcrackan bc72aad30c refactor(metadata): flatten Audible's HTML at the API boundary, not on read
Parsing HTML belongs before the data reaches the data layer, not in it. Move the
flattening to AudibleUtilities, where the rest of the Item translation lives, so
Book.Description holds plain text and DataLayer no longer references
HtmlAgilityPack.

Import now updates a description as well as creating one, which it never did
before: books imported while descriptions were stored as raw HTML are still
holding markup, and a re-scan is what clears it. A scan that reports no summary
leaves the stored one alone.

This also cleans up the CSV/Excel export, which was emitting raw HTML too.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-26 13:15:02 +00:00

45 lines
1.7 KiB
C#

using AudibleApi.Common;
using System;
using System.Linq;
namespace AudibleUtilities;
public static class Extensions
{
extension(Item item)
{
/// <summary>
/// Determines when your audible plus or free book will expire from your library
/// plan.IsAyce from underlying AudibleApi project determines the plans to look at, first plan found is used.
/// In some cases current date is later than end date so exclude.
/// </summary>
/// <returns>The DateTime that this title will become unavailable, otherwise null</returns>
public DateTime? GetExpirationDate()
=> item.Plans
?.Where(p => p.IsAyce)
.Select(p => p.EndDate)
.FirstOrDefault(end => end.HasValue && end.Value.Year is not (2099 or 9999) && end.Value.LocalDateTime >= DateTime.Now)
?.DateTime;
/// <summary>
/// The book's summary with Audible's HTML markup flattened away, ready to be stored and used
/// as-is. See <see cref="HtmlText.ToPlainText"/>.
/// </summary>
public string PlainTextDescription()
=> HtmlText.ToPlainText(item.Description);
/// <summary>
/// The publisher's copyright line, e.g. "&#169;2024 Bentley Little (P)2025 Journalstone".
/// Only present when the product_details response group was requested, and often null anyway.
/// </summary>
/// <remarks><see cref="Item.Copyright"/> is typed as <see cref="object"/> because Audible has
/// never been observed returning anything but a string or null there. Anything else is
/// discarded rather than stringified, so a shape we do not understand cannot reach a file tag.
/// </remarks>
public string? CopyrightString()
=> item.Copyright is string copyright && !string.IsNullOrWhiteSpace(copyright)
? copyright.Trim()
: null;
}
}