@@ -23,7 +23,7 @@ public class PuppeteerCookieRetriever : ICookieRetriever, IDisposable
2323 private IPuppeteerSettings _settings ;
2424 private bool _isHeadlessBrowser ;
2525 private bool _isRemoteBrowser ;
26-
26+ private bool _shouldTryAutoLogin ;
2727
2828 /// <summary>
2929 /// Create new instance of PuppeteerCookieRetriever
@@ -40,6 +40,7 @@ public PuppeteerCookieRetriever()
4040 public Task BeforeStart ( IUniversalDownloaderPlatformSettings settings )
4141 {
4242 _settings = settings as IPuppeteerSettings ;
43+ _shouldTryAutoLogin = ! string . IsNullOrWhiteSpace ( _settings . LoginEmail ) && ! string . IsNullOrWhiteSpace ( _settings . LoginPassword ) ;
4344
4445 if ( _settings . RemoteBrowserAddress != null )
4546 {
@@ -83,12 +84,12 @@ protected virtual async Task Login()
8384 if ( ! await IsLoggedIn ( response ) )
8485 {
8586 _logger . Debug ( "We are NOT logged in, opening login page" ) ;
86- if ( _isRemoteBrowser )
87+ if ( _isRemoteBrowser && ! _shouldTryAutoLogin )
8788 {
8889 await page . CloseAsync ( ) ;
8990 throw new Exception ( "You are not logged in into your account in remote browser. Please login and restart application." ) ;
9091 }
91- if ( _puppeteerEngine . IsHeadless )
92+ if ( _puppeteerEngine . IsHeadless && ! _shouldTryAutoLogin )
9293 {
9394 _logger . Debug ( "Puppeteer is in headless mode, restarting in full mode" ) ;
9495 browser = await RestartBrowser ( false ) ;
@@ -100,7 +101,16 @@ protected virtual async Task Login()
100101 page . GoToAsync ( _settings . LoginPageAddress , null ) ;
101102#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
102103
103- await page . WaitForRequestAsync ( request => { return request . Url . Contains ( _settings . LoginCheckAddress ) ; } ) ;
104+ if ( _shouldTryAutoLogin )
105+ {
106+ _logger . Debug ( "Credentials were supplied, attempting automatic Patreon login" ) ;
107+ await TryAutoLogin ( page ) ;
108+ }
109+ else
110+ {
111+ _logger . Debug ( "Waiting for user to log in Patreon manually" ) ;
112+ await page . WaitForRequestAsync ( request => { return request . Url . Contains ( _settings . LoginCheckAddress ) ; } ) ;
113+ }
104114 }
105115 else
106116 {
@@ -118,6 +128,62 @@ protected virtual async Task Login()
118128 await page . CloseAsync ( ) ;
119129 }
120130
131+ private async Task TryAutoLogin ( IWebPage page )
132+ {
133+ const string emailSelector = "input[aria-label=\" Email\" ]" ;
134+ const string passwordSelector = "input[aria-label=\" Password\" ]" ;
135+ const string submitSelector = "button[type=\" submit\" ][aria-disabled=\" false\" ]" ;
136+
137+ try
138+ {
139+ await page . WaitForNetworkIdleAsync ( new WaitForNetworkIdleOptions ( )
140+ {
141+ // The hanging requests are:
142+ // https://accounts.google.com/gsi/client
143+ // https://www.facebook.com/x/oauth/status
144+ // https://www.google.com/recaptcha/enterprise/webworker.js
145+ Concurrency = 3 ,
146+ IdleTime = 1000 ,
147+ Timeout = 10000 ,
148+ } ) ;
149+ }
150+ catch ( WaitTaskTimeoutException ) // in case there are other hanging requests (they seem to appear randomly)
151+ {
152+ _logger . Debug ( "Waiting for network idle timeout; proceed anyway and hope for the best" ) ;
153+ }
154+
155+ WaitForSelectorOptions options = new WaitForSelectorOptions ( ) { Timeout = 10000 } ; // prevent indefinite hang
156+ await page . WaitForSelectorAsync ( emailSelector , options ) ;
157+ _logger . Debug ( "Entering email" ) ;
158+ await Task . Delay ( 300 ) ;
159+ await page . TypeAsync ( emailSelector , _settings . LoginEmail ) ;
160+ await page . WaitForSelectorAsync ( submitSelector , options ) ;
161+ await Task . Delay ( 300 ) ;
162+ await page . ClickAsync ( submitSelector ) ;
163+ await CheckAuth ( page ) ;
164+
165+ await page . WaitForSelectorAsync ( passwordSelector , options ) ;
166+ _logger . Debug ( "Entering password" ) ;
167+ await Task . Delay ( 300 ) ;
168+ await page . TypeAsync ( passwordSelector , _settings . LoginPassword ) ;
169+ await page . WaitForSelectorAsync ( submitSelector , options ) ;
170+ await Task . Delay ( 300 ) ;
171+ await page . ClickAsync ( submitSelector ) ;
172+ await CheckAuth ( page ) ;
173+ }
174+
175+ private async Task CheckAuth ( IWebPage page )
176+ {
177+ IWebResponse authResponse = await page . WaitForResponseAsync (
178+ response => { return response . Url . Contains ( _settings . AuthAddress ) ; } ,
179+ new WaitForOptions ( ) { Timeout = 10000 }
180+ ) ;
181+ if ( authResponse . Status != HttpStatusCode . OK )
182+ {
183+ throw new Exception ( $ "The provided credentials are invalid: { authResponse . Status } ") ;
184+ }
185+ }
186+
121187 /// <summary>
122188 /// Perform check if the received response contains data which can be used to assume that we are logged in
123189 /// </summary>
0 commit comments