From c2ab3dd79f900c91210db80826ea7ed02da4e489 Mon Sep 17 00:00:00 2001 From: whimsical-c4lic0 Date: Wed, 11 Feb 2026 13:20:06 -0600 Subject: [PATCH] Update work with recent major refactor --- OF DL.Core/OF DL.Core.csproj | 2 +- OF DL.Core/Services/AuthService.cs | 158 +++++++++++++---------- OF DL.Core/Services/IAuthService.cs | 2 +- OF DL.Tests/Services/AuthServiceTests.cs | 16 +-- OF DL/OF DL.csproj | 3 +- OF DL/Program.cs | 2 - 6 files changed, 100 insertions(+), 83 deletions(-) diff --git a/OF DL.Core/OF DL.Core.csproj b/OF DL.Core/OF DL.Core.csproj index 2ef833a..ed0b30d 100644 --- a/OF DL.Core/OF DL.Core.csproj +++ b/OF DL.Core/OF DL.Core.csproj @@ -13,9 +13,9 @@ + - diff --git a/OF DL.Core/Services/AuthService.cs b/OF DL.Core/Services/AuthService.cs index a8b725c..a277a34 100644 --- a/OF DL.Core/Services/AuthService.cs +++ b/OF DL.Core/Services/AuthService.cs @@ -1,9 +1,8 @@ using System.Text.RegularExpressions; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Playwright; using Newtonsoft.Json; using OF_DL.Models; -using PuppeteerSharp; -using PuppeteerSharp.BrowserData; using Serilog; using UserEntities = OF_DL.Models.Entities.Users; @@ -11,8 +10,25 @@ namespace OF_DL.Services; public class AuthService(IServiceProvider serviceProvider) : IAuthService { - private const int LoginTimeout = 600000; // 10 minutes - private const int FeedLoadTimeout = 60000; // 1 minute + private const float LoginTimeout = 600000f; // 10 minutes + private const float FeedLoadTimeout = 60000f; // 1 minute + private const int AdditionalWaitAfterPageLoad = 3000; // 3 seconds + + 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 = [ @@ -20,14 +36,6 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService "sess" ]; - private readonly LaunchOptions _options = new() - { - Headless = false, - Channel = ChromeReleaseChannel.Stable, - DefaultViewport = null, - Args = ["--no-sandbox", "--disable-setuid-sandbox"], - UserDataDir = Path.GetFullPath("chrome-data") - }; /// /// Gets or sets the current authentication state. @@ -35,7 +43,7 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService public Auth? CurrentAuth { get; set; } /// - /// Loads authentication data from disk. + /// Loads authentication data from the disk. /// /// The auth file path. /// True when auth data is loaded successfully. @@ -107,43 +115,37 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService } } - private async Task SetupBrowser(bool runningInDocker) + private 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 - { - BrowserFetcher browserFetcher = new(); - List installedBrowsers = browserFetcher.GetInstalledBrowsers().ToList(); - if (installedBrowsers.Count == 0) - { - Log.Information("Downloading browser."); - InstalledBrowser? 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"]; + _options.Args = + [ + "--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu", + "--disable-blink-features=AutomationControlled", "--disable-infobars" + ]; + + // If chromium is already downloaded, skip installation + string? playwrightBrowsersPath = Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH"); + IEnumerable folders = Directory.GetDirectories(playwrightBrowsersPath ?? "/config/chromium") + .Where(folder => folder.Contains("chromium-")); + + if (folders.Any()) + { + Log.Information("chromium already downloaded. Skipping install step."); + return Task.CompletedTask; + } } + + int exitCode = Program.Main(["install", "--with-deps", "chromium"]); + return exitCode != 0 + ? throw new Exception($"Playwright chromium download failed. Exited with code {exitCode}") + : Task.CompletedTask; } - private async Task GetBcToken(IPage page) => - await page.EvaluateExpressionAsync("window.localStorage.getItem('bcTokenSha') || ''"); + private static async Task GetBcToken(IPage page) => + await page.EvaluateAsync("window.localStorage.getItem('bcTokenSha') || ''"); /// /// Normalizes the stored cookie string to only include required cookie values. @@ -194,10 +196,10 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService /// public void Logout() { - if (Directory.Exists("chrome-data")) + if (Directory.Exists("chromium-data")) { - Log.Information("Deleting chrome-data folder"); - Directory.Delete("chrome-data", true); + Log.Information("Deleting chromium-data folder"); + Directory.Delete("chromium-data", true); } if (File.Exists("auth.json")) @@ -211,18 +213,23 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService { try { - IBrowser? browser; + IBrowserContext? browser; try { - browser = await Puppeteer.LaunchAsync(_options); + IPlaywright playwright = await Playwright.CreateAsync(); + browser = await playwright.Chromium.LaunchPersistentContextAsync(_userDataDir, _options); } - catch (ProcessException e) + catch (Exception e) { - if (e.Message.Contains("Failed to launch browser") && Directory.Exists(_options.UserDataDir)) + if (( + e.Message.Contains("An error occurred trying to start process") || + e.Message.Contains("The profile appears to be in use by another Chromium process") + ) && Directory.Exists(_userDataDir)) { - Log.Error("Failed to launch browser. Deleting chrome-data directory and trying again."); - Directory.Delete(_options.UserDataDir, true); - browser = await Puppeteer.LaunchAsync(_options); + Log.Error("Failed to launch browser. Deleting chromium-data directory and trying again."); + Directory.Delete(_userDataDir, true); + IPlaywright playwright = await Playwright.CreateAsync(); + browser = await playwright.Chromium.LaunchPersistentContextAsync(_userDataDir, _options); } else { @@ -235,27 +242,39 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService throw new Exception("Could not get browser"); } - IPage[]? pages = await browser.PagesAsync(); - IPage? page = pages.First(); + 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"); + await page.GotoAsync("https://onlyfans.com"); Log.Debug("Waiting for user to login"); - await page.WaitForSelectorAsync(".b-feed", new WaitForSelectorOptions { Timeout = LoginTimeout }); + await page.WaitForSelectorAsync(".b-feed", new PageWaitForSelectorOptions { Timeout = LoginTimeout }); Log.Debug("Feed element detected (user logged in)"); - await page.ReloadAsync(); + await page.ReloadAsync( + new PageReloadOptions { Timeout = FeedLoadTimeout, WaitUntil = WaitUntilState.DOMContentLoaded }); + + // Wait for an additional time to ensure the DOM is fully loaded + await Task.Delay(AdditionalWaitAfterPageLoad); - await page.WaitForNavigationAsync(new NavigationOptions - { - WaitUntil = [WaitUntilNavigation.Networkidle2], Timeout = FeedLoadTimeout - }); Log.Debug("DOM loaded. Getting BC token and cookies ..."); string xBc; @@ -265,35 +284,40 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService } catch (Exception e) { - Log.Error(e, "Error getting bcToken"); - throw new Exception("Error getting bcToken"); + await browser.CloseAsync(); + throw new Exception($"Error getting bcToken. {e.Message}"); } - Dictionary mappedCookies = (await page.GetCookiesAsync()) + 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 browser.GetUserAgentAsync(); - if (userAgent == null) + string userAgent = await page.EvaluateAsync("navigator.userAgent"); + if (string.IsNullOrWhiteSpace(userAgent)) { + 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, UserAgent = userAgent, UserId = userId, XBc = xBc }; } catch (Exception e) diff --git a/OF DL.Core/Services/IAuthService.cs b/OF DL.Core/Services/IAuthService.cs index 432d13f..5078615 100644 --- a/OF DL.Core/Services/IAuthService.cs +++ b/OF DL.Core/Services/IAuthService.cs @@ -36,7 +36,7 @@ public interface IAuthService Task ValidateAuthAsync(); /// - /// Logs out by deleting chrome-data and auth.json. + /// Logs out by deleting chromium-data and auth.json. /// void Logout(); } diff --git a/OF DL.Tests/Services/AuthServiceTests.cs b/OF DL.Tests/Services/AuthServiceTests.cs index b25a576..f4dab59 100644 --- a/OF DL.Tests/Services/AuthServiceTests.cs +++ b/OF DL.Tests/Services/AuthServiceTests.cs @@ -29,10 +29,7 @@ public class AuthServiceTests AuthService service = CreateService(); service.CurrentAuth = new Auth { - UserId = "123", - UserAgent = "agent", - XBc = "xbc", - Cookie = "auth_id=123; sess=abc;" + UserId = "123", UserAgent = "agent", XBc = "xbc", Cookie = "auth_id=123; sess=abc;" }; await service.SaveToFileAsync(); @@ -53,10 +50,7 @@ public class AuthServiceTests using TempFolder temp = new(); using CurrentDirectoryScope _ = new(temp.Path); AuthService service = CreateService(); - service.CurrentAuth = new Auth - { - Cookie = "auth_id=123; other=1; sess=abc" - }; + service.CurrentAuth = new Auth { Cookie = "auth_id=123; other=1; sess=abc" }; service.ValidateCookieString(); @@ -74,13 +68,13 @@ public class AuthServiceTests using TempFolder temp = new(); using CurrentDirectoryScope _ = new(temp.Path); AuthService service = CreateService(); - Directory.CreateDirectory("chrome-data"); - File.WriteAllText("chrome-data/test.txt", "x"); + Directory.CreateDirectory("chromium-data"); + File.WriteAllText("chromium-data/test.txt", "x"); File.WriteAllText("auth.json", "{}"); service.Logout(); - Assert.False(Directory.Exists("chrome-data")); + Assert.False(Directory.Exists("chromium-data")); Assert.False(File.Exists("auth.json")); } diff --git a/OF DL/OF DL.csproj b/OF DL/OF DL.csproj index 8285c32..8ace189 100644 --- a/OF DL/OF DL.csproj +++ b/OF DL/OF DL.csproj @@ -23,8 +23,9 @@ + - + diff --git a/OF DL/Program.cs b/OF DL/Program.cs index f43d269..2716455 100644 --- a/OF DL/Program.cs +++ b/OF DL/Program.cs @@ -37,8 +37,6 @@ public class Program(IServiceProvider serviceProvider) { AnsiConsole.MarkupLine( "[yellow]In the new window that has opened, please log in to your OF account. Do not close the window or tab. Do not navigate away from the page.[/]\n"); - AnsiConsole.MarkupLine( - "[yellow]Note: Some users have reported that \"Sign in with Google\" has not been working with the new authentication method.[/]"); AnsiConsole.MarkupLine( "[yellow]If you use this method or encounter other issues while logging in, use one of the legacy authentication methods documented here:[/]"); AnsiConsole.MarkupLine("[link]https://docs.ofdl.tools/config/auth/#legacy-methods[/]");