* 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:
parent
5a1303c33a
commit
51020ef99e
@ -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>
|
||||||
|
|||||||
@ -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}");
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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>
|
||||||
|
public class ApiExtended
|
||||||
{
|
{
|
||||||
/// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary>
|
public Api Api { get; private set; }
|
||||||
public static Task<Api> GetApiAsync(string username, string localeName, ILoginCallback loginCallback = null)
|
|
||||||
|
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(
|
|
||||||
Localization.Get(localeName),
|
var api = await EzApiCreator.GetApiAsync(
|
||||||
AudibleApiStorage.AccountsSettingsFile,
|
Localization.Get(localeName),
|
||||||
AudibleApiStorage.GetIdentityTokensJsonPath(username, localeName),
|
AudibleApiStorage.AccountsSettingsFile,
|
||||||
loginCallback);
|
AudibleApiStorage.GetIdentityTokensJsonPath(username, localeName));
|
||||||
|
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()
|
||||||
{
|
{
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user