mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-31 18:08:32 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96ffa619ec | ||
|
|
de1147ac1b | ||
|
|
926a7a1148 | ||
|
|
51020ef99e | ||
|
|
5a1303c33a | ||
|
|
a0e2d78b9b | ||
|
|
6b711190c3 | ||
|
|
b4a6342513 | ||
|
|
988b137d67 | ||
|
|
dae9c9c9b6 | ||
|
|
420b7529c6 | ||
|
|
4cf999c84d | ||
|
|
8fe3896d76 | ||
|
|
adcba34560 | ||
|
|
8e09d7e617 | ||
|
|
197b50e3ac | ||
|
|
ac2114e270 |
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AAXClean" Version="0.1.8" />
|
||||
<PackageReference Include="AAXClean" Version="0.1.9" />
|
||||
<PackageReference Include="Dinah.Core" Version="1.1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace AaxDecrypter
|
||||
if (File.Exists(outputFileName))
|
||||
FileExt.SafeDelete(outputFileName);
|
||||
|
||||
FileStream outFile = File.OpenWrite(outputFileName);
|
||||
FileStream outFile = File.Open(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
|
||||
|
||||
aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Version>5.7.0.1</Version>
|
||||
<Version>6.0.2.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace AppScaffolding
|
||||
//
|
||||
|
||||
Migrations.migrate_to_v5_2_0__post_config(config);
|
||||
Migrations.migrate_to_v5_7_1(config);
|
||||
}
|
||||
|
||||
/// <summary>Initialize logging. Run after migration</summary>
|
||||
@@ -321,5 +322,12 @@ namespace AppScaffolding
|
||||
config.DecryptToLossy = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
// add config.BadBook
|
||||
public static void migrate_to_v5_7_1(Configuration config)
|
||||
{
|
||||
if (!config.Exists(nameof(config.BadBook)))
|
||||
config.BadBook = Configuration.BadBookAction.Ask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace ApplicationServices
|
||||
{
|
||||
private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
|
||||
|
||||
public static async Task<List<LibraryBook>> FindInactiveBooks(Func<Account, ILoginCallback> loginCallbackFactoryFunc, List<LibraryBook> existingLibrary, params Account[] accounts)
|
||||
public static async Task<List<LibraryBook>> FindInactiveBooks(Func<Account, Task<ApiExtended>> apiExtendedfunc, List<LibraryBook> existingLibrary, params Account[] accounts)
|
||||
{
|
||||
logRestart();
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace ApplicationServices
|
||||
try
|
||||
{
|
||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||
var libraryItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts);
|
||||
var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts);
|
||||
logTime($"post {nameof(scanAccountsAsync)} all");
|
||||
|
||||
var totalCount = libraryItems.Count;
|
||||
@@ -75,7 +75,7 @@ namespace ApplicationServices
|
||||
}
|
||||
|
||||
#region FULL LIBRARY scan and import
|
||||
public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, params Account[] accounts)
|
||||
public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, params Account[] accounts)
|
||||
{
|
||||
logRestart();
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace ApplicationServices
|
||||
try
|
||||
{
|
||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||
var importItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts);
|
||||
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts);
|
||||
logTime($"post {nameof(scanAccountsAsync)} all");
|
||||
|
||||
var totalCount = importItems.Count;
|
||||
@@ -129,18 +129,16 @@ namespace ApplicationServices
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, Account[] accounts)
|
||||
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts)
|
||||
{
|
||||
var tasks = new List<Task<List<ImportItem>>>();
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var callback = loginCallbackFactoryFunc(account);
|
||||
|
||||
// get APIs in serial, esp b/c of logins
|
||||
var api = await AudibleApiActions.GetApiAsync(callback, account);
|
||||
// get APIs in serial b/c of logins. do NOT move inside of parallel (Task.WhenAll)
|
||||
var apiExtended = await apiExtendedfunc(account);
|
||||
|
||||
// add scanAccountAsync as a TASK: do not await
|
||||
tasks.Add(scanAccountAsync(api, account));
|
||||
tasks.Add(scanAccountAsync(apiExtended, account));
|
||||
}
|
||||
|
||||
// import library in parallel
|
||||
@@ -149,7 +147,7 @@ namespace ApplicationServices
|
||||
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));
|
||||
|
||||
@@ -160,7 +158,7 @@ namespace ApplicationServices
|
||||
|
||||
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}");
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="1.0.5.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9">
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="1.0.5.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.10">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.10">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace DataLayer
|
||||
|
||||
// simple assigns
|
||||
Title = title.Trim();
|
||||
Description = description.Trim();
|
||||
Description = description?.Trim();
|
||||
LengthInMinutes = lengthInMinutes;
|
||||
ContentType = contentType;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using AudibleApi.Common;
|
||||
|
||||
namespace InternalUtilities
|
||||
namespace DtoImporterService
|
||||
{
|
||||
public class ImportItem
|
||||
{
|
||||
@@ -84,8 +84,7 @@ namespace FileLiberator
|
||||
{
|
||||
validate(libraryBook);
|
||||
|
||||
var api = await InternalUtilities.AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale);
|
||||
|
||||
var api = await libraryBook.GetApiAsync();
|
||||
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
|
||||
|
||||
var aaxcDecryptDlLic = new DownloadLicense
|
||||
@@ -93,7 +92,7 @@ namespace FileLiberator
|
||||
contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl,
|
||||
contentLic?.Voucher?.Key,
|
||||
contentLic?.Voucher?.Iv,
|
||||
Resources.UserAgent
|
||||
Resources.USER_AGENT
|
||||
);
|
||||
|
||||
if (Configuration.Instance.AllowLibationFixup)
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace FileLiberator
|
||||
|
||||
private async Task<string> downloadPdfAsync(LibraryBook libraryBook, string proposedDownloadFilePath)
|
||||
{
|
||||
var api = await GetApiAsync(libraryBook);
|
||||
var api = await libraryBook.GetApiAsync();
|
||||
var downloadUrl = await api.GetPdfDownloadLinkAsync(libraryBook.Book.AudibleProductId);
|
||||
|
||||
var client = new HttpClient();
|
||||
|
||||
@@ -40,9 +40,6 @@ namespace FileLiberator
|
||||
}
|
||||
}
|
||||
|
||||
protected static Task<AudibleApi.Api> GetApiAsync(LibraryBook libraryBook)
|
||||
=> InternalUtilities.AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale);
|
||||
|
||||
protected async Task<string> PerformDownloadAsync(string proposedDownloadFilePath, Func<Progress<DownloadProgress>, Task<string>> func)
|
||||
{
|
||||
var progress = new Progress<DownloadProgress>();
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.ErrorHandling;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
public static class LoggerUtilities
|
||||
public static class UtilityExtensions
|
||||
{
|
||||
public static (string id, string title, string locale, string account) LogFriendly(this LibraryBook libraryBook)
|
||||
=> (
|
||||
@@ -19,5 +16,11 @@ namespace FileLiberator
|
||||
locale: libraryBook.Book.Locale,
|
||||
account: libraryBook.Account.ToMask()
|
||||
);
|
||||
|
||||
public static async Task<AudibleApi.Api> GetApiAsync(this LibraryBook libraryBook)
|
||||
{
|
||||
var apiExtended = await InternalUtilities.ApiExtended.CreateAsync(libraryBook.Account, libraryBook.Book.Locale);
|
||||
return apiExtended.Api;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,29 @@ namespace FileManager
|
||||
set => persistentDictionary.SetNonString(nameof(DecryptToLossy), value);
|
||||
}
|
||||
|
||||
public enum BadBookAction
|
||||
{
|
||||
[Description("Ask each time what action to take.")]
|
||||
Ask = 0,
|
||||
[Description("Stop processing books.")]
|
||||
Abort = 1,
|
||||
[Description("Retry book later. Skip for now. Continue processing books.")]
|
||||
Retry = 2,
|
||||
[Description("Permanently ignore book. Continue processing books. Do not try book again.")]
|
||||
Ignore = 3
|
||||
}
|
||||
|
||||
[Description("When liberating books and there is an error, Libation should:")]
|
||||
public BadBookAction BadBook
|
||||
{
|
||||
get
|
||||
{
|
||||
var badBookStr = persistentDictionary.GetString(nameof(BadBook));
|
||||
return Enum.TryParse<BadBookAction>(badBookStr, out var badBookEnum) ? badBookEnum : BadBookAction.Ask;
|
||||
}
|
||||
set => persistentDictionary.SetString(nameof(BadBook), value.ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region known directories
|
||||
@@ -178,16 +201,8 @@ namespace FileManager
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var logLevelStr = persistentDictionary.GetStringFromJsonPath("Serilog", "MinimumLevel");
|
||||
var logLevelEnum = Enum<LogEventLevel>.Parse(logLevelStr);
|
||||
return logLevelEnum;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return LogEventLevel.Information;
|
||||
}
|
||||
var logLevelStr = persistentDictionary.GetStringFromJsonPath("Serilog", "MinimumLevel");
|
||||
return Enum.TryParse<LogEventLevel>(logLevelStr, out var logLevelEnum) ? logLevelEnum : LogEventLevel.Information;
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -219,7 +234,6 @@ namespace FileManager
|
||||
#endregion
|
||||
|
||||
#region LibationFiles
|
||||
|
||||
private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "appsettings.json");
|
||||
private const string LIBATION_FILES_KEY = "LibationFiles";
|
||||
|
||||
|
||||
@@ -10,36 +10,95 @@ using Polly.Retry;
|
||||
|
||||
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 static Task<Api> GetApiAsync(string username, string localeName, ILoginCallback loginCallback = null)
|
||||
public Api Api { get; private set; }
|
||||
|
||||
private ApiExtended(Api api) => Api = api;
|
||||
|
||||
/// <summary>Get api from existing tokens else login with 'eager' choice. External browser url is provided. Response can be external browser login or continuing with native api callbacks.</summary>
|
||||
public static async Task<ApiExtended> CreateAsync(Account account, ILoginChoiceEager loginChoiceEager)
|
||||
{
|
||||
Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new
|
||||
Serilog.Log.Logger.Information("{@DebugInfo}", new
|
||||
{
|
||||
LoginType = nameof(ILoginChoiceEager),
|
||||
Account = account?.MaskedLogEntry ?? "[null]",
|
||||
LocaleName = account?.Locale?.Name
|
||||
});
|
||||
|
||||
var api = await EzApiCreator.GetApiAsync(
|
||||
loginChoiceEager,
|
||||
account.Locale,
|
||||
AudibleApiStorage.AccountsSettingsFile,
|
||||
account.GetIdentityTokensJsonPath());
|
||||
return new ApiExtended(api);
|
||||
}
|
||||
|
||||
/// <summary>Get api from existing tokens else login with native api callbacks.</summary>
|
||||
public static async Task<ApiExtended> CreateAsync(Account account, ILoginCallback loginCallback)
|
||||
{
|
||||
Serilog.Log.Logger.Information("{@DebugInfo}", new
|
||||
{
|
||||
LoginType = nameof(ILoginCallback),
|
||||
Account = account?.MaskedLogEntry ?? "[null]",
|
||||
LocaleName = account?.Locale?.Name
|
||||
});
|
||||
|
||||
var api = await EzApiCreator.GetApiAsync(
|
||||
loginCallback,
|
||||
account.Locale,
|
||||
AudibleApiStorage.AccountsSettingsFile,
|
||||
account.GetIdentityTokensJsonPath());
|
||||
return new ApiExtended(api);
|
||||
}
|
||||
|
||||
/// <summary>Get api from existing tokens else login with external browser</summary>
|
||||
public static async Task<ApiExtended> CreateAsync(Account account, ILoginExternal loginExternal)
|
||||
{
|
||||
Serilog.Log.Logger.Information("{@DebugInfo}", new
|
||||
{
|
||||
LoginType = nameof(ILoginExternal),
|
||||
Account = account?.MaskedLogEntry ?? "[null]",
|
||||
LocaleName = account?.Locale?.Name
|
||||
});
|
||||
|
||||
var api = await EzApiCreator.GetApiAsync(
|
||||
loginExternal,
|
||||
account.Locale,
|
||||
AudibleApiStorage.AccountsSettingsFile,
|
||||
account.GetIdentityTokensJsonPath());
|
||||
return new ApiExtended(api);
|
||||
}
|
||||
|
||||
/// <summary>Get api from existing tokens. Assumes you have valid login tokens. Else exception</summary>
|
||||
public static async Task<ApiExtended> CreateAsync(Account account)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
ArgumentValidator.EnsureNotNull(account.Locale, nameof(account.Locale));
|
||||
|
||||
Serilog.Log.Logger.Information("{@DebugInfo}", new
|
||||
{
|
||||
AccountMaskedLogEntry = account.MaskedLogEntry
|
||||
});
|
||||
|
||||
return await CreateAsync(account.AccountId, account.Locale.Name);
|
||||
}
|
||||
|
||||
/// <summary>Get api from existing tokens. Assumes you have valid login tokens. Else exception</summary>
|
||||
public static async Task<ApiExtended> CreateAsync(string username, string localeName)
|
||||
{
|
||||
Serilog.Log.Logger.Information("{@DebugInfo}", new
|
||||
{
|
||||
Username = username.ToMask(),
|
||||
LocaleName = localeName,
|
||||
});
|
||||
return EzApiCreator.GetApiAsync(
|
||||
Localization.Get(localeName),
|
||||
AudibleApiStorage.AccountsSettingsFile,
|
||||
AudibleApiStorage.GetIdentityTokensJsonPath(username, localeName),
|
||||
loginCallback);
|
||||
}
|
||||
|
||||
/// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary>
|
||||
public static Task<Api> GetApiAsync(ILoginCallback loginCallback, Account account)
|
||||
{
|
||||
Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new
|
||||
{
|
||||
Account = account?.MaskedLogEntry ?? "[null]",
|
||||
LocaleName = account?.Locale?.Name
|
||||
});
|
||||
return EzApiCreator.GetApiAsync(
|
||||
account.Locale,
|
||||
AudibleApiStorage.AccountsSettingsFile,
|
||||
account.GetIdentityTokensJsonPath(),
|
||||
loginCallback);
|
||||
var api = await EzApiCreator.GetApiAsync(
|
||||
Localization.Get(localeName),
|
||||
AudibleApiStorage.AccountsSettingsFile,
|
||||
AudibleApiStorage.GetIdentityTokensJsonPath(username, localeName));
|
||||
return new ApiExtended(api);
|
||||
}
|
||||
|
||||
private static AsyncRetryPolicy policy { get; }
|
||||
@@ -47,16 +106,16 @@ namespace InternalUtilities
|
||||
// 2 retries == 3 total
|
||||
.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
|
||||
// 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 });
|
||||
// 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>();
|
||||
#if DEBUG
|
||||
@@ -68,12 +127,12 @@ namespace InternalUtilities
|
||||
//}
|
||||
#endif
|
||||
if (!items.Any())
|
||||
items = await api.GetAllLibraryItemsAsync(responseGroups);
|
||||
items = await Api.GetAllLibraryItemsAsync(responseGroups);
|
||||
#if DEBUG
|
||||
//System.IO.File.WriteAllText("library.json", AudibleApi.Common.Converter.ToJson(items));
|
||||
#endif
|
||||
|
||||
await manageEpisodesAsync(api, items);
|
||||
await manageEpisodesAsync(items);
|
||||
|
||||
var validators = new List<IValidator>();
|
||||
validators.AddRange(getValidators());
|
||||
@@ -88,7 +147,7 @@ namespace InternalUtilities
|
||||
}
|
||||
|
||||
#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
|
||||
try
|
||||
@@ -110,7 +169,7 @@ namespace InternalUtilities
|
||||
items.RemoveAll(i => i.IsEpisodes);
|
||||
|
||||
// add children
|
||||
var children = await getEpisodesAsync(api, parents);
|
||||
var children = await getEpisodesAsync(parents);
|
||||
Serilog.Log.Logger.Information($"{children.Count} episodes of shows/podcasts found");
|
||||
items.AddRange(children);
|
||||
}
|
||||
@@ -120,13 +179,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>();
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
var children = await getEpisodeChildrenAsync(api, parent);
|
||||
var children = await getEpisodeChildrenAsync(parent);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
@@ -159,7 +218,7 @@ namespace InternalUtilities
|
||||
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
|
||||
.Where(r => r.RelationshipToProduct == RelationshipToProduct.Child && r.RelationshipType == RelationshipType.Episode)
|
||||
@@ -180,7 +239,7 @@ namespace InternalUtilities
|
||||
List<Item> childrenBatch;
|
||||
try
|
||||
{
|
||||
childrenBatch = await api.GetCatalogProductsAsync(idBatch, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
|
||||
childrenBatch = await Api.GetCatalogProductsAsync(idBatch, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
|
||||
#if DEBUG
|
||||
//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);
|
||||
@@ -222,7 +281,7 @@ namespace InternalUtilities
|
||||
|
||||
return results;
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
private static List<IValidator> getValidators()
|
||||
{
|
||||
@@ -33,8 +33,10 @@ namespace InternalUtilities
|
||||
|
||||
if (items.Any(i => string.IsNullOrWhiteSpace(i.ProductId)))
|
||||
exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.ProductId)}", nameof(items)));
|
||||
if (items.Any(i => string.IsNullOrWhiteSpace(i.Title)))
|
||||
exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.Title)}", nameof(items)));
|
||||
|
||||
// this can happen with podcast episodes
|
||||
foreach (var i in items.Where(i => string.IsNullOrWhiteSpace(i.Title)))
|
||||
i.Title = "[blank title]";
|
||||
|
||||
return exceptions;
|
||||
}
|
||||
@@ -76,9 +78,9 @@ namespace InternalUtilities
|
||||
|
||||
var distinct = items.GetSeriesDistinct();
|
||||
if (distinct.Any(s => s.SeriesId is null))
|
||||
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApi.Common.Series.SeriesId)}", nameof(items)));
|
||||
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesId)}", nameof(items)));
|
||||
if (distinct.Any(s => s.SeriesName is null))
|
||||
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApi.Common.Series.SeriesName)}", nameof(items)));
|
||||
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesName)}", nameof(items)));
|
||||
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="1.2.1.2" />
|
||||
<PackageReference Include="AudibleApi" Version="2.1.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -36,6 +36,9 @@ EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationSearchEngine", "LibationSearchEngine\LibationSearchEngine.csproj", "{2E1F5DB4-40CC-4804-A893-5DCE0193E598}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationWinForms", "LibationWinForms\LibationWinForms.csproj", "{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{428163C3-D558-4914-B570-A92069521877} = {428163C3-D558-4914-B570-A92069521877}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DtoImporterService", "DtoImporterService\DtoImporterService.csproj", "{401865F5-1942-4713-B230-04544C0A97B0}"
|
||||
EndProject
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
When LibationWinForms and LibationCli output to the same dir, LibationCli must build before LibationWinForms
|
||||
|
||||
VS > rt-clik solution > Project Build Order...
|
||||
Dependencies [tab]
|
||||
Projects: LibationWinForms
|
||||
manually check LibationCli
|
||||
-->
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\LibationWinForms\bin\Debug</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -32,9 +32,7 @@ namespace LibationCli
|
||||
: $"Scanning Audible library: {_accounts.Length} accounts. This may take a few minutes per account.";
|
||||
Console.WriteLine(intro);
|
||||
|
||||
var (TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.ImportAccountAsync(
|
||||
(account) => null,
|
||||
_accounts);
|
||||
var (TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.ImportAccountAsync((a) => ApiExtended.CreateAsync(a), _accounts);
|
||||
|
||||
Console.WriteLine("Scan complete.");
|
||||
Console.WriteLine($"Total processed: {TotalBooksProcessed}\r\nNew: {NewBooksAdded}");
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace LibationCli
|
||||
string input = null;
|
||||
|
||||
//input = " export --help";
|
||||
//input = " scan cupidneedsglasses";
|
||||
//input = " scan rmcrackan";
|
||||
//input = " liberate ";
|
||||
|
||||
|
||||
|
||||
@@ -196,8 +196,6 @@ namespace LibationWinForms.BookLiberation
|
||||
|
||||
protected async Task<bool> ProcessOneAsync(LibraryBook libraryBook, bool validate)
|
||||
{
|
||||
string logMessage;
|
||||
|
||||
try
|
||||
{
|
||||
var statusHandler = await Processable.ProcessSingleAsync(libraryBook, validate);
|
||||
@@ -207,18 +205,28 @@ namespace LibationWinForms.BookLiberation
|
||||
|
||||
foreach (var errorMessage in statusHandler.Errors)
|
||||
LogMe.Error(errorMessage);
|
||||
|
||||
logMessage = statusHandler.Errors.Aggregate((a, b) => $"{a}\r\n{b}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMe.Error(ex);
|
||||
|
||||
logMessage = ex.Message + "\r\n|\r\n" + ex.StackTrace;
|
||||
}
|
||||
|
||||
return showRetry(libraryBook);
|
||||
}
|
||||
|
||||
private bool showRetry(LibraryBook libraryBook)
|
||||
{
|
||||
LogMe.Error("ERROR. All books have not been processed. Most recent book: processing failed");
|
||||
|
||||
DialogResult? dialogResult = FileManager.Configuration.Instance.BadBook switch
|
||||
{
|
||||
FileManager.Configuration.BadBookAction.Abort => DialogResult.Abort,
|
||||
FileManager.Configuration.BadBookAction.Retry => DialogResult.Retry,
|
||||
FileManager.Configuration.BadBookAction.Ignore => DialogResult.Ignore,
|
||||
FileManager.Configuration.BadBookAction.Ask => null,
|
||||
_ => null
|
||||
};
|
||||
|
||||
string details;
|
||||
try
|
||||
{
|
||||
@@ -238,7 +246,8 @@ $@" Title: {libraryBook.Book.Title}
|
||||
details = "[Error retrieving details]";
|
||||
}
|
||||
|
||||
var dialogResult = MessageBox.Show(string.Format(SkipDialogText, details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton);
|
||||
// if null then ask user
|
||||
dialogResult ??= MessageBox.Show(string.Format(SkipDialogText + "\r\n\r\nSee Settings to avoid this box in the future.", details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton);
|
||||
|
||||
if (dialogResult == DialogResult.Abort)
|
||||
return false;
|
||||
@@ -288,7 +297,7 @@ An error occurred while trying to process this book. Skip this book permanently?
|
||||
An error occurred while trying to process this book.
|
||||
{0}
|
||||
|
||||
- ABORT: stop processing books.
|
||||
- ABORT: Stop processing books.
|
||||
|
||||
- RETRY: retry this book later. Just skip it for now. Continue processing books. (Will try this book again later.)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
try
|
||||
{
|
||||
(TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.ImportAccountAsync((account) => new WinformResponder(account), _accounts);
|
||||
(TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.ImportAccountAsync((account) => ApiExtended.CreateAsync(account, new WinformLoginChoiceEager(account)), _accounts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
partial class AudibleLoginDialog
|
||||
partial class LoginCallbackDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
@@ -38,25 +38,29 @@
|
||||
// passwordLbl
|
||||
//
|
||||
this.passwordLbl.AutoSize = true;
|
||||
this.passwordLbl.Location = new System.Drawing.Point(12, 41);
|
||||
this.passwordLbl.Location = new System.Drawing.Point(14, 47);
|
||||
this.passwordLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.passwordLbl.Name = "passwordLbl";
|
||||
this.passwordLbl.Size = new System.Drawing.Size(53, 13);
|
||||
this.passwordLbl.Size = new System.Drawing.Size(57, 15);
|
||||
this.passwordLbl.TabIndex = 2;
|
||||
this.passwordLbl.Text = "Password";
|
||||
//
|
||||
// passwordTb
|
||||
//
|
||||
this.passwordTb.Location = new System.Drawing.Point(71, 38);
|
||||
this.passwordTb.Location = new System.Drawing.Point(83, 44);
|
||||
this.passwordTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.passwordTb.Name = "passwordTb";
|
||||
this.passwordTb.PasswordChar = '*';
|
||||
this.passwordTb.Size = new System.Drawing.Size(200, 20);
|
||||
this.passwordTb.Size = new System.Drawing.Size(233, 23);
|
||||
this.passwordTb.TabIndex = 3;
|
||||
//
|
||||
// submitBtn
|
||||
//
|
||||
this.submitBtn.Location = new System.Drawing.Point(196, 64);
|
||||
this.submitBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.submitBtn.Location = new System.Drawing.Point(229, 74);
|
||||
this.submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.submitBtn.Name = "submitBtn";
|
||||
this.submitBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.submitBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.submitBtn.TabIndex = 4;
|
||||
this.submitBtn.Text = "Submit";
|
||||
this.submitBtn.UseVisualStyleBackColor = true;
|
||||
@@ -65,36 +69,39 @@
|
||||
// localeLbl
|
||||
//
|
||||
this.localeLbl.AutoSize = true;
|
||||
this.localeLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.localeLbl.Location = new System.Drawing.Point(14, 10);
|
||||
this.localeLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.localeLbl.Name = "localeLbl";
|
||||
this.localeLbl.Size = new System.Drawing.Size(59, 13);
|
||||
this.localeLbl.Size = new System.Drawing.Size(61, 15);
|
||||
this.localeLbl.TabIndex = 0;
|
||||
this.localeLbl.Text = "Locale: {0}";
|
||||
//
|
||||
// usernameLbl
|
||||
//
|
||||
this.usernameLbl.AutoSize = true;
|
||||
this.usernameLbl.Location = new System.Drawing.Point(12, 22);
|
||||
this.usernameLbl.Location = new System.Drawing.Point(14, 25);
|
||||
this.usernameLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.usernameLbl.Name = "usernameLbl";
|
||||
this.usernameLbl.Size = new System.Drawing.Size(75, 13);
|
||||
this.usernameLbl.Size = new System.Drawing.Size(80, 15);
|
||||
this.usernameLbl.TabIndex = 1;
|
||||
this.usernameLbl.Text = "Username: {0}";
|
||||
//
|
||||
// AudibleLoginDialog
|
||||
// LoginCallbackDialog
|
||||
//
|
||||
this.AcceptButton = this.submitBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(283, 99);
|
||||
this.ClientSize = new System.Drawing.Size(330, 114);
|
||||
this.Controls.Add(this.usernameLbl);
|
||||
this.Controls.Add(this.localeLbl);
|
||||
this.Controls.Add(this.submitBtn);
|
||||
this.Controls.Add(this.passwordLbl);
|
||||
this.Controls.Add(this.passwordTb);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "AudibleLoginDialog";
|
||||
this.Name = "LoginCallbackDialog";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Audible Login";
|
||||
@@ -1,28 +1,25 @@
|
||||
using Dinah.Core;
|
||||
using InternalUtilities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public partial class AudibleLoginDialog : Form
|
||||
public partial class LoginCallbackDialog : Form
|
||||
{
|
||||
private string locale { get; }
|
||||
private string accountId { get; }
|
||||
|
||||
public string Email { get; private set; }
|
||||
public string Password { get; private set; }
|
||||
|
||||
public AudibleLoginDialog(Account account)
|
||||
public LoginCallbackDialog(Account account)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
locale = account.Locale.Name;
|
||||
accountId = account.AccountId;
|
||||
|
||||
// do not allow user to change login id here. if they do then jsonpath will fail
|
||||
this.localeLbl.Text = string.Format(this.localeLbl.Text, locale);
|
||||
this.localeLbl.Text = string.Format(this.localeLbl.Text, account.Locale.Name);
|
||||
this.usernameLbl.Text = string.Format(this.usernameLbl.Text, accountId);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
161
LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.Designer.cs
generated
Normal file
161
LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,161 @@
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
partial class LoginChoiceEagerDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.passwordLbl = new System.Windows.Forms.Label();
|
||||
this.passwordTb = new System.Windows.Forms.TextBox();
|
||||
this.submitBtn = new System.Windows.Forms.Button();
|
||||
this.localeLbl = new System.Windows.Forms.Label();
|
||||
this.usernameLbl = new System.Windows.Forms.Label();
|
||||
this.externalLoginLink = new System.Windows.Forms.LinkLabel();
|
||||
this.externalLoginLbl2 = new System.Windows.Forms.Label();
|
||||
this.externalLoginLbl1 = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// passwordLbl
|
||||
//
|
||||
this.passwordLbl.AutoSize = true;
|
||||
this.passwordLbl.Location = new System.Drawing.Point(14, 47);
|
||||
this.passwordLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.passwordLbl.Name = "passwordLbl";
|
||||
this.passwordLbl.Size = new System.Drawing.Size(57, 15);
|
||||
this.passwordLbl.TabIndex = 2;
|
||||
this.passwordLbl.Text = "Password";
|
||||
//
|
||||
// passwordTb
|
||||
//
|
||||
this.passwordTb.Location = new System.Drawing.Point(83, 44);
|
||||
this.passwordTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.passwordTb.Name = "passwordTb";
|
||||
this.passwordTb.PasswordChar = '*';
|
||||
this.passwordTb.Size = new System.Drawing.Size(233, 23);
|
||||
this.passwordTb.TabIndex = 3;
|
||||
//
|
||||
// submitBtn
|
||||
//
|
||||
this.submitBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.submitBtn.Location = new System.Drawing.Point(293, 176);
|
||||
this.submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.submitBtn.Name = "submitBtn";
|
||||
this.submitBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.submitBtn.TabIndex = 7;
|
||||
this.submitBtn.Text = "Submit";
|
||||
this.submitBtn.UseVisualStyleBackColor = true;
|
||||
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
|
||||
//
|
||||
// localeLbl
|
||||
//
|
||||
this.localeLbl.AutoSize = true;
|
||||
this.localeLbl.Location = new System.Drawing.Point(14, 10);
|
||||
this.localeLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.localeLbl.Name = "localeLbl";
|
||||
this.localeLbl.Size = new System.Drawing.Size(61, 15);
|
||||
this.localeLbl.TabIndex = 0;
|
||||
this.localeLbl.Text = "Locale: {0}";
|
||||
//
|
||||
// usernameLbl
|
||||
//
|
||||
this.usernameLbl.AutoSize = true;
|
||||
this.usernameLbl.Location = new System.Drawing.Point(14, 25);
|
||||
this.usernameLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.usernameLbl.Name = "usernameLbl";
|
||||
this.usernameLbl.Size = new System.Drawing.Size(80, 15);
|
||||
this.usernameLbl.TabIndex = 1;
|
||||
this.usernameLbl.Text = "Username: {0}";
|
||||
//
|
||||
// externalLoginLink
|
||||
//
|
||||
this.externalLoginLink.AutoSize = true;
|
||||
this.externalLoginLink.Location = new System.Drawing.Point(14, 93);
|
||||
this.externalLoginLink.Name = "externalLoginLink";
|
||||
this.externalLoginLink.Size = new System.Drawing.Size(107, 15);
|
||||
this.externalLoginLink.TabIndex = 4;
|
||||
this.externalLoginLink.TabStop = true;
|
||||
this.externalLoginLink.Text = "Or click here to log";
|
||||
this.externalLoginLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.externalLoginLink_LinkClicked);
|
||||
//
|
||||
// externalLoginLbl2
|
||||
//
|
||||
this.externalLoginLbl2.AutoSize = true;
|
||||
this.externalLoginLbl2.Location = new System.Drawing.Point(14, 108);
|
||||
this.externalLoginLbl2.Name = "externalLoginLbl2";
|
||||
this.externalLoginLbl2.Size = new System.Drawing.Size(352, 45);
|
||||
this.externalLoginLbl2.TabIndex = 6;
|
||||
this.externalLoginLbl2.Text = "This more advanced login is recommended if you\'re experiencing\r\nerrors logging in" +
|
||||
" the conventional way above or if you\'re not\r\ncomfortable typing your password h" +
|
||||
"ere.";
|
||||
//
|
||||
// externalLoginLbl1
|
||||
//
|
||||
this.externalLoginLbl1.AutoSize = true;
|
||||
this.externalLoginLbl1.Location = new System.Drawing.Point(83, 93);
|
||||
this.externalLoginLbl1.Name = "externalLoginLbl1";
|
||||
this.externalLoginLbl1.Size = new System.Drawing.Size(158, 15);
|
||||
this.externalLoginLbl1.TabIndex = 5;
|
||||
this.externalLoginLbl1.Text = "to log in using your browser.";
|
||||
//
|
||||
// LoginChoiceEagerDialog
|
||||
//
|
||||
this.AcceptButton = this.submitBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(394, 216);
|
||||
this.Controls.Add(this.externalLoginLbl2);
|
||||
this.Controls.Add(this.externalLoginLbl1);
|
||||
this.Controls.Add(this.externalLoginLink);
|
||||
this.Controls.Add(this.usernameLbl);
|
||||
this.Controls.Add(this.localeLbl);
|
||||
this.Controls.Add(this.submitBtn);
|
||||
this.Controls.Add(this.passwordLbl);
|
||||
this.Controls.Add(this.passwordTb);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "LoginChoiceEagerDialog";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Audible Login";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label passwordLbl;
|
||||
private System.Windows.Forms.TextBox passwordTb;
|
||||
private System.Windows.Forms.Button submitBtn;
|
||||
private System.Windows.Forms.Label localeLbl;
|
||||
private System.Windows.Forms.Label usernameLbl;
|
||||
private System.Windows.Forms.LinkLabel externalLoginLink;
|
||||
private System.Windows.Forms.Label externalLoginLbl2;
|
||||
private System.Windows.Forms.Label externalLoginLbl1;
|
||||
}
|
||||
}
|
||||
47
LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.cs
Normal file
47
LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public partial class LoginChoiceEagerDialog : Form
|
||||
{
|
||||
private string accountId { get; }
|
||||
|
||||
public AudibleApi.LoginMethod LoginMethod { get; private set; }
|
||||
|
||||
public string Email { get; private set; }
|
||||
public string Password { get; private set; }
|
||||
|
||||
public LoginChoiceEagerDialog(Account account)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
accountId = account.AccountId;
|
||||
|
||||
// do not allow user to change login id here. if they do then jsonpath will fail
|
||||
this.localeLbl.Text = string.Format(this.localeLbl.Text, account.Locale.Name);
|
||||
this.usernameLbl.Text = string.Format(this.usernameLbl.Text, accountId);
|
||||
}
|
||||
|
||||
private void externalLoginLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
{
|
||||
LoginMethod = AudibleApi.LoginMethod.External;
|
||||
DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void submitBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
Email = accountId;
|
||||
Password = this.passwordTb.Text;
|
||||
|
||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { email = Email?.ToMask(), passwordLength = Password.Length });
|
||||
|
||||
LoginMethod = AudibleApi.LoginMethod.Api;
|
||||
DialogResult = DialogResult.OK;
|
||||
// Close() not needed for AcceptButton
|
||||
}
|
||||
}
|
||||
}
|
||||
60
LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.resx
Normal file
60
LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.resx
Normal file
@@ -0,0 +1,60 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
179
LibationWinForms/Dialogs/Login/LoginExternalDialog.Designer.cs
generated
Normal file
179
LibationWinForms/Dialogs/Login/LoginExternalDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,179 @@
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
partial class LoginExternalDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LoginExternalDialog));
|
||||
this.submitBtn = new System.Windows.Forms.Button();
|
||||
this.localeLbl = new System.Windows.Forms.Label();
|
||||
this.usernameLbl = new System.Windows.Forms.Label();
|
||||
this.loginUrlLbl = new System.Windows.Forms.Label();
|
||||
this.loginUrlTb = new System.Windows.Forms.TextBox();
|
||||
this.copyBtn = new System.Windows.Forms.Button();
|
||||
this.launchBrowserBtn = new System.Windows.Forms.Button();
|
||||
this.instructionsLbl = new System.Windows.Forms.Label();
|
||||
this.responseUrlTb = new System.Windows.Forms.TextBox();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// submitBtn
|
||||
//
|
||||
this.submitBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.submitBtn.Location = new System.Drawing.Point(665, 400);
|
||||
this.submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.submitBtn.Name = "submitBtn";
|
||||
this.submitBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.submitBtn.TabIndex = 8;
|
||||
this.submitBtn.Text = "Submit";
|
||||
this.submitBtn.UseVisualStyleBackColor = true;
|
||||
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
|
||||
//
|
||||
// localeLbl
|
||||
//
|
||||
this.localeLbl.AutoSize = true;
|
||||
this.localeLbl.Location = new System.Drawing.Point(14, 10);
|
||||
this.localeLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.localeLbl.Name = "localeLbl";
|
||||
this.localeLbl.Size = new System.Drawing.Size(61, 15);
|
||||
this.localeLbl.TabIndex = 0;
|
||||
this.localeLbl.Text = "Locale: {0}";
|
||||
//
|
||||
// usernameLbl
|
||||
//
|
||||
this.usernameLbl.AutoSize = true;
|
||||
this.usernameLbl.Location = new System.Drawing.Point(14, 25);
|
||||
this.usernameLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.usernameLbl.Name = "usernameLbl";
|
||||
this.usernameLbl.Size = new System.Drawing.Size(80, 15);
|
||||
this.usernameLbl.TabIndex = 1;
|
||||
this.usernameLbl.Text = "Username: {0}";
|
||||
//
|
||||
// loginUrlLbl
|
||||
//
|
||||
this.loginUrlLbl.AutoSize = true;
|
||||
this.loginUrlLbl.Location = new System.Drawing.Point(14, 61);
|
||||
this.loginUrlLbl.Name = "loginUrlLbl";
|
||||
this.loginUrlLbl.Size = new System.Drawing.Size(180, 15);
|
||||
this.loginUrlLbl.TabIndex = 2;
|
||||
this.loginUrlLbl.Text = "Paste this URL into your browser:";
|
||||
//
|
||||
// loginUrlTb
|
||||
//
|
||||
this.loginUrlTb.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.loginUrlTb.Location = new System.Drawing.Point(14, 79);
|
||||
this.loginUrlTb.Multiline = true;
|
||||
this.loginUrlTb.Name = "loginUrlTb";
|
||||
this.loginUrlTb.ReadOnly = true;
|
||||
this.loginUrlTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.loginUrlTb.Size = new System.Drawing.Size(739, 92);
|
||||
this.loginUrlTb.TabIndex = 3;
|
||||
//
|
||||
// copyBtn
|
||||
//
|
||||
this.copyBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.copyBtn.Location = new System.Drawing.Point(14, 177);
|
||||
this.copyBtn.Name = "copyBtn";
|
||||
this.copyBtn.Size = new System.Drawing.Size(165, 23);
|
||||
this.copyBtn.TabIndex = 4;
|
||||
this.copyBtn.Text = "Copy URL to clipboard";
|
||||
this.copyBtn.UseVisualStyleBackColor = true;
|
||||
this.copyBtn.Click += new System.EventHandler(this.copyBtn_Click);
|
||||
//
|
||||
// launchBrowserBtn
|
||||
//
|
||||
this.launchBrowserBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.launchBrowserBtn.Location = new System.Drawing.Point(588, 177);
|
||||
this.launchBrowserBtn.Name = "launchBrowserBtn";
|
||||
this.launchBrowserBtn.Size = new System.Drawing.Size(165, 23);
|
||||
this.launchBrowserBtn.TabIndex = 5;
|
||||
this.launchBrowserBtn.Text = "Launch in browser";
|
||||
this.launchBrowserBtn.UseVisualStyleBackColor = true;
|
||||
this.launchBrowserBtn.Click += new System.EventHandler(this.launchBrowserBtn_Click);
|
||||
//
|
||||
// instructionsLbl
|
||||
//
|
||||
this.instructionsLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.instructionsLbl.AutoSize = true;
|
||||
this.instructionsLbl.Location = new System.Drawing.Point(14, 203);
|
||||
this.instructionsLbl.Name = "instructionsLbl";
|
||||
this.instructionsLbl.Size = new System.Drawing.Size(436, 90);
|
||||
this.instructionsLbl.TabIndex = 6;
|
||||
this.instructionsLbl.Text = resources.GetString("instructionsLbl.Text");
|
||||
//
|
||||
// responseUrlTb
|
||||
//
|
||||
this.responseUrlTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.responseUrlTb.Location = new System.Drawing.Point(14, 296);
|
||||
this.responseUrlTb.Multiline = true;
|
||||
this.responseUrlTb.Name = "responseUrlTb";
|
||||
this.responseUrlTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.responseUrlTb.Size = new System.Drawing.Size(739, 98);
|
||||
this.responseUrlTb.TabIndex = 7;
|
||||
//
|
||||
// LoginExternalDialog
|
||||
//
|
||||
this.AcceptButton = this.submitBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(766, 440);
|
||||
this.Controls.Add(this.responseUrlTb);
|
||||
this.Controls.Add(this.instructionsLbl);
|
||||
this.Controls.Add(this.launchBrowserBtn);
|
||||
this.Controls.Add(this.copyBtn);
|
||||
this.Controls.Add(this.loginUrlTb);
|
||||
this.Controls.Add(this.loginUrlLbl);
|
||||
this.Controls.Add(this.usernameLbl);
|
||||
this.Controls.Add(this.localeLbl);
|
||||
this.Controls.Add(this.submitBtn);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "LoginExternalDialog";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Audible External Login";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Button submitBtn;
|
||||
private System.Windows.Forms.Label localeLbl;
|
||||
private System.Windows.Forms.Label usernameLbl;
|
||||
private System.Windows.Forms.Label loginUrlLbl;
|
||||
private System.Windows.Forms.TextBox loginUrlTb;
|
||||
private System.Windows.Forms.Button copyBtn;
|
||||
private System.Windows.Forms.Button launchBrowserBtn;
|
||||
private System.Windows.Forms.Label instructionsLbl;
|
||||
private System.Windows.Forms.TextBox responseUrlTb;
|
||||
}
|
||||
}
|
||||
42
LibationWinForms/Dialogs/Login/LoginExternalDialog.cs
Normal file
42
LibationWinForms/Dialogs/Login/LoginExternalDialog.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public partial class LoginExternalDialog : Form
|
||||
{
|
||||
public string ResponseUrl { get; private set; }
|
||||
|
||||
public LoginExternalDialog(Account account, string loginUrl)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// do not allow user to change login id here. if they do then jsonpath will fail
|
||||
this.localeLbl.Text = string.Format(this.localeLbl.Text, account.Locale.Name);
|
||||
this.usernameLbl.Text = string.Format(this.usernameLbl.Text, account.AccountId);
|
||||
|
||||
this.loginUrlTb.Text = loginUrl;
|
||||
}
|
||||
|
||||
private void copyBtn_Click(object sender, EventArgs e) => Clipboard.SetText(this.loginUrlTb.Text);
|
||||
|
||||
private void launchBrowserBtn_Click(object sender, EventArgs e) => Go.To.Url(this.loginUrlTb.Text);
|
||||
|
||||
private void submitBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
ResponseUrl = this.responseUrlTb.Text?.Trim();
|
||||
|
||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { ResponseUrl });
|
||||
if (!Uri.TryCreate(ResponseUrl, UriKind.Absolute, out var result))
|
||||
{
|
||||
MessageBox.Show("Invalid response URL");
|
||||
return;
|
||||
}
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
// Close() not needed for AcceptButton
|
||||
}
|
||||
}
|
||||
}
|
||||
68
LibationWinForms/Dialogs/Login/LoginExternalDialog.resx
Normal file
68
LibationWinForms/Dialogs/Login/LoginExternalDialog.resx
Normal file
@@ -0,0 +1,68 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="instructionsLbl.Text" xml:space="preserve">
|
||||
<value>Login with your Amazon/Audible credentials.
|
||||
After login is complete, your browser will show you an error page similar to:
|
||||
Looking for Something?
|
||||
We're sorry. The Web address you entered is not a functioning page on our site
|
||||
Don't worry -- this is ACTUALLY A SUCCESSFUL LOGIN.
|
||||
Copy the current url from your browser's address bar and paste it here:</value>
|
||||
</data>
|
||||
</root>
|
||||
15
LibationWinForms/Dialogs/Login/WinformLoginBase.cs
Normal file
15
LibationWinForms/Dialogs/Login/WinformLoginBase.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public abstract class WinformLoginBase
|
||||
{
|
||||
/// <returns>True if ShowDialog's DialogResult == OK</returns>
|
||||
protected static bool ShowDialog(System.Windows.Forms.Form dialog)
|
||||
{
|
||||
var result = dialog.ShowDialog();
|
||||
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
|
||||
return result == System.Windows.Forms.DialogResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
using AudibleApi;
|
||||
using System;
|
||||
using AudibleApi;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms.Dialogs.Login;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationWinForms.Login
|
||||
{
|
||||
public class WinformResponder : ILoginCallback
|
||||
public class WinformLoginCallback : WinformLoginBase, ILoginCallback
|
||||
{
|
||||
private Account _account { get; }
|
||||
|
||||
public WinformResponder(Account account)
|
||||
public WinformLoginCallback(Account account)
|
||||
{
|
||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
}
|
||||
@@ -18,7 +17,7 @@ namespace LibationWinForms.Login
|
||||
public string Get2faCode()
|
||||
{
|
||||
using var dialog = new _2faCodeDialog();
|
||||
if (showDialog(dialog))
|
||||
if (ShowDialog(dialog))
|
||||
return dialog.Code;
|
||||
return null;
|
||||
}
|
||||
@@ -26,7 +25,7 @@ namespace LibationWinForms.Login
|
||||
public string GetCaptchaAnswer(byte[] captchaImage)
|
||||
{
|
||||
using var dialog = new CaptchaDialog(captchaImage);
|
||||
if (showDialog(dialog))
|
||||
if (ShowDialog(dialog))
|
||||
return dialog.Answer;
|
||||
return null;
|
||||
}
|
||||
@@ -34,15 +33,15 @@ namespace LibationWinForms.Login
|
||||
public (string name, string value) GetMfaChoice(MfaConfig mfaConfig)
|
||||
{
|
||||
using var dialog = new MfaDialog(mfaConfig);
|
||||
if (showDialog(dialog))
|
||||
if (ShowDialog(dialog))
|
||||
return (dialog.SelectedName, dialog.SelectedValue);
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
public (string email, string password) GetLogin()
|
||||
{
|
||||
using var dialog = new AudibleLoginDialog(_account);
|
||||
if (showDialog(dialog))
|
||||
using var dialog = new LoginCallbackDialog(_account);
|
||||
if (ShowDialog(dialog))
|
||||
return (dialog.Email, dialog.Password);
|
||||
return (null, null);
|
||||
}
|
||||
@@ -50,15 +49,7 @@ namespace LibationWinForms.Login
|
||||
public void ShowApprovalNeeded()
|
||||
{
|
||||
using var dialog = new ApprovalNeededDialog();
|
||||
showDialog(dialog);
|
||||
}
|
||||
|
||||
/// <returns>True if ShowDialog's DialogResult == OK</returns>
|
||||
private static bool showDialog(System.Windows.Forms.Form dialog)
|
||||
{
|
||||
var result = dialog.ShowDialog();
|
||||
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
|
||||
return result == System.Windows.Forms.DialogResult.OK;
|
||||
ShowDialog(dialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs
Normal file
43
LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using AudibleApi;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms.Dialogs.Login;
|
||||
|
||||
namespace LibationWinForms.Login
|
||||
{
|
||||
public class WinformLoginChoiceEager : WinformLoginBase, ILoginChoiceEager
|
||||
{
|
||||
public ILoginCallback LoginCallback { get; private set; }
|
||||
|
||||
private Account _account { get; }
|
||||
|
||||
public WinformLoginChoiceEager(Account account)
|
||||
{
|
||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
LoginCallback = new WinformLoginCallback(_account);
|
||||
}
|
||||
|
||||
public ChoiceOut Start(ChoiceIn choiceIn)
|
||||
{
|
||||
using var dialog = new LoginChoiceEagerDialog(_account);
|
||||
|
||||
if (!ShowDialog(dialog))
|
||||
return null;
|
||||
|
||||
switch (dialog.LoginMethod)
|
||||
{
|
||||
case LoginMethod.Api:
|
||||
return ChoiceOut.WithApi(dialog.Email, dialog.Password);
|
||||
case LoginMethod.External:
|
||||
{
|
||||
using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
|
||||
return ShowDialog(externalDialog)
|
||||
? ChoiceOut.External(externalDialog.ResponseUrl)
|
||||
: null;
|
||||
}
|
||||
default:
|
||||
throw new Exception($"Unknown {nameof(LoginMethod)} value");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace LibationWinForms.Dialogs
|
||||
return;
|
||||
try
|
||||
{
|
||||
var removedBooks = await LibraryCommands.FindInactiveBooks((account) => new WinformResponder(account), _libraryBooks, _accounts);
|
||||
var removedBooks = await LibraryCommands.FindInactiveBooks((account) => ApiExtended.CreateAsync(account, new WinformLoginChoiceEager(account)), _libraryBooks, _accounts);
|
||||
|
||||
var removable = _removableGridEntries.Where(rge => removedBooks.Any(rb => rb.Book.AudibleProductId == rge.AudibleProductId)).ToList();
|
||||
|
||||
|
||||
165
LibationWinForms/Dialogs/SettingsDialog.Designer.cs
generated
165
LibationWinForms/Dialogs/SettingsDialog.Designer.cs
generated
@@ -33,16 +33,24 @@
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.advancedSettingsGb = new System.Windows.Forms.GroupBox();
|
||||
this.badBookGb = new System.Windows.Forms.GroupBox();
|
||||
this.badBookIgnoreRb = new System.Windows.Forms.RadioButton();
|
||||
this.badBookRetryRb = new System.Windows.Forms.RadioButton();
|
||||
this.badBookAbortRb = new System.Windows.Forms.RadioButton();
|
||||
this.badBookAskRb = new System.Windows.Forms.RadioButton();
|
||||
this.decryptAndConvertGb = new System.Windows.Forms.GroupBox();
|
||||
this.allowLibationFixupCbox = new System.Windows.Forms.CheckBox();
|
||||
this.convertLossyRb = new System.Windows.Forms.RadioButton();
|
||||
this.convertLosslessRb = new System.Windows.Forms.RadioButton();
|
||||
this.inProgressSelectControl = new LibationWinForms.Dialogs.DirectorySelectControl();
|
||||
this.allowLibationFixupCbox = new System.Windows.Forms.CheckBox();
|
||||
this.logsBtn = new System.Windows.Forms.Button();
|
||||
this.booksSelectControl = new LibationWinForms.Dialogs.DirectoryOrCustomSelectControl();
|
||||
this.booksGb = new System.Windows.Forms.GroupBox();
|
||||
this.loggingLevelLbl = new System.Windows.Forms.Label();
|
||||
this.loggingLevelCb = new System.Windows.Forms.ComboBox();
|
||||
this.advancedSettingsGb.SuspendLayout();
|
||||
this.badBookGb.SuspendLayout();
|
||||
this.decryptAndConvertGb.SuspendLayout();
|
||||
this.booksGb.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
@@ -53,27 +61,27 @@
|
||||
this.booksLocationDescLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.booksLocationDescLbl.Name = "booksLocationDescLbl";
|
||||
this.booksLocationDescLbl.Size = new System.Drawing.Size(69, 15);
|
||||
this.booksLocationDescLbl.TabIndex = 2;
|
||||
this.booksLocationDescLbl.TabIndex = 1;
|
||||
this.booksLocationDescLbl.Text = "[book desc]";
|
||||
//
|
||||
// inProgressDescLbl
|
||||
//
|
||||
this.inProgressDescLbl.AutoSize = true;
|
||||
this.inProgressDescLbl.Location = new System.Drawing.Point(8, 127);
|
||||
this.inProgressDescLbl.Location = new System.Drawing.Point(8, 149);
|
||||
this.inProgressDescLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.inProgressDescLbl.Name = "inProgressDescLbl";
|
||||
this.inProgressDescLbl.Size = new System.Drawing.Size(43, 45);
|
||||
this.inProgressDescLbl.TabIndex = 1;
|
||||
this.inProgressDescLbl.TabIndex = 15;
|
||||
this.inProgressDescLbl.Text = "[desc]\r\n[line 2]\r\n[line 3]";
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(714, 419);
|
||||
this.saveBtn.Location = new System.Drawing.Point(714, 445);
|
||||
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.saveBtn.TabIndex = 4;
|
||||
this.saveBtn.TabIndex = 17;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||
@@ -82,40 +90,122 @@
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(832, 419);
|
||||
this.cancelBtn.Location = new System.Drawing.Point(832, 445);
|
||||
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.cancelBtn.TabIndex = 5;
|
||||
this.cancelBtn.TabIndex = 18;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// advancedSettingsGb
|
||||
//
|
||||
this.advancedSettingsGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
this.advancedSettingsGb.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.advancedSettingsGb.Controls.Add(this.convertLossyRb);
|
||||
this.advancedSettingsGb.Controls.Add(this.convertLosslessRb);
|
||||
this.advancedSettingsGb.Controls.Add(this.badBookGb);
|
||||
this.advancedSettingsGb.Controls.Add(this.decryptAndConvertGb);
|
||||
this.advancedSettingsGb.Controls.Add(this.inProgressSelectControl);
|
||||
this.advancedSettingsGb.Controls.Add(this.allowLibationFixupCbox);
|
||||
this.advancedSettingsGb.Controls.Add(this.inProgressDescLbl);
|
||||
this.advancedSettingsGb.Location = new System.Drawing.Point(12, 176);
|
||||
this.advancedSettingsGb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.advancedSettingsGb.Name = "advancedSettingsGb";
|
||||
this.advancedSettingsGb.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.advancedSettingsGb.Size = new System.Drawing.Size(908, 232);
|
||||
this.advancedSettingsGb.TabIndex = 5;
|
||||
this.advancedSettingsGb.Size = new System.Drawing.Size(908, 258);
|
||||
this.advancedSettingsGb.TabIndex = 6;
|
||||
this.advancedSettingsGb.TabStop = false;
|
||||
this.advancedSettingsGb.Text = "Advanced settings for control freaks";
|
||||
//
|
||||
// badBookGb
|
||||
//
|
||||
this.badBookGb.Controls.Add(this.badBookIgnoreRb);
|
||||
this.badBookGb.Controls.Add(this.badBookRetryRb);
|
||||
this.badBookGb.Controls.Add(this.badBookAbortRb);
|
||||
this.badBookGb.Controls.Add(this.badBookAskRb);
|
||||
this.badBookGb.Location = new System.Drawing.Point(372, 22);
|
||||
this.badBookGb.Name = "badBookGb";
|
||||
this.badBookGb.Size = new System.Drawing.Size(529, 124);
|
||||
this.badBookGb.TabIndex = 11;
|
||||
this.badBookGb.TabStop = false;
|
||||
this.badBookGb.Text = "[bad book desc]";
|
||||
//
|
||||
// badBookIgnoreRb
|
||||
//
|
||||
this.badBookIgnoreRb.AutoSize = true;
|
||||
this.badBookIgnoreRb.Location = new System.Drawing.Point(6, 97);
|
||||
this.badBookIgnoreRb.Name = "badBookIgnoreRb";
|
||||
this.badBookIgnoreRb.Size = new System.Drawing.Size(94, 19);
|
||||
this.badBookIgnoreRb.TabIndex = 15;
|
||||
this.badBookIgnoreRb.TabStop = true;
|
||||
this.badBookIgnoreRb.Text = "[ignore desc]";
|
||||
this.badBookIgnoreRb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// badBookRetryRb
|
||||
//
|
||||
this.badBookRetryRb.AutoSize = true;
|
||||
this.badBookRetryRb.Location = new System.Drawing.Point(6, 72);
|
||||
this.badBookRetryRb.Name = "badBookRetryRb";
|
||||
this.badBookRetryRb.Size = new System.Drawing.Size(84, 19);
|
||||
this.badBookRetryRb.TabIndex = 14;
|
||||
this.badBookRetryRb.TabStop = true;
|
||||
this.badBookRetryRb.Text = "[retry desc]";
|
||||
this.badBookRetryRb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// badBookAbortRb
|
||||
//
|
||||
this.badBookAbortRb.AutoSize = true;
|
||||
this.badBookAbortRb.Location = new System.Drawing.Point(6, 47);
|
||||
this.badBookAbortRb.Name = "badBookAbortRb";
|
||||
this.badBookAbortRb.Size = new System.Drawing.Size(88, 19);
|
||||
this.badBookAbortRb.TabIndex = 13;
|
||||
this.badBookAbortRb.TabStop = true;
|
||||
this.badBookAbortRb.Text = "[abort desc]";
|
||||
this.badBookAbortRb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// badBookAskRb
|
||||
//
|
||||
this.badBookAskRb.AutoSize = true;
|
||||
this.badBookAskRb.Location = new System.Drawing.Point(6, 22);
|
||||
this.badBookAskRb.Name = "badBookAskRb";
|
||||
this.badBookAskRb.Size = new System.Drawing.Size(77, 19);
|
||||
this.badBookAskRb.TabIndex = 12;
|
||||
this.badBookAskRb.TabStop = true;
|
||||
this.badBookAskRb.Text = "[ask desc]";
|
||||
this.badBookAskRb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// decryptAndConvertGb
|
||||
//
|
||||
this.decryptAndConvertGb.Controls.Add(this.allowLibationFixupCbox);
|
||||
this.decryptAndConvertGb.Controls.Add(this.convertLossyRb);
|
||||
this.decryptAndConvertGb.Controls.Add(this.convertLosslessRb);
|
||||
this.decryptAndConvertGb.Location = new System.Drawing.Point(7, 22);
|
||||
this.decryptAndConvertGb.Name = "decryptAndConvertGb";
|
||||
this.decryptAndConvertGb.Size = new System.Drawing.Size(359, 124);
|
||||
this.decryptAndConvertGb.TabIndex = 7;
|
||||
this.decryptAndConvertGb.TabStop = false;
|
||||
this.decryptAndConvertGb.Text = "Decrypt and convert";
|
||||
//
|
||||
// allowLibationFixupCbox
|
||||
//
|
||||
this.allowLibationFixupCbox.AutoSize = true;
|
||||
this.allowLibationFixupCbox.Checked = true;
|
||||
this.allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.allowLibationFixupCbox.Location = new System.Drawing.Point(6, 22);
|
||||
this.allowLibationFixupCbox.Name = "allowLibationFixupCbox";
|
||||
this.allowLibationFixupCbox.Size = new System.Drawing.Size(262, 19);
|
||||
this.allowLibationFixupCbox.TabIndex = 8;
|
||||
this.allowLibationFixupCbox.Text = "Allow Libation to fix up audiobook metadata";
|
||||
this.allowLibationFixupCbox.UseVisualStyleBackColor = true;
|
||||
this.allowLibationFixupCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
|
||||
//
|
||||
// convertLossyRb
|
||||
//
|
||||
this.convertLossyRb.AutoSize = true;
|
||||
this.convertLossyRb.Location = new System.Drawing.Point(7, 88);
|
||||
this.convertLossyRb.Location = new System.Drawing.Point(6, 81);
|
||||
this.convertLossyRb.Name = "convertLossyRb";
|
||||
this.convertLossyRb.Size = new System.Drawing.Size(242, 19);
|
||||
this.convertLossyRb.TabIndex = 0;
|
||||
this.convertLossyRb.TabIndex = 10;
|
||||
this.convertLossyRb.Text = "Download my books as .MP3 files (Lossy)";
|
||||
this.convertLossyRb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -123,10 +213,10 @@
|
||||
//
|
||||
this.convertLosslessRb.AutoSize = true;
|
||||
this.convertLosslessRb.Checked = true;
|
||||
this.convertLosslessRb.Location = new System.Drawing.Point(7, 63);
|
||||
this.convertLosslessRb.Location = new System.Drawing.Point(6, 56);
|
||||
this.convertLosslessRb.Name = "convertLosslessRb";
|
||||
this.convertLosslessRb.Size = new System.Drawing.Size(327, 19);
|
||||
this.convertLosslessRb.TabIndex = 0;
|
||||
this.convertLosslessRb.TabIndex = 9;
|
||||
this.convertLosslessRb.TabStop = true;
|
||||
this.convertLosslessRb.Text = "Download my books as .M4B files (Lossless Mp4a format)";
|
||||
this.convertLosslessRb.UseVisualStyleBackColor = true;
|
||||
@@ -135,30 +225,17 @@
|
||||
//
|
||||
this.inProgressSelectControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.inProgressSelectControl.Location = new System.Drawing.Point(10, 175);
|
||||
this.inProgressSelectControl.Location = new System.Drawing.Point(7, 197);
|
||||
this.inProgressSelectControl.Name = "inProgressSelectControl";
|
||||
this.inProgressSelectControl.Size = new System.Drawing.Size(552, 52);
|
||||
this.inProgressSelectControl.TabIndex = 2;
|
||||
//
|
||||
// allowLibationFixupCbox
|
||||
//
|
||||
this.allowLibationFixupCbox.AutoSize = true;
|
||||
this.allowLibationFixupCbox.Checked = true;
|
||||
this.allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.allowLibationFixupCbox.Location = new System.Drawing.Point(7, 22);
|
||||
this.allowLibationFixupCbox.Name = "allowLibationFixupCbox";
|
||||
this.allowLibationFixupCbox.Size = new System.Drawing.Size(262, 19);
|
||||
this.allowLibationFixupCbox.TabIndex = 0;
|
||||
this.allowLibationFixupCbox.Text = "Allow Libation to fix up audiobook metadata";
|
||||
this.allowLibationFixupCbox.UseVisualStyleBackColor = true;
|
||||
this.allowLibationFixupCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
|
||||
this.inProgressSelectControl.TabIndex = 16;
|
||||
//
|
||||
// logsBtn
|
||||
//
|
||||
this.logsBtn.Location = new System.Drawing.Point(262, 147);
|
||||
this.logsBtn.Name = "logsBtn";
|
||||
this.logsBtn.Size = new System.Drawing.Size(132, 23);
|
||||
this.logsBtn.TabIndex = 4;
|
||||
this.logsBtn.TabIndex = 5;
|
||||
this.logsBtn.Text = "Open log folder";
|
||||
this.logsBtn.UseVisualStyleBackColor = true;
|
||||
this.logsBtn.Click += new System.EventHandler(this.logsBtn_Click);
|
||||
@@ -170,7 +247,7 @@
|
||||
this.booksSelectControl.Location = new System.Drawing.Point(7, 37);
|
||||
this.booksSelectControl.Name = "booksSelectControl";
|
||||
this.booksSelectControl.Size = new System.Drawing.Size(895, 87);
|
||||
this.booksSelectControl.TabIndex = 1;
|
||||
this.booksSelectControl.TabIndex = 2;
|
||||
//
|
||||
// booksGb
|
||||
//
|
||||
@@ -181,7 +258,7 @@
|
||||
this.booksGb.Location = new System.Drawing.Point(12, 12);
|
||||
this.booksGb.Name = "booksGb";
|
||||
this.booksGb.Size = new System.Drawing.Size(908, 129);
|
||||
this.booksGb.TabIndex = 1;
|
||||
this.booksGb.TabIndex = 0;
|
||||
this.booksGb.TabStop = false;
|
||||
this.booksGb.Text = "Books location";
|
||||
//
|
||||
@@ -191,7 +268,7 @@
|
||||
this.loggingLevelLbl.Location = new System.Drawing.Point(12, 150);
|
||||
this.loggingLevelLbl.Name = "loggingLevelLbl";
|
||||
this.loggingLevelLbl.Size = new System.Drawing.Size(78, 15);
|
||||
this.loggingLevelLbl.TabIndex = 2;
|
||||
this.loggingLevelLbl.TabIndex = 3;
|
||||
this.loggingLevelLbl.Text = "Logging level";
|
||||
//
|
||||
// loggingLevelCb
|
||||
@@ -201,7 +278,7 @@
|
||||
this.loggingLevelCb.Location = new System.Drawing.Point(96, 147);
|
||||
this.loggingLevelCb.Name = "loggingLevelCb";
|
||||
this.loggingLevelCb.Size = new System.Drawing.Size(129, 23);
|
||||
this.loggingLevelCb.TabIndex = 3;
|
||||
this.loggingLevelCb.TabIndex = 4;
|
||||
//
|
||||
// SettingsDialog
|
||||
//
|
||||
@@ -209,7 +286,7 @@
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(933, 462);
|
||||
this.ClientSize = new System.Drawing.Size(933, 488);
|
||||
this.Controls.Add(this.logsBtn);
|
||||
this.Controls.Add(this.loggingLevelCb);
|
||||
this.Controls.Add(this.loggingLevelLbl);
|
||||
@@ -225,6 +302,10 @@
|
||||
this.Load += new System.EventHandler(this.SettingsDialog_Load);
|
||||
this.advancedSettingsGb.ResumeLayout(false);
|
||||
this.advancedSettingsGb.PerformLayout();
|
||||
this.badBookGb.ResumeLayout(false);
|
||||
this.badBookGb.PerformLayout();
|
||||
this.decryptAndConvertGb.ResumeLayout(false);
|
||||
this.decryptAndConvertGb.PerformLayout();
|
||||
this.booksGb.ResumeLayout(false);
|
||||
this.booksGb.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
@@ -247,5 +328,11 @@
|
||||
private System.Windows.Forms.Button logsBtn;
|
||||
private System.Windows.Forms.Label loggingLevelLbl;
|
||||
private System.Windows.Forms.ComboBox loggingLevelCb;
|
||||
private System.Windows.Forms.GroupBox decryptAndConvertGb;
|
||||
private System.Windows.Forms.GroupBox badBookGb;
|
||||
private System.Windows.Forms.RadioButton badBookRetryRb;
|
||||
private System.Windows.Forms.RadioButton badBookAbortRb;
|
||||
private System.Windows.Forms.RadioButton badBookAskRb;
|
||||
private System.Windows.Forms.RadioButton badBookIgnoreRb;
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,21 @@ namespace LibationWinForms.Dialogs
|
||||
Configuration.KnownDirectories.LibationFiles
|
||||
}, Configuration.KnownDirectories.WinTemp);
|
||||
inProgressSelectControl.SelectDirectory(config.InProgress);
|
||||
|
||||
badBookGb.Text = desc(nameof(config.BadBook));
|
||||
badBookAskRb.Text = Configuration.BadBookAction.Ask.GetDescription();
|
||||
badBookAbortRb.Text = Configuration.BadBookAction.Abort.GetDescription();
|
||||
badBookRetryRb.Text = Configuration.BadBookAction.Retry.GetDescription();
|
||||
badBookIgnoreRb.Text = Configuration.BadBookAction.Ignore.GetDescription();
|
||||
var rb = config.BadBook switch
|
||||
{
|
||||
Configuration.BadBookAction.Ask => this.badBookAskRb,
|
||||
Configuration.BadBookAction.Abort => this.badBookAbortRb,
|
||||
Configuration.BadBookAction.Retry => this.badBookRetryRb,
|
||||
Configuration.BadBookAction.Ignore => this.badBookIgnoreRb,
|
||||
_ => this.badBookAskRb
|
||||
};
|
||||
rb.Checked = true;
|
||||
}
|
||||
|
||||
private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e)
|
||||
@@ -111,6 +126,13 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
config.InProgress = inProgressSelectControl.SelectedDirectory;
|
||||
|
||||
config.BadBook
|
||||
= badBookAskRb.Checked ? Configuration.BadBookAction.Ask
|
||||
: badBookAbortRb.Checked ? Configuration.BadBookAction.Abort
|
||||
: badBookRetryRb.Checked ? Configuration.BadBookAction.Retry
|
||||
: badBookIgnoreRb.Checked ? Configuration.BadBookAction.Ignore
|
||||
: Configuration.BadBookAction.Ask;
|
||||
|
||||
this.DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
@@ -18,6 +18,16 @@
|
||||
<!-- Version is now in AppScaffolding.csproj -->
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
|
||||
- Not using SatelliteResourceLanguages will load all language packs: works
|
||||
- Specifying 'en' semicolon 1 more should load 1 language pack: works
|
||||
- Specifying only 'en' should load no language packs: broken, still loads all
|
||||
-->
|
||||
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="1.1.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -183,7 +183,8 @@ namespace LibationWinForms
|
||||
authorize.DeregisterAsync(identity.ExistingAccessToken, identity.Cookies.ToKeyValuePair()).GetAwaiter().GetResult();
|
||||
identity.Invalidate();
|
||||
|
||||
var api = AudibleApiActions.GetApiAsync(new LibationWinForms.Login.WinformResponder(account), account).GetAwaiter().GetResult();
|
||||
// re-registers device
|
||||
ApiExtended.CreateAsync(account, new Login.WinformLoginChoiceEager(account)).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
47
README.md
47
README.md
@@ -27,6 +27,7 @@
|
||||
- [Files and folders](#files-and-folders)
|
||||
- [Linux and Mac (unofficial)](#linux-and-mac)
|
||||
- [Settings](#settings)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
|
||||
## Audible audiobook manager
|
||||
|
||||
@@ -241,3 +242,49 @@ Although Libation only currently officially supports Windows, [some users](https
|
||||
### Settings
|
||||
|
||||
* Allow Libation to fix up audiobook metadata. After decrypting a title, Libation attempts to fix details like chapters and cover art. Some power users and/or control freaks prefer to manage this themselves. By unchecking this setting, Libation will only decrypt the book and will leave metadata as-is, warts and all.
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
Libationcli.exe allows limited access to Libation's functionalities as a CLI.
|
||||
|
||||
Warnings about relying solely on on the CLI:
|
||||
* CLI will not perform any upgrades.
|
||||
* It will show that there is an upgrade, but that will likely scroll by too fast to notice.
|
||||
* It will not perform all post-upgrade migrations. Some migrations are only be possible by launching GUI.
|
||||
|
||||
```
|
||||
help
|
||||
libationcli --help
|
||||
|
||||
verb-specific help
|
||||
libationcli scan --help
|
||||
|
||||
scan all libraries
|
||||
libationcli scan
|
||||
scan only libraries for specific accounts
|
||||
libationcli scan nickname1 nickname2
|
||||
|
||||
convert all m4b files to mp3
|
||||
libationcli convert
|
||||
|
||||
liberate all books and pdfs
|
||||
libationcli liberate
|
||||
liberate pdfs only
|
||||
libationcli liberate --pdf
|
||||
libationcli liberate -p
|
||||
|
||||
export library to file
|
||||
libationcli export --path "C:\foo\bar\my.json" --json
|
||||
libationcli export -p "C:\foo\bar\my.json" -j
|
||||
```
|
||||
|
||||
Currently logs are written to Console and to file. This means they'll be printed in the CLI. To disable, find this in Settings.json and delete the 3 lines after `"WriteTo": [`
|
||||
|
||||
```
|
||||
"Serilog": {
|
||||
"MinimumLevel": "Information",
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console"
|
||||
},
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user