forked from sim0n00ps/OF-DL
Replace helper classes with services
This commit is contained in:
parent
d7bae3e260
commit
4711c53746
28
OF DL/CLI/SpectreProgressReporter.cs
Normal file
28
OF DL/CLI/SpectreProgressReporter.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using OF_DL.Services;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace OF_DL.CLI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation of IProgressReporter that uses Spectre.Console's ProgressTask for CLI output.
|
||||||
|
/// </summary>
|
||||||
|
public class SpectreProgressReporter : IProgressReporter
|
||||||
|
{
|
||||||
|
private readonly ProgressTask _task;
|
||||||
|
|
||||||
|
public SpectreProgressReporter(ProgressTask task)
|
||||||
|
{
|
||||||
|
_task = task ?? throw new ArgumentNullException(nameof(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReportProgress(long increment)
|
||||||
|
{
|
||||||
|
_task.Increment(increment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReportStatus(string message)
|
||||||
|
{
|
||||||
|
// Optionally update task description or handle status messages
|
||||||
|
// For now, we'll leave this empty as the task description is set when creating the progress bar
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,12 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using OF_DL.Enumerations;
|
using OF_DL.Enumerations;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities
|
||||||
{
|
{
|
||||||
|
|
||||||
public class Config : IDownloadConfig, IFileNameFormatConfig
|
public class Config : IFileNameFormatConfig
|
||||||
{
|
{
|
||||||
[ToggleableConfig]
|
[ToggleableConfig]
|
||||||
public bool DownloadAvatarHeaderPhoto { get; set; } = true;
|
public bool DownloadAvatarHeaderPhoto { get; set; } = true;
|
||||||
@ -107,6 +108,39 @@ namespace OF_DL.Entities
|
|||||||
// When enabled, post/message text is stored as-is without XML stripping.
|
// When enabled, post/message text is stored as-is without XML stripping.
|
||||||
[ToggleableConfig]
|
[ToggleableConfig]
|
||||||
public bool DisableTextSanitization { get; set; } = false;
|
public bool DisableTextSanitization { get; set; } = false;
|
||||||
|
|
||||||
|
public IFileNameFormatConfig GetCreatorFileNameFormatConfig(string username)
|
||||||
|
{
|
||||||
|
FileNameFormatConfig createFileNameFormatConfig = new FileNameFormatConfig();
|
||||||
|
|
||||||
|
Func<string?, string?, string?> func = (val1, val2) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(val1))
|
||||||
|
return val2;
|
||||||
|
else
|
||||||
|
return val1;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(CreatorConfigs.TryGetValue(username, out CreatorConfig? creatorConfig))
|
||||||
|
{
|
||||||
|
createFileNameFormatConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat;
|
||||||
|
createFileNameFormatConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat;
|
||||||
|
createFileNameFormatConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat;
|
||||||
|
createFileNameFormatConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
createFileNameFormatConfig.PaidMessageFileNameFormat = func(createFileNameFormatConfig.PaidMessageFileNameFormat, PaidMessageFileNameFormat);
|
||||||
|
createFileNameFormatConfig.PostFileNameFormat = func(createFileNameFormatConfig.PostFileNameFormat, PostFileNameFormat);
|
||||||
|
createFileNameFormatConfig.MessageFileNameFormat = func(createFileNameFormatConfig.MessageFileNameFormat, MessageFileNameFormat);
|
||||||
|
createFileNameFormatConfig.PaidPostFileNameFormat = func(createFileNameFormatConfig.PaidPostFileNameFormat, PaidPostFileNameFormat);
|
||||||
|
|
||||||
|
Log.Debug("PaidMessageFilenameFormat: {CombinedConfigPaidMessageFileNameFormat}", createFileNameFormatConfig.PaidMessageFileNameFormat);
|
||||||
|
Log.Debug("PostFileNameFormat: {CombinedConfigPostFileNameFormat}", createFileNameFormatConfig.PostFileNameFormat);
|
||||||
|
Log.Debug("MessageFileNameFormat: {CombinedConfigMessageFileNameFormat}", createFileNameFormatConfig.MessageFileNameFormat);
|
||||||
|
Log.Debug("PaidPostFileNameFormat: {CombinedConfigPaidPostFileNameFormat}", createFileNameFormatConfig.PaidPostFileNameFormat);
|
||||||
|
|
||||||
|
return createFileNameFormatConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreatorConfig : IFileNameFormatConfig
|
public class CreatorConfig : IFileNameFormatConfig
|
||||||
|
|||||||
37
OF DL/Entities/DownloadResult.cs
Normal file
37
OF DL/Entities/DownloadResult.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the result of a download operation.
|
||||||
|
/// </summary>
|
||||||
|
public class DownloadResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of media items processed.
|
||||||
|
/// </summary>
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of newly downloaded media items.
|
||||||
|
/// </summary>
|
||||||
|
public int NewDownloads { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of media items that were already downloaded.
|
||||||
|
/// </summary>
|
||||||
|
public int ExistingDownloads { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of media downloaded (e.g., "Posts", "Messages", "Stories", etc.).
|
||||||
|
/// </summary>
|
||||||
|
public string MediaType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the download operation was successful.
|
||||||
|
/// </summary>
|
||||||
|
public bool Success { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional error message if the download failed.
|
||||||
|
/// </summary>
|
||||||
|
public string? ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
@ -1,56 +0,0 @@
|
|||||||
using OF_DL.Enumerations;
|
|
||||||
|
|
||||||
namespace OF_DL.Entities
|
|
||||||
{
|
|
||||||
public interface IDownloadConfig
|
|
||||||
{
|
|
||||||
bool DownloadAvatarHeaderPhoto { get; set; }
|
|
||||||
bool DownloadPaidPosts { get; set; }
|
|
||||||
bool DownloadPosts { get; set; }
|
|
||||||
bool DownloadArchived { get; set; }
|
|
||||||
bool DownloadStreams { get; set; }
|
|
||||||
bool DownloadStories { get; set; }
|
|
||||||
bool DownloadHighlights { get; set; }
|
|
||||||
bool DownloadMessages { get; set; }
|
|
||||||
bool DownloadPaidMessages { get; set; }
|
|
||||||
bool DownloadImages { get; set; }
|
|
||||||
bool DownloadVideos { get; set; }
|
|
||||||
bool DownloadAudios { get; set; }
|
|
||||||
|
|
||||||
VideoResolution DownloadVideoResolution { get; set; }
|
|
||||||
|
|
||||||
int? Timeout { get; set; }
|
|
||||||
bool FolderPerPaidPost { get; set; }
|
|
||||||
bool FolderPerPost { get; set; }
|
|
||||||
bool FolderPerPaidMessage { get; set; }
|
|
||||||
bool FolderPerMessage { get; set; }
|
|
||||||
|
|
||||||
bool RenameExistingFilesWhenCustomFormatIsSelected { get; set; }
|
|
||||||
bool ShowScrapeSize { get; set; }
|
|
||||||
bool LimitDownloadRate { get; set; }
|
|
||||||
int DownloadLimitInMbPerSec { get; set; }
|
|
||||||
string? FFmpegPath { get; set; }
|
|
||||||
|
|
||||||
bool SkipAds { get; set; }
|
|
||||||
bool BypassContentForCreatorsWhoNoLongerExist { get; set; }
|
|
||||||
|
|
||||||
#region Download Date Configurations
|
|
||||||
|
|
||||||
bool DownloadOnlySpecificDates { get; set; }
|
|
||||||
|
|
||||||
// This enum will define if we want data from before or after the CustomDate.
|
|
||||||
DownloadDateSelection DownloadDateSelection { get; set; }
|
|
||||||
|
|
||||||
// This is the specific date used in combination with the above enum.
|
|
||||||
DateTime? CustomDate { get; set; }
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
bool DownloadPostsIncrementally { get; set; }
|
|
||||||
|
|
||||||
bool DownloadDuplicatedMedia { get; set; }
|
|
||||||
public LoggingLevel LoggingLevel { get; set; }
|
|
||||||
|
|
||||||
bool IgnoreOwnMessages { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,167 +0,0 @@
|
|||||||
using OF_DL.Entities;
|
|
||||||
using OF_DL.Helpers.Interfaces;
|
|
||||||
using PuppeteerSharp;
|
|
||||||
using PuppeteerSharp.BrowserData;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace OF_DL.Helpers;
|
|
||||||
|
|
||||||
public class AuthHelper : IAuthHelper
|
|
||||||
{
|
|
||||||
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<string> GetBcToken(IPage page)
|
|
||||||
{
|
|
||||||
return await page.EvaluateExpressionAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Auth?> 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<string, string> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +1,26 @@
|
|||||||
using OF_DL.Entities;
|
using OF_DL.Entities;
|
||||||
|
using OF_DL.Services;
|
||||||
|
|
||||||
namespace OF_DL.Helpers
|
namespace OF_DL.Helpers
|
||||||
{
|
{
|
||||||
internal interface IDownloadContext
|
internal interface IDownloadContext
|
||||||
{
|
{
|
||||||
public IDownloadConfig DownloadConfig { get; }
|
|
||||||
public IFileNameFormatConfig FileNameFormatConfig { get; }
|
public IFileNameFormatConfig FileNameFormatConfig { get; }
|
||||||
public IAPIHelper ApiHelper { get; }
|
public IAPIService ApiService { get; }
|
||||||
public IDBHelper DBHelper { get; }
|
public IDBService DBService { get; }
|
||||||
public IDownloadHelper DownloadHelper { get; }
|
public IDownloadService DownloadService { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DownloadContext(
|
internal class DownloadContext(
|
||||||
IDownloadConfig downloadConfig,
|
|
||||||
IFileNameFormatConfig fileNameFormatConfig,
|
IFileNameFormatConfig fileNameFormatConfig,
|
||||||
IAPIHelper apiHelper,
|
IAPIService apiService,
|
||||||
IDBHelper dBHelper,
|
IDBService dBService,
|
||||||
IDownloadHelper downloadHelper)
|
IDownloadService downloadService)
|
||||||
: IDownloadContext
|
: IDownloadContext
|
||||||
{
|
{
|
||||||
public IAPIHelper ApiHelper { get; } = apiHelper;
|
public IAPIService ApiService { get; } = apiService;
|
||||||
public IDBHelper DBHelper { get; } = dBHelper;
|
public IDBService DBService { get; } = dBService;
|
||||||
public IDownloadHelper DownloadHelper { get; } = downloadHelper;
|
public IDownloadService DownloadService { get; } = downloadService;
|
||||||
public IDownloadConfig DownloadConfig { get; } = downloadConfig;
|
|
||||||
public IFileNameFormatConfig FileNameFormatConfig { get; } = fileNameFormatConfig;
|
public IFileNameFormatConfig FileNameFormatConfig { get; } = fileNameFormatConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,10 +0,0 @@
|
|||||||
using OF_DL.Entities;
|
|
||||||
|
|
||||||
namespace OF_DL.Helpers.Interfaces
|
|
||||||
{
|
|
||||||
public interface IAuthHelper
|
|
||||||
{
|
|
||||||
Task SetupBrowser(bool runningInDocker);
|
|
||||||
Task<Auth?> GetAuthFromBrowser(bool isDocker = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
using OF_DL.Entities;
|
|
||||||
using OF_DL.Entities.Archived;
|
|
||||||
using OF_DL.Entities.Messages;
|
|
||||||
using OF_DL.Entities.Post;
|
|
||||||
using OF_DL.Entities.Purchased;
|
|
||||||
using OF_DL.Entities.Streams;
|
|
||||||
using Spectre.Console;
|
|
||||||
using static OF_DL.Entities.Messages.Messages;
|
|
||||||
using FromUser = OF_DL.Entities.Messages.FromUser;
|
|
||||||
|
|
||||||
namespace OF_DL.Helpers
|
|
||||||
{
|
|
||||||
public interface IDownloadHelper
|
|
||||||
{
|
|
||||||
Task<long> CalculateTotalFileSize(List<string> urls);
|
|
||||||
Task<bool> DownloadArchivedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Archived.List messageInfo, Archived.Medium messageMedia, Archived.Author author, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Archived.List postInfo, Archived.Medium postMedia, Archived.Author author, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary<string, long> users);
|
|
||||||
Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username);
|
|
||||||
Task<bool> DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Messages.List messageInfo, Messages.Medium messageMedia, Messages.FromUser fromUser, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadMessageMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Messages.List messageInfo, Messages.Medium messageMedia, Messages.FromUser fromUser, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Post.List postInfo, Post.Medium postMedia, Post.Author author, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, long> users);
|
|
||||||
|
|
||||||
Task<bool> DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, long> users);
|
|
||||||
|
|
||||||
Task<bool> DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List postInfo, Medium postMedia, Purchased.FromUser fromUser, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadPurchasedPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadStoryMedia(string url, string folder, long media_id, string api_type, ProgressTask task);
|
|
||||||
Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Streams.List streamInfo, Streams.Medium streamMedia, Streams.Author author, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url,
|
|
||||||
string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type,
|
|
||||||
ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
|
||||||
FromUser? fromUser, Dictionary<string, long> users);
|
|
||||||
|
|
||||||
Task<bool> DownloadMessagePreviewMedia(string url, string folder, long media_id, string api_type,
|
|
||||||
ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
|
||||||
FromUser? fromUser, Dictionary<string, long> users);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1353
OF DL/Program.cs
1353
OF DL/Program.cs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,31 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OF_DL.Entities;
|
using OF_DL.Entities;
|
||||||
using OF_DL.Helpers.Interfaces;
|
using PuppeteerSharp;
|
||||||
|
using PuppeteerSharp.BrowserData;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services
|
||||||
{
|
{
|
||||||
public class AuthService(IAuthHelper authHelper) : IAuthService
|
public class AuthService : IAuthService
|
||||||
{
|
{
|
||||||
|
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 Auth? CurrentAuth { get; set; }
|
public Auth? CurrentAuth { get; set; }
|
||||||
|
|
||||||
public async Task<bool> LoadFromFileAsync(string filePath = "auth.json")
|
public async Task<bool> LoadFromFileAsync(string filePath = "auth.json")
|
||||||
@ -37,8 +56,8 @@ namespace OF_DL.Services
|
|||||||
{
|
{
|
||||||
bool runningInDocker = Environment.GetEnvironmentVariable("OFDL_DOCKER") != null;
|
bool runningInDocker = Environment.GetEnvironmentVariable("OFDL_DOCKER") != null;
|
||||||
|
|
||||||
await authHelper.SetupBrowser(runningInDocker);
|
await SetupBrowser(runningInDocker);
|
||||||
CurrentAuth = await authHelper.GetAuthFromBrowser();
|
CurrentAuth = await GetAuthFromBrowser();
|
||||||
|
|
||||||
return CurrentAuth != null;
|
return CurrentAuth != null;
|
||||||
}
|
}
|
||||||
@ -68,5 +87,144 @@ namespace OF_DL.Services
|
|||||||
Log.Error(ex, "Failed to save auth to file");
|
Log.Error(ex, "Failed to save auth to file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private 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<string> GetBcToken(IPage page)
|
||||||
|
{
|
||||||
|
return await page.EvaluateExpressionAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Auth?> 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<string, string> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ namespace OF_DL.Services
|
|||||||
{
|
{
|
||||||
public class ConfigService(ILoggingService loggingService) : IConfigService
|
public class ConfigService(ILoggingService loggingService) : IConfigService
|
||||||
{
|
{
|
||||||
public Config? CurrentConfig { get; private set; }
|
public Config CurrentConfig { get; private set; } = new();
|
||||||
public bool IsCliNonInteractive { get; private set; }
|
public bool IsCliNonInteractive { get; private set; }
|
||||||
|
|
||||||
public async Task<bool> LoadConfigurationAsync(string[] args)
|
public async Task<bool> LoadConfigurationAsync(string[] args)
|
||||||
|
|||||||
@ -1,25 +1,11 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using OF_DL.Enumurations;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using OF_DL.Entities;
|
|
||||||
|
|
||||||
namespace OF_DL.Helpers
|
namespace OF_DL.Services
|
||||||
{
|
{
|
||||||
public class DBHelper : IDBHelper
|
public class DBService(IConfigService configService) : IDBService
|
||||||
{
|
{
|
||||||
private readonly IDownloadConfig downloadConfig;
|
|
||||||
|
|
||||||
public DBHelper(IDownloadConfig downloadConfig)
|
|
||||||
{
|
|
||||||
this.downloadConfig = downloadConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateDB(string folder)
|
public async Task CreateDB(string folder)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -366,7 +352,7 @@ namespace OF_DL.Helpers
|
|||||||
connection.Open();
|
connection.Open();
|
||||||
await EnsureCreatedAtColumnExists(connection, "medias");
|
await EnsureCreatedAtColumnExists(connection, "medias");
|
||||||
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
|
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
|
||||||
if (downloadConfig.DownloadDuplicatedMedia)
|
if (configService.CurrentConfig.DownloadDuplicatedMedia)
|
||||||
{
|
{
|
||||||
sql.Append(" and api_type=@api_type");
|
sql.Append(" and api_type=@api_type");
|
||||||
}
|
}
|
||||||
@ -405,7 +391,7 @@ namespace OF_DL.Helpers
|
|||||||
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
||||||
{
|
{
|
||||||
StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id");
|
StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id");
|
||||||
if(downloadConfig.DownloadDuplicatedMedia)
|
if(configService.CurrentConfig.DownloadDuplicatedMedia)
|
||||||
{
|
{
|
||||||
sql.Append(" and api_type=@api_type");
|
sql.Append(" and api_type=@api_type");
|
||||||
}
|
}
|
||||||
@ -440,7 +426,7 @@ namespace OF_DL.Helpers
|
|||||||
|
|
||||||
// Construct the update command
|
// Construct the update command
|
||||||
StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id");
|
StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id");
|
||||||
if (downloadConfig.DownloadDuplicatedMedia)
|
if (configService.CurrentConfig.DownloadDuplicatedMedia)
|
||||||
{
|
{
|
||||||
sql.Append(" and api_type=@api_type");
|
sql.Append(" and api_type=@api_type");
|
||||||
}
|
}
|
||||||
2630
OF DL/Services/DownloadService.cs
Normal file
2630
OF DL/Services/DownloadService.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,10 @@
|
|||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
using OF_DL.Entities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace OF_DL.Helpers
|
namespace OF_DL.Services
|
||||||
{
|
{
|
||||||
public class FileNameHelper : IFileNameHelper
|
public class FileNameService(IAuthService authService) : IFileNameService
|
||||||
{
|
{
|
||||||
private readonly Auth auth;
|
|
||||||
|
|
||||||
public FileNameHelper(Auth auth)
|
|
||||||
{
|
|
||||||
this.auth = auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, long> users = null)
|
public async Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, long> users = null)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> values = new();
|
Dictionary<string, string> values = new();
|
||||||
@ -43,7 +30,7 @@ namespace OF_DL.Helpers
|
|||||||
object policy = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontPolicy");
|
object policy = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontPolicy");
|
||||||
object signature = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontSignature");
|
object signature = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontSignature");
|
||||||
object kvp = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontKeyPairId");
|
object kvp = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontKeyPairId");
|
||||||
DateTime lastModified = await DownloadHelper.GetDRMVideoLastModified(string.Join(",", mpdurl, policy, signature, kvp), auth);
|
DateTime lastModified = await DownloadService.GetDRMVideoLastModified(string.Join(",", mpdurl, policy, signature, kvp), authService.CurrentAuth);
|
||||||
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -52,7 +39,7 @@ namespace OF_DL.Helpers
|
|||||||
object source = GetNestedPropertyValue(obj2, "files.full.url");
|
object source = GetNestedPropertyValue(obj2, "files.full.url");
|
||||||
if(source != null)
|
if(source != null)
|
||||||
{
|
{
|
||||||
DateTime lastModified = await DownloadHelper.GetMediaLastModified(source.ToString());
|
DateTime lastModified = await DownloadService.GetMediaLastModified(source.ToString());
|
||||||
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -61,7 +48,7 @@ namespace OF_DL.Helpers
|
|||||||
object preview = GetNestedPropertyValue(obj2, "preview");
|
object preview = GetNestedPropertyValue(obj2, "preview");
|
||||||
if(preview != null)
|
if(preview != null)
|
||||||
{
|
{
|
||||||
DateTime lastModified = await DownloadHelper.GetMediaLastModified(preview.ToString());
|
DateTime lastModified = await DownloadService.GetMediaLastModified(preview.ToString());
|
||||||
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -8,32 +8,32 @@ using OF_DL.Entities.Streams;
|
|||||||
using OF_DL.Enumurations;
|
using OF_DL.Enumurations;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace OF_DL.Helpers
|
namespace OF_DL.Services
|
||||||
{
|
{
|
||||||
public interface IAPIHelper
|
public interface IAPIService
|
||||||
{
|
{
|
||||||
Task<string> GetDecryptionKeyCDRMProject(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
Task<string> GetDecryptionKeyCDRMProject(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
||||||
Task<string> GetDecryptionKeyCDM(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
Task<string> GetDecryptionKeyCDM(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
||||||
Task<DateTime> GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp);
|
Task<DateTime> GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp);
|
||||||
Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp);
|
Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp);
|
||||||
Task<Dictionary<string, long>> GetLists(string endpoint, IDownloadConfig config);
|
Task<Dictionary<string, long>> GetLists(string endpoint);
|
||||||
Task<List<string>> GetListUsers(string endpoint, IDownloadConfig config);
|
Task<List<string>> GetListUsers(string endpoint);
|
||||||
Task<Dictionary<long, string>> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, IDownloadConfig config, List<long> paid_post_ids);
|
Task<Dictionary<long, string>> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, List<long> paid_post_ids);
|
||||||
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
|
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, List<long> paid_post_ids, StatusContext ctx);
|
||||||
Task<PostCollection> GetPosts(string endpoint, string folder, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
|
Task<PostCollection> GetPosts(string endpoint, string folder, List<long> paid_post_ids, StatusContext ctx);
|
||||||
Task<SinglePostCollection> GetPost(string endpoint, string folder, IDownloadConfig config);
|
Task<SinglePostCollection> GetPost(string endpoint, string folder);
|
||||||
Task<StreamsCollection> GetStreams(string endpoint, string folder, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
|
Task<StreamsCollection> GetStreams(string endpoint, string folder, List<long> paid_post_ids, StatusContext ctx);
|
||||||
Task<ArchivedCollection> GetArchived(string endpoint, string folder, IDownloadConfig config, StatusContext ctx);
|
Task<ArchivedCollection> GetArchived(string endpoint, string folder, StatusContext ctx);
|
||||||
Task<MessageCollection> GetMessages(string endpoint, string folder, IDownloadConfig config, StatusContext ctx);
|
Task<MessageCollection> GetMessages(string endpoint, string folder, StatusContext ctx);
|
||||||
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, IDownloadConfig config, StatusContext ctx);
|
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, StatusContext ctx);
|
||||||
Task<SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder, IDownloadConfig config);
|
Task<SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder);
|
||||||
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary<string, long> users);
|
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users);
|
||||||
Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary<string, long> users);
|
Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, Dictionary<string, long> users);
|
||||||
Task<User> GetUserInfo(string endpoint);
|
Task<User> GetUserInfo(string endpoint);
|
||||||
Task<JObject> GetUserInfoById(string endpoint);
|
Task<JObject> GetUserInfoById(string endpoint);
|
||||||
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);
|
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);
|
||||||
Task<Dictionary<string, long>> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config);
|
Task<Dictionary<string, long>> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
||||||
Task<Dictionary<string, long>> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config);
|
Task<Dictionary<string, long>> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
||||||
Task<string> GetDecryptionKeyOFDL(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
Task<string> GetDecryptionKeyOFDL(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@ namespace OF_DL.Services
|
|||||||
{
|
{
|
||||||
public interface IConfigService
|
public interface IConfigService
|
||||||
{
|
{
|
||||||
Config? CurrentConfig { get; }
|
Config CurrentConfig { get; }
|
||||||
bool IsCliNonInteractive { get; }
|
bool IsCliNonInteractive { get; }
|
||||||
Task<bool> LoadConfigurationAsync(string[] args);
|
Task<bool> LoadConfigurationAsync(string[] args);
|
||||||
Task SaveConfigurationAsync(string filePath = "config.conf");
|
Task SaveConfigurationAsync(string filePath = "config.conf");
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
namespace OF_DL.Helpers
|
namespace OF_DL.Services
|
||||||
{
|
{
|
||||||
public interface IDBHelper
|
public interface IDBService
|
||||||
{
|
{
|
||||||
Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, long user_id);
|
Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, long user_id);
|
||||||
Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at);
|
Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at);
|
||||||
50
OF DL/Services/IDownloadService.cs
Normal file
50
OF DL/Services/IDownloadService.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using OF_DL.Entities;
|
||||||
|
using OF_DL.Entities.Archived;
|
||||||
|
using OF_DL.Entities.Messages;
|
||||||
|
using OF_DL.Entities.Post;
|
||||||
|
using OF_DL.Entities.Purchased;
|
||||||
|
using OF_DL.Entities.Streams;
|
||||||
|
using static OF_DL.Entities.Messages.Messages;
|
||||||
|
using FromUser = OF_DL.Entities.Messages.FromUser;
|
||||||
|
|
||||||
|
namespace OF_DL.Services
|
||||||
|
{
|
||||||
|
public interface IDownloadService
|
||||||
|
{
|
||||||
|
Task<long> CalculateTotalFileSize(List<string> urls);
|
||||||
|
Task<bool> ProcessMediaDownload(string folder, long media_id, string api_type, string url, string path, string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter);
|
||||||
|
Task<bool> DownloadArchivedMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Archived.List? messageInfo, Archived.Medium? messageMedia, Archived.Author? author, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Archived.List? postInfo, Archived.Medium? postMedia, Archived.Author? author, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, long> users);
|
||||||
|
Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username);
|
||||||
|
Task<bool> DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadMessageMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? postInfo, Medium? postMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadPurchasedPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadStoryMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter);
|
||||||
|
Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url,
|
||||||
|
string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type,
|
||||||
|
IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
||||||
|
FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<bool> DownloadMessagePreviewMedia(string url, string folder, long media_id, string api_type,
|
||||||
|
IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
||||||
|
FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
Task<DownloadResult> DownloadHighlights(string username, long userId, string path, HashSet<long> paidPostIds, IProgressReporter progressReporter);
|
||||||
|
Task<DownloadResult> DownloadStories(string username, long userId, string path, HashSet<long> paidPostIds, IProgressReporter progressReporter);
|
||||||
|
Task<DownloadResult> DownloadArchived(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, ArchivedCollection archived, IProgressReporter progressReporter);
|
||||||
|
Task<DownloadResult> DownloadMessages(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, MessageCollection messages, IProgressReporter progressReporter);
|
||||||
|
Task<DownloadResult> DownloadPaidMessages(string username, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter);
|
||||||
|
Task<DownloadResult> DownloadStreams(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamsCollection streams, IProgressReporter progressReporter);
|
||||||
|
Task<DownloadResult> DownloadFreePosts(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostCollection posts, IProgressReporter progressReporter);
|
||||||
|
Task<DownloadResult> DownloadPaidPosts(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidPostCollection purchasedPosts, IProgressReporter progressReporter);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
OF DL/Services/IFileNameService.cs
Normal file
8
OF DL/Services/IFileNameService.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace OF_DL.Services
|
||||||
|
{
|
||||||
|
public interface IFileNameService
|
||||||
|
{
|
||||||
|
Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values);
|
||||||
|
Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, long> users = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
OF DL/Services/IProgressReporter.cs
Normal file
20
OF DL/Services/IProgressReporter.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for reporting download progress in a UI-agnostic way.
|
||||||
|
/// This allows the download service to report progress without being coupled to any specific UI framework.
|
||||||
|
/// </summary>
|
||||||
|
public interface IProgressReporter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reports progress increment. The value represents either bytes downloaded or file count depending on configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="increment">The amount to increment progress by</param>
|
||||||
|
void ReportProgress(long increment);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reports a status message (optional for implementations).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The status message to report</param>
|
||||||
|
void ReportStatus(string message);
|
||||||
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using WidevineClient.Widevine;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace WidevineClient.Widevine
|
namespace OF_DL.Widevine
|
||||||
{
|
{
|
||||||
public class CDMApi
|
public class CDMApi
|
||||||
{
|
{
|
||||||
Loading…
x
Reference in New Issue
Block a user