2019-12-08 17:00:22 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2020-02-11 00:30:38 +00:00
using System.Net.Http ;
2019-12-08 17:00:22 +00:00
using System.Text ;
2019-12-08 19:46:30 +00:00
using System.Threading ;
2019-12-08 17:00:22 +00:00
using System.Threading.Tasks ;
2021-06-14 03:31:09 +00:00
using System.Web ;
2019-12-08 17:00:22 +00:00
using System.Windows ;
using System.Windows.Threading ;
2020-02-06 05:30:31 +00:00
using CefSharp ;
2019-12-08 17:00:22 +00:00
using ReactiveUI ;
2019-12-08 19:46:30 +00:00
using Wabbajack.Common ;
2021-05-20 11:40:05 +00:00
using Wabbajack.Common.StatusFeed ;
2020-01-07 13:03:46 +00:00
using Wabbajack.Lib ;
2019-12-08 17:00:22 +00:00
using Wabbajack.Lib.Downloaders ;
2020-02-11 00:30:38 +00:00
using Wabbajack.Lib.LibCefHelpers ;
2019-12-08 17:00:22 +00:00
using Wabbajack.Lib.NexusApi ;
2019-12-26 23:26:53 +00:00
using Wabbajack.Lib.WebAutomation ;
2021-06-14 03:31:09 +00:00
using WebSocketSharp ;
2019-12-08 17:00:22 +00:00
namespace Wabbajack
{
public class UserInterventionHandlers
{
2019-12-08 19:46:30 +00:00
public MainWindowVM MainWindow { get ; }
2021-06-30 21:57:35 +00:00
private AsyncLock _browserLock = new ( ) ;
2019-12-08 17:00:22 +00:00
2019-12-08 19:46:30 +00:00
public UserInterventionHandlers ( MainWindowVM mvm )
{
MainWindow = mvm ;
2019-12-08 17:00:22 +00:00
}
2019-12-08 19:46:30 +00:00
private async Task WrapBrowserJob ( IUserIntervention intervention , Func < WebBrowserVM , CancellationTokenSource , Task > toDo )
2019-12-08 17:00:22 +00:00
{
2022-02-01 22:00:43 +00:00
using var wait = await _browserLock . WaitAsync ( ) ;
2020-02-11 00:30:38 +00:00
var cancel = new CancellationTokenSource ( ) ;
2019-12-08 19:46:30 +00:00
var oldPane = MainWindow . ActivePane ;
2020-04-20 22:36:11 +00:00
using var vm = await WebBrowserVM . GetNew ( ) ;
2020-01-05 02:50:05 +00:00
MainWindow . NavigateTo ( vm ) ;
2019-12-08 19:46:30 +00:00
vm . BackCommand = ReactiveCommand . Create ( ( ) = >
2019-12-08 17:00:22 +00:00
{
2019-12-08 19:46:30 +00:00
cancel . Cancel ( ) ;
2020-01-05 02:50:05 +00:00
MainWindow . NavigateTo ( oldPane ) ;
2019-12-08 19:46:30 +00:00
intervention . Cancel ( ) ;
2019-12-08 17:00:22 +00:00
} ) ;
2019-12-08 19:46:30 +00:00
try
{
await toDo ( vm , cancel ) ;
}
catch ( TaskCanceledException )
{
intervention . Cancel ( ) ;
}
catch ( Exception ex )
{
Utils . Error ( ex ) ;
intervention . Cancel ( ) ;
}
2020-01-05 02:50:05 +00:00
MainWindow . NavigateTo ( oldPane ) ;
2019-12-08 17:00:22 +00:00
}
2021-05-20 11:40:05 +00:00
public async Task Handle ( IStatusMessage msg )
2019-12-08 17:00:22 +00:00
{
switch ( msg )
{
case RequestNexusAuthorization c :
2021-05-20 11:40:05 +00:00
await WrapBrowserJob ( c , async ( vm , cancel ) = >
2019-12-08 19:46:30 +00:00
{
2019-12-26 23:26:53 +00:00
await vm . Driver . WaitForInitialized ( ) ;
2022-03-29 04:25:29 +00:00
var cef = new CefSharpWrapper ( vm . Browser ) ;
var key = await NexusApiClient . SetupNexusLogin ( ( ) = > vm . SyncTo ( cef ) , cef , m = > vm . Instructions = m , cancel . Token ) ;
2019-12-08 19:46:30 +00:00
c . Resume ( key ) ;
} ) ;
2019-12-08 17:00:22 +00:00
break ;
2020-02-06 05:30:31 +00:00
case ManuallyDownloadNexusFile c :
2021-05-20 11:40:05 +00:00
await WrapBrowserJob ( c , ( vm , cancel ) = > HandleManualNexusDownload ( vm , cancel , c ) ) ;
2020-02-06 05:30:31 +00:00
break ;
2020-02-11 00:30:38 +00:00
case ManuallyDownloadFile c :
2021-05-20 11:40:05 +00:00
await WrapBrowserJob ( c , ( vm , cancel ) = > HandleManualDownload ( vm , cancel , c ) ) ;
2020-02-11 00:30:38 +00:00
break ;
2022-02-01 06:07:23 +00:00
case ManuallyDownloadMegaFile c :
await WrapBrowserJob ( c , ( vm , cancel ) = > HandleManualMegaDownload ( vm , cancel , c ) ) ;
break ;
2022-02-27 20:28:04 +00:00
case ManuallyDownloadLoversLabFile c :
await WrapBrowserJob ( c , ( vm , cancel ) = > HandleManualLoversLabDownload ( vm , cancel , c ) ) ;
break ;
2020-01-05 05:38:08 +00:00
case AbstractNeedsLoginDownloader . RequestSiteLogin c :
2021-05-20 11:40:05 +00:00
await WrapBrowserJob ( c , async ( vm , cancel ) = >
2019-12-08 19:46:30 +00:00
{
2019-12-26 23:26:53 +00:00
await vm . Driver . WaitForInitialized ( ) ;
2022-03-29 04:25:29 +00:00
var cef = new CefSharpWrapper ( vm . Browser ) ;
var data = await c . Downloader . GetAndCacheCookies ( url = > vm . SyncTo ( cef ) , cef , m = > vm . Instructions = m , cancel . Token ) ;
2019-12-08 19:46:30 +00:00
c . Resume ( data ) ;
} ) ;
2021-06-14 03:31:09 +00:00
break ;
case RequestOAuthLogin oa :
await WrapBrowserJob ( oa , async ( vm , cancel ) = >
{
await OAuthLogin ( oa , vm , cancel ) ;
} ) ;
2019-12-08 17:00:22 +00:00
break ;
2020-01-07 13:03:46 +00:00
case CriticalFailureIntervention c :
MessageBox . Show ( c . ExtendedDescription , c . ShortDescription , MessageBoxButton . OK ,
MessageBoxImage . Error ) ;
c . Cancel ( ) ;
2021-05-20 11:40:05 +00:00
if ( c . ExitApplication ) await MainWindow . ShutdownApplication ( ) ;
2020-01-07 13:03:46 +00:00
break ;
2019-12-09 00:19:36 +00:00
case ConfirmationIntervention c :
break ;
2019-12-08 17:00:22 +00:00
default :
throw new NotImplementedException ( $"No handler for {msg}" ) ;
}
}
2019-12-20 20:51:10 +00:00
2021-06-14 03:31:09 +00:00
private async Task OAuthLogin ( RequestOAuthLogin oa , WebBrowserVM vm , CancellationTokenSource cancel )
{
await vm . Driver . WaitForInitialized ( ) ;
2021-06-19 21:19:29 +00:00
vm . Instructions = $"Please log in and allow Wabbajack to access your {oa.SiteName} account" ;
2021-06-14 03:31:09 +00:00
var wrapper = new CefSharpWrapper ( vm . Browser ) ;
2021-06-15 04:55:07 +00:00
var scopes = string . Join ( " " , oa . Scopes ) ;
2021-06-14 22:18:57 +00:00
var state = Guid . NewGuid ( ) . ToString ( ) ;
2021-06-19 21:19:29 +00:00
2021-06-14 03:31:09 +00:00
2021-06-30 02:16:48 +00:00
var oldHandler = Helpers . SchemeHandler ;
2021-06-14 03:31:09 +00:00
Helpers . SchemeHandler = ( browser , frame , _ , request ) = >
{
var req = new Uri ( request . Url ) ;
2021-06-30 21:57:35 +00:00
Utils . LogStraightToFile ( $"Got Scheme callback {req}" ) ;
2021-06-14 03:31:09 +00:00
var parsed = HttpUtility . ParseQueryString ( req . Query ) ;
2021-06-14 22:18:57 +00:00
if ( parsed . Contains ( "state" ) )
{
if ( parsed . Get ( "state" ) ! = state )
{
Utils . Log ( "Bad OAuth state, state, this shouldn't happen" ) ;
oa . Cancel ( ) ;
return new ResourceHandler ( ) ;
}
}
2021-06-14 03:31:09 +00:00
if ( parsed . Contains ( "code" ) )
{
2021-06-30 21:57:35 +00:00
Helpers . SchemeHandler = oldHandler ;
2022-02-27 20:28:04 +00:00
oa . Resume ( parsed . Get ( "code" ) ! ) . FireAndForget ( ) ;
2021-06-14 03:31:09 +00:00
}
else
{
oa . Cancel ( ) ;
}
return new ResourceHandler ( ) ;
} ;
2021-06-19 21:19:29 +00:00
await wrapper . NavigateTo ( new Uri ( oa . AuthorizationEndpoint + $"?response_type=code&client_id={oa.ClientID}&state={state}&scope={scopes}" ) ) ;
2022-03-29 04:25:29 +00:00
vm . SyncTo ( wrapper ) ;
2021-06-14 03:31:09 +00:00
while ( ! oa . Task . IsCanceled & & ! oa . Task . IsCompleted & & ! cancel . IsCancellationRequested )
await Task . Delay ( 250 ) ;
}
2020-02-11 00:30:38 +00:00
private async Task HandleManualDownload ( WebBrowserVM vm , CancellationTokenSource cancel , ManuallyDownloadFile manuallyDownloadFile )
{
var browser = new CefSharpWrapper ( vm . Browser ) ;
2022-02-23 23:11:25 +00:00
var prompt = manuallyDownloadFile . State . Prompt ;
if ( string . IsNullOrWhiteSpace ( prompt ) )
{
prompt = $"Please locate and download {manuallyDownloadFile.State.Url}" ;
}
vm . Instructions = prompt ;
2020-02-11 00:30:38 +00:00
var result = new TaskCompletionSource < Uri > ( ) ;
2022-02-01 22:00:43 +00:00
2020-02-11 00:30:38 +00:00
await vm . Driver . WaitForInitialized ( ) ;
2022-02-01 22:00:43 +00:00
using var _ = browser . SetDownloadHandler ( new ManualDownloadHandler ( result ) ) ;
2020-02-11 00:30:38 +00:00
await browser . NavigateTo ( new Uri ( manuallyDownloadFile . State . Url ) ) ;
2022-03-29 04:25:29 +00:00
vm . SyncTo ( browser ) ;
2020-02-11 00:30:38 +00:00
while ( ! cancel . IsCancellationRequested )
{
if ( result . Task . IsCompleted )
{
var cookies = await Helpers . GetCookies ( ) ;
var referer = browser . Location ;
var client = Helpers . GetClient ( cookies , referer ) ;
manuallyDownloadFile . Resume ( result . Task . Result , client ) ;
break ;
}
await Task . Delay ( 100 ) ;
}
}
2022-02-01 22:00:43 +00:00
private class ManualDownloadHandler : IDownloadHandler
{
private readonly TaskCompletionSource < Uri > _tcs ;
public ManualDownloadHandler ( TaskCompletionSource < Uri > tcs )
{
_tcs = tcs ;
}
public void OnBeforeDownload ( IWebBrowser chromiumWebBrowser , IBrowser browser , DownloadItem downloadItem ,
IBeforeDownloadCallback callback )
{
_tcs . TrySetResult ( new Uri ( downloadItem . Url ) ) ;
}
public void OnDownloadUpdated ( IWebBrowser chromiumWebBrowser , IBrowser browser , DownloadItem downloadItem ,
IDownloadItemCallback callback )
{
callback . Cancel ( ) ;
}
}
2022-02-01 06:07:23 +00:00
private async Task HandleManualMegaDownload ( WebBrowserVM vm , CancellationTokenSource cancel , ManuallyDownloadMegaFile manuallyDownloadFile )
{
var browser = new CefSharpWrapper ( vm . Browser ) ;
2022-02-23 23:11:25 +00:00
var prompt = manuallyDownloadFile . State . Prompt ;
if ( string . IsNullOrWhiteSpace ( prompt ) )
{
prompt = $"Please locate and download {manuallyDownloadFile.State.Url}" ;
}
vm . Instructions = prompt ;
2022-02-01 06:07:23 +00:00
await vm . Driver . WaitForInitialized ( ) ;
var tcs = new TaskCompletionSource ( ) ;
2022-02-27 20:28:04 +00:00
using var _ = browser . SetDownloadHandler ( new BlobDownloadHandler ( manuallyDownloadFile . Destination , tcs ) ) ;
await browser . NavigateTo ( new Uri ( manuallyDownloadFile . State . Url ) ) ;
2022-03-29 04:25:29 +00:00
vm . SyncTo ( browser ) ;
2022-02-27 20:28:04 +00:00
while ( ! cancel . IsCancellationRequested & & ! tcs . Task . IsCompleted )
{
await Task . Delay ( 100 ) ;
}
manuallyDownloadFile . Resume ( ) ;
}
private async Task HandleManualLoversLabDownload ( WebBrowserVM vm , CancellationTokenSource cancel , ManuallyDownloadLoversLabFile manuallyDownloadFile )
{
var browser = new CefSharpWrapper ( vm . Browser ) ;
var prompt = manuallyDownloadFile . State . Prompt ;
if ( string . IsNullOrWhiteSpace ( prompt ) )
{
prompt = $"Please locate and download {manuallyDownloadFile.State.Url}" ;
}
vm . Instructions = prompt ;
await vm . Driver . WaitForInitialized ( ) ;
var tcs = new TaskCompletionSource ( ) ;
using var _ = browser . SetDownloadHandler ( new BlobDownloadHandler ( manuallyDownloadFile . Destination , tcs ,
p = >
{
vm . Instructions = $"Downloading: {p}" ;
2022-02-27 20:50:02 +00:00
} , manuallyDownloadFile . Archive ) ) ;
2022-02-27 20:28:04 +00:00
2022-02-01 06:07:23 +00:00
await browser . NavigateTo ( new Uri ( manuallyDownloadFile . State . Url ) ) ;
2022-03-29 04:25:29 +00:00
vm . SyncTo ( browser ) ;
2022-02-01 06:07:23 +00:00
while ( ! cancel . IsCancellationRequested & & ! tcs . Task . IsCompleted )
{
2022-02-27 20:50:02 +00:00
await browser . EvaluateJavaScript (
"Array.from(document.getElementsByClassName('ll_adblock')).forEach(c => c.remove())" ) ;
2022-02-01 06:07:23 +00:00
await Task . Delay ( 100 ) ;
}
manuallyDownloadFile . Resume ( ) ;
}
private class BlobDownloadHandler : IDownloadHandler
{
2022-02-27 20:28:04 +00:00
private readonly AbsolutePath _destination ;
2022-02-01 06:07:23 +00:00
private readonly TaskCompletionSource _tcs ;
2022-02-27 20:28:04 +00:00
private readonly Action < Percent > _progress ;
2022-02-27 20:50:02 +00:00
private Archive _archive ;
2022-02-01 06:07:23 +00:00
2022-02-27 20:50:02 +00:00
public BlobDownloadHandler ( AbsolutePath f , TaskCompletionSource tcs , Action < Percent > progress = null , Archive archive = null )
2022-02-01 06:07:23 +00:00
{
2022-02-27 20:28:04 +00:00
_progress = progress ;
_destination = f ;
2022-02-01 06:07:23 +00:00
_tcs = tcs ;
2022-02-27 20:50:02 +00:00
_archive = archive ;
2022-02-01 06:07:23 +00:00
}
public void OnBeforeDownload ( IWebBrowser chromiumWebBrowser , IBrowser browser , DownloadItem downloadItem ,
IBeforeDownloadCallback callback )
{
2022-02-27 20:28:04 +00:00
callback . Continue ( _destination . ToString ( ) , false ) ;
2022-02-01 06:07:23 +00:00
}
public void OnDownloadUpdated ( IWebBrowser chromiumWebBrowser , IBrowser browser , DownloadItem downloadItem ,
IDownloadItemCallback callback )
{
2022-02-27 21:38:23 +00:00
if ( _archive ? . Size ! = null & & _archive ? . Size ! = 0 & & downloadItem . TotalBytes ! = _archive ? . Size )
2022-02-27 20:50:02 +00:00
{
2022-02-27 21:38:23 +00:00
_tcs . TrySetCanceled ( ) ;
2022-02-27 20:50:02 +00:00
Utils . Error (
$"Download of {_archive!.Name} (from {downloadItem.OriginalUrl}) aborted, selected file was {downloadItem.TotalBytes.ToFileSizeString()} expected size was {_archive!.Size.ToFileSizeString()}" ) ;
2022-02-27 21:38:23 +00:00
callback . Cancel ( ) ;
2022-02-27 20:50:02 +00:00
return ;
}
2022-02-27 20:28:04 +00:00
_progress ? . Invoke ( Percent . FactoryPutInRange ( downloadItem . PercentComplete , 100 ) ) ;
2022-02-01 06:07:23 +00:00
if ( downloadItem . IsComplete )
{
_tcs . TrySetResult ( ) ;
}
callback . Resume ( ) ;
2022-02-01 22:00:43 +00:00
}
2022-02-01 06:07:23 +00:00
}
2020-02-11 00:30:38 +00:00
2020-02-06 05:30:31 +00:00
private async Task HandleManualNexusDownload ( WebBrowserVM vm , CancellationTokenSource cancel , ManuallyDownloadNexusFile manuallyDownloadNexusFile )
{
var state = manuallyDownloadNexusFile . State ;
2020-03-30 22:26:34 +00:00
var game = state . Game . MetaData ( ) ;
2020-02-06 05:30:31 +00:00
await vm . Driver . WaitForInitialized ( ) ;
2021-06-20 22:36:56 +00:00
vm . Instructions = $"Click the download button to continue (get a NexusMods.com Premium account to automate this)" ;
2022-02-01 22:00:43 +00:00
var browser = new CefSharpWrapper ( vm . Browser ) ;
var tcs = new TaskCompletionSource < Uri > ( ) ;
using var _ = browser . SetDownloadHandler ( new ManualDownloadHandler ( tcs ) ) ;
2021-06-20 22:36:56 +00:00
var url = new Uri ( @ $"https://www.nexusmods.com/{game.NexusName}/mods/{state.ModID}?tab=files&file_id={state.FileID}" ) ;
await browser . NavigateTo ( url ) ;
2022-03-29 04:25:29 +00:00
vm . SyncTo ( browser ) ;
2021-06-20 22:36:56 +00:00
2022-02-01 22:00:43 +00:00
while ( ! cancel . IsCancellationRequested & & ! tcs . Task . IsCompleted ) {
2020-02-06 05:30:31 +00:00
await Task . Delay ( 250 ) ;
}
2022-02-01 22:00:43 +00:00
if ( tcs . Task . IsFaulted )
{
manuallyDownloadNexusFile . Cancel ( ) ;
}
else
{
var uri = await tcs . Task ;
manuallyDownloadNexusFile . Resume ( uri ) ;
}
2020-02-06 05:30:31 +00:00
}
2019-12-08 17:00:22 +00:00
}
}