mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-02-18 00:17:43 +01:00
Use xplat webview control for Audible login
- Use Avalonia-based webview control for Audible login with Chardonnay - Remove webview interfaces from IInteropFunctions - Remove Microsoft.Web.WebView2 package from WindowsConfigApp - Add Microsoft.Web.WebView2 to LibationWinForms - Remove all other login forms except the external login dialog (fallback in case webview doesn't work). The AudibleApi login with username/password doesn't work anymore. Need to use external browser login method.
This commit is contained in:
@@ -113,6 +113,7 @@ Essential: no
|
||||
Priority: optional
|
||||
Maintainer: github.com/rmcrackan
|
||||
Description: liberate your audiobooks
|
||||
Recommends: libgtk-3-0, libwebkit2gtk-4.1-0
|
||||
" >> $FOLDER_DEBIAN/control
|
||||
|
||||
echo "Changing permissions for pre- and post-install files..."
|
||||
|
||||
@@ -62,7 +62,7 @@ License: GPLv3+
|
||||
URL: https://github.com/rmcrackan/Libation
|
||||
Source0: https://github.com/rmcrackan/Libation
|
||||
|
||||
Requires: bash
|
||||
Requires: bash gtk3 webkit2gtk4.1
|
||||
|
||||
|
||||
%define __os_install_post %{nil}
|
||||
|
||||
@@ -23,11 +23,6 @@ namespace LibationAvalonia
|
||||
? dialogWindow.ShowDialog<DialogResult>(window)
|
||||
: Task.FromResult(DialogResult.None);
|
||||
|
||||
public static Task<DialogResult> ShowDialogAsync(this Dialogs.Login.WebLoginDialog dialogWindow, Window? owner = null)
|
||||
=> ((owner ?? App.MainWindow) is Window window)
|
||||
? dialogWindow.ShowDialog<DialogResult>(window)
|
||||
: Task.FromResult(DialogResult.None);
|
||||
|
||||
public static Window? GetParentWindow(this Control control) => control.GetVisualRoot() as Window;
|
||||
|
||||
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Controls;
|
||||
|
||||
#nullable enable
|
||||
public class NativeWebView : NativeControlHost, IWebView
|
||||
{
|
||||
private IWebViewAdapter? _webViewAdapter;
|
||||
private Uri? _delayedSource;
|
||||
private TaskCompletionSource _webViewReadyCompletion = new();
|
||||
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
|
||||
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
|
||||
public event EventHandler? DOMContentLoaded;
|
||||
|
||||
public bool CanGoBack => _webViewAdapter?.CanGoBack ?? false;
|
||||
|
||||
public bool CanGoForward => _webViewAdapter?.CanGoForward ?? false;
|
||||
|
||||
public Uri? Source
|
||||
{
|
||||
get => _webViewAdapter?.Source ?? throw new InvalidOperationException("Control was not initialized");
|
||||
set
|
||||
{
|
||||
if (_webViewAdapter is null)
|
||||
{
|
||||
_delayedSource = value;
|
||||
return;
|
||||
}
|
||||
_webViewAdapter.Source = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
return _webViewAdapter?.GoBack() ?? throw new InvalidOperationException("Control was not initialized");
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
return _webViewAdapter?.GoForward() ?? throw new InvalidOperationException("Control was not initialized");
|
||||
}
|
||||
|
||||
public Task<string?> InvokeScriptAsync(string scriptName)
|
||||
{
|
||||
return _webViewAdapter is null
|
||||
? throw new InvalidOperationException("Control was not initialized")
|
||||
: _webViewAdapter.InvokeScriptAsync(scriptName);
|
||||
}
|
||||
|
||||
public void Navigate(Uri url)
|
||||
{
|
||||
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
|
||||
.Navigate(url);
|
||||
}
|
||||
|
||||
public Task NavigateToString(string text)
|
||||
{
|
||||
return (_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
|
||||
.NavigateToString(text);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
|
||||
.Refresh();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
|
||||
.Stop();
|
||||
}
|
||||
|
||||
public Task WaitForNativeHost()
|
||||
{
|
||||
return _webViewReadyCompletion.Task;
|
||||
}
|
||||
|
||||
private class PlatformHandle : IPlatformHandle
|
||||
{
|
||||
public nint Handle { get; init; }
|
||||
|
||||
public string? HandleDescriptor { get; init; }
|
||||
}
|
||||
|
||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||
{
|
||||
_webViewAdapter = InteropFactory.Create().CreateWebViewAdapter();
|
||||
|
||||
if (_webViewAdapter is null)
|
||||
return base.CreateNativeControlCore(parent);
|
||||
else
|
||||
{
|
||||
SubscribeOnEvents();
|
||||
var handle = new PlatformHandle
|
||||
{
|
||||
Handle = _webViewAdapter.PlatformHandle.Handle,
|
||||
HandleDescriptor = _webViewAdapter.PlatformHandle.HandleDescriptor
|
||||
};
|
||||
|
||||
if (_delayedSource is not null)
|
||||
{
|
||||
_webViewAdapter.Source = _delayedSource;
|
||||
}
|
||||
|
||||
_webViewReadyCompletion.TrySetResult();
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeOnEvents()
|
||||
{
|
||||
if (_webViewAdapter is not null)
|
||||
{
|
||||
_webViewAdapter.NavigationStarted += WebViewAdapterOnNavigationStarted;
|
||||
_webViewAdapter.NavigationCompleted += WebViewAdapterOnNavigationCompleted;
|
||||
_webViewAdapter.DOMContentLoaded += _webViewAdapter_DOMContentLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
private void _webViewAdapter_DOMContentLoaded(object? sender, EventArgs e)
|
||||
{
|
||||
DOMContentLoaded?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void WebViewAdapterOnNavigationStarted(object? sender, WebViewNavigationEventArgs e)
|
||||
{
|
||||
NavigationStarted?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void WebViewAdapterOnNavigationCompleted(object? sender, WebViewNavigationEventArgs e)
|
||||
{
|
||||
NavigationCompleted?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == BoundsProperty && change.NewValue is Rect rect)
|
||||
{
|
||||
var scaling = (float)(VisualRoot?.RenderScaling ?? 1.0f);
|
||||
_webViewAdapter?.HandleResize((int)(rect.Width * scaling), (int)(rect.Height * scaling), scaling);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (_webViewAdapter != null)
|
||||
{
|
||||
e.Handled = _webViewAdapter.HandleKeyDown((uint)e.Key, (uint)e.KeyModifiers);
|
||||
}
|
||||
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||
{
|
||||
if (_webViewAdapter is not null)
|
||||
{
|
||||
_webViewReadyCompletion = new TaskCompletionSource();
|
||||
_webViewAdapter.NavigationStarted -= WebViewAdapterOnNavigationStarted;
|
||||
_webViewAdapter.NavigationCompleted -= WebViewAdapterOnNavigationCompleted;
|
||||
(_webViewAdapter as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
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="240" d:DesignHeight="140"
|
||||
MinWidth="240" MinHeight="140"
|
||||
MaxWidth="240" MaxHeight="140"
|
||||
Width="240" Height="140"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.ApprovalNeededDialog"
|
||||
Title="Approval Alert Detected">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="10"
|
||||
TextWrapping="Wrap"
|
||||
Text="Amazon is sending you an email."/>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1" Margin="10,0,10,0"
|
||||
TextWrapping="Wrap"
|
||||
Text="Please press this button after you've approved the notification."/>
|
||||
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
VerticalAlignment="Bottom"
|
||||
Padding="30,3,30,3"
|
||||
Content="Approve"
|
||||
Click="Approve_Click" />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,22 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class ApprovalNeededDialog : DialogWindow
|
||||
{
|
||||
public ApprovalNeededDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override Task SaveAndCloseAsync()
|
||||
{
|
||||
Serilog.Log.Logger.Information("Approve button clicked");
|
||||
|
||||
return base.SaveAndCloseAsync();
|
||||
}
|
||||
|
||||
public async void Approve_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await SaveAndCloseAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,19 @@
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Threading;
|
||||
using LibationUiBase.Forms;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public class AvaloniaLoginCallback : ILoginCallback
|
||||
{
|
||||
private Account _account { get; }
|
||||
|
||||
public string DeviceName { get; } = "Libation";
|
||||
|
||||
public AvaloniaLoginCallback(Account account)
|
||||
{
|
||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
}
|
||||
|
||||
public async Task<string> Get2faCodeAsync(string prompt)
|
||||
=> await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
var dialog = new _2faCodeDialog(prompt);
|
||||
if (await dialog.ShowDialogAsync() is DialogResult.OK)
|
||||
return dialog.Code;
|
||||
return null;
|
||||
});
|
||||
|
||||
public async Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
|
||||
=> await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
var dialog = new CaptchaDialog(password, captchaImage);
|
||||
if (await dialog.ShowDialogAsync() is DialogResult.OK)
|
||||
return (dialog.Password, dialog.Answer);
|
||||
return (null, null);
|
||||
});
|
||||
|
||||
public async Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
|
||||
=> await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
var dialog = new MfaDialog(mfaConfig);
|
||||
if (await dialog.ShowDialogAsync() is DialogResult.OK)
|
||||
return (dialog.SelectedName, dialog.SelectedValue);
|
||||
return (null, null);
|
||||
});
|
||||
|
||||
public async Task<(string email, string password)> GetLoginAsync()
|
||||
=> await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
var dialog = new LoginCallbackDialog(_account);
|
||||
if (await dialog.ShowDialogAsync() is DialogResult.OK)
|
||||
return (_account.AccountId, dialog.Password);
|
||||
return (null, null);
|
||||
});
|
||||
|
||||
public async Task ShowApprovalNeededAsync()
|
||||
=> await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
var dialog = new ApprovalNeededDialog();
|
||||
await dialog.ShowDialogAsync();
|
||||
});
|
||||
public Task<string> Get2faCodeAsync(string prompt) => throw new System.NotSupportedException();
|
||||
public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
|
||||
=> throw new System.NotSupportedException();
|
||||
public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
|
||||
=> throw new System.NotSupportedException();
|
||||
public Task<(string email, string password)> GetLoginAsync()
|
||||
=> throw new System.NotSupportedException();
|
||||
public Task ShowApprovalNeededAsync() => throw new System.NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase.Forms;
|
||||
@@ -11,14 +12,13 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public class AvaloniaLoginChoiceEager : ILoginChoiceEager
|
||||
{
|
||||
public ILoginCallback LoginCallback { get; }
|
||||
public ILoginCallback LoginCallback { get; } = new AvaloniaLoginCallback();
|
||||
|
||||
private readonly Account _account;
|
||||
|
||||
public AvaloniaLoginChoiceEager(Account account)
|
||||
{
|
||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
LoginCallback = new AvaloniaLoginCallback(_account);
|
||||
}
|
||||
|
||||
public async Task<ChoiceOut?> StartAsync(ChoiceIn choiceIn)
|
||||
@@ -26,41 +26,84 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
|
||||
private async Task<ChoiceOut?> StartAsyncInternal(ChoiceIn choiceIn)
|
||||
{
|
||||
if (Configuration.IsWindows && Environment.OSVersion.Version.Major >= 10)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var weblogin = new WebLoginDialog(_account.AccountId, choiceIn.LoginUrl);
|
||||
if (await weblogin.ShowDialogAsync(App.MainWindow) is DialogResult.OK)
|
||||
return ChoiceOut.External(weblogin.ResponseUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"Failed to run {nameof(WebLoginDialog)}");
|
||||
}
|
||||
if (await BrowserLoginAsync(choiceIn.LoginUrl) is ChoiceOut external)
|
||||
return external;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"Failed to use the {nameof(NativeWebDialog)}");
|
||||
}
|
||||
|
||||
var dialog = new LoginChoiceEagerDialog(_account);
|
||||
var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
|
||||
return await externalDialog.ShowDialogAsync() is DialogResult.OK
|
||||
? ChoiceOut.External(externalDialog.ResponseUrl)
|
||||
: null;
|
||||
}
|
||||
|
||||
if (await dialog.ShowDialogAsync() is not DialogResult.OK ||
|
||||
(dialog.LoginMethod is LoginMethod.Api && string.IsNullOrWhiteSpace(dialog.Password)))
|
||||
return null;
|
||||
private async Task<ChoiceOut?> BrowserLoginAsync(string url)
|
||||
{
|
||||
TaskCompletionSource<ChoiceOut?> tcs = new();
|
||||
|
||||
switch (dialog.LoginMethod)
|
||||
NativeWebDialog dialog = new()
|
||||
{
|
||||
case LoginMethod.Api:
|
||||
return ChoiceOut.WithApi(dialog.Account.AccountId, dialog.Password);
|
||||
case LoginMethod.External:
|
||||
{
|
||||
var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
|
||||
return await externalDialog.ShowDialogAsync() is DialogResult.OK
|
||||
? ChoiceOut.External(externalDialog.ResponseUrl)
|
||||
: null;
|
||||
}
|
||||
default:
|
||||
throw new Exception($"Unknown {nameof(LoginMethod)} value");
|
||||
Title = "Audible Login",
|
||||
CanUserResize = true,
|
||||
Source = new Uri(url)
|
||||
};
|
||||
|
||||
dialog.AdapterCreated += Dialog_AdapterCreated;
|
||||
dialog.NavigationCompleted += Dialog_NavigationCompleted;
|
||||
dialog.Closing += (_, _) => tcs.TrySetResult(null);
|
||||
dialog.NavigationStarted += (_, e) =>
|
||||
{
|
||||
if (e.Request?.AbsolutePath.StartsWith("/ap/maplanding") is true)
|
||||
{
|
||||
tcs.TrySetResult(ChoiceOut.External(e.Request.ToString()));
|
||||
dialog.Close();
|
||||
}
|
||||
};
|
||||
|
||||
if (!Configuration.IsLinux && App.MainWindow is TopLevel topLevel)
|
||||
dialog.Show(topLevel);
|
||||
else
|
||||
dialog.Show();
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
private async void Dialog_NavigationCompleted(object? sender, WebViewNavigationCompletedEventArgs e)
|
||||
{
|
||||
if (e.IsSuccess && sender is NativeWebDialog dialog)
|
||||
{
|
||||
await dialog.InvokeScript(getScript(_account.AccountId));
|
||||
}
|
||||
}
|
||||
|
||||
private void Dialog_AdapterCreated(object? sender, WebViewAdapterEventArgs e)
|
||||
{
|
||||
if ((sender as NativeWebDialog)?.TryGetWindow() is Window window)
|
||||
{
|
||||
window.Width = 450;
|
||||
window.Height = 700;
|
||||
}
|
||||
}
|
||||
|
||||
private static string getScript(string accountID) => $$"""
|
||||
(function() {
|
||||
function populateForm(){
|
||||
var email = document.querySelector("input[name='email']");
|
||||
if (email !== null)
|
||||
email.value = '{{accountID}}';
|
||||
|
||||
var pass = document.querySelector("input[name='password']");
|
||||
if (pass !== null)
|
||||
pass.focus();
|
||||
}
|
||||
window.addEventListener("load", (event) => { populateForm(); });
|
||||
populateForm();
|
||||
})()
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
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="250"
|
||||
MinWidth="220" MinHeight="250"
|
||||
MaxWidth="220" MaxHeight="250"
|
||||
Width="220" Height="250"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.CaptchaDialog"
|
||||
Title="CAPTCHA">
|
||||
|
||||
<Grid
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,*"
|
||||
ColumnDefinitions="Auto,*"
|
||||
Margin="10">
|
||||
|
||||
<Panel
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
MinWidth="200"
|
||||
MinHeight="70"
|
||||
Background="LightGray">
|
||||
|
||||
<Image
|
||||
Stretch="None"
|
||||
Source="{Binding CaptchaImage}" />
|
||||
|
||||
</Panel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,10,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Password:" />
|
||||
|
||||
<TextBox
|
||||
Name="passwordBox"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,10,0,0"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="0,10,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="CAPTCHA
answer:" />
|
||||
|
||||
<TextBox
|
||||
Name="captchaBox"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="0,10,0,0"
|
||||
Text="{Binding Answer, Mode=TwoWay}" />
|
||||
|
||||
<Button
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Padding="0,5,0,5"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Content="Submit"
|
||||
Click="Submit_Click" />
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,120 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media.Imaging;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using ReactiveUI;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class CaptchaDialog : DialogWindow
|
||||
{
|
||||
public string Password => _viewModel.Password;
|
||||
public string Answer => _viewModel.Answer;
|
||||
|
||||
private readonly CaptchaDialogViewModel _viewModel;
|
||||
public CaptchaDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
passwordBox = this.FindControl<TextBox>(nameof(passwordBox));
|
||||
captchaBox = this.FindControl<TextBox>(nameof(captchaBox));
|
||||
}
|
||||
|
||||
public CaptchaDialog(string password, byte[] captchaImage) : 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];
|
||||
var frameDelayMs = new int[gif.Frames.Count];
|
||||
|
||||
for (int i = 0; i < gif.Frames.Count; i++)
|
||||
{
|
||||
var frameMetadata = gif.Frames[i].Metadata.GetFormatMetadata(SixLabors.ImageSharp.Formats.Gif.GifFormat.Instance);
|
||||
|
||||
using var clonedFrame = gif.Frames.CloneFrame(i);
|
||||
using var framems = new MemoryStream();
|
||||
|
||||
clonedFrame.Save(framems, gifEncoder);
|
||||
framems.Position = 0;
|
||||
|
||||
gifFrames[i] = new Bitmap(framems);
|
||||
frameDelayMs[i] = frameMetadata.FrameDelay * 10;
|
||||
}
|
||||
|
||||
DataContext = _viewModel = new(password, gifFrames, frameDelayMs);
|
||||
|
||||
Opened += (_, _) => (string.IsNullOrEmpty(password) ? passwordBox : captchaBox).Focus();
|
||||
}
|
||||
|
||||
protected override async Task SaveAndCloseAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_viewModel.Password))
|
||||
{
|
||||
await MessageBox.Show(this, "Please re-enter your password");
|
||||
return;
|
||||
}
|
||||
|
||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { _viewModel.Answer });
|
||||
|
||||
await _viewModel.StopAsync();
|
||||
await base.SaveAndCloseAsync();
|
||||
}
|
||||
|
||||
protected override async Task CancelAndCloseAsync()
|
||||
{
|
||||
await _viewModel.StopAsync();
|
||||
await base.CancelAndCloseAsync();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
public CaptchaDialogViewModel(string password, Bitmap[] gifFrames, int[] frameDelayMs)
|
||||
{
|
||||
Password = password;
|
||||
if (gifFrames.Length == 1)
|
||||
{
|
||||
FrameSwitch = Task.CompletedTask;
|
||||
CaptchaImage = gifFrames[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
FrameSwitch = SwitchFramesAsync(gifFrames, frameDelayMs);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
keepSwitching = false;
|
||||
await FrameSwitch;
|
||||
}
|
||||
|
||||
private async Task SwitchFramesAsync(Bitmap[] gifFrames, int[] frameDelayMs)
|
||||
{
|
||||
int index = 0;
|
||||
while (keepSwitching)
|
||||
{
|
||||
CaptchaImage = gifFrames[index];
|
||||
await Task.Delay(frameDelayMs[index++]);
|
||||
|
||||
index %= gifFrames.Length;
|
||||
}
|
||||
|
||||
foreach (var frame in gifFrames)
|
||||
frame.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
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="300" d:DesignHeight="120"
|
||||
MinWidth="300" MinHeight="120"
|
||||
Width="300" Height="120"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.LoginCallbackDialog"
|
||||
Title="Audible Login">
|
||||
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,*" ColumnDefinitions="*" Margin="5">
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||
<TextBlock Text="Locale: " />
|
||||
<TextBlock Text="{Binding Account.Locale.Name}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal">
|
||||
<TextBlock Text="Username: " />
|
||||
<TextBlock Text="{Binding Account.AccountId}" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Margin="0,5,0,5" Grid.Row="2" Grid.Column="0" ColumnDefinitions="Auto,*">
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="Password: " />
|
||||
<TextBox Grid.Column="1" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
|
||||
<Button
|
||||
Grid.Row="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Padding="30,5,30,5"
|
||||
Content="Submit"
|
||||
Click="Submit_Click"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,42 +0,0 @@
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Controls;
|
||||
using Dinah.Core;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class LoginCallbackDialog : DialogWindow
|
||||
{
|
||||
public Account Account { get; }
|
||||
public string Password { get; set; }
|
||||
|
||||
public LoginCallbackDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.Accounts;
|
||||
Account = accounts.FirstOrDefault();
|
||||
DataContext = this;
|
||||
}
|
||||
}
|
||||
public LoginCallbackDialog(Account account) : this()
|
||||
{
|
||||
Account = account;
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
protected override Task SaveAndCloseAsync()
|
||||
{
|
||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { email = Account?.AccountId?.ToMask(), passwordLength = Password?.Length });
|
||||
|
||||
return base.SaveAndCloseAsync();
|
||||
}
|
||||
|
||||
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await SaveAndCloseAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
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="360" d:DesignHeight="200"
|
||||
MinWidth="370" MinHeight="200"
|
||||
Width="370" Height="200"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.LoginChoiceEagerDialog"
|
||||
Title="Audible Login">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,*" ColumnDefinitions="*" Margin="5">
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBlock Text="Locale: " />
|
||||
<TextBlock Text="{Binding Account.Locale.Name}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBlock Text="Username: " />
|
||||
<TextBlock Text="{Binding Account.AccountId}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,5,0,5"
|
||||
ColumnDefinitions="Auto,*,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Password: " />
|
||||
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}" />
|
||||
<Button
|
||||
Margin="5,0"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="Submit"
|
||||
Command="{Binding SaveAndCloseAsync}" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="3"
|
||||
VerticalAlignment="Bottom">
|
||||
|
||||
<controls:LinkLabel
|
||||
Tapped="ExternalLoginLink_Tapped"
|
||||
Text="Trouble logging in? Click here to log in with your browser." />
|
||||
|
||||
<TextBlock
|
||||
TextWrapping="Wrap"
|
||||
Text="This more advanced login is recommended if you're experiencing errors logging in the conventional way above or if you're not comfortable typing your password here." />
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,50 +0,0 @@
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Controls;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class LoginChoiceEagerDialog : DialogWindow
|
||||
{
|
||||
public Account Account { get; }
|
||||
public string Password { get; set; }
|
||||
public LoginMethod LoginMethod { get; private set; }
|
||||
|
||||
public LoginChoiceEagerDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.Accounts;
|
||||
Account = accounts.FirstOrDefault();
|
||||
DataContext = this;
|
||||
}
|
||||
}
|
||||
public LoginChoiceEagerDialog(Account account) : this()
|
||||
{
|
||||
Account = account;
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
protected override async Task SaveAndCloseAsync()
|
||||
{
|
||||
if (LoginMethod is LoginMethod.Api && string.IsNullOrWhiteSpace(Password))
|
||||
{
|
||||
await MessageBox.Show(this, "Please enter your password");
|
||||
return;
|
||||
}
|
||||
|
||||
await base.SaveAndCloseAsync();
|
||||
}
|
||||
|
||||
public async void ExternalLoginLink_Tapped(object sender, Avalonia.Input.TappedEventArgs e)
|
||||
{
|
||||
LoginMethod = LoginMethod.External;
|
||||
await SaveAndCloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
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="200"
|
||||
MinWidth="400" MinHeight="200"
|
||||
MaxWidth="400" MaxHeight="400"
|
||||
Width="400" Height="200"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.MfaDialog"
|
||||
Title="Two-Step Verification">
|
||||
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
|
||||
<StackPanel Grid.Row="0" Margin="10,0,10,10" Name="rbStackPanel" Orientation="Vertical"/>
|
||||
<Button Grid.Row="1" Content="Submit" Margin="10" Padding="30,5,30,5" Click="Submit_Click" />
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,137 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using LibationUiBase.Forms;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class MfaDialog : DialogWindow
|
||||
{
|
||||
public string SelectedName { get; private set; }
|
||||
public string SelectedValue { get; private set; }
|
||||
private RbValues Values { get; } = new();
|
||||
|
||||
public MfaDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
var mfaConfig = new AudibleApi.MfaConfig { Title = "My title" };
|
||||
mfaConfig.Buttons.Add(new() { Text = "Enter the OTP from the authenticator app", Name = "otpDeviceContext", Value = "aAbBcC=, TOTP" });
|
||||
mfaConfig.Buttons.Add(new() { Text = "Send an SMS to my number ending with 123", Name = "otpDeviceContext", Value = "dDeEfE=, SMS" });
|
||||
mfaConfig.Buttons.Add(new() { Text = "Call me on my number ending with 123", Name = "otpDeviceContext", Value = "dDeEfE=, VOICE" });
|
||||
|
||||
loadRadioButtons(mfaConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public MfaDialog(AudibleApi.MfaConfig mfaConfig) : this()
|
||||
{
|
||||
loadRadioButtons(mfaConfig);
|
||||
}
|
||||
|
||||
private void loadRadioButtons(AudibleApi.MfaConfig mfaConfig)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(mfaConfig.Title))
|
||||
Title = mfaConfig.Title;
|
||||
|
||||
rbStackPanel = this.Find<StackPanel>(nameof(rbStackPanel));
|
||||
|
||||
foreach (var conf in mfaConfig.Buttons)
|
||||
{
|
||||
var rb = new RbValue(conf);
|
||||
Values.AddButton(rb);
|
||||
|
||||
RadioButton radioButton = new()
|
||||
{
|
||||
Content = new TextBlock { Text = conf.Text },
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
};
|
||||
|
||||
radioButton.Bind(
|
||||
RadioButton.IsCheckedProperty,
|
||||
new Binding
|
||||
{
|
||||
Source = rb,
|
||||
Path = nameof(rb.IsChecked)
|
||||
});
|
||||
|
||||
rbStackPanel.Children.Add(radioButton);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task SaveAndCloseAsync()
|
||||
{
|
||||
var selected = Values.CheckedButton;
|
||||
|
||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new
|
||||
{
|
||||
text = selected?.Text,
|
||||
name = selected?.Name,
|
||||
value = selected?.Value
|
||||
});
|
||||
if (selected is null)
|
||||
{
|
||||
await MessageBox.Show("No MFA option selected", "None selected", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedName = selected.Name;
|
||||
SelectedValue = selected.Value;
|
||||
|
||||
await base.SaveAndCloseAsync();
|
||||
}
|
||||
|
||||
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await SaveAndCloseAsync();
|
||||
|
||||
|
||||
private class RbValue : ViewModels.ViewModelBase
|
||||
{
|
||||
private bool _isChecked;
|
||||
public bool IsChecked
|
||||
{
|
||||
get => _isChecked;
|
||||
set => this.RaiseAndSetIfChanged(ref _isChecked, value);
|
||||
}
|
||||
public AudibleApi.MfaConfigButton MfaConfigButton { get; }
|
||||
public RbValue(AudibleApi.MfaConfigButton mfaConfig)
|
||||
{
|
||||
MfaConfigButton = mfaConfig;
|
||||
}
|
||||
}
|
||||
|
||||
private class RbValues
|
||||
{
|
||||
private List<RbValue> ButtonValues { get; } = new();
|
||||
|
||||
public AudibleApi.MfaConfigButton CheckedButton => ButtonValues.SingleOrDefault(rb => rb.IsChecked)?.MfaConfigButton;
|
||||
|
||||
public void AddButton(RbValue rbValue)
|
||||
{
|
||||
if (ButtonValues.Contains(rbValue))
|
||||
return;
|
||||
|
||||
rbValue.PropertyChanged += RbValue_PropertyChanged;
|
||||
rbValue.IsChecked = ButtonValues.Count == 0;
|
||||
ButtonValues.Add(rbValue);
|
||||
}
|
||||
|
||||
private void RbValue_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
var button = sender as RbValue;
|
||||
|
||||
if (button.IsChecked)
|
||||
{
|
||||
foreach (var rb in ButtonValues.Where(rb => rb != button))
|
||||
rb.IsChecked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
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="800" d:DesignHeight="450"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.WebLoginDialog"
|
||||
Width="500" Height="800"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="Audible Login">
|
||||
<controls:NativeWebView Name="webView" />
|
||||
</Window>
|
||||
@@ -1,55 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Dinah.Core;
|
||||
using LibationUiBase.Forms;
|
||||
using System;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class WebLoginDialog : Window
|
||||
{
|
||||
public string ResponseUrl { get; private set; }
|
||||
private readonly string accountID;
|
||||
|
||||
public WebLoginDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
webView.NavigationStarted += WebView_NavigationStarted;
|
||||
webView.DOMContentLoaded += WebView_NavigationCompleted;
|
||||
}
|
||||
|
||||
public WebLoginDialog(string accountID, string loginUrl) : this()
|
||||
{
|
||||
this.accountID = ArgumentValidator.EnsureNotNullOrWhiteSpace(accountID, nameof(accountID));
|
||||
webView.Source = new Uri(ArgumentValidator.EnsureNotNullOrWhiteSpace(loginUrl, nameof(loginUrl)));
|
||||
}
|
||||
|
||||
private void WebView_NavigationStarted(object sender, LibationFileManager.WebViewNavigationEventArgs e)
|
||||
{
|
||||
if (e.Request?.AbsolutePath.Contains("/ap/maplanding") is true)
|
||||
{
|
||||
ResponseUrl = e.Request.ToString();
|
||||
Close(DialogResult.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private async void WebView_NavigationCompleted(object sender, EventArgs e)
|
||||
{
|
||||
await webView.InvokeScriptAsync(getScript(accountID));
|
||||
}
|
||||
|
||||
private static string getScript(string accountID) => $$"""
|
||||
(function() {
|
||||
var inputs = document.getElementsByTagName('input');
|
||||
for (index = 0; index < inputs.length; ++index) {
|
||||
if (inputs[index].name.includes('email')) {
|
||||
inputs[index].value = '{{accountID}}';
|
||||
}
|
||||
if (inputs[index].name.includes('password')) {
|
||||
inputs[index].focus();
|
||||
}
|
||||
}
|
||||
})()
|
||||
""";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
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="200" d:DesignHeight="200"
|
||||
MinWidth="200" MinHeight="200"
|
||||
MaxWidth="200" MaxHeight="200"
|
||||
Width="200" Height="200"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login._2faCodeDialog"
|
||||
Title="2FA Code">
|
||||
|
||||
<Grid
|
||||
VerticalAlignment="Stretch"
|
||||
ColumnDefinitions="*" Margin="5"
|
||||
RowDefinitions="*,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Prompt}" />
|
||||
|
||||
<TextBlock
|
||||
Margin="5"
|
||||
Grid.Row="1"
|
||||
TextAlignment="Center"
|
||||
Text="Enter 2FA Code" />
|
||||
|
||||
<TextBox
|
||||
Name="_2FABox"
|
||||
Margin="5,0,5,0"
|
||||
Grid.Row="2"
|
||||
HorizontalContentAlignment="Center"
|
||||
Text="{Binding Code, Mode=TwoWay}" />
|
||||
|
||||
<Button
|
||||
Margin="5"
|
||||
Grid.Row="3"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Content="Submit"
|
||||
Click="Submit_Click" />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,35 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class _2faCodeDialog : DialogWindow
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string Prompt { get; } = "For added security, please enter the One Time Password (OTP) generated by your Authenticator App";
|
||||
|
||||
|
||||
public _2faCodeDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
_2FABox = this.FindControl<TextBox>(nameof(_2FABox));
|
||||
}
|
||||
|
||||
public _2faCodeDialog(string prompt) : this()
|
||||
{
|
||||
Prompt = prompt;
|
||||
DataContext = this;
|
||||
Opened += (_, _) => _2FABox.Focus();
|
||||
}
|
||||
|
||||
protected override Task SaveAndCloseAsync()
|
||||
{
|
||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Code });
|
||||
|
||||
return base.SaveAndCloseAsync();
|
||||
}
|
||||
|
||||
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
=> await SaveAndCloseAsync();
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationIcon>Assets/libation.ico</ApplicationIcon>
|
||||
<AssemblyName>Libation</AssemblyName>
|
||||
@@ -79,6 +78,7 @@
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.8" />
|
||||
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.8" />
|
||||
<PackageReference Include="WebViewControlAvaloniaFree" Version="11.3.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7,10 +7,6 @@ namespace LibationFileManager
|
||||
{
|
||||
public interface IInteropFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of native web view control https://github.com/maxkatz6/AvaloniaWebView
|
||||
/// </summary>
|
||||
IWebViewAdapter? CreateWebViewAdapter();
|
||||
void SetFolderIcon(string image, string directory);
|
||||
void DeleteFolderIcon(string directory);
|
||||
Process RunAsRoot(string exe, string args);
|
||||
@@ -19,39 +15,4 @@ namespace LibationFileManager
|
||||
string ReleaseIdString { get; }
|
||||
}
|
||||
|
||||
public class WebViewNavigationEventArgs : EventArgs
|
||||
{
|
||||
public Uri? Request { get; init; }
|
||||
}
|
||||
|
||||
public interface IWebView
|
||||
{
|
||||
event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
|
||||
event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
|
||||
event EventHandler? DOMContentLoaded;
|
||||
bool CanGoBack { get; }
|
||||
bool CanGoForward { get; }
|
||||
Uri? Source { get; set; }
|
||||
bool GoBack();
|
||||
bool GoForward();
|
||||
Task<string?> InvokeScriptAsync(string scriptName);
|
||||
void Navigate(Uri url);
|
||||
Task NavigateToString(string text);
|
||||
void Refresh();
|
||||
void Stop();
|
||||
}
|
||||
|
||||
public interface IWebViewAdapter : IWebView
|
||||
{
|
||||
object NativeWebView { get; }
|
||||
IPlatformHandle2 PlatformHandle { get; }
|
||||
void HandleResize(int width, int height, float zoom);
|
||||
bool HandleKeyDown(uint key, uint keyModifiers);
|
||||
}
|
||||
|
||||
public interface IPlatformHandle2
|
||||
{
|
||||
IntPtr Handle { get; }
|
||||
string? HandleDescriptor { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace LibationFileManager
|
||||
public NullInteropFunctions() { }
|
||||
public NullInteropFunctions(params object[] values) { }
|
||||
|
||||
public IWebViewAdapter? CreateWebViewAdapter() => throw new PlatformNotSupportedException();
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
public bool CanUpgrade => throw new PlatformNotSupportedException();
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
partial class ApprovalNeededDialog
|
||||
{
|
||||
/// <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.approvedBtn = new System.Windows.Forms.Button();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// approvedBtn
|
||||
//
|
||||
this.approvedBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.approvedBtn.Location = new System.Drawing.Point(18, 75);
|
||||
this.approvedBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.approvedBtn.Name = "approvedBtn";
|
||||
this.approvedBtn.Size = new System.Drawing.Size(92, 27);
|
||||
this.approvedBtn.TabIndex = 1;
|
||||
this.approvedBtn.Text = "Approved";
|
||||
this.approvedBtn.UseVisualStyleBackColor = true;
|
||||
this.approvedBtn.Click += new System.EventHandler(this.approvedBtn_Click);
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(14, 10);
|
||||
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(314, 45);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "Amazon is sending you an email.\r\n\r\nPlease press this button after you approve the" +
|
||||
" notification.";
|
||||
//
|
||||
// ApprovalNeededDialog
|
||||
//
|
||||
this.AcceptButton = this.approvedBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.ClientSize = new System.Drawing.Size(345, 115);
|
||||
this.Controls.Add(this.label1);
|
||||
this.Controls.Add(this.approvedBtn);
|
||||
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 = "ApprovalNeededDialog";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Approval Alert Detected";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Button approvedBtn;
|
||||
private System.Windows.Forms.Label label1;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public partial class ApprovalNeededDialog : Form
|
||||
{
|
||||
public ApprovalNeededDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void approvedBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Submit button clicked");
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
@@ -1,131 +0,0 @@
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
partial class CaptchaDialog
|
||||
{
|
||||
/// <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()
|
||||
{
|
||||
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
|
||||
//
|
||||
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
|
||||
//
|
||||
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
|
||||
//
|
||||
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
|
||||
//
|
||||
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
|
||||
//
|
||||
AcceptButton = submitBtn;
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
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
|
||||
|
||||
private System.Windows.Forms.PictureBox captchaPb;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
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() => InitializeComponent();
|
||||
public CaptchaDialog(string password, byte[] captchaImage) : this()
|
||||
{
|
||||
this.FormClosed += (_, __) => { ms?.Dispose(); image?.Dispose(); };
|
||||
|
||||
ms = new MemoryStream(captchaImage);
|
||||
image = Image.FromStream(ms);
|
||||
this.captchaPb.Image = image;
|
||||
|
||||
passwordTb.Text = password;
|
||||
|
||||
(string.IsNullOrEmpty(password) ? passwordTb : answerTb).Select();
|
||||
}
|
||||
|
||||
private void submitBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
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 });
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
// Close() not needed for AcceptButton
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
@@ -1,121 +0,0 @@
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
partial class LoginCallbackDialog
|
||||
{
|
||||
/// <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.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(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(88, 27);
|
||||
this.submitBtn.TabIndex = 4;
|
||||
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}";
|
||||
//
|
||||
// LoginCallbackDialog
|
||||
//
|
||||
this.AcceptButton = this.submitBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
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 = "LoginCallbackDialog";
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using AudibleUtilities;
|
||||
using Dinah.Core;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public partial class LoginCallbackDialog : Form
|
||||
{
|
||||
private string accountId { get; }
|
||||
|
||||
public string Email { get; private set; }
|
||||
public string Password { get; private set; }
|
||||
|
||||
public LoginCallbackDialog(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 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 });
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
// Close() not needed for AcceptButton
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
<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>
|
||||
@@ -1,158 +0,0 @@
|
||||
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()
|
||||
{
|
||||
passwordLbl = new System.Windows.Forms.Label();
|
||||
passwordTb = new System.Windows.Forms.TextBox();
|
||||
submitBtn = new System.Windows.Forms.Button();
|
||||
localeLbl = new System.Windows.Forms.Label();
|
||||
usernameLbl = new System.Windows.Forms.Label();
|
||||
externalLoginLink = new System.Windows.Forms.LinkLabel();
|
||||
externalLoginLbl2 = new System.Windows.Forms.Label();
|
||||
externalLoginLbl1 = new System.Windows.Forms.Label();
|
||||
SuspendLayout();
|
||||
//
|
||||
// passwordLbl
|
||||
//
|
||||
passwordLbl.AutoSize = true;
|
||||
passwordLbl.Location = new System.Drawing.Point(14, 47);
|
||||
passwordLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
passwordLbl.Name = "passwordLbl";
|
||||
passwordLbl.Size = new System.Drawing.Size(57, 15);
|
||||
passwordLbl.TabIndex = 2;
|
||||
passwordLbl.Text = "Password";
|
||||
//
|
||||
// passwordTb
|
||||
//
|
||||
passwordTb.Location = new System.Drawing.Point(83, 44);
|
||||
passwordTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
passwordTb.Name = "passwordTb";
|
||||
passwordTb.PasswordChar = '*';
|
||||
passwordTb.Size = new System.Drawing.Size(233, 23);
|
||||
passwordTb.TabIndex = 3;
|
||||
//
|
||||
// submitBtn
|
||||
//
|
||||
submitBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
submitBtn.Location = new System.Drawing.Point(293, 176);
|
||||
submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
submitBtn.Name = "submitBtn";
|
||||
submitBtn.Size = new System.Drawing.Size(88, 27);
|
||||
submitBtn.TabIndex = 7;
|
||||
submitBtn.Text = "Submit";
|
||||
submitBtn.UseVisualStyleBackColor = true;
|
||||
submitBtn.Click += submitBtn_Click;
|
||||
//
|
||||
// localeLbl
|
||||
//
|
||||
localeLbl.AutoSize = true;
|
||||
localeLbl.Location = new System.Drawing.Point(14, 10);
|
||||
localeLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
localeLbl.Name = "localeLbl";
|
||||
localeLbl.Size = new System.Drawing.Size(61, 15);
|
||||
localeLbl.TabIndex = 0;
|
||||
localeLbl.Text = "Locale: {0}";
|
||||
//
|
||||
// usernameLbl
|
||||
//
|
||||
usernameLbl.AutoSize = true;
|
||||
usernameLbl.Location = new System.Drawing.Point(14, 25);
|
||||
usernameLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
usernameLbl.Name = "usernameLbl";
|
||||
usernameLbl.Size = new System.Drawing.Size(80, 15);
|
||||
usernameLbl.TabIndex = 1;
|
||||
usernameLbl.Text = "Username: {0}";
|
||||
//
|
||||
// externalLoginLink
|
||||
//
|
||||
externalLoginLink.AutoSize = true;
|
||||
externalLoginLink.Location = new System.Drawing.Point(14, 93);
|
||||
externalLoginLink.Name = "externalLoginLink";
|
||||
externalLoginLink.Size = new System.Drawing.Size(166, 15);
|
||||
externalLoginLink.TabIndex = 4;
|
||||
externalLoginLink.TabStop = true;
|
||||
externalLoginLink.Text = "Trouble Logging in? Click here";
|
||||
externalLoginLink.LinkClicked += externalLoginLink_LinkClicked;
|
||||
//
|
||||
// externalLoginLbl2
|
||||
//
|
||||
externalLoginLbl2.AutoSize = true;
|
||||
externalLoginLbl2.Location = new System.Drawing.Point(14, 108);
|
||||
externalLoginLbl2.Name = "externalLoginLbl2";
|
||||
externalLoginLbl2.Size = new System.Drawing.Size(352, 45);
|
||||
externalLoginLbl2.TabIndex = 6;
|
||||
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 here.";
|
||||
//
|
||||
// externalLoginLbl1
|
||||
//
|
||||
externalLoginLbl1.AutoSize = true;
|
||||
externalLoginLbl1.Location = new System.Drawing.Point(177, 93);
|
||||
externalLoginLbl1.Name = "externalLoginLbl1";
|
||||
externalLoginLbl1.Size = new System.Drawing.Size(158, 15);
|
||||
externalLoginLbl1.TabIndex = 5;
|
||||
externalLoginLbl1.Text = "to log in using your browser.";
|
||||
//
|
||||
// LoginChoiceEagerDialog
|
||||
//
|
||||
AcceptButton = submitBtn;
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
ClientSize = new System.Drawing.Size(394, 216);
|
||||
Controls.Add(externalLoginLbl2);
|
||||
Controls.Add(externalLoginLbl1);
|
||||
Controls.Add(externalLoginLink);
|
||||
Controls.Add(usernameLbl);
|
||||
Controls.Add(localeLbl);
|
||||
Controls.Add(submitBtn);
|
||||
Controls.Add(passwordLbl);
|
||||
Controls.Add(passwordTb);
|
||||
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "LoginChoiceEagerDialog";
|
||||
ShowIcon = false;
|
||||
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
Text = "Audible Login";
|
||||
ResumeLayout(false);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using AudibleUtilities;
|
||||
using Dinah.Core;
|
||||
|
||||
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;
|
||||
|
||||
if (LoginMethod is AudibleApi.LoginMethod.Api && string.IsNullOrWhiteSpace(Password))
|
||||
{
|
||||
MessageBox.Show("Please enter your password");
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
<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>
|
||||
@@ -1,113 +0,0 @@
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
partial class MfaDialog
|
||||
{
|
||||
/// <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.submitBtn = new System.Windows.Forms.Button();
|
||||
this.radioButton1 = new System.Windows.Forms.RadioButton();
|
||||
this.radioButton2 = new System.Windows.Forms.RadioButton();
|
||||
this.radioButton3 = new System.Windows.Forms.RadioButton();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// submitBtn
|
||||
//
|
||||
this.submitBtn.Location = new System.Drawing.Point(14, 93);
|
||||
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 = 3;
|
||||
this.submitBtn.Text = "Submit";
|
||||
this.submitBtn.UseVisualStyleBackColor = true;
|
||||
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
|
||||
//
|
||||
// radioButton1
|
||||
//
|
||||
this.radioButton1.AutoSize = true;
|
||||
this.radioButton1.Checked = true;
|
||||
this.radioButton1.Location = new System.Drawing.Point(14, 14);
|
||||
this.radioButton1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.radioButton1.Name = "radioButton1";
|
||||
this.radioButton1.Size = new System.Drawing.Size(242, 19);
|
||||
this.radioButton1.TabIndex = 0;
|
||||
this.radioButton1.TabStop = true;
|
||||
this.radioButton1.Text = "Enter the OTP from the authenticator app";
|
||||
this.radioButton1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// radioButton2
|
||||
//
|
||||
this.radioButton2.AutoSize = true;
|
||||
this.radioButton2.Location = new System.Drawing.Point(14, 40);
|
||||
this.radioButton2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.radioButton2.Name = "radioButton2";
|
||||
this.radioButton2.Size = new System.Drawing.Size(172, 19);
|
||||
this.radioButton2.TabIndex = 1;
|
||||
this.radioButton2.Text = "Send an SMS to my number";
|
||||
this.radioButton2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// radioButton3
|
||||
//
|
||||
this.radioButton3.AutoSize = true;
|
||||
this.radioButton3.Location = new System.Drawing.Point(14, 67);
|
||||
this.radioButton3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.radioButton3.Name = "radioButton3";
|
||||
this.radioButton3.Size = new System.Drawing.Size(147, 19);
|
||||
this.radioButton3.TabIndex = 2;
|
||||
this.radioButton3.Text = "Call me on my number";
|
||||
this.radioButton3.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// MfaDialog
|
||||
//
|
||||
this.AcceptButton = this.submitBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.ClientSize = new System.Drawing.Size(398, 129);
|
||||
this.Controls.Add(this.radioButton3);
|
||||
this.Controls.Add(this.radioButton2);
|
||||
this.Controls.Add(this.radioButton1);
|
||||
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 = "MfaDialog";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Two-step verification";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Button submitBtn;
|
||||
private System.Windows.Forms.RadioButton radioButton1;
|
||||
private System.Windows.Forms.RadioButton radioButton2;
|
||||
private System.Windows.Forms.RadioButton radioButton3;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public partial class MfaDialog : Form
|
||||
{
|
||||
private RadioButton[] radioButtons { get; }
|
||||
|
||||
private AudibleApi.MfaConfig _mfaConfig { get; }
|
||||
|
||||
public MfaDialog(AudibleApi.MfaConfig mfaConfig)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_mfaConfig = mfaConfig;
|
||||
|
||||
radioButtons = new[] { this.radioButton1, this.radioButton2, this.radioButton3 };
|
||||
|
||||
// optional string settings
|
||||
if (!string.IsNullOrWhiteSpace(mfaConfig.Title))
|
||||
this.Text = mfaConfig.Title;
|
||||
|
||||
setRadioButton(0, this.radioButton1);
|
||||
setRadioButton(1, this.radioButton2);
|
||||
setRadioButton(2, this.radioButton3);
|
||||
|
||||
Serilog.Log.Logger.Information("{@DebugInfo}", new
|
||||
{
|
||||
paramButtonCount = mfaConfig.Buttons.Count,
|
||||
visibleRadioButtonCount = radioButtons.Count(rb => rb.Visible)
|
||||
});
|
||||
}
|
||||
|
||||
private void setRadioButton(int pos, RadioButton rb)
|
||||
{
|
||||
if (_mfaConfig.Buttons.Count <= pos)
|
||||
{
|
||||
rb.Checked = false;
|
||||
rb.Enabled = false;
|
||||
rb.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var btn = _mfaConfig.Buttons[pos];
|
||||
|
||||
// optional
|
||||
if (!string.IsNullOrWhiteSpace(btn.Text))
|
||||
rb.Text = btn.Text;
|
||||
|
||||
// mandatory values
|
||||
rb.Name = btn.Name;
|
||||
rb.Tag = btn.Value;
|
||||
}
|
||||
|
||||
public string SelectedName { get; private set; }
|
||||
public string SelectedValue { get; private set; }
|
||||
private void submitBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
var selected = radioButtons.FirstOrDefault(rb => rb.Checked);
|
||||
|
||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new
|
||||
{
|
||||
rb1_visible = radioButton1.Visible,
|
||||
rb1_checked = radioButton1.Checked,
|
||||
|
||||
rb2_visible = radioButton2.Visible,
|
||||
rb2_checked = radioButton2.Checked,
|
||||
|
||||
rb3_visible = radioButton3.Visible,
|
||||
rb3_checked = radioButton3.Checked,
|
||||
|
||||
isSelected = selected is not null,
|
||||
name = selected?.Name,
|
||||
value = selected?.Tag
|
||||
});
|
||||
|
||||
if (selected is null)
|
||||
{
|
||||
MessageBox.Show("No MFA option selected", "None selected", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedName = selected.Name;
|
||||
SelectedValue = (string)selected.Tag;
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
// Close() not needed for AcceptButton
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
@@ -1,5 +1,5 @@
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using Microsoft.Web.WebView2.WinForms;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@@ -9,19 +9,19 @@ namespace LibationWinForms.Login
|
||||
{
|
||||
public string ResponseUrl { get; private set; }
|
||||
private readonly string accountID;
|
||||
private readonly IWebViewAdapter webView;
|
||||
private readonly WebView2 webView;
|
||||
public WebLoginDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
webView = InteropFactory.Create().CreateWebViewAdapter();
|
||||
webView = new WebView2();
|
||||
|
||||
var webViewControl = webView.NativeWebView as Control;
|
||||
webViewControl.Dock = DockStyle.Fill;
|
||||
Controls.Add(webViewControl);
|
||||
webView.Dock = DockStyle.Fill;
|
||||
Controls.Add(webView);
|
||||
|
||||
webView.NavigationStarted += WebView_NavigationStarted;
|
||||
webView.DOMContentLoaded += WebView_DOMContentLoaded;
|
||||
webView.NavigationStarting += WebView_NavigationStarting;
|
||||
webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted;
|
||||
this.SetLibationIcon();
|
||||
|
||||
}
|
||||
|
||||
public WebLoginDialog(string accountID, string loginUrl) : this()
|
||||
@@ -30,32 +30,36 @@ namespace LibationWinForms.Login
|
||||
webView.Source = new Uri(ArgumentValidator.EnsureNotNullOrWhiteSpace(loginUrl, nameof(loginUrl)));
|
||||
}
|
||||
|
||||
private void WebView_NavigationStarted(object sender, WebViewNavigationEventArgs e)
|
||||
private void WebView_NavigationStarting(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
|
||||
{
|
||||
if (e.Request?.AbsolutePath.Contains("/ap/maplanding") is true)
|
||||
if (e.Uri.Contains("/ap/maplanding") is true)
|
||||
{
|
||||
ResponseUrl = e.Request.ToString();
|
||||
ResponseUrl = e.Uri;
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private async void WebView_DOMContentLoaded(object sender, EventArgs e)
|
||||
private void WebView_CoreWebView2InitializationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
|
||||
{
|
||||
await webView.InvokeScriptAsync(getScript(accountID));
|
||||
webView.CoreWebView2.DOMContentLoaded -= CoreWebView2_DOMContentLoaded;
|
||||
webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
|
||||
}
|
||||
|
||||
private async void CoreWebView2_DOMContentLoaded(object sender, Microsoft.Web.WebView2.Core.CoreWebView2DOMContentLoadedEventArgs e)
|
||||
{
|
||||
await webView.ExecuteScriptAsync(getScript(accountID));
|
||||
}
|
||||
|
||||
private static string getScript(string accountID) => $$"""
|
||||
(function() {
|
||||
var inputs = document.getElementsByTagName('input');
|
||||
for (index = 0; index < inputs.length; ++index) {
|
||||
if (inputs[index].name.includes('email')) {
|
||||
inputs[index].value = '{{accountID}}';
|
||||
}
|
||||
if (inputs[index].name.includes('password')) {
|
||||
inputs[index].focus();
|
||||
}
|
||||
}
|
||||
var email = document.querySelector("input[name='email']");
|
||||
if (email !== null)
|
||||
email.value = '{{accountID}}';
|
||||
|
||||
var pass = document.querySelector("input[name='password']");
|
||||
if (pass !== null)
|
||||
pass.focus();
|
||||
})()
|
||||
""";
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public abstract class WinformLoginBase
|
||||
{
|
||||
protected Control Owner { get; }
|
||||
protected WinformLoginBase(Control owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
/// <returns>True if ShowDialog's DialogResult == OK</returns>
|
||||
protected bool ShowDialog(Form dialog)
|
||||
=> Owner.Invoke(() =>
|
||||
{
|
||||
var result = dialog.ShowDialog(Owner);
|
||||
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
|
||||
return result == DialogResult.OK;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,59 +7,17 @@ using LibationWinForms.Dialogs.Login;
|
||||
|
||||
namespace LibationWinForms.Login
|
||||
{
|
||||
public class WinformLoginCallback : WinformLoginBase, ILoginCallback
|
||||
public class WinformLoginCallback : ILoginCallback
|
||||
{
|
||||
private Account _account { get; }
|
||||
|
||||
public string DeviceName { get; } = "Libation";
|
||||
|
||||
public WinformLoginCallback(Account account, Control owner) : base(owner)
|
||||
{
|
||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
}
|
||||
|
||||
public Task<string> Get2faCodeAsync(string prompt)
|
||||
=> Owner.Invoke(() =>
|
||||
{
|
||||
using var dialog = new _2faCodeDialog(prompt);
|
||||
if (ShowDialog(dialog))
|
||||
return Task.FromResult(dialog.Code);
|
||||
return Task.FromResult<string>(null);
|
||||
});
|
||||
|
||||
public Task<string> Get2faCodeAsync(string prompt) => throw new System.NotSupportedException();
|
||||
public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
|
||||
=> Owner.Invoke(() =>
|
||||
{
|
||||
using var dialog = new CaptchaDialog(password, captchaImage);
|
||||
if (ShowDialog(dialog))
|
||||
return Task.FromResult((dialog.Password, dialog.Answer));
|
||||
return Task.FromResult<(string, string)>((null, null));
|
||||
});
|
||||
|
||||
=> throw new System.NotSupportedException();
|
||||
public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
|
||||
=> Owner.Invoke(() =>
|
||||
{
|
||||
using var dialog = new MfaDialog(mfaConfig);
|
||||
if (ShowDialog(dialog))
|
||||
return Task.FromResult((dialog.SelectedName, dialog.SelectedValue));
|
||||
return Task.FromResult<(string, string)>((null, null));
|
||||
});
|
||||
|
||||
=> throw new System.NotSupportedException();
|
||||
public Task<(string email, string password)> GetLoginAsync()
|
||||
=> Owner.Invoke(() =>
|
||||
{
|
||||
using var dialog = new LoginCallbackDialog(_account);
|
||||
if (ShowDialog(dialog))
|
||||
return Task.FromResult((dialog.Email, dialog.Password));
|
||||
return Task.FromResult<(string, string)>((null, null));
|
||||
});
|
||||
|
||||
public Task ShowApprovalNeededAsync()
|
||||
=> Owner.Invoke(() =>
|
||||
{
|
||||
using var dialog = new ApprovalNeededDialog();
|
||||
ShowDialog(dialog);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
=> throw new System.NotSupportedException();
|
||||
public Task ShowApprovalNeededAsync() => throw new System.NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,16 @@ using LibationWinForms.Dialogs.Login;
|
||||
|
||||
namespace LibationWinForms.Login
|
||||
{
|
||||
public class WinformLoginChoiceEager : WinformLoginBase, ILoginChoiceEager
|
||||
public class WinformLoginChoiceEager : ILoginChoiceEager
|
||||
{
|
||||
public ILoginCallback LoginCallback { get; private set; }
|
||||
public ILoginCallback LoginCallback { get; } = new WinformLoginCallback();
|
||||
|
||||
private Account _account { get; }
|
||||
|
||||
public WinformLoginChoiceEager(Account account, Control owner) : base(owner)
|
||||
private Control Owner { get; }
|
||||
public WinformLoginChoiceEager(Account account, Control owner)
|
||||
{
|
||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
LoginCallback = new WinformLoginCallback(_account, owner);
|
||||
Owner = Dinah.Core.ArgumentValidator.EnsureNotNull(owner, nameof(owner));
|
||||
}
|
||||
|
||||
public Task<ChoiceOut> StartAsync(ChoiceIn choiceIn)
|
||||
@@ -38,26 +38,20 @@ namespace LibationWinForms.Login
|
||||
}
|
||||
}
|
||||
|
||||
using var dialog = new LoginChoiceEagerDialog(_account);
|
||||
|
||||
if (!ShowDialog(dialog) || (dialog.LoginMethod is LoginMethod.Api && string.IsNullOrWhiteSpace(dialog.Password)))
|
||||
return null;
|
||||
|
||||
switch (dialog.LoginMethod)
|
||||
{
|
||||
case LoginMethod.Api:
|
||||
return Task.FromResult(ChoiceOut.WithApi(dialog.Email, dialog.Password));
|
||||
case LoginMethod.External:
|
||||
{
|
||||
using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
|
||||
return Task.FromResult(
|
||||
ShowDialog(externalDialog)
|
||||
? ChoiceOut.External(externalDialog.ResponseUrl)
|
||||
: null);
|
||||
}
|
||||
default:
|
||||
throw new Exception($"Unknown {nameof(LoginMethod)} value");
|
||||
}
|
||||
using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
|
||||
return Task.FromResult(
|
||||
ShowDialog(externalDialog)
|
||||
? ChoiceOut.External(externalDialog.ResponseUrl)
|
||||
: null);
|
||||
}
|
||||
|
||||
/// <returns>True if ShowDialog's DialogResult == OK</returns>
|
||||
private bool ShowDialog(Form dialog)
|
||||
=> Owner.Invoke(() =>
|
||||
{
|
||||
var result = dialog.ShowDialog(Owner);
|
||||
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
|
||||
return result == DialogResult.OK;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
partial class _2faCodeDialog
|
||||
{
|
||||
/// <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()
|
||||
{
|
||||
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
|
||||
//
|
||||
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
|
||||
//
|
||||
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
|
||||
//
|
||||
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
|
||||
//
|
||||
AcceptButton = submitBtn;
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public partial class _2faCodeDialog : Form
|
||||
{
|
||||
public string Code { get; private set; }
|
||||
|
||||
public _2faCodeDialog() => InitializeComponent();
|
||||
public _2faCodeDialog(string prompt) : this()
|
||||
{
|
||||
promptLbl.Text = prompt;
|
||||
}
|
||||
|
||||
private void submitBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
Code = this.codeTb.Text.Trim();
|
||||
|
||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Code });
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
@@ -5,12 +5,10 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>libation.ico</ApplicationIcon>
|
||||
<AssemblyName>Libation</AssemblyName>
|
||||
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
@@ -43,6 +41,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="9.0.3.1" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3595.46" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=Libation
|
||||
Exec=/usr/bin/libation
|
||||
Exec=sh -c 'export WEBKIT_DISABLE_COMPOSITING_MODE=1; libation'
|
||||
Icon=libation
|
||||
Comment=Liberate your Audiobooks
|
||||
Terminal=false
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace LinuxConfigApp
|
||||
public LinuxInterop() { }
|
||||
public LinuxInterop(params object[] values) { }
|
||||
|
||||
public IWebViewAdapter CreateWebViewAdapter() => null;
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace MacOSConfigApp
|
||||
public MacOSInterop() { }
|
||||
public MacOSInterop(params object[] values) { }
|
||||
|
||||
public IWebViewAdapter CreateWebViewAdapter() => null;
|
||||
public void SetFolderIcon(string image, string directory)
|
||||
{
|
||||
Process.Start("fileicon", $"set {directory.SurroundWithQuotes()} {image.SurroundWithQuotes()}").WaitForExit();
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/* Work-in-progress
|
||||
*
|
||||
*
|
||||
using LibationFileManager;
|
||||
using ObjCRuntime;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using WebKit;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace MacOSConfigApp;
|
||||
|
||||
internal class WKNavigationDelegate1 : WKNavigationDelegate
|
||||
{
|
||||
public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
|
||||
{
|
||||
base.DidStartProvisionalNavigation(webView, navigation);
|
||||
}
|
||||
}
|
||||
internal class MacWebViewAdapter : IWebViewAdapter, IDisposable
|
||||
{
|
||||
private readonly WKWebView _webView;
|
||||
public IPlatformHandle2 PlatformHandle { get; }
|
||||
|
||||
public bool CanGoBack => _webView.CanGoBack;
|
||||
|
||||
public bool CanGoForward => _webView.CanGoForward;
|
||||
|
||||
public Uri? Source { get => _webView?.Url; set => throw new NotImplementedException(); }
|
||||
|
||||
public object NativeWebView { get; }
|
||||
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
|
||||
public event EventHandler? DOMContentLoaded;
|
||||
|
||||
WKNavigationDelegate1 navDelegate;
|
||||
public MacWebViewAdapter()
|
||||
{
|
||||
var frame = new CGRect(0, 0, 500, 800);
|
||||
NativeWebView = _webView = new WKWebView(frame, new WKWebViewConfiguration());
|
||||
_webView.NavigationDelegate = navDelegate = new WKNavigationDelegate1();
|
||||
PlatformHandle = new MacViewHandle(_webView.Handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_webView?.Dispose();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
if (_webView.CanGoBack)
|
||||
{
|
||||
_webView.GoBack();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
if (_webView.CanGoForward)
|
||||
{
|
||||
_webView.GoForward();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public bool HandleKeyDown(uint key, uint keyModifiers)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void HandleResize(int width, int height, float zoom)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task<string?> InvokeScriptAsync(string scriptName)
|
||||
{
|
||||
var result = await _webView.EvaluateJavaScriptAsync(scriptName);
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
public void Navigate(Uri url)
|
||||
{
|
||||
NSUrl? nsurl = url;
|
||||
if (nsurl is null) return;
|
||||
|
||||
var request = new NSUrlRequest(nsurl);
|
||||
|
||||
_webView.LoadRequest(request);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Task NavigateToString(string text)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class MacViewHandle : IPlatformHandle2
|
||||
{
|
||||
private NativeHandle? _view;
|
||||
|
||||
public MacViewHandle(NativeHandle view)
|
||||
{
|
||||
_view = view;
|
||||
}
|
||||
|
||||
public nint Handle => _view?.Handle ?? 0;
|
||||
public string HandleDescriptor => "NativeHandle";
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -11,10 +11,6 @@ namespace WindowsConfigApp
|
||||
{
|
||||
public WinInterop() { }
|
||||
public WinInterop(params object[] values) { }
|
||||
|
||||
#nullable enable
|
||||
public IWebViewAdapter? CreateWebViewAdapter() => new WindowsWebView2Adapter();
|
||||
#nullable disable
|
||||
public void SetFolderIcon(string image, string directory)
|
||||
{
|
||||
var icon = Image.Load(image).ToIcon();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
@@ -25,10 +24,6 @@
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3595.46" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\LibationUiBase\LibationUiBase.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
using LibationFileManager;
|
||||
using Microsoft.Web.WebView2.WinForms;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#nullable enable
|
||||
namespace WindowsConfigApp;
|
||||
|
||||
internal class WindowsWebView2Adapter : IWebViewAdapter, IDisposable
|
||||
{
|
||||
public object NativeWebView { get; }
|
||||
private readonly WebView2 _webView;
|
||||
|
||||
public WindowsWebView2Adapter()
|
||||
{
|
||||
NativeWebView = _webView = new WebView2();
|
||||
PlatformHandle = new WebView2Handle { Handle = _webView.Handle };
|
||||
|
||||
_webView.CoreWebView2InitializationCompleted += _webView_CoreWebView2InitializationCompleted;
|
||||
|
||||
_webView.NavigationStarting += (s, a) =>
|
||||
{
|
||||
NavigationStarted?.Invoke(this, new WebViewNavigationEventArgs { Request = new Uri(a.Uri) });
|
||||
};
|
||||
_webView.NavigationCompleted += (s, a) =>
|
||||
{
|
||||
NavigationCompleted?.Invoke(this, new WebViewNavigationEventArgs { Request = _webView.Source });
|
||||
};
|
||||
}
|
||||
|
||||
private void _webView_CoreWebView2InitializationCompleted(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
|
||||
{
|
||||
_webView.CoreWebView2.DOMContentLoaded -= CoreWebView2_DOMContentLoaded;
|
||||
_webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
|
||||
}
|
||||
|
||||
private void CoreWebView2_DOMContentLoaded(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2DOMContentLoadedEventArgs e)
|
||||
=> DOMContentLoaded?.Invoke(this, e);
|
||||
|
||||
public IPlatformHandle2 PlatformHandle { get; }
|
||||
|
||||
public bool CanGoBack => _webView.CanGoBack;
|
||||
|
||||
public bool CanGoForward => _webView.CanGoForward;
|
||||
|
||||
public Uri? Source
|
||||
{
|
||||
get => _webView.Source;
|
||||
set => _webView.Source = value;
|
||||
}
|
||||
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
|
||||
public event EventHandler? DOMContentLoaded;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_webView.Dispose();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
_webView.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
_webView.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<string?> InvokeScriptAsync(string scriptName)
|
||||
{
|
||||
return await _webView.ExecuteScriptAsync(scriptName);
|
||||
}
|
||||
|
||||
public void Navigate(Uri url)
|
||||
{
|
||||
_webView.Source = url;
|
||||
}
|
||||
|
||||
public async Task NavigateToString(string text)
|
||||
{
|
||||
await _webView.EnsureCoreWebView2Async();
|
||||
|
||||
_webView.NavigateToString(text);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
_webView.Refresh();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_webView.Stop();
|
||||
}
|
||||
|
||||
public void HandleResize(int width, int height, float zoom)
|
||||
{
|
||||
}
|
||||
|
||||
public bool HandleKeyDown(uint key, uint keyModifiers)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class WebView2Handle : IPlatformHandle2
|
||||
{
|
||||
public IntPtr Handle { get; init; }
|
||||
public string HandleDescriptor => "HWND";
|
||||
}
|
||||
Reference in New Issue
Block a user