diff --git a/Source/AaxDecrypter/AudiobookDownloadBase.cs b/Source/AaxDecrypter/AudiobookDownloadBase.cs
index fff17d58..9f8bb535 100644
--- a/Source/AaxDecrypter/AudiobookDownloadBase.cs
+++ b/Source/AaxDecrypter/AudiobookDownloadBase.cs
@@ -3,7 +3,6 @@ using Dinah.Core.Net.Http;
using Dinah.Core.StepRunner;
using FileManager;
using System;
-using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
@@ -225,6 +224,7 @@ namespace AaxDecrypter
}
finally
{
+ nfsp.NetworkFileStream.RequestHeaders["User-Agent"] = DownloadOptions.UserAgent;
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
}
diff --git a/Source/AudibleUtilities/ApiExtended.cs b/Source/AudibleUtilities/ApiExtended.cs
index 46f173ac..78b7002b 100644
--- a/Source/AudibleUtilities/ApiExtended.cs
+++ b/Source/AudibleUtilities/ApiExtended.cs
@@ -18,7 +18,7 @@ namespace AudibleUtilities
public class ApiExtended
{
public Api Api { get; private set; }
-
+ private const string DeviceName = "Libation";
private ApiExtended(Api api) => Api = api;
/// 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.
@@ -39,42 +39,6 @@ namespace AudibleUtilities
return new ApiExtended(api);
}
- /// Get api from existing tokens else login with native api callbacks.
- public static async Task 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);
- }
-
- /// Get api from existing tokens else login with external browser
- public static async Task 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);
- }
-
/// Get api from existing tokens. Assumes you have valid login tokens. Else exception
public static async Task CreateAsync(Account account)
{
diff --git a/Source/AudibleUtilities/AudibleUtilities.csproj b/Source/AudibleUtilities/AudibleUtilities.csproj
index 95ffd248..48394512 100644
--- a/Source/AudibleUtilities/AudibleUtilities.csproj
+++ b/Source/AudibleUtilities/AudibleUtilities.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/Source/AudibleUtilities/Mkb79Auth.cs b/Source/AudibleUtilities/Mkb79Auth.cs
index f3bc6db5..c1f06158 100644
--- a/Source/AudibleUtilities/Mkb79Auth.cs
+++ b/Source/AudibleUtilities/Mkb79Auth.cs
@@ -4,13 +4,14 @@ using System.Linq;
using System.Threading.Tasks;
using AudibleApi;
using AudibleApi.Authorization;
+using AudibleApi.Cryptography;
using Dinah.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace AudibleUtilities
{
- public partial class Mkb79Auth : IIdentityMaintainer
+ public partial class Mkb79Auth : IIdentityMaintainer
{
[JsonProperty("website_cookies")]
private JObject _websiteCookies { get; set; }
@@ -178,7 +179,7 @@ namespace AudibleUtilities
LocaleCode = account.Locale.CountryCode,
RefreshToken = account.IdentityTokens.RefreshToken.Value,
StoreAuthenticationCookie = account.IdentityTokens.StoreAuthenticationCookie,
- WebsiteCookies = new(account.IdentityTokens.Cookies.ToKeyValuePair()),
+ WebsiteCookies = new(account.IdentityTokens.Cookies),
};
}
diff --git a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml
index 8e63af27..c49b9dbb 100644
--- a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml
+++ b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml
@@ -36,6 +36,7 @@
Width="60"
Height="30"
Content="X"
+ HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}"
Click="DeleteButton_Clicked" />
diff --git a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs
index b12816ee..606af6ac 100644
--- a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs
+++ b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs
@@ -9,26 +9,28 @@ namespace LibationAvalonia.Dialogs.Login
{
private Account _account { get; }
+ public string DeviceName { get; } = "Libation";
+
public AvaloniaLoginCallback(Account account)
{
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
}
- public async Task Get2faCodeAsync()
+ public async Task Get2faCodeAsync(string prompt)
{
- var dialog = new _2faCodeDialog();
+ var dialog = new _2faCodeDialog(prompt);
if (await ShowDialog(dialog))
return dialog.Code;
return null;
}
- public async Task GetCaptchaAnswerAsync(byte[] captchaImage)
+ public async Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
{
- var dialog = new CaptchaDialog(captchaImage);
+ var dialog = new CaptchaDialog(password, captchaImage);
if (await ShowDialog(dialog))
- return dialog.Answer;
- return null;
+ return (dialog.Password, dialog.Answer);
+ return (null,null);
}
public async Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
diff --git a/Source/LibationAvalonia/Dialogs/Login/CaptchaDialog.axaml b/Source/LibationAvalonia/Dialogs/Login/CaptchaDialog.axaml
index 5fa498fe..0faaacf7 100644
--- a/Source/LibationAvalonia/Dialogs/Login/CaptchaDialog.axaml
+++ b/Source/LibationAvalonia/Dialogs/Login/CaptchaDialog.axaml
@@ -2,22 +2,22 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d" d:DesignWidth="220" d:DesignHeight="180"
- MinWidth="220" MinHeight="180"
- MaxWidth="220" MaxHeight="180"
+ mc:Ignorable="d" d:DesignWidth="220" d:DesignHeight="250"
+ MinWidth="220" MinHeight="250"
+ MaxWidth="220" MaxHeight="250"
x:Class="LibationAvalonia.Dialogs.Login.CaptchaDialog"
Title="CAPTCHA"
Icon="/Assets/libation.ico">
+ RowDefinitions="Auto,Auto,Auto,Auto,*"
+ ColumnDefinitions="Auto,*"
+ Margin="10">
@@ -30,23 +30,39 @@
+
+
+
+
+ Margin="0,10,0,0"
+ Text="{Binding Answer}" />
diff --git a/Source/LibationAvalonia/Dialogs/Login/CaptchaDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/Login/CaptchaDialog.axaml.cs
index ad796d97..cff80196 100644
--- a/Source/LibationAvalonia/Dialogs/Login/CaptchaDialog.axaml.cs
+++ b/Source/LibationAvalonia/Dialogs/Login/CaptchaDialog.axaml.cs
@@ -1,5 +1,7 @@
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
+using LibationAvalonia.ViewModels;
+using ReactiveUI;
using System.IO;
using System.Threading.Tasks;
@@ -7,18 +9,35 @@ namespace LibationAvalonia.Dialogs.Login
{
public partial class CaptchaDialog : DialogWindow
{
- public string Answer { get; set; }
- public Bitmap CaptchaImage { get; }
+ public string Password => _viewModel.Password;
+ public string Answer => _viewModel.Answer;
+
+ private CaptchaDialogViewModel _viewModel;
public CaptchaDialog()
{
InitializeComponent();
}
- public CaptchaDialog(byte[] captchaImage) :this()
+ public CaptchaDialog(string password, byte[] captchaImage) :this()
{
- using var ms = new MemoryStream(captchaImage);
- CaptchaImage = new Bitmap(ms);
- DataContext = this;
+ //Avalonia doesn't support animated gifs.
+ //Deconstruct gifs into frames and manually switch them.
+ using var gif = SixLabors.ImageSharp.Image.Load(captchaImage);
+ var gifEncoder = new SixLabors.ImageSharp.Formats.Gif.GifEncoder();
+ var gifFrames = new Bitmap[gif.Frames.Count];
+
+ for (int i = 0; i < gif.Frames.Count; i++)
+ {
+ using var framems = new MemoryStream();
+
+ using var clonedFrame = gif.Frames.CloneFrame(i);
+
+ clonedFrame.Save(framems, gifEncoder);
+ framems.Position = 0;
+ gifFrames[i] = new Bitmap(framems);
+ }
+
+ DataContext = _viewModel = new(password, gifFrames);
}
private void InitializeComponent()
@@ -26,15 +45,59 @@ namespace LibationAvalonia.Dialogs.Login
AvaloniaXamlLoader.Load(this);
}
-
- protected override Task SaveAndCloseAsync()
+ protected override async Task SaveAndCloseAsync()
{
- Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer });
+ if (string.IsNullOrWhiteSpace(_viewModel.Password))
+ {
+ await MessageBox.Show(this, "Please re-enter your password");
+ return;
+ }
- return base.SaveAndCloseAsync();
+ Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { _viewModel.Answer });
+
+ await _viewModel.StopAsync();
+ await base.SaveAndCloseAsync();
}
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
}
+
+ public class CaptchaDialogViewModel : ViewModelBase
+ {
+ public string Answer { get; set; }
+ public string Password { get; set; }
+ public Bitmap CaptchaImage { get => _captchaImage; private set => this.RaiseAndSetIfChanged(ref _captchaImage, value); }
+
+ private Bitmap _captchaImage;
+ private bool keepSwitching = true;
+ private readonly Task FrameSwitch;
+ private readonly Bitmap[] GifFrames;
+ private const int FRAME_INTERVAL_MS = 100;
+
+ public CaptchaDialogViewModel(string password, Bitmap[] gifFrames)
+ {
+ Password = password;
+ GifFrames = gifFrames;
+ FrameSwitch = SwitchFramesAsync();
+ }
+
+ public async Task StopAsync()
+ {
+ keepSwitching = false;
+ await FrameSwitch;
+ }
+
+ private async Task SwitchFramesAsync()
+ {
+ int index = 0;
+ while(keepSwitching)
+ {
+ CaptchaImage = GifFrames[index++];
+
+ index %= GifFrames.Length;
+ await Task.Delay(FRAME_INTERVAL_MS);
+ }
+ }
+ }
}
diff --git a/Source/LibationAvalonia/Dialogs/Login/LoginExternalDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/Login/LoginExternalDialog.axaml.cs
index 85f1cb1a..125e3813 100644
--- a/Source/LibationAvalonia/Dialogs/Login/LoginExternalDialog.axaml.cs
+++ b/Source/LibationAvalonia/Dialogs/Login/LoginExternalDialog.axaml.cs
@@ -1,4 +1,3 @@
-using AudibleApi;
using AudibleUtilities;
using Avalonia;
using Avalonia.Controls;
@@ -49,7 +48,7 @@ namespace LibationAvalonia.Dialogs.Login
protected override async Task SaveAndCloseAsync()
{
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { ResponseUrl });
- if (!Uri.TryCreate(ResponseUrl, UriKind.Absolute, out var result))
+ if (!Uri.TryCreate(ResponseUrl, UriKind.Absolute, out _))
{
await MessageBox.Show("Invalid response URL");
return;
diff --git a/Source/LibationAvalonia/Dialogs/Login/MfaDialog.axaml b/Source/LibationAvalonia/Dialogs/Login/MfaDialog.axaml
index a9a7df6b..264f2d05 100644
--- a/Source/LibationAvalonia/Dialogs/Login/MfaDialog.axaml
+++ b/Source/LibationAvalonia/Dialogs/Login/MfaDialog.axaml
@@ -2,9 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="160"
- MinWidth="400" MinHeight="160"
- MaxWidth="400" MaxHeight="160"
+ mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="200"
+ MinWidth="400" MinHeight="200"
+ MaxWidth="400" MaxHeight="200"
x:Class="LibationAvalonia.Dialogs.Login.MfaDialog"
Title="Two-Step Verification"
Icon="/Assets/libation.ico">
diff --git a/Source/LibationAvalonia/Dialogs/Login/_2faCodeDialog.axaml b/Source/LibationAvalonia/Dialogs/Login/_2faCodeDialog.axaml
index 325bfb9d..2b50de62 100644
--- a/Source/LibationAvalonia/Dialogs/Login/_2faCodeDialog.axaml
+++ b/Source/LibationAvalonia/Dialogs/Login/_2faCodeDialog.axaml
@@ -2,30 +2,38 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d" d:DesignWidth="140" d:DesignHeight="100"
- MinWidth="140" MinHeight="100"
- MaxWidth="140" MaxHeight="100"
+ mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200"
+ MinWidth="200" MinHeight="200"
+ MaxWidth="200" MaxHeight="200"
x:Class="LibationAvalonia.Dialogs.Login._2faCodeDialog"
Title="2FA Code"
Icon="/Assets/libation.ico">
-
+
+
+
diff --git a/Source/LibationAvalonia/Dialogs/Login/_2faCodeDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/Login/_2faCodeDialog.axaml.cs
index 4e82bce9..580603bc 100644
--- a/Source/LibationAvalonia/Dialogs/Login/_2faCodeDialog.axaml.cs
+++ b/Source/LibationAvalonia/Dialogs/Login/_2faCodeDialog.axaml.cs
@@ -8,10 +8,14 @@ namespace LibationAvalonia.Dialogs.Login
public partial class _2faCodeDialog : DialogWindow
{
public string Code { get; set; }
+ public string Prompt { get; }
- public _2faCodeDialog()
+
+ public _2faCodeDialog() => InitializeComponent();
+
+ public _2faCodeDialog(string prompt) : this()
{
- InitializeComponent();
+ Prompt = prompt;
DataContext = this;
}
diff --git a/Source/LibationAvalonia/LibationAvalonia.csproj b/Source/LibationAvalonia/LibationAvalonia.csproj
index 40399412..9987e0dd 100644
--- a/Source/LibationAvalonia/LibationAvalonia.csproj
+++ b/Source/LibationAvalonia/LibationAvalonia.csproj
@@ -109,7 +109,8 @@
-
+
+
diff --git a/Source/LibationAvalonia/Views/MainWindow.ScanAuto.cs b/Source/LibationAvalonia/Views/MainWindow.ScanAuto.cs
index 422872e4..f201e57f 100644
--- a/Source/LibationAvalonia/Views/MainWindow.ScanAuto.cs
+++ b/Source/LibationAvalonia/Views/MainWindow.ScanAuto.cs
@@ -9,7 +9,6 @@ using System.Linq;
namespace LibationAvalonia.Views
{
- //DONE
public partial class MainWindow
{
private InterruptableTimer autoScanTimer;
@@ -17,11 +16,7 @@ namespace LibationAvalonia.Views
private void Configure_ScanAuto()
{
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
- var hours = 0;
- var minutes = 5;
- var seconds = 0;
- var _5_minutes = new TimeSpan(hours, minutes, seconds);
- autoScanTimer = new InterruptableTimer(_5_minutes);
+ autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
autoScanTimer.Elapsed += async (_, __) =>
@@ -44,9 +39,9 @@ namespace LibationAvalonia.Views
};
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
-
+
// if enabled: begin on load
- Load += startAutoScan;
+ Opened += startAutoScan;
// if new 'default' account is added, run autoscan
AccountsSettingsPersister.Saving += accountsPreSave;
diff --git a/Source/LibationWinForms/Dialogs/Login/CaptchaDialog.Designer.cs b/Source/LibationWinForms/Dialogs/Login/CaptchaDialog.Designer.cs
index 8a50529f..a3e99abc 100644
--- a/Source/LibationWinForms/Dialogs/Login/CaptchaDialog.Designer.cs
+++ b/Source/LibationWinForms/Dialogs/Login/CaptchaDialog.Designer.cs
@@ -28,68 +28,95 @@
///
private void InitializeComponent()
{
- this.captchaPb = new System.Windows.Forms.PictureBox();
- this.answerTb = new System.Windows.Forms.TextBox();
- this.submitBtn = new System.Windows.Forms.Button();
- this.answerLbl = new System.Windows.Forms.Label();
- ((System.ComponentModel.ISupportInitialize)(this.captchaPb)).BeginInit();
- this.SuspendLayout();
+ captchaPb = new System.Windows.Forms.PictureBox();
+ answerTb = new System.Windows.Forms.TextBox();
+ submitBtn = new System.Windows.Forms.Button();
+ answerLbl = new System.Windows.Forms.Label();
+ label1 = new System.Windows.Forms.Label();
+ passwordTb = new System.Windows.Forms.TextBox();
+ ((System.ComponentModel.ISupportInitialize)captchaPb).BeginInit();
+ SuspendLayout();
//
// captchaPb
//
- this.captchaPb.Location = new System.Drawing.Point(12, 12);
- this.captchaPb.Name = "captchaPb";
- this.captchaPb.Size = new System.Drawing.Size(200, 70);
- this.captchaPb.TabIndex = 0;
- this.captchaPb.TabStop = false;
+ captchaPb.Location = new System.Drawing.Point(13, 14);
+ captchaPb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ captchaPb.Name = "captchaPb";
+ captchaPb.Size = new System.Drawing.Size(235, 81);
+ captchaPb.TabIndex = 0;
+ captchaPb.TabStop = false;
//
// answerTb
//
- this.answerTb.Location = new System.Drawing.Point(118, 88);
- this.answerTb.Name = "answerTb";
- this.answerTb.Size = new System.Drawing.Size(94, 20);
- this.answerTb.TabIndex = 1;
+ answerTb.Location = new System.Drawing.Point(136, 130);
+ answerTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ answerTb.Name = "answerTb";
+ answerTb.Size = new System.Drawing.Size(111, 23);
+ answerTb.TabIndex = 2;
//
// submitBtn
//
- this.submitBtn.Location = new System.Drawing.Point(137, 114);
- this.submitBtn.Name = "submitBtn";
- this.submitBtn.Size = new System.Drawing.Size(75, 23);
- this.submitBtn.TabIndex = 2;
- this.submitBtn.Text = "Submit";
- this.submitBtn.UseVisualStyleBackColor = true;
- this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
+ submitBtn.Location = new System.Drawing.Point(159, 171);
+ submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ submitBtn.Name = "submitBtn";
+ submitBtn.Size = new System.Drawing.Size(88, 27);
+ submitBtn.TabIndex = 2;
+ submitBtn.Text = "Submit";
+ submitBtn.UseVisualStyleBackColor = true;
+ submitBtn.Click += submitBtn_Click;
//
// answerLbl
//
- this.answerLbl.AutoSize = true;
- this.answerLbl.Location = new System.Drawing.Point(12, 91);
- this.answerLbl.Name = "answerLbl";
- this.answerLbl.Size = new System.Drawing.Size(100, 13);
- this.answerLbl.TabIndex = 0;
- this.answerLbl.Text = "CAPTCHA answer: ";
+ answerLbl.AutoSize = true;
+ answerLbl.Location = new System.Drawing.Point(13, 133);
+ answerLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ answerLbl.Name = "answerLbl";
+ answerLbl.Size = new System.Drawing.Size(106, 15);
+ answerLbl.TabIndex = 0;
+ answerLbl.Text = "CAPTCHA answer: ";
+ //
+ // label1
+ //
+ label1.AutoSize = true;
+ label1.Location = new System.Drawing.Point(13, 104);
+ label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ label1.Name = "label1";
+ label1.Size = new System.Drawing.Size(60, 15);
+ label1.TabIndex = 0;
+ label1.Text = "Password:";
+ //
+ // passwordTb
+ //
+ passwordTb.Location = new System.Drawing.Point(81, 101);
+ passwordTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ passwordTb.Name = "passwordTb";
+ passwordTb.PasswordChar = '*';
+ passwordTb.Size = new System.Drawing.Size(167, 23);
+ passwordTb.TabIndex = 1;
//
// CaptchaDialog
//
- this.AcceptButton = this.submitBtn;
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(224, 149);
- this.Controls.Add(this.answerLbl);
- this.Controls.Add(this.submitBtn);
- this.Controls.Add(this.answerTb);
- this.Controls.Add(this.captchaPb);
- this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
- this.MaximizeBox = false;
- this.MinimizeBox = false;
- this.Name = "CaptchaDialog";
- this.ShowIcon = false;
- this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
- this.Text = "CAPTCHA";
- ((System.ComponentModel.ISupportInitialize)(this.captchaPb)).EndInit();
- this.ResumeLayout(false);
- this.PerformLayout();
-
+ AcceptButton = submitBtn;
+ AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ ClientSize = new System.Drawing.Size(261, 210);
+ Controls.Add(passwordTb);
+ Controls.Add(label1);
+ Controls.Add(answerLbl);
+ Controls.Add(submitBtn);
+ Controls.Add(answerTb);
+ Controls.Add(captchaPb);
+ FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ MaximizeBox = false;
+ MinimizeBox = false;
+ Name = "CaptchaDialog";
+ ShowIcon = false;
+ StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ Text = "CAPTCHA";
+ ((System.ComponentModel.ISupportInitialize)captchaPb).EndInit();
+ ResumeLayout(false);
+ PerformLayout();
}
#endregion
@@ -98,5 +125,7 @@
private System.Windows.Forms.TextBox answerTb;
private System.Windows.Forms.Button submitBtn;
private System.Windows.Forms.Label answerLbl;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.TextBox passwordTb;
}
}
\ No newline at end of file
diff --git a/Source/LibationWinForms/Dialogs/Login/CaptchaDialog.cs b/Source/LibationWinForms/Dialogs/Login/CaptchaDialog.cs
index ad5264d4..38ef825f 100644
--- a/Source/LibationWinForms/Dialogs/Login/CaptchaDialog.cs
+++ b/Source/LibationWinForms/Dialogs/Login/CaptchaDialog.cs
@@ -9,23 +9,37 @@ namespace LibationWinForms.Dialogs.Login
public partial class CaptchaDialog : Form
{
public string Answer { get; private set; }
+ public string Password { get; private set; }
private MemoryStream ms { get; }
private Image image { get; }
- public CaptchaDialog(byte[] captchaImage)
+ public CaptchaDialog() => InitializeComponent();
+ public CaptchaDialog(string password, byte[] captchaImage) : this()
{
- InitializeComponent();
this.FormClosed += (_, __) => { ms?.Dispose(); image?.Dispose(); };
ms = new MemoryStream(captchaImage);
image = Image.FromStream(ms);
this.captchaPb.Image = image;
+
+ passwordTb.Text = password;
+ passwordTb.Enabled = string.IsNullOrEmpty(password);
+
+ if (passwordTb.Enabled)
+ answerTb.Select();
}
private void submitBtn_Click(object sender, EventArgs e)
{
- Answer = this.answerTb.Text;
+ if (string.IsNullOrWhiteSpace(passwordTb.Text))
+ {
+ MessageBox.Show(this, "Please re-enter your password");
+ return;
+ }
+
+ Answer = answerTb.Text;
+ Password = passwordTb.Text;
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer });
diff --git a/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs b/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs
index 4728b8ea..fd3e0e83 100644
--- a/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs
+++ b/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs
@@ -10,25 +10,27 @@ namespace LibationWinForms.Login
{
private Account _account { get; }
+ public string DeviceName { get; } = "Libation";
+
public WinformLoginCallback(Account account)
{
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
}
- public Task Get2faCodeAsync()
+ public Task Get2faCodeAsync(string prompt)
{
- using var dialog = new _2faCodeDialog();
+ using var dialog = new _2faCodeDialog(prompt);
if (ShowDialog(dialog))
return Task.FromResult(dialog.Code);
return Task.FromResult(null);
}
- public Task GetCaptchaAnswerAsync(byte[] captchaImage)
+ public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
{
- using var dialog = new CaptchaDialog(captchaImage);
+ using var dialog = new CaptchaDialog(password, captchaImage);
if (ShowDialog(dialog))
- return Task.FromResult(dialog.Answer);
- return Task.FromResult(null);
+ return Task.FromResult((dialog.Password, dialog.Answer));
+ return Task.FromResult<(string, string)>((null,null));
}
public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
diff --git a/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.Designer.cs b/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.Designer.cs
index ed5d4690..201fde13 100644
--- a/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.Designer.cs
+++ b/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.Designer.cs
@@ -28,62 +28,77 @@
///
private void InitializeComponent()
{
- this.submitBtn = new System.Windows.Forms.Button();
- this.codeTb = new System.Windows.Forms.TextBox();
- this.label1 = new System.Windows.Forms.Label();
- this.SuspendLayout();
+ submitBtn = new System.Windows.Forms.Button();
+ codeTb = new System.Windows.Forms.TextBox();
+ label1 = new System.Windows.Forms.Label();
+ promptLbl = new System.Windows.Forms.Label();
+ SuspendLayout();
//
// submitBtn
//
- this.submitBtn.Location = new System.Drawing.Point(15, 51);
- this.submitBtn.Name = "SaveBtn";
- this.submitBtn.Size = new System.Drawing.Size(79, 23);
- this.submitBtn.TabIndex = 1;
- this.submitBtn.Text = "Submit";
- this.submitBtn.UseVisualStyleBackColor = true;
- this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
+ submitBtn.Location = new System.Drawing.Point(18, 108);
+ submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ submitBtn.Name = "submitBtn";
+ submitBtn.Size = new System.Drawing.Size(191, 27);
+ submitBtn.TabIndex = 1;
+ submitBtn.Text = "Submit";
+ submitBtn.UseVisualStyleBackColor = true;
+ submitBtn.Click += submitBtn_Click;
//
// codeTb
//
- this.codeTb.Location = new System.Drawing.Point(15, 25);
- this.codeTb.Name = "newTagsTb";
- this.codeTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
- this.codeTb.Size = new System.Drawing.Size(79, 20);
- this.codeTb.TabIndex = 0;
+ codeTb.Location = new System.Drawing.Point(108, 79);
+ codeTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ codeTb.Name = "codeTb";
+ codeTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
+ codeTb.Size = new System.Drawing.Size(101, 23);
+ codeTb.TabIndex = 0;
//
// label1
//
- this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(12, 9);
- this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(82, 13);
- this.label1.TabIndex = 2;
- this.label1.Text = "Enter 2FA Code";
+ label1.AutoSize = true;
+ label1.Location = new System.Drawing.Point(13, 82);
+ label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ label1.Name = "label1";
+ label1.Size = new System.Drawing.Size(87, 15);
+ label1.TabIndex = 2;
+ label1.Text = "Enter 2FA Code";
+ //
+ // promptLbl
+ //
+ promptLbl.Location = new System.Drawing.Point(13, 9);
+ promptLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ promptLbl.Name = "promptLbl";
+ promptLbl.Size = new System.Drawing.Size(196, 59);
+ promptLbl.TabIndex = 2;
+ promptLbl.Text = "[Prompt]";
//
// _2faCodeDialog
//
- this.AcceptButton = this.submitBtn;
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(106, 86);
- this.Controls.Add(this.label1);
- this.Controls.Add(this.codeTb);
- this.Controls.Add(this.submitBtn);
- this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
- this.MaximizeBox = false;
- this.MinimizeBox = false;
- this.Name = "_2faCodeDialog";
- this.ShowIcon = false;
- this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
- this.Text = "2FA Code";
- this.ResumeLayout(false);
- this.PerformLayout();
-
+ AcceptButton = submitBtn;
+ AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ ClientSize = new System.Drawing.Size(222, 147);
+ Controls.Add(promptLbl);
+ Controls.Add(label1);
+ Controls.Add(codeTb);
+ Controls.Add(submitBtn);
+ FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ MaximizeBox = false;
+ MinimizeBox = false;
+ Name = "_2faCodeDialog";
+ ShowIcon = false;
+ StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ Text = "2FA Code";
+ ResumeLayout(false);
+ PerformLayout();
}
#endregion
private System.Windows.Forms.Button submitBtn;
private System.Windows.Forms.TextBox codeTb;
private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Label promptLbl;
}
}
\ No newline at end of file
diff --git a/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.cs b/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.cs
index 4156468a..343c3488 100644
--- a/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.cs
+++ b/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.cs
@@ -8,9 +8,10 @@ namespace LibationWinForms.Dialogs.Login
{
public string Code { get; private set; }
- public _2faCodeDialog()
+ public _2faCodeDialog() => InitializeComponent();
+ public _2faCodeDialog(string prompt) : this()
{
- InitializeComponent();
+ promptLbl.Text = prompt;
}
private void submitBtn_Click(object sender, EventArgs e)
diff --git a/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.resx b/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.resx
index e8ae276d..f298a7be 100644
--- a/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.resx
+++ b/Source/LibationWinForms/Dialogs/Login/_2faCodeDialog.resx
@@ -1,5 +1,4 @@
-
-
+
diff --git a/Source/LibationWinForms/Form1.ScanAuto.cs b/Source/LibationWinForms/Form1.ScanAuto.cs
index 916d49b6..cd7f0963 100644
--- a/Source/LibationWinForms/Form1.ScanAuto.cs
+++ b/Source/LibationWinForms/Form1.ScanAuto.cs
@@ -17,11 +17,8 @@ namespace LibationWinForms
private void Configure_ScanAuto()
{
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
- var hours = 0;
- var minutes = 5;
- var seconds = 0;
- var _5_minutes = new TimeSpan(hours, minutes, seconds);
- autoScanTimer = new InterruptableTimer(_5_minutes);
+
+ autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
autoScanTimer.Elapsed += async (_, __) =>