using OF_DL.Entities; using Serilog; using Microsoft.Playwright; namespace OF_DL.Helpers; public class AuthHelper { private readonly string _userDataDir = Path.GetFullPath("chromium-data"); private const string _initScriptsDirName = "chromium-scripts"; private readonly BrowserTypeLaunchPersistentContextOptions _options = new() { Headless = false, Channel = "chromium", Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-blink-features=AutomationControlled", "--disable-infobars"], }; private readonly string[] _desiredCookies = [ "auth_id", "sess" ]; private const float LoginTimeout = 600000f; // 10 minutes private const float FeedLoadTimeout = 60000f; // 1 minute public async Task SetupBrowser(bool runningInDocker) { if (runningInDocker) { Log.Information("Running in Docker. Disabling sandbox and GPU."); _options.Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu", "--disable-blink-features=AutomationControlled", "--disable-infobars"]; } else { var exitCode = Microsoft.Playwright.Program.Main(new[] {"install", "--with-deps", "chromium"}); if (exitCode != 0) { throw new Exception($"Playwright chromium download failed. Exited with code {exitCode}"); } } } private async Task GetBcToken(IPage page) { return await page.EvaluateAsync("window.localStorage.getItem('bcTokenSha') || ''"); } public async Task GetAuthFromBrowser(bool isDocker = false) { try { IBrowserContext? browser = null; try { var playwright = await Playwright.CreateAsync(); browser = await playwright.Chromium.LaunchPersistentContextAsync(_userDataDir, _options); } catch (Exception e) { if (e.Message.Contains("An error occurred trying to start process") && Directory.Exists(_userDataDir)) { Log.Error("Failed to launch browser. Deleting chrome-data directory and trying again."); Directory.Delete(_userDataDir, true); IPlaywright playwright = await Playwright.CreateAsync(); browser = await playwright.Chromium.LaunchPersistentContextAsync(_userDataDir, _options); } else { throw; } } if (browser == null) { throw new Exception("Could not get browser"); } IPage? page = browser.Pages[0]; if (page == null) { throw new Exception("Could not get page"); } string exeDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? string.Empty; string initScriptsDir = Path.Combine(exeDirectory, _initScriptsDirName); if (Directory.Exists(initScriptsDir)) { Log.Information("Loading init scripts from {initScriptsDir}", initScriptsDir); foreach (string initScript in Directory.GetFiles(initScriptsDir, "*.js")) { Log.Debug("Loading init script {initScript}", initScript); await page.AddInitScriptAsync(initScript); } } Log.Debug("Navigating to OnlyFans."); await page.GotoAsync("https://onlyfans.com"); Log.Debug("Waiting for user to login"); await page.WaitForSelectorAsync(".b-feed", new PageWaitForSelectorOptions { Timeout = LoginTimeout }); Log.Debug("Feed element detected (user logged in)"); await page.ReloadAsync(); await page.WaitForNavigationAsync(new PageWaitForNavigationOptions { WaitUntil = WaitUntilState.DOMContentLoaded, Timeout = FeedLoadTimeout }); Log.Debug("DOM loaded. Getting BC token and cookies ..."); string xBc; try { xBc = await GetBcToken(page); } catch (Exception e) { await browser.CloseAsync(); throw new Exception("Error getting bcToken"); } Dictionary mappedCookies = (await browser.CookiesAsync()) .Where(cookie => cookie.Domain.Contains("onlyfans.com")) .ToDictionary(cookie => cookie.Name, cookie => cookie.Value); mappedCookies.TryGetValue("auth_id", out string? userId); if (userId == null) { await browser.CloseAsync(); throw new Exception("Could not find 'auth_id' cookie"); } mappedCookies.TryGetValue("sess", out string? sess); if (sess == null) { await browser.CloseAsync(); throw new Exception("Could not find 'sess' cookie"); } string? userAgent = await page.EvaluateAsync("navigator.userAgent"); if (userAgent == null) { await browser.CloseAsync(); throw new Exception("Could not get user agent"); } string cookies = string.Join(" ", mappedCookies.Keys.Where(key => _desiredCookies.Contains(key)) .Select(key => $"${key}={mappedCookies[key]};")); await browser.CloseAsync(); 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; } } }