* upgrade api. lay the groundwork for supporting external logins. When complete, this will be v6.0

* replace AudibleApiActions with ApiExtended
This commit is contained in:
Robert McRackan 2021-09-17 15:30:06 -04:00
parent 5a1303c33a
commit 51020ef99e
9 changed files with 49 additions and 43 deletions

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<Version>5.7.5.1</Version> <Version>6.0.0.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -136,11 +136,11 @@ namespace ApplicationServices
{ {
var callback = loginCallbackFactoryFunc(account); var callback = loginCallbackFactoryFunc(account);
// get APIs in serial, esp b/c of logins // get APIs in serial b/c of logins
var api = await AudibleApiActions.GetApiAsync(callback, account); var apiExtended = await ApiExtended.CreateAsync(callback, account);
// add scanAccountAsync as a TASK: do not await // add scanAccountAsync as a TASK: do not await
tasks.Add(scanAccountAsync(api, account)); tasks.Add(scanAccountAsync(apiExtended, account));
} }
// import library in parallel // import library in parallel
@ -149,7 +149,7 @@ namespace ApplicationServices
return importItems; return importItems;
} }
private static async Task<List<ImportItem>> scanAccountAsync(Api api, Account account) private static async Task<List<ImportItem>> scanAccountAsync(ApiExtended apiExtended, Account account)
{ {
ArgumentValidator.EnsureNotNull(account, nameof(account)); ArgumentValidator.EnsureNotNull(account, nameof(account));
@ -160,7 +160,7 @@ namespace ApplicationServices
logTime($"pre scanAccountAsync {account.AccountName}"); logTime($"pre scanAccountAsync {account.AccountName}");
var dtoItems = await AudibleApiActions.GetLibraryValidatedAsync(api, LibraryResponseGroups); var dtoItems = await apiExtended.GetLibraryValidatedAsync(LibraryResponseGroups);
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}"); logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");

View File

@ -84,16 +84,16 @@ namespace FileLiberator
{ {
validate(libraryBook); validate(libraryBook);
var api = await InternalUtilities.AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale); var apiExtended = await InternalUtilities.ApiExtended.CreateAsync(libraryBook.Account, libraryBook.Book.Locale);
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId); var contentLic = await apiExtended.Api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
var aaxcDecryptDlLic = new DownloadLicense var aaxcDecryptDlLic = new DownloadLicense
( (
contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl, contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl,
contentLic?.Voucher?.Key, contentLic?.Voucher?.Key,
contentLic?.Voucher?.Iv, contentLic?.Voucher?.Iv,
Resources.UserAgent Resources.USER_AGENT
); );
if (Configuration.Instance.AllowLibationFixup) if (Configuration.Instance.AllowLibationFixup)

View File

