Update work with recent major refactor

This commit is contained in:
whimsical-c4lic0 2026-02-11 13:20:06 -06:00
parent de97336f6c
commit c2ab3dd79f
6 changed files with 100 additions and 83 deletions

View File

@ -13,9 +13,9 @@
<PackageReference Include="HtmlAgilityPack" Version="1.12.4"/>
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3"/>
<PackageReference Include="Microsoft.Playwright" Version="1.58.0"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
<PackageReference Include="protobuf-net" Version="3.2.56"/>
<PackageReference Include="PuppeteerSharp" Version="20.2.6"/>
<PackageReference Include="Serilog" Version="4.3.1"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1"/>
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0"/>

View File

@ -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")
};
/// <summary>
/// Gets or sets the current authentication state.
@ -35,7 +43,7 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
public Auth? CurrentAuth { get; set; }
/// <summary>
/// Loads authentication data from disk.
/// Loads authentication data from the disk.
/// </summary>
/// <param name="filePath">The auth file path.</param>
/// <returns>True when auth data is loaded successfully.</returns>
@ -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<InstalledBrowser> 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<string> 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;
}
}
private async Task<string> GetBcToken(IPage page) =>
await page.EvaluateExpressionAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
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 static async Task<string> GetBcToken(IPage page) =>
await page.EvaluateAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
/// <summary>
/// Normalizes the stored cookie string to only include required cookie values.
@ -194,10 +196,10 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
/// </summary>
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<string, string> mappedCookies = (await page.GetCookiesAsync())
Dictionary<string, string> 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<string>("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)

View File

@ -36,7 +36,7 @@ public interface IAuthService
Task<UserEntities.User?> ValidateAuthAsync();
/// <summary>
/// Logs out by deleting chrome-data and auth.json.
/// Logs out by deleting chromium-data and auth.json.
/// </summary>
void Logout();
}

View File

@ -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"));
}

View File

@ -23,8 +23,9 @@
<PackageReference Include="HtmlAgilityPack" Version="1.12.4"/>
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3"/>
<PackageReference Include="Microsoft.Playwright" Version="1.58.0"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
<PackageReference Include="protobuf-net" Version="3.2.56"/><PackageReference Include="Microsoft.Playwright" Version="1.55.0"/>
<PackageReference Include="protobuf-net" Version="3.2.56"/>
<PackageReference Include="Serilog" Version="4.3.1"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1"/>
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0"/>

View File

@ -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[/]");