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 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? 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(); } } }