Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
610 changes: 317 additions & 293 deletions MainCore.Test/packages.lock.json

Large diffs are not rendered by default.

10 changes: 0 additions & 10 deletions MainCore/AppMixins.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using MainCore.Behaviors;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ReactiveMarbles.Extensions.Hosting.AppServices;
using Serilog;
using Serilog.Events;
using Serilog.Templates;
Expand Down Expand Up @@ -82,14 +80,6 @@ public static IHostBuilder GetHostBuilder()
{
var hostBuilder = Host.CreateDefaultBuilder()
.ConfigureSplatForMicrosoftDependencyResolver()
.ConfigureSingleInstance(builder =>
{
builder.MutexId = "{hcmmn304-1975-4a2d-afed-615d4a318283}";
builder.WhenNotFirstInstance = (hostingEnvironment, logger) =>
{
logger.LogWarning("Application {0} already running.", hostingEnvironment.ApplicationName);
};
})
.ConfigureLogging()
.ConfigureDbContext()
.ConfigureServices();
Expand Down
3 changes: 2 additions & 1 deletion MainCore/Commands/UI/MainLayoutViewModel/LoginCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ CancellationToken cancellationToken
taskManager.SetStatus(accountId, StatusEnums.Starting);
await openBrowserCommand.HandleAsync(new(accountId, access), cancellationToken);
}
catch
catch (Exception ex)
{
_ = ex;
taskManager.SetStatus(accountId, StatusEnums.Offline);
return;
}
Expand Down
35 changes: 17 additions & 18 deletions MainCore/MainCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,25 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="9.2.0" />
<PackageReference Include="CP.Extensions.Hosting.SingleInstance" Version="2.1.13" />
<PackageReference Include="FluentValidation" Version="12.0.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.0.0" />
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="9.3.1" />
<PackageReference Include="FluentValidation" Version="12.1.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.0" />
<PackageReference Include="FluentResults" Version="4.0.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.2" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Immediate.Handlers" Version="2.2.0" />
<PackageReference Include="Injectio" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
<PackageReference Include="Polly" Version="8.6.2" />
<PackageReference Include="ReactiveUI" Version="20.4.1" />
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.3.1">
<PackageReference Include="Immediate.Handlers" Version="3.1.0" />
<PackageReference Include="Injectio" Version="5.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.10" />
<PackageReference Include="Polly" Version="8.6.4" />
<PackageReference Include="ReactiveUI" Version="22.2.1" />
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Riok.Mapperly" Version="4.2.1" />
<PackageReference Include="Selenium.Support" Version="4.35.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.35.0" />
<PackageReference Include="Riok.Mapperly" Version="4.3.0" />
<PackageReference Include="Selenium.Support" Version="4.38.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.38.0" />
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
Expand All @@ -55,9 +54,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Splat" Version="15.4.1" />
<PackageReference Include="Splat.Drawing" Version="15.4.1" />
<PackageReference Include="Splat.Microsoft.Extensions.DependencyInjection" Version="15.4.1" />
<PackageReference Include="Splat" Version="17.1.1" />
<PackageReference Include="Splat.Drawing" Version="17.1.1" />
<PackageReference Include="Splat.Microsoft.Extensions.DependencyInjection" Version="17.1.1" />
<PackageReference Include="StronglyTypedId" Version="1.0.0-beta06" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>

Expand Down
140 changes: 81 additions & 59 deletions MainCore/Services/ChromeBrowser.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.BiDi;
using OpenQA.Selenium.BiDi.BrowsingContext;
using OpenQA.Selenium.BiDi.Network;
using OpenQA.Selenium.BiDi.WebExtension;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
using System.IO.Compression;
using System.Runtime.CompilerServices;

namespace MainCore.Services
Expand All @@ -15,6 +18,11 @@
private readonly string[] _extensionsPath;
private readonly HtmlDocument _htmlDoc = new();

private BiDi? _bidi;

private BrowsingContext? _context;
private Intercept? _authIntercept;

