diff --git a/AppScaffolding/AppScaffolding.csproj b/AppScaffolding/AppScaffolding.csproj index 4afd6076..e7dcd424 100644 --- a/AppScaffolding/AppScaffolding.csproj +++ b/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ net5.0 - 6.0.0.2 + 6.0.1.1 diff --git a/ApplicationServices/LibraryCommands.cs b/ApplicationServices/LibraryCommands.cs index 5422f987..75ae93f1 100644 --- a/ApplicationServices/LibraryCommands.cs +++ b/ApplicationServices/LibraryCommands.cs @@ -16,7 +16,7 @@ namespace ApplicationServices { private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS; - public static async Task> FindInactiveBooks(Func loginCallbackFactoryFunc, List existingLibrary, params Account[] accounts) + public static async Task> FindInactiveBooks(Func> apiExtendedfunc, List 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 loginCallbackFactoryFunc, params Account[] accounts) + public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func> 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,15 +129,13 @@ namespace ApplicationServices } } - private static async Task> scanAccountsAsync(Func loginCallbackFactoryFunc, Account[] accounts) + private static async Task> scanAccountsAsync(Func> apiExtendedfunc, Account[] accounts) { var tasks = new List>>(); foreach (var account in accounts) { - var callback = loginCallbackFactoryFunc(account); - - // get APIs in serial b/c of logins - var apiExtended = await ApiExtended.CreateAsync(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(apiExtended, account)); diff --git a/InternalUtilities/ApiExtended.cs b/InternalUtilities/ApiExtended.cs index 66d4d7ce..382afabb 100644 --- a/InternalUtilities/ApiExtended.cs +++ b/InternalUtilities/ApiExtended.cs @@ -17,26 +17,28 @@ namespace InternalUtilities private ApiExtended(Api api) => Api = api; - public static async Task CreateAsync(ILoginChoice loginChoice, Account account) + /// 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. + public static async Task 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]", LocaleName = account?.Locale?.Name }); var api = await EzApiCreator.GetApiAsync( + loginChoiceEager, account.Locale, AudibleApiStorage.AccountsSettingsFile, - loginChoice, account.GetIdentityTokensJsonPath()); return new ApiExtended(api); } - public static async Task CreateAsync(ILoginCallback loginCallback, Account account) + /// Get api from existing tokens else login with native api callbacks. + public static async Task CreateAsync(Account account, ILoginCallback loginCallback) { - Serilog.Log.Logger.Information("GetApiAsync ILoginCallback. {@DebugInfo}", new + Serilog.Log.Logger.Information("{@DebugInfo}", new { LoginType = nameof(ILoginCallback), Account = account?.MaskedLogEntry ?? "[null]", @@ -44,16 +46,17 @@ namespace InternalUtilities }); var api = await EzApiCreator.GetApiAsync( + loginCallback, account.Locale, AudibleApiStorage.AccountsSettingsFile, - loginCallback, account.GetIdentityTokensJsonPath()); return new ApiExtended(api); } - public static async Task CreateAsync(ILoginExternal loginExternal, Account account) + /// Get api from existing tokens else login with external browser + public static async Task CreateAsync(Account account, ILoginExternal loginExternal) { - Serilog.Log.Logger.Information("GetApiAsync ILoginExternal. {@DebugInfo}", new + Serilog.Log.Logger.Information("{@DebugInfo}", new { LoginType = nameof(ILoginExternal), Account = account?.MaskedLogEntry ?? "[null]", @@ -61,16 +64,31 @@ namespace InternalUtilities }); var api = await EzApiCreator.GetApiAsync( + loginExternal, account.Locale, AudibleApiStorage.AccountsSettingsFile, - loginExternal, account.GetIdentityTokensJsonPath()); return new ApiExtended(api); } + /// Get api from existing tokens. Assumes you have valid login tokens. Else exception + public static async Task 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); + } + + /// Get api from existing tokens. Assumes you have valid login tokens. Else exception public static async Task CreateAsync(string username, string localeName) { - Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new + Serilog.Log.Logger.Information("{@DebugInfo}", new { Username = username.ToMask(), LocaleName = localeName, diff --git a/InternalUtilities/InternalUtilities.csproj b/InternalUtilities/InternalUtilities.csproj index eddf21f6..bcdc48ef 100644 --- a/InternalUtilities/InternalUtilities.csproj +++ b/InternalUtilities/InternalUtilities.csproj @@ -5,7 +5,7 @@ - + diff --git a/LibationCli/Options/ScanOptions.cs b/LibationCli/Options/ScanOptions.cs index 9c9aa825..87daca99 100644 --- a/LibationCli/Options/ScanOptions.cs +++ b/LibationCli/Options/ScanOptions.cs @@ -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}"); diff --git a/LibationCli/Program.cs b/LibationCli/Program.cs index 6f85c5a6..2681d195 100644 --- a/LibationCli/Program.cs +++ b/LibationCli/Program.cs @@ -35,7 +35,7 @@ namespace LibationCli string input = null; //input = " export --help"; - //input = " scan cupidneedsglasses"; + //input = " scan rmcrackan"; //input = " liberate "; diff --git a/LibationWinForms/Dialogs/IndexLibraryDialog.cs b/LibationWinForms/Dialogs/IndexLibraryDialog.cs index 1adb538a..ba4e4342 100644 --- a/LibationWinForms/Dialogs/IndexLibraryDialog.cs +++ b/LibationWinForms/Dialogs/IndexLibraryDialog.cs @@ -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) { diff --git a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.Designer.cs b/LibationWinForms/Dialogs/Login/LoginCallbackDialog.Designer.cs similarity index 67% rename from LibationWinForms/Dialogs/Login/AudibleLoginDialog.Designer.cs rename to LibationWinForms/Dialogs/Login/LoginCallbackDialog.Designer.cs index e725f483..558cd626 100644 --- a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.Designer.cs +++ b/LibationWinForms/Dialogs/Login/LoginCallbackDialog.Designer.cs @@ -1,6 +1,6 @@ namespace LibationWinForms.Dialogs.Login { - partial class AudibleLoginDialog + partial class LoginCallbackDialog { /// /// 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"; diff --git a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs b/LibationWinForms/Dialogs/Login/LoginCallbackDialog.cs similarity index 74% rename from LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs rename to LibationWinForms/Dialogs/Login/LoginCallbackDialog.cs index 2e366e88..93d2b043 100644 --- a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs +++ b/LibationWinForms/Dialogs/Login/LoginCallbackDialog.cs @@ -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); } diff --git a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx b/LibationWinForms/Dialogs/Login/LoginCallbackDialog.resx similarity index 98% rename from LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx rename to LibationWinForms/Dialogs/Login/LoginCallbackDialog.resx index e8ae276d..f298a7be 100644 --- a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx +++ b/LibationWinForms/Dialogs/Login/LoginCallbackDialog.resx @@ -1,5 +1,4 @@ - - + diff --git a/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.Designer.cs b/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.Designer.cs new file mode 100644 index 00000000..6cda284f --- /dev/null +++ b/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.Designer.cs @@ -0,0 +1,161 @@ +namespace LibationWinForms.Dialogs.Login +{ + partial class LoginChoiceEagerDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.cs b/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.cs new file mode 100644 index 00000000..827a8273 --- /dev/null +++ b/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.cs @@ -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 + } + } +} diff --git a/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.resx b/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.resx new file mode 100644 index 00000000..f298a7be --- /dev/null +++ b/LibationWinForms/Dialogs/Login/LoginChoiceEagerDialog.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/LibationWinForms/Dialogs/Login/LoginExternalDialog.Designer.cs b/LibationWinForms/Dialogs/Login/LoginExternalDialog.Designer.cs new file mode 100644 index 00000000..e3355b42 --- /dev/null +++ b/LibationWinForms/Dialogs/Login/LoginExternalDialog.Designer.cs @@ -0,0 +1,179 @@ +namespace LibationWinForms.Dialogs.Login +{ + partial class LoginExternalDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/LibationWinForms/Dialogs/Login/LoginExternalDialog.cs b/LibationWinForms/Dialogs/Login/LoginExternalDialog.cs new file mode 100644 index 00000000..c4028ca9 --- /dev/null +++ b/LibationWinForms/Dialogs/Login/LoginExternalDialog.cs @@ -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 + } + } +} diff --git a/LibationWinForms/Dialogs/Login/LoginExternalDialog.resx b/LibationWinForms/Dialogs/Login/LoginExternalDialog.resx new file mode 100644 index 00000000..4a1293ad --- /dev/null +++ b/LibationWinForms/Dialogs/Login/LoginExternalDialog.resx @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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: + + \ No newline at end of file diff --git a/LibationWinForms/Dialogs/Login/WinformLoginBase.cs b/LibationWinForms/Dialogs/Login/WinformLoginBase.cs new file mode 100644 index 00000000..1de76464 --- /dev/null +++ b/LibationWinForms/Dialogs/Login/WinformLoginBase.cs @@ -0,0 +1,15 @@ +using System; + +namespace LibationWinForms.Dialogs.Login +{ + public abstract class WinformLoginBase + { + /// True if ShowDialog's DialogResult == OK + 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; + } + } +} diff --git a/LibationWinForms/Dialogs/Login/WinformResponder.cs b/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs similarity index 62% rename from LibationWinForms/Dialogs/Login/WinformResponder.cs rename to LibationWinForms/Dialogs/Login/WinformLoginCallback.cs index 28507f4d..5e134863 100644 --- a/LibationWinForms/Dialogs/Login/WinformResponder.cs +++ b/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs @@ -5,11 +5,11 @@ using LibationWinForms.Dialogs.Login; 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)); } @@ -17,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; } @@ -25,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; } @@ -33,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); } @@ -49,15 +49,7 @@ namespace LibationWinForms.Login public void ShowApprovalNeeded() { using var dialog = new ApprovalNeededDialog(); - showDialog(dialog); - } - - /// True if ShowDialog's DialogResult == OK - 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); } } } \ No newline at end of file diff --git a/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs b/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs new file mode 100644 index 00000000..29245662 --- /dev/null +++ b/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs @@ -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"); + } + } + } +} diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs index c4faa3f1..f905c54f 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -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(); diff --git a/LibationWinForms/Program.cs b/LibationWinForms/Program.cs index 66ee6630..cb9bf0ef 100644 --- a/LibationWinForms/Program.cs +++ b/LibationWinForms/Program.cs @@ -184,7 +184,7 @@ namespace LibationWinForms identity.Invalidate(); // re-registers device - ApiExtended.CreateAsync(new Login.WinformResponder(account), account).GetAwaiter().GetResult(); + ApiExtended.CreateAsync(account, new Login.WinformLoginChoiceEager(account)).GetAwaiter().GetResult(); } catch {