using OF_DL.Entities; using PuppeteerSharp; using PuppeteerSharp.BrowserData; using Serilog; namespace OF_DL.Helpers; public class AuthHelper { private readonly LaunchOptions _options = new() { Headless = false, Channel = ChromeReleaseChannel.Stable, DefaultViewport = null, Args = ["--no-sandbox", "--disable-setuid-sandbox"], UserDataDir = Path.GetFullPath("chrome-data") }; private readonly string[] _desiredCookies = [ "auth_id", "sess" ]; private const int LoginTimeout = 600000; // 10 minutes private const int FeedLoadTimeout = 60000; // 1 minute public async Task SetupBrowser(bool runningInDocker) { string? executablePath = Environment.GetEnvironmentVariable("OFDL_PUPPETEER_EXECUTABLE_PATH"); if (executablePath != null) { Log.Information("OFDL_PUPPETEER_EXECUTABLE_PATH environment variable found. Using browser executable path: {executablePath}", executablePath); _options.ExecutablePath = executablePath; } else { var browserFetcher = new BrowserFetcher(); var installedBrowsers = browserFetcher.GetInstalledBrowsers().ToList(); if (installedBrowsers.Count == 0) { Log.Information("Downloading browser."); var downloadedBrowser = await browserFetcher.DownloadAsync(); Log.Information("Browser downloaded. Path: {executablePath}", downloadedBrowser.GetExecutablePath()); _options.ExecutablePath = downloadedBrowser.GetExecutablePath(); } else { _options.ExecutablePath = installedBrowsers.First().GetExecutablePath(); } } if (runningInDocker) { Log.Information("Running in Docker. Disabling sandbox and GPU."); _options.Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"]; } } private async Task GetBcToken(IPage page) { return await page.EvaluateExpressionAsync("window.localStorage.getItem('bcTokenSha') || ''"); } public async Task GetAuthFromBrowser(bool isDocker = false) { try { IBrowser? browser; try { browser = await Puppeteer.LaunchAsync(_options); } catch (ProcessException e) { if (e.Message.Contains("Failed to launch browser") && Directory.Exists(_options.UserDataDir)) { Log.Error("Failed to launch browser. Deleting chrome-data directory and trying again."); Directory.Delete(_options.UserDataDir, true); browser = await Puppeteer.LaunchAsync(_options); } else { throw; } } if (browser == null) { throw new Exception("Could not get browser"); } IPage[]? pages = await browser.PagesAsync(); IPage? page = pages.First(); if (page == null) { throw new Exception("Could not get page"); } Log.Debug("Navigating to OnlyFans."); await page.GoToAsync("https://onlyfans.com"); Log.Debug("Waiting for user to login"); await page.WaitForSelectorAsync(".b-feed", new WaitForSelectorOptions { Timeout = LoginTimeout }); Log.Debug("Feed element detected (user logged in)"); await page.ReloadAsync(); await page.WaitForNavigationAsync(new NavigationOptions { WaitUntil = [WaitUntilNavigation.Networkidle2], Timeout = FeedLoadTimeout }); Log.Debug("DOM loaded. Getting BC token and cookies ..."); string xBc; try { xBc = await GetBcToken(page); } catch (Exception e) { throw new Exception("Error getting bcToken"); } Dictionary mappedCookies = (await page.GetCookiesAsync()) .Where(cookie => cookie.Domain.Contains("onlyfans.com")) .ToDictionary(cookie => cookie.Name, cookie => cookie.Value); mappedCookies.TryGetValue("auth_id", out string? userId); if (userId == null) { throw new Exception("Could not find 'auth_id' cookie"); } mappedCookies.TryGetValue("sess", out string? sess); if (sess == null) { throw new Exception("Could not find 'sess' cookie"); } string? userAgent = await browser.GetUserAgentAsync(); if (userAgent == null) { throw new Exception("Could not get user agent"); } string cookies = string.Join(" ", mappedCookies.Keys.Where(key => _desiredCookies.Contains(key)) .Select(key => $"${key}={mappedCookies[key]};")); return new Auth() { COOKIE = cookies, USER_AGENT = userAgent, USER_ID = userId, X_BC = xBc }; } catch (Exception e) { Log.Error(e, "Error getting auth from browser"); return null; } } }