External login completed

This commit is contained in:
Robert McRackan 2021-09-20 15:31:07 -04:00
parent 926a7a1148
commit de1147ac1b
21 changed files with 695 additions and 71 deletions

View File

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

View File

@ -16,7 +16,7 @@ namespace ApplicationServices
{ {
private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS; 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(); logRestart();
@ -33,7 +33,7 @@ namespace ApplicationServices
try try
{ {
logTime($"pre {nameof(scanAccountsAsync)} all"); logTime($"pre {nameof(scanAccountsAsync)} all");
var libraryItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts); var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts);
logTime($"post {nameof(scanAccountsAsync)} all"); logTime($"post {nameof(scanAccountsAsync)} all");
var totalCount = libraryItems.Count; var totalCount = libraryItems.Count;
@ -75,7 +75,7 @@ namespace ApplicationServices
} }
#region FULL LIBRARY scan and import #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(); logRestart();
@ -85,7 +85,7 @@ namespace ApplicationServices
try try
{ {
logTime($"pre {nameof(scanAccountsAsync)} all"); logTime($"pre {nameof(scanAccountsAsync)} all");
var importItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts); var importItems = await scanAccountsAsync(apiExtendedfunc, accounts);
logTime($"post {nameof(scanAccountsAsync)} all"); logTime($"post {nameof(scanAccountsAsync)} all");
var totalCount = importItems.Count; var totalCount = importItems.Count;
@ -129,15 +129,13 @@ 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>>>(); var tasks = new List<Task<List<ImportItem>>>();
foreach (var account in accounts) foreach (var account in accounts)
{ {
var callback = loginCallbackFactoryFunc(account); // get APIs in serial b/c of logins. do NOT move inside of parallel (Task.WhenAll)
var apiExtended = await apiExtendedfunc(account);
// get APIs in serial b/c of logins
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(apiExtended, account)); tasks.Add(scanAccountAsync(apiExtended, account));

View File

@ -17,26 +17,28 @@ namespace InternalUtilities
private ApiExtended(Api api) => Api = api; private ApiExtended(Api api) => Api = api;
public static async Task<ApiExtended> CreateAsync(ILoginChoice loginChoice, Account account) /// <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(ILoginChoice), LoginType = nameof(ILoginChoiceEager),
Account = account?.MaskedLogEntry ?? "[null]", Account = account?.MaskedLogEntry ?? "[null]",
LocaleName = account?.Locale?.Name LocaleName = account?.Locale?.Name
}); });
var api = await EzApiCreator.GetApiAsync( var api = await EzApiCreator.GetApiAsync(
loginChoiceEager,
account.Locale, account.Locale,
AudibleApiStorage.AccountsSettingsFile, AudibleApiStorage.AccountsSettingsFile,
loginChoice,
account.GetIdentityTokensJsonPath()); account.GetIdentityTokensJsonPath());
return new ApiExtended(api); return new ApiExtended(api);
} }
public static async Task<ApiExtended> CreateAsync(ILoginCallback loginCallback, Account account) /// <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("GetApiAsync ILoginCallback. {@DebugInfo}", new Serilog.Log.Logger.Information("{@DebugInfo}", new
{ {
LoginType = nameof(ILoginCallback), LoginType = nameof(ILoginCallback),
Account = account?.MaskedLogEntry ?? "[null]", Account = account?.MaskedLogEntry ?? "[null]",
@ -44,16 +46,17 @@ namespace InternalUtilities
}); });
var api = await EzApiCreator.GetApiAsync( var api = await EzApiCreator.GetApiAsync(
loginCallback,
account.Locale, account.Locale,
AudibleApiStorage.AccountsSettingsFile, AudibleApiStorage.AccountsSettingsFile,
loginCallback,
account.GetIdentityTokensJsonPath()); account.GetIdentityTokensJsonPath());
return new ApiExtended(api); return new ApiExtended(api);
} }
public static async Task<ApiExtended> CreateAsync(ILoginExternal loginExternal, Account account) /// <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("GetApiAsync ILoginExternal. {@DebugInfo}", new Serilog.Log.Logger.Information("{@DebugInfo}", new
{ {
LoginType = nameof(ILoginExternal), LoginType = nameof(ILoginExternal),
Account = account?.MaskedLogEntry ?? "[null]", Account = account?.MaskedLogEntry ?? "[null]",
@ -61,16 +64,31 @@ namespace InternalUtilities
}); });
var api = await EzApiCreator.GetApiAsync( var api = await EzApiCreator.GetApiAsync(
loginExternal,
account.Locale, account.Locale,
AudibleApiStorage.AccountsSettingsFile, AudibleApiStorage.AccountsSettingsFile,
loginExternal,
account.GetIdentityTokensJsonPath()); account.GetIdentityTokensJsonPath());
return new ApiExtended(api); 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) public static async Task<ApiExtended> CreateAsync(string username, string localeName)
{ {
Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new Serilog.Log.Logger.Information("{@DebugInfo}", new
{ {
Username = username.ToMask(), Username = username.ToMask(),
LocaleName = localeName, LocaleName = localeName,

View File

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

View File

@ -32,9 +32,7 @@ namespace LibationCli
: $"Scanning Audible library: {_accounts.Length} accounts. This may take a few minutes per account."; : $"Scanning Audible library: {_accounts.Length} accounts. This may take a few minutes per account.";
Console.WriteLine(intro); Console.WriteLine(intro);
var (TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.ImportAccountAsync( var (TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.ImportAccountAsync((a) => ApiExtended.CreateAsync(a), _accounts);
(account) => null,
_accounts);
Console.WriteLine("Scan complete."); Console.WriteLine("Scan complete.");
Console.WriteLine($"Total processed: {TotalBooksProcessed}\r\nNew: {NewBooksAdded}"); Console.WriteLine($"Total processed: {TotalBooksProcessed}\r\nNew: {NewBooksAdded}");

View File

@ -35,7 +35,7 @@ namespace LibationCli
string input = null; string input = null;
//input = " export --help"; //input = " export --help";
//input = " scan cupidneedsglasses"; //input = " scan rmcrackan";
//input = " liberate "; //input = " liberate ";

View File

@ -32,7 +32,7 @@ namespace LibationWinForms.Dialogs
try 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) catch (Exception ex)
{ {

View File

@ -1,6 +1,6 @@
namespace LibationWinForms.Dialogs.Login namespace LibationWinForms.Dialogs.Login
{ {
partial class AudibleLoginDialog partial class LoginCallbackDialog
{ {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
@ -38,25 +38,29 @@
// passwordLbl // passwordLbl
// //
this.passwordLbl.AutoSize = true; 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.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.TabIndex = 2;
this.passwordLbl.Text = "Password"; this.passwordLbl.Text = "Password";
// //
// passwordTb // 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.Name = "passwordTb";
this.passwordTb.PasswordChar = '*'; 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; this.passwordTb.TabIndex = 3;
// //
// submitBtn // 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.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.TabIndex = 4;
this.submitBtn.Text = "Submit"; this.submitBtn.Text = "Submit";
this.submitBtn.UseVisualStyleBackColor = true; this.submitBtn.UseVisualStyleBackColor = true;
@ -65,36 +69,39 @@
// localeLbl // localeLbl
// //
this.localeLbl.AutoSize = true; 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.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.TabIndex = 0;
this.localeLbl.Text = "Locale: {0}"; this.localeLbl.Text = "Locale: {0}";
// //
// usernameLbl // usernameLbl
// //
this.usernameLbl.AutoSize = true; 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.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.TabIndex = 1;
this.usernameLbl.Text = "Username: {0}"; this.usernameLbl.Text = "Username: {0}";
// //
// AudibleLoginDialog // LoginCallbackDialog
// //
this.AcceptButton = this.submitBtn; 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.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.usernameLbl);
this.Controls.Add(this.localeLbl); this.Controls.Add(this.localeLbl);
this.Controls.Add(this.submitBtn); this.Controls.Add(this.submitBtn);
this.Controls.Add(this.passwordLbl); this.Controls.Add(this.passwordLbl);
this.Controls.Add(this.passwordTb); this.Controls.Add(this.passwordTb);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.MaximizeBox = false; this.MaximizeBox = false;
this.MinimizeBox = false; this.MinimizeBox = false;
this.Name = "AudibleLoginDialog"; this.Name = "LoginCallbackDialog";
this.ShowIcon = false; this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Audible Login"; this.Text = "Audible Login";

View File

@ -1,28 +1,25 @@
using Dinah.Core; using System;
using InternalUtilities;
using System;
using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using Dinah.Core;
using InternalUtilities;
namespace LibationWinForms.Dialogs.Login namespace LibationWinForms.Dialogs.Login
{ {
public partial class AudibleLoginDialog : Form public partial class LoginCallbackDialog : Form
{ {
private string locale { get; }
private string accountId { get; } private string accountId { get; }
public string Email { get; private set; } public string Email { get; private set; }
public string Password { get; private set; } public string Password { get; private set; }
public AudibleLoginDialog(Account account) public LoginCallbackDialog(Account account)
{ {
InitializeComponent(); InitializeComponent();
locale = account.Locale.Name;
accountId = account.AccountId; accountId = account.AccountId;
// do not allow user to change login id here. if they do then jsonpath will fail // 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); this.usernameLbl.Text = string.Format(this.usernameLbl.Text, accountId);
} }

View File

@ -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: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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View 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;
}
}

View 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
}
}
}

View 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>

View 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;
}
}

View 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
}
}
}

View 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>

View 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;
}
}
}

View File

@ -5,11 +5,11 @@ using LibationWinForms.Dialogs.Login;
namespace LibationWinForms.Login namespace LibationWinForms.Login
{ {
public class WinformResponder : ILoginCallback public class WinformLoginCallback : WinformLoginBase, ILoginCallback
{ {
private Account _account { get; } private Account _account { get; }
public WinformResponder(Account account) public WinformLoginCallback(Account account)
{ {
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account)); _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
} }
@ -17,7 +17,7 @@ namespace LibationWinForms.Login
public string Get2faCode() public string Get2faCode()
{ {
using var dialog = new _2faCodeDialog(); using var dialog = new _2faCodeDialog();
if (showDialog(dialog)) if (ShowDialog(dialog))
return dialog.Code; return dialog.Code;
return null; return null;
} }
@ -25,7 +25,7 @@ namespace LibationWinForms.Login
public string GetCaptchaAnswer(byte[] captchaImage) public string GetCaptchaAnswer(byte[] captchaImage)
{ {
using var dialog = new CaptchaDialog(captchaImage); using var dialog = new CaptchaDialog(captchaImage);
if (showDialog(dialog)) if (ShowDialog(dialog))
return dialog.Answer; return dialog.Answer;
return null; return null;
} }
@ -33,15 +33,15 @@ namespace LibationWinForms.Login
public (string name, string value) GetMfaChoice(MfaConfig mfaConfig) public (string name, string value) GetMfaChoice(MfaConfig mfaConfig)
{ {
using var dialog = new MfaDialog(mfaConfig); using var dialog = new MfaDialog(mfaConfig);
if (showDialog(dialog)) if (ShowDialog(dialog))
return (dialog.SelectedName, dialog.SelectedValue); return (dialog.SelectedName, dialog.SelectedValue);
return (null, null); return (null, null);
} }
public (string email, string password) GetLogin() public (string email, string password) GetLogin()
{ {
using var dialog = new AudibleLoginDialog(_account); using var dialog = new LoginCallbackDialog(_account);
if (showDialog(dialog)) if (ShowDialog(dialog))
return (dialog.Email, dialog.Password); return (dialog.Email, dialog.Password);
return (null, null); return (null, null);
} }
@ -49,15 +49,7 @@ namespace LibationWinForms.Login
public void ShowApprovalNeeded() public void ShowApprovalNeeded()
{ {
using var dialog = new ApprovalNeededDialog(); using var dialog = new ApprovalNeededDialog();
showDialog(dialog); 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;
} }
} }
} }

View 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");
}
}
}
}

View File

@ -62,7 +62,7 @@ namespace LibationWinForms.Dialogs
return; return;
try 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(); var removable = _removableGridEntries.Where(rge => removedBooks.Any(rb => rb.Book.AudibleProductId == rge.AudibleProductId)).ToList();

View File

@ -184,7 +184,7 @@ namespace LibationWinForms
identity.Invalidate(); identity.Invalidate();
// re-registers device // re-registers device
ApiExtended.CreateAsync(new Login.WinformResponder(account), account).GetAwaiter().GetResult(); ApiExtended.CreateAsync(account, new Login.WinformLoginChoiceEager(account)).GetAwaiter().GetResult();
} }
catch catch
{ {