6f37d217db
Introduces the Addon record to represent browser addons and updates StealthConfig to support loading multiple utility addons per browser. The Firefox driver now installs specified addons from the UtilityAddons array, improving extensibility for browser automation.
148 lines
6.0 KiB
C#
148 lines
6.0 KiB
C#
using System;
|
|
using System.IO;
|
|
using Microsoft.Extensions.Logging;
|
|
using OpenQA.Selenium;
|
|
using OpenQA.Selenium.Firefox;
|
|
using OpenQA.Selenium.Chrome;
|
|
using OpenQA.Selenium.Chromium;
|
|
using OpenQA.Selenium.Edge;
|
|
using OpenQA.Selenium.Remote;
|
|
|
|
namespace Beam.Stealth {
|
|
public enum Browser {
|
|
Firefox = 0,
|
|
Chrome = 1,
|
|
Chromium = 2,
|
|
Edge = 3,
|
|
}
|
|
|
|
public sealed class StealthConfig : IDisposable {
|
|
public bool ShowBrowser { get; init; }
|
|
public TimeSpan TimeOut { get; init; } = Timeout.InfiniteTimeSpan;
|
|
public string DownloadsDirectory { get; }
|
|
public ILogger? Logger { get; init; }
|
|
public required IWebDriver Driver { get; init; }
|
|
|
|
public string? RemoteAddress { get; init; }
|
|
|
|
private StealthConfig(string downloadDir) => DownloadsDirectory = downloadDir;
|
|
|
|
/* ---------- browser-specific option builders ---------- */
|
|
|
|
private static FirefoxOptions GetFirefoxOptions(string downloadDir, bool headless) {
|
|
var o = new FirefoxOptions();
|
|
if (headless) o.AddArgument("--headless");
|
|
|
|
o.SetPreference("browser.download.folderList", 2); // use custom dir
|
|
o.SetPreference("browser.download.dir", downloadDir);
|
|
o.SetPreference("browser.download.useDownloadDir", true);
|
|
o.SetPreference("browser.helperApps.neverAsk.saveToDisk",
|
|
"application/octet-stream,application/pdf,application/zip");
|
|
o.SetPreference("pdfjs.disabled", true); // open PDFs externally
|
|
o.SetPreference("browser.download.manager.showWhenStarting", false);
|
|
|
|
return o;
|
|
}
|
|
|
|
private static ChromeOptions GetChromeOptions(string downloadDir, bool headless) {
|
|
var o = new ChromeOptions();
|
|
if (headless) o.AddArgument("--headless=new");
|
|
|
|
// download prefs
|
|
o.AddUserProfilePreference("download.default_directory", downloadDir);
|
|
o.AddUserProfilePreference("download.prompt_for_download", false);
|
|
o.AddUserProfilePreference("safebrowsing.enabled", false);
|
|
|
|
// common stability flags
|
|
o.AddArgument("--no-sandbox");
|
|
o.AddArgument("--disable-dev-shm-usage");
|
|
|
|
return o;
|
|
}
|
|
|
|
private static EdgeOptions GetEdgeOptions(string downloadDir, bool headless) {
|
|
var o = new EdgeOptions();
|
|
if (headless) o.AddArgument("--headless=new");
|
|
|
|
o.AddUserProfilePreference("download.default_directory", downloadDir);
|
|
o.AddUserProfilePreference("download.prompt_for_download", false);
|
|
o.AddUserProfilePreference("safebrowsing.enabled", false);
|
|
|
|
return o;
|
|
}
|
|
|
|
/* ---------- factory ---------- */
|
|
private record DriverDefinition(Func<DriverOptions, IWebDriver> Factory, DriverOptions Options);
|
|
public static StealthConfig Create(
|
|
bool showBrowser = false,
|
|
string? downloadDir = null,
|
|
TimeSpan? timeOut = null,
|
|
Browser preferredBrowser = Browser.Firefox,
|
|
string? remoteAddress = null,
|
|
Addon[]? utilityAddons = null,
|
|
ILogger? logger = null) {
|
|
// pick or create a dedicated download folder
|
|
downloadDir ??= Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
|
Directory.CreateDirectory(downloadDir);
|
|
|
|
bool headless = !showBrowser;
|
|
IWebDriver? driver = null;
|
|
List<Exception>? errors = [];
|
|
|
|
while (preferredBrowser <= Browser.Edge) {
|
|
try {
|
|
DriverDefinition def = preferredBrowser switch {
|
|
Browser.Chrome or Browser.Chromium
|
|
=> new((DriverOptions opts) => new ChromeDriver(opts as ChromeOptions), GetChromeOptions(downloadDir, headless)),
|
|
Browser.Edge
|
|
=> new((DriverOptions opts) => new EdgeDriver(opts as EdgeOptions), GetEdgeOptions(downloadDir, headless)),
|
|
Browser.Firefox or _
|
|
=> new((DriverOptions opts) => new FirefoxDriver(opts as FirefoxOptions), GetFirefoxOptions(downloadDir, headless)),
|
|
};
|
|
|
|
if (remoteAddress is not null)
|
|
driver = new RemoteWebDriver(new Uri(remoteAddress), def.Options);
|
|
else
|
|
driver = def.Factory(def.Options);
|
|
|
|
try {
|
|
foreach(var addon in utilityAddons)
|
|
if (addon.Browser == Browser.Firefox && driver is FirefoxDriver fd)
|
|
fd.InstallAddOnFromFile(addon.AddonPath);
|
|
}
|
|
catch {
|
|
logger?.LogWarning("Unable to load utility addons");
|
|
}
|
|
|
|
break;
|
|
}
|
|
catch (Exception e) {
|
|
if (preferredBrowser != Browser.Edge)
|
|
logger?.LogWarning(e, "Failed to create driver for browser {Browser}, falling back to {Fallback}", preferredBrowser, Enum.GetName(preferredBrowser + 1));
|
|
else {
|
|
logger?.LogCritical(e, "Failed to create driver for browser {Browser}, no more fallbacks remaining!", preferredBrowser);
|
|
}
|
|
|
|
preferredBrowser++;
|
|
errors.Add(e);
|
|
}
|
|
}
|
|
|
|
if (driver is null)
|
|
throw new AggregateException(errors);
|
|
|
|
return new StealthConfig(downloadDir) {
|
|
ShowBrowser = showBrowser,
|
|
TimeOut = timeOut ?? Timeout.InfiniteTimeSpan,
|
|
RemoteAddress = remoteAddress,
|
|
Logger = logger,
|
|
Driver = driver
|
|
};
|
|
}
|
|
|
|
public void Dispose() {
|
|
Driver.Dispose();
|
|
}
|
|
}
|
|
}
|