Check warning on line 24 in MainCore/Services/ChromeBrowser.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this unread private field '_authIntercept' or refactor the code to use its value. (https://rules.sonarsource.com/csharp/RSPEC-4487)

public ChromeBrowser(string[] extensionsPath)
{
_extensionsPath = extensionsPath;
Expand All @@ -27,33 +35,26 @@
{
var options = new ChromeOptions();

options.AddExtensions(_extensionsPath);

if (!string.IsNullOrEmpty(setting.ProxyHost))
{
if (!string.IsNullOrEmpty(setting.ProxyUsername) && !string.IsNullOrEmpty(setting.ProxyPassword))
{
options.AddHttpProxy(setting.ProxyHost, setting.ProxyPort, setting.ProxyUsername, setting.ProxyPassword);
}
else
{
options.AddArgument($"--proxy-server={setting.ProxyHost}:{setting.ProxyPort}");
}
options.AddArgument($"--proxy-server={setting.ProxyHost}:{setting.ProxyPort}");
}

options.AddArgument($"--user-agent={setting.UserAgent}");
options.AddArgument("--ignore-certificate-errors");
options.AddArguments("--no-default-browser-check", "--no-first-run", "--ash-no-nudges");
options.AddArguments("--mute-audio", "--disable-gpu", "--disable-search-engine-choice-screen");

options.AddArgument("--enable-unsafe-extension-debugging");
options.AddArgument("--remote-debugging-pipe");

options.AddExcludedArgument("enable-automation");
options.AddAdditionalOption("useAutomationExtension", "undefined");

options.AddArgument("--disable-background-timer-throttling");
options.AddArgument("--disable-backgrounding-occluded-windows");
options.AddArgument("--disable-features=CalculateNativeWinOcclusion");
options.AddArgument("--disable-features=UserAgentClientHint");
options.AddArgument("--disable-features=DisableLoadExtensionCommandLineSwitch");
options.AddArgument("--disable-blink-features=AutomationControlled");

if (setting.IsHeadless)
Expand All @@ -71,11 +72,31 @@
pathUserData = Path.Combine(pathUserData, string.IsNullOrEmpty(setting.ProxyHost) ? "default" : setting.ProxyHost);

options.AddArguments($"user-data-dir={pathUserData}");
options.UseWebSocketUrl = true;
options.UnhandledPromptBehavior = UnhandledPromptBehavior.Ignore;

_driver = await Task.Run(() => new ChromeDriver(_chromeService, options, TimeSpan.FromMinutes(3)));

_driver.Manage().Timeouts().PageLoad = TimeSpan.FromMinutes(3);
_wait = new WebDriverWait(_driver, TimeSpan.FromMinutes(3)); // watch ads

_bidi = await _driver.AsBiDiAsync();
_context = (await _bidi.BrowsingContext.GetTreeAsync()).Contexts[0].Context;

foreach (var path in _extensionsPath)
{
var result = await _bidi.WebExtension.InstallAsync(new ExtensionPath(path));

Check warning on line 88 in MainCore/Services/ChromeBrowser.cs

View workflow job for this annotation

GitHub Actions / Build

Remove the unused local variable 'result'. (https://rules.sonarsource.com/csharp/RSPEC-1481)
Logger.Information("- Installed extension: {path}", Path.GetFileNameWithoutExtension(path));

Check warning on line 89 in MainCore/Services/ChromeBrowser.cs

View workflow job for this annotation

GitHub Actions / Build

Use PascalCase for named placeholders. (https://rules.sonarsource.com/csharp/RSPEC-6678)
}

if (!string.IsNullOrEmpty(setting.ProxyHost) && !string.IsNullOrEmpty(setting.ProxyUsername) && !string.IsNullOrEmpty(setting.ProxyPassword))
{
_authIntercept = await _bidi.Network.InterceptAuthAsync(async auth =>
{
Logger.Information("- Providing proxy auth credentials", auth.Request.Url);
await auth.ContinueAsync(new AuthCredentials(setting.ProxyUsername, setting.ProxyPassword), new ContinueWithAuthCredentialsOptions());
});
}
}

public ChromeDriver? Driver => _driver;
Expand Down Expand Up @@ -111,15 +132,15 @@

public async Task<Result> Refresh(CancellationToken cancellationToken)
{
if (Driver is null) return Stop.DriverNotReady;
await Driver.Navigate().RefreshAsync();
if (_context is null) return Stop.DriverNotReady;
await _context.ReloadAsync(new() { Wait = ReadinessState.Complete });
return Result.Ok();
}

public async Task<Result> Navigate(string url, CancellationToken cancellationToken)
{
if (Driver is null) return Stop.DriverNotReady;
await Driver.Navigate().GoToUrlAsync(url);
if (_context is null) return Stop.DriverNotReady;
await _context.NavigateAsync(url, new() { Wait = ReadinessState.Complete });
return Result.Ok();
}

Expand Down Expand Up @@ -264,13 +285,24 @@

public async Task Close()
{
await Task.Run(() => _driver?.Quit());
try
{
if (_bidi is not null)
{
await _bidi.DisposeAsync();
}

await Task.Run(() => _driver?.Quit());
}
catch
{
// ignore
}
}
}

public static class ChromeOptionsExtensions
{
private const string background_js = @"
public static class ChromeOptionsExtensions
{
private const string background_js = @"
var config = {
mode: ""fixed_servers"",
rules: {
Expand Down Expand Up @@ -302,7 +334,7 @@
['blocking']
);";

private const string manifest_json = @"
private const string manifest_json = @"
{
""version"": ""1.0.0"",
""manifest_version"": 3,
Expand All @@ -324,49 +356,39 @@
""minimum_chrome_version"": ""108""
}";

/// <summary>
/// Add HTTP-proxy by <paramref name="userName"/> and <paramref name="password"/>
/// </summary>
/// <param name="options">Chrome options</param>
/// <param name="host">Proxy host</param>
/// <param name="port">Proxy port</param>
/// <param name="userName">Proxy username</param>
/// <param name="password">Proxy password</param>
public static void AddHttpProxy(this ChromeOptions options, string host, int port, string userName, string password)
{
var background_proxy_js = ReplaceTemplates(background_js, host, port, userName, password);
/// <summary>
/// Add HTTP-proxy by <paramref name="userName"/> and <paramref name="password"/>
/// </summary>
/// <param name="options">Chrome options</param>
/// <param name="host">Proxy host</param>
/// <param name="port">Proxy port</param>
/// <param name="userName">Proxy username</param>
/// <param name="password">Proxy password</param>
public static string CreateHttpProxyExtension(string host, int port, string userName, string password)
{
var background_proxy_js = ReplaceTemplates(background_js, host, port, userName, password);

if (!Directory.Exists("Plugins"))
Directory.CreateDirectory("Plugins");
const string path = "Plugins";
if (Directory.Exists(path)) Directory.Delete(path);
Directory.CreateDirectory(path);

var guid = Guid.NewGuid().ToString();
var manifestPath = $"{path}/manifest.json";
var backgroundPath = $"{path}/background.js";

var manifestPath = $"Plugins/manifest_{guid}.json";
var backgroundPath = $"Plugins/background_{guid}.js";
var archiveFilePath = $"Plugins/proxy_auth_plugin_{guid}.zip";
File.WriteAllText(manifestPath, manifest_json);
File.WriteAllText(backgroundPath, background_proxy_js);

File.WriteAllText(manifestPath, manifest_json);
File.WriteAllText(backgroundPath, background_proxy_js);
return path;
}

using (var zip = ZipFile.Open(archiveFilePath, ZipArchiveMode.Create))
private static string ReplaceTemplates(string str, string host, int port, string userName, string password)
{
zip.CreateEntryFromFile(manifestPath, "manifest.json");
zip.CreateEntryFromFile(backgroundPath, "background.js");
return str
.Replace("{HOST}", host)
.Replace("{PORT}", port.ToString())
.Replace("{USERNAME}", userName)
.Replace("{PASSWORD}", password);
}

File.Delete(manifestPath);
File.Delete(backgroundPath);

options.AddExtension(archiveFilePath);
}

private static string ReplaceTemplates(string str, string host, int port, string userName, string password)
{
return str
.Replace("{HOST}", host)
.Replace("{PORT}", port.ToString())
.Replace("{USERNAME}", userName)
.Replace("{PASSWORD}", password);
}
}
}
Loading
Loading