@ -59,8 +59,8 @@ namespace FileLiberator
private async Task<string> downloadPdfAsync(LibraryBook libraryBook, string proposedDownloadFilePath) private async Task<string> downloadPdfAsync(LibraryBook libraryBook, string proposedDownloadFilePath)
{ {
var api = await GetApiAsync(libraryBook); var apiExtended = await GetApiExtendedAsync(libraryBook);
var downloadUrl = await api.GetPdfDownloadLinkAsync(libraryBook.Book.AudibleProductId); var downloadUrl = await apiExtended.Api.GetPdfDownloadLinkAsync(libraryBook.Book.AudibleProductId);
var client = new HttpClient(); var client = new HttpClient();
var actualDownloadedFilePath = await PerformDownloadAsync( var actualDownloadedFilePath = await PerformDownloadAsync(

View File

@ -40,8 +40,8 @@ namespace FileLiberator
} }
} }
protected static Task<AudibleApi.Api> GetApiAsync(LibraryBook libraryBook) protected static Task<InternalUtilities.ApiExtended> GetApiExtendedAsync(LibraryBook libraryBook)
=> InternalUtilities.AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale); => InternalUtilities.ApiExtended.CreateAsync(libraryBook.Account, libraryBook.Book.Locale);
protected async Task<string> PerformDownloadAsync(string proposedDownloadFilePath, Func<Progress<DownloadProgress>, Task<string>> func) protected async Task<string> PerformDownloadAsync(string proposedDownloadFilePath, Func<Progress<DownloadProgress>, Task<string>> func)
{ {

View File

@ -10,36 +10,42 @@ using Polly.Retry;
namespace InternalUtilities namespace InternalUtilities
{ {
public static class AudibleApiActions
{
/// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary> /// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary>
public static Task<Api> GetApiAsync(string username, string localeName, ILoginCallback loginCallback = null) public class ApiExtended
{
public Api Api { get; private set; }
private ApiExtended(Api api) => Api = api;
public static async Task<ApiExtended> CreateAsync(string username, string localeName)
{ {
Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new
{ {
Username = username.ToMask(), Username = username.ToMask(),
LocaleName = localeName, LocaleName = localeName,
}); });
return EzApiCreator.GetApiAsync(
var api = await EzApiCreator.GetApiAsync(
Localization.Get(localeName), Localization.Get(localeName),
AudibleApiStorage.AccountsSettingsFile, AudibleApiStorage.AccountsSettingsFile,
AudibleApiStorage.GetIdentityTokensJsonPath(username, localeName), AudibleApiStorage.GetIdentityTokensJsonPath(username, localeName));
loginCallback); return new ApiExtended(api);
} }
/// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary> public static async Task<ApiExtended> CreateAsync(ILoginCallback loginCallback, Account account)
public static Task<Api> GetApiAsync(ILoginCallback loginCallback, Account account)
{ {
Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new
{ {
Account = account?.MaskedLogEntry ?? "[null]", Account = account?.MaskedLogEntry ?? "[null]",
LocaleName = account?.Locale?.Name LocaleName = account?.Locale?.Name
}); });
return EzApiCreator.GetApiAsync(
var api = await EzApiCreator.GetApiAsync(
account.Locale, account.Locale,
AudibleApiStorage.AccountsSettingsFile, AudibleApiStorage.AccountsSettingsFile,
account.GetIdentityTokensJsonPath(), loginCallback,
loginCallback); account.GetIdentityTokensJsonPath());
return new ApiExtended(api);
} }
private static AsyncRetryPolicy policy { get; } private static AsyncRetryPolicy policy { get; }
@ -47,16 +53,16 @@ namespace InternalUtilities
// 2 retries == 3 total // 2 retries == 3 total
.RetryAsync(2); .RetryAsync(2);
public static Task<List<Item>> GetLibraryValidatedAsync(Api api, LibraryOptions.ResponseGroupOptions responseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS) public Task<List<Item>> GetLibraryValidatedAsync(LibraryOptions.ResponseGroupOptions responseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS)
{ {
// bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or tokens are refreshed // bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or tokens are refreshed
// worse, this 1st dummy call doesn't seem to help: // worse, this 1st dummy call doesn't seem to help:
// var page = await api.GetLibraryAsync(new AudibleApi.LibraryOptions { NumberOfResultPerPage = 1, PageNumber = 1, PurchasedAfter = DateTime.Now.AddYears(-20), ResponseGroups = AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS }); // var page = await api.GetLibraryAsync(new AudibleApi.LibraryOptions { NumberOfResultPerPage = 1, PageNumber = 1, PurchasedAfter = DateTime.Now.AddYears(-20), ResponseGroups = AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS });
// i don't want to incur the cost of making a full dummy call every time because it fails sometimes // i don't want to incur the cost of making a full dummy call every time because it fails sometimes
return policy.ExecuteAsync(() => getItemsAsync(api, responseGroups)); return policy.ExecuteAsync(() => getItemsAsync(responseGroups));
} }
private static async Task<List<Item>> getItemsAsync(Api api, LibraryOptions.ResponseGroupOptions responseGroups) private async Task<List<Item>> getItemsAsync(LibraryOptions.ResponseGroupOptions responseGroups)
{ {
var items = new List<Item>(); var items = new List<Item>();
#if DEBUG #if DEBUG
@ -68,12 +74,12 @@ namespace InternalUtilities
//} //}
#endif #endif
if (!items.Any()) if (!items.Any())
items = await api.GetAllLibraryItemsAsync(responseGroups); items = await Api.GetAllLibraryItemsAsync(responseGroups);
#if DEBUG #if DEBUG
//System.IO.File.WriteAllText("library.json", AudibleApi.Common.Converter.ToJson(items)); //System.IO.File.WriteAllText("library.json", AudibleApi.Common.Converter.ToJson(items));
#endif #endif
await manageEpisodesAsync(api, items); await manageEpisodesAsync(items);
var validators = new List<IValidator>(); var validators = new List<IValidator>();
validators.AddRange(getValidators()); validators.AddRange(getValidators());
@ -88,7 +94,7 @@ namespace InternalUtilities
} }
#region episodes and podcasts #region episodes and podcasts
private static async Task manageEpisodesAsync(Api api, List<Item> items) private async Task manageEpisodesAsync(List<Item> items)
{ {
// add podcasts and episodes to list. If fail, don't let it de-rail the rest of the import // add podcasts and episodes to list. If fail, don't let it de-rail the rest of the import
try try
@ -110,7 +116,7 @@ namespace InternalUtilities
items.RemoveAll(i => i.IsEpisodes); items.RemoveAll(i => i.IsEpisodes);
// add children // add children
var children = await getEpisodesAsync(api, parents); var children = await getEpisodesAsync(parents);
Serilog.Log.Logger.Information($"{children.Count} episodes of shows/podcasts found"); Serilog.Log.Logger.Information($"{children.Count} episodes of shows/podcasts found");
items.AddRange(children); items.AddRange(children);
} }
@ -120,13 +126,13 @@ namespace InternalUtilities
} }
} }
private static async Task<List<Item>> getEpisodesAsync(Api api, List<Item> parents) private async Task<List<Item>> getEpisodesAsync(List<Item> parents)
{ {
var results = new List<Item>(); var results = new List<Item>();
foreach (var parent in parents) foreach (var parent in parents)
{ {
var children = await getEpisodeChildrenAsync(api, parent); var children = await getEpisodeChildrenAsync(parent);
foreach (var child in children) foreach (var child in children)
{ {
@ -159,7 +165,7 @@ namespace InternalUtilities
return results; return results;
} }
private static async Task<List<Item>> getEpisodeChildrenAsync(Api api, Item parent) private async Task<List<Item>> getEpisodeChildrenAsync(Item parent)
{ {
var childrenIds = parent.Relationships var childrenIds = parent.Relationships
.Where(r => r.RelationshipToProduct == RelationshipToProduct.Child && r.RelationshipType == RelationshipType.Episode) .Where(r => r.RelationshipToProduct == RelationshipToProduct.Child && r.RelationshipType == RelationshipType.Episode)
@ -180,7 +186,7 @@ namespace InternalUtilities
List<Item> childrenBatch; List<Item> childrenBatch;
try try
{ {
childrenBatch = await api.GetCatalogProductsAsync(idBatch, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS); childrenBatch = await Api.GetCatalogProductsAsync(idBatch, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
#if DEBUG #if DEBUG
//var childrenBatchDebug = childrenBatch.Select(i => i.ToJson()).Aggregate((a, b) => $"{a}\r\n\r\n{b}"); //var childrenBatchDebug = childrenBatch.Select(i => i.ToJson()).Aggregate((a, b) => $"{a}\r\n\r\n{b}");
//System.IO.File.WriteAllText($"children of {parent.Asin}.json", childrenBatchDebug); //System.IO.File.WriteAllText($"children of {parent.Asin}.json", childrenBatchDebug);
@ -222,7 +228,7 @@ namespace InternalUtilities
return results; return results;
} }
#endregion #endregion
private static List<IValidator> getValidators() private static List<IValidator> getValidators()
{ {

View File

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AudibleApi" Version="1.2.1.2" /> <PackageReference Include="AudibleApi" Version="2.0.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,8 +1,7 @@
using AudibleApi; using System;
using AudibleApi;
using InternalUtilities; using InternalUtilities;
using LibationWinForms.Dialogs.Login; using LibationWinForms.Dialogs.Login;
using System;
using System.Linq;
namespace LibationWinForms.Login namespace LibationWinForms.Login
{ {

View File

@ -183,7 +183,8 @@ namespace LibationWinForms
authorize.DeregisterAsync(identity.ExistingAccessToken, identity.Cookies.ToKeyValuePair()).GetAwaiter().GetResult(); authorize.DeregisterAsync(identity.ExistingAccessToken, identity.Cookies.ToKeyValuePair()).GetAwaiter().GetResult();
identity.Invalidate(); identity.Invalidate();
var api = AudibleApiActions.GetApiAsync(new Login.WinformResponder(account), account).GetAwaiter().GetResult(); // re-registers device
ApiExtended.CreateAsync(new Login.WinformResponder(account), account).GetAwaiter().GetResult();
} }
catch catch
{ {