Skip to content

Commit a1e738b

Browse files
committed
Auto-login
1 parent f61b4d0 commit a1e738b

5 files changed

Lines changed: 120 additions & 5 deletions

File tree

UniversalDownloaderPlatform.PuppeteerEngine/Interfaces/IPuppeteerSettings.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public interface IPuppeteerSettings
2121
/// </summary>
2222
public string CaptchaCookieRetrievalAddress { get; }
2323
/// <summary>
24+
/// Address of API request for checking account existence used by Patreon login page. Finishing this request signifies being ready to enter password.
25+
/// </summary>
26+
public string AuthAddress { get; }
27+
/// <summary>
2428
/// Address of the remote browser, if not set internal browser will be used
2529
/// </summary>
2630
public Uri RemoteBrowserAddress { get; init; }
@@ -32,5 +36,13 @@ public interface IPuppeteerSettings
3236
/// Proxy server address
3337
/// </summary>
3438
public string ProxyServerAddress { get; init; }
39+
/// <summary>
40+
/// Email used for optional automatic login
41+
/// </summary>
42+
public string LoginEmail { get; init; }
43+
/// <summary>
44+
/// Password used for optional automatic login
45+
/// </summary>
46+
public string LoginPassword { get; init; }
3547
}
3648
}

UniversalDownloaderPlatform.PuppeteerEngine/Interfaces/Wrappers/Browser/IWebPage.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Threading.Tasks;
33
using PuppeteerSharp;
4+
using PuppeteerSharp.Input;
45

56
namespace UniversalDownloaderPlatform.PuppeteerEngine.Interfaces.Wrappers.Browser
67
{
@@ -15,6 +16,11 @@ public interface IWebPage
1516
Task SetUserAgentAsync(string userAgent);
1617
Task<string> GetContentAsync();
1718
Task<IWebRequest> WaitForRequestAsync(Func<IRequest, bool> predicate, WaitForOptions options = null);
19+
Task<IWebResponse> WaitForResponseAsync(Func<IResponse, bool> predicate, WaitForOptions options = null);
20+
Task WaitForNetworkIdleAsync(WaitForNetworkIdleOptions options = null);
21+
Task WaitForSelectorAsync(string selector, WaitForSelectorOptions options = null);
22+
Task TypeAsync(string selector, string text, TypeOptions options = null);
23+
Task ClickAsync(string selector, ClickOptions options = null);
1824
Task<CookieParam[]> GetCookiesAsync(params string[] urls);
1925
Task CloseAsync(PageCloseOptions options = null);
2026
}

UniversalDownloaderPlatform.PuppeteerEngine/PuppeteerCookieRetriever.cs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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>

UniversalDownloaderPlatform.PuppeteerEngine/UniversalDownloaderPlatform.PuppeteerEngine.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<PackageReference Include="Ninject" Version="3.3.6" />
1010
<PackageReference Include="ninject.extensions.conventions" Version="3.3.0" />
1111
<PackageReference Include="NLog" Version="6.0.3" />
12-
<PackageReference Include="PuppeteerSharp" Version="20.2.2" />
12+
<PackageReference Include="PuppeteerSharp" Version="24.40.0" />
1313
</ItemGroup>
1414

1515
<ItemGroup>

UniversalDownloaderPlatform.PuppeteerEngine/Wrappers/Browser/WebPage.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Threading.Tasks;
44
using PuppeteerSharp;
5+
using PuppeteerSharp.Input;
56
using UniversalDownloaderPlatform.PuppeteerEngine.Interfaces.Wrappers.Browser;
67

78
namespace UniversalDownloaderPlatform.PuppeteerEngine.Wrappers.Browser
@@ -55,6 +56,36 @@ public async Task<IWebRequest> WaitForRequestAsync(Func<IRequest, bool> predicat
5556
return webRequest;
5657
}
5758

59+
public async Task<IWebResponse> WaitForResponseAsync(Func<IResponse, bool> predicate, WaitForOptions options = null)
60+
{
61+
await ConfigurePage();
62+
IResponse response = await _page.WaitForResponseAsync(predicate, options);
63+
IWebResponse webResponse = new WebResponse(response);
64+
return webResponse;
65+
}
66+
67+
public async Task WaitForNetworkIdleAsync(WaitForNetworkIdleOptions options = null)
68+
{
69+
await ConfigurePage();
70+
await _page.WaitForNetworkIdleAsync(options);
71+
}
72+
73+
public async Task WaitForSelectorAsync(string selector, WaitForSelectorOptions options = null)
74+
{
75+
await ConfigurePage();
76+
await _page.WaitForSelectorAsync(selector, options);
77+
}
78+
79+
public async Task TypeAsync(string selector, string text, TypeOptions options = null)
80+
{
81+
await _page.TypeAsync(selector, text, options);
82+
}
83+
84+
public async Task ClickAsync(string selector, ClickOptions options = null)
85+
{
86+
await _page.ClickAsync(selector, options);
87+
}
88+
5889
public async Task<CookieParam[]> GetCookiesAsync(params string[] urls)
5990
{
6091
return await _page.GetCookiesAsync(urls);

0 commit comments

Comments
 (0)