Major refactor #141

Merged
sim0n00ps merged 55 commits from whimsical-c4lic0/OF-DL:refactor-architecture into master 2026-02-13 00:21:58 +00:00
13 changed files with 363 additions and 405 deletions
Showing only changes of commit 4889be1890 - Show all commits

View File

@ -37,8 +37,8 @@ using SinglePostCollection = OF_DL.Models.Entities.Posts.SinglePostCollection;
namespace OF_DL.Services; namespace OF_DL.Services;
public class ApiService(IAuthService authService, IConfigService configService, IDBService dbService) public class ApiService(IAuthService authService, IConfigService configService, IDbService dbService)
: IAPIService : IApiService
{ {
private const int MaxAttempts = 30; private const int MaxAttempts = 30;
private const int DelayBetweenAttempts = 3000; private const int DelayBetweenAttempts = 3000;
@ -200,9 +200,9 @@ public class ApiService(IAuthService authService, IConfigService configService,
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync(); string body = await response.Content.ReadAsStringAsync();
//if the content creator doesnt exist, we get a 200 response, but the content isnt usable // if the content creator doesn't exist, we get a 200 response, but the content isn't usable
//so let's not throw an exception, since "content creator no longer exists" is handled elsewhere // so let's not throw an exception, since "content creator no longer exists" is handled elsewhere
//which means we wont get loads of exceptions // which means we won't get loads of exceptions
if (body.Equals("[]")) if (body.Equals("[]"))
{ {
return null; return null;
@ -2897,7 +2897,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
} }
public async Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp) public async Task<string> GetDrmMpdPssh(string mpdUrl, string policy, string signature, string kvp)
{ {
try try
{ {
@ -2941,9 +2941,9 @@ public class ApiService(IAuthService authService, IConfigService configService,
} }
public async Task<DateTime> GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp) public async Task<DateTime> GetDrmMpdLastModified(string mpdUrl, string policy, string signature, string kvp)
{ {
Log.Debug("Calling GetDRMMPDLastModified"); Log.Debug("Calling GetDrmMpdLastModified");
Log.Debug($"mpdUrl: {mpdUrl}"); Log.Debug($"mpdUrl: {mpdUrl}");
Log.Debug($"policy: {policy}"); Log.Debug($"policy: {policy}");
Log.Debug($"signature: {signature}"); Log.Debug($"signature: {signature}");
@ -2989,10 +2989,10 @@ public class ApiService(IAuthService authService, IConfigService configService,
return DateTime.Now; return DateTime.Now;
} }
public async Task<string> GetDecryptionKeyOFDL(Dictionary<string, string> drmHeaders, string licenceUrl, public async Task<string> GetDecryptionKeyOfdl(Dictionary<string, string> drmHeaders, string licenceUrl,
string pssh) string pssh)
{ {
Log.Debug("Calling GetDecryptionOFDL"); Log.Debug("Calling GetDecryptionKeyOfdl");
try try
{ {
@ -3052,7 +3052,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
return string.Empty; return string.Empty;
} }
public async Task<string> GetDecryptionKeyCDM(Dictionary<string, string> drmHeaders, string licenceUrl, public async Task<string> GetDecryptionKeyCdm(Dictionary<string, string> drmHeaders, string licenceUrl,
string pssh) string pssh)
{ {
Log.Debug("Calling GetDecryptionKeyCDM"); Log.Debug("Calling GetDecryptionKeyCDM");

View File

@ -161,8 +161,8 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
public async Task<UserEntities.User?> ValidateAuthAsync() public async Task<UserEntities.User?> ValidateAuthAsync()
{ {
// Resolve IAPIService lazily to avoid circular dependency // Resolve IApiService lazily to avoid circular dependency
IAPIService apiService = serviceProvider.GetRequiredService<IAPIService>(); IApiService apiService = serviceProvider.GetRequiredService<IApiService>();
return await apiService.GetUserInfo("/users/me"); return await apiService.GetUserInfo("/users/me");
} }

View File

@ -4,9 +4,9 @@ using Serilog;
namespace OF_DL.Services; namespace OF_DL.Services;
public class DBService(IConfigService configService) : IDBService public class DbService(IConfigService configService) : IDbService
{ {
public async Task CreateDB(string folder) public async Task CreateDb(string folder)
{ {
try try
{ {
@ -18,15 +18,15 @@ public class DBService(IConfigService configService) : IDBService
string dbFilePath = $"{folder}/Metadata/user_data.db"; string dbFilePath = $"{folder}/Metadata/user_data.db";
// connect to the new database file // connect to the new database file
using SqliteConnection connection = new($"Data Source={dbFilePath}"); await using SqliteConnection connection = new($"Data Source={dbFilePath}");
// open the connection // open the connection
connection.Open(); connection.Open();
// create the 'medias' table // create the 'medias' table
using (SqliteCommand cmd = await using (SqliteCommand cmd =
new( new(
"CREATE TABLE IF NOT EXISTS medias (id INTEGER NOT NULL, media_id INTEGER, post_id INTEGER NOT NULL, link VARCHAR, directory VARCHAR, filename VARCHAR, size INTEGER, api_type VARCHAR, media_type VARCHAR, preview INTEGER, linked VARCHAR, downloaded INTEGER, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(media_id));", "CREATE TABLE IF NOT EXISTS medias (id INTEGER NOT NULL, media_id INTEGER, post_id INTEGER NOT NULL, link VARCHAR, directory VARCHAR, filename VARCHAR, size INTEGER, api_type VARCHAR, media_type VARCHAR, preview INTEGER, linked VARCHAR, downloaded INTEGER, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(media_id));",
connection)) connection))
{ {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
@ -36,7 +36,7 @@ public class DBService(IConfigService configService) : IDBService
// //
// Alter existing databases to create unique constraint on `medias` // Alter existing databases to create unique constraint on `medias`
// //
using (SqliteCommand cmd = new(@" await using (SqliteCommand cmd = new(@"
PRAGMA foreign_keys=off; PRAGMA foreign_keys=off;
BEGIN TRANSACTION; BEGIN TRANSACTION;
@ -74,55 +74,55 @@ public class DBService(IConfigService configService) : IDBService
} }
// create the 'messages' table // create the 'messages' table
using (SqliteCommand cmd = await using (SqliteCommand cmd =
new( new(
"CREATE TABLE IF NOT EXISTS messages (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, user_id INTEGER, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", "CREATE TABLE IF NOT EXISTS messages (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, user_id INTEGER, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
connection)) connection))
{ {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
// create the 'posts' table // create the 'posts' table
using (SqliteCommand cmd = await using (SqliteCommand cmd =
new( new(
"CREATE TABLE IF NOT EXISTS posts (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", "CREATE TABLE IF NOT EXISTS posts (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
connection)) connection))
{ {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
// create the 'stories' table // create the 'stories' table
using (SqliteCommand cmd = await using (SqliteCommand cmd =
new( new(
"CREATE TABLE IF NOT EXISTS stories (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", "CREATE TABLE IF NOT EXISTS stories (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
connection)) connection))
{ {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
// create the 'others' table // create the 'others' table
using (SqliteCommand cmd = await using (SqliteCommand cmd =
new( new(
"CREATE TABLE IF NOT EXISTS others (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", "CREATE TABLE IF NOT EXISTS others (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
connection)) connection))
{ {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
// create the 'products' table // create the 'products' table
using (SqliteCommand cmd = await using (SqliteCommand cmd =
new( new(
"CREATE TABLE IF NOT EXISTS products (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, title VARCHAR, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", "CREATE TABLE IF NOT EXISTS products (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, title VARCHAR, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
connection)) connection))
{ {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
// create the 'profiles' table // create the 'profiles' table
using (SqliteCommand cmd = await using (SqliteCommand cmd =
new( new(
"CREATE TABLE IF NOT EXISTS profiles (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(username));", "CREATE TABLE IF NOT EXISTS profiles (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(username));",
connection)) connection))
{ {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
@ -144,19 +144,19 @@ public class DBService(IConfigService configService) : IDBService
} }
} }
public async Task CreateUsersDB(Dictionary<string, long> users) public async Task CreateUsersDb(Dictionary<string, long> users)
{ {
try try
{ {
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db"); await using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
Log.Debug("Database data source: " + connection.DataSource); Log.Debug("Database data source: " + connection.DataSource);
connection.Open(); connection.Open();
using (SqliteCommand cmd = await using (SqliteCommand cmd =
new( new(
"CREATE TABLE IF NOT EXISTS users (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, PRIMARY KEY(id), UNIQUE(username));", "CREATE TABLE IF NOT EXISTS users (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, PRIMARY KEY(id), UNIQUE(username));",
connection)) connection))
{ {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
@ -164,29 +164,23 @@ public class DBService(IConfigService configService) : IDBService
Log.Debug("Adding missing creators"); Log.Debug("Adding missing creators");
foreach (KeyValuePair<string, long> user in users) foreach (KeyValuePair<string, long> user in users)
{ {
using (SqliteCommand checkCmd = new("SELECT user_id, username FROM users WHERE user_id = @userId;", await using SqliteCommand checkCmd = new("SELECT user_id, username FROM users WHERE user_id = @userId;",
connection)) connection);
checkCmd.Parameters.AddWithValue("@userId", user.Value);
await using SqliteDataReader reader = await checkCmd.ExecuteReaderAsync();
if (!reader.Read())
{ {
checkCmd.Parameters.AddWithValue("@userId", user.Value); await using SqliteCommand insertCmd =
using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync()) new("INSERT INTO users (user_id, username) VALUES (@userId, @username);",
{ connection);
if (!reader.Read()) insertCmd.Parameters.AddWithValue("@userId", user.Value);
{ insertCmd.Parameters.AddWithValue("@username", user.Key);
using (SqliteCommand insertCmd = await insertCmd.ExecuteNonQueryAsync();
new("INSERT INTO users (user_id, username) VALUES (@userId, @username);", Log.Debug("Inserted new creator: " + user.Key);
connection)) }
{ else
insertCmd.Parameters.AddWithValue("@userId", user.Value); {
insertCmd.Parameters.AddWithValue("@username", user.Key); Log.Debug("Creator " + user.Key + " already exists");
await insertCmd.ExecuteNonQueryAsync();
Log.Debug("Inserted new creator: " + user.Key);
}
}
else
{
Log.Debug("Creator " + user.Key + " already exists");
}
}
} }
} }
@ -211,12 +205,12 @@ public class DBService(IConfigService configService) : IDBService
{ {
try try
{ {
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db"); await using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
connection.Open(); connection.Open();
using (SqliteCommand checkCmd = new("SELECT user_id, username FROM users WHERE user_id = @userId;", await using (SqliteCommand checkCmd = new("SELECT user_id, username FROM users WHERE user_id = @userId;",
connection)) connection))
{ {
checkCmd.Parameters.AddWithValue("@userId", user.Value); checkCmd.Parameters.AddWithValue("@userId", user.Value);
await using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync()) await using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync())
@ -264,16 +258,16 @@ public class DBService(IConfigService configService) : IDBService
} }
} }
public async Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, public async Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid,
bool is_archived, DateTime created_at, long user_id) bool isArchived, DateTime createdAt, long userId)
{ {
try try
{ {
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open(); connection.Open();
await EnsureCreatedAtColumnExists(connection, "messages"); await EnsureCreatedAtColumnExists(connection, "messages");
using SqliteCommand cmd = new("SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection); await using SqliteCommand cmd = new("SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection);
cmd.Parameters.AddWithValue("@post_id", post_id); cmd.Parameters.AddWithValue("@post_id", postId);
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync()); int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
if (count == 0) if (count == 0)
{ {
@ -282,13 +276,13 @@ public class DBService(IConfigService configService) : IDBService
new( new(
"INSERT INTO messages(post_id, text, price, paid, archived, created_at, user_id, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @user_id, @record_created_at)", "INSERT INTO messages(post_id, text, price, paid, archived, created_at, user_id, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @user_id, @record_created_at)",
connection); connection);
insertCmd.Parameters.AddWithValue("@post_id", post_id); insertCmd.Parameters.AddWithValue("@post_id", postId);
insertCmd.Parameters.AddWithValue("@message_text", message_text); insertCmd.Parameters.AddWithValue("@message_text", messageText);
insertCmd.Parameters.AddWithValue("@price", price); insertCmd.Parameters.AddWithValue("@price", price);
insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_paid", isPaid);
insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@is_archived", isArchived);
insertCmd.Parameters.AddWithValue("@created_at", created_at); insertCmd.Parameters.AddWithValue("@created_at", createdAt);
insertCmd.Parameters.AddWithValue("@user_id", user_id); insertCmd.Parameters.AddWithValue("@user_id", userId);
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await insertCmd.ExecuteNonQueryAsync(); await insertCmd.ExecuteNonQueryAsync();
} }
@ -309,30 +303,30 @@ public class DBService(IConfigService configService) : IDBService
} }
public async Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid, public async Task AddPost(string folder, long postId, string messageText, string price, bool isPaid,
bool is_archived, DateTime created_at) bool isArchived, DateTime createdAt)
{ {
try try
{ {
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open(); connection.Open();
await EnsureCreatedAtColumnExists(connection, "posts"); await EnsureCreatedAtColumnExists(connection, "posts");
using SqliteCommand cmd = new("SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection); await using SqliteCommand cmd = new("SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection);
cmd.Parameters.AddWithValue("@post_id", post_id); cmd.Parameters.AddWithValue("@post_id", postId);
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync()); int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
if (count == 0) if (count == 0)
{ {
// If the record doesn't exist, insert a new one // If the record doesn't exist, insert a new one
using SqliteCommand insertCmd = await using SqliteCommand insertCmd =
new( new(
"INSERT INTO posts(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)", "INSERT INTO posts(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)",
connection); connection);
insertCmd.Parameters.AddWithValue("@post_id", post_id); insertCmd.Parameters.AddWithValue("@post_id", postId);
insertCmd.Parameters.AddWithValue("@message_text", message_text); insertCmd.Parameters.AddWithValue("@message_text", messageText);
insertCmd.Parameters.AddWithValue("@price", price); insertCmd.Parameters.AddWithValue("@price", price);
insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_paid", isPaid);
insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@is_archived", isArchived);
insertCmd.Parameters.AddWithValue("@created_at", created_at); insertCmd.Parameters.AddWithValue("@created_at", createdAt);
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await insertCmd.ExecuteNonQueryAsync(); await insertCmd.ExecuteNonQueryAsync();
} }
@ -353,30 +347,30 @@ public class DBService(IConfigService configService) : IDBService
} }
public async Task AddStory(string folder, long post_id, string message_text, string price, bool is_paid, public async Task AddStory(string folder, long postId, string messageText, string price, bool isPaid,
bool is_archived, DateTime created_at) bool isArchived, DateTime createdAt)
{ {
try try
{ {
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open(); connection.Open();
await EnsureCreatedAtColumnExists(connection, "stories"); await EnsureCreatedAtColumnExists(connection, "stories");
using SqliteCommand cmd = new("SELECT COUNT(*) FROM stories WHERE post_id=@post_id", connection); await using SqliteCommand cmd = new("SELECT COUNT(*) FROM stories WHERE post_id=@post_id", connection);
cmd.Parameters.AddWithValue("@post_id", post_id); cmd.Parameters.AddWithValue("@post_id", postId);
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync()); int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
if (count == 0) if (count == 0)
{ {
// If the record doesn't exist, insert a new one // If the record doesn't exist, insert a new one
using SqliteCommand insertCmd = await using SqliteCommand insertCmd =
new( new(
"INSERT INTO stories(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)", "INSERT INTO stories(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)",
connection); connection);
insertCmd.Parameters.AddWithValue("@post_id", post_id); insertCmd.Parameters.AddWithValue("@post_id", postId);
insertCmd.Parameters.AddWithValue("@message_text", message_text); insertCmd.Parameters.AddWithValue("@message_text", messageText);
insertCmd.Parameters.AddWithValue("@price", price); insertCmd.Parameters.AddWithValue("@price", price);
insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_paid", isPaid);
insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@is_archived", isArchived);
insertCmd.Parameters.AddWithValue("@created_at", created_at); insertCmd.Parameters.AddWithValue("@created_at", createdAt);
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await insertCmd.ExecuteNonQueryAsync(); await insertCmd.ExecuteNonQueryAsync();
} }
@ -397,13 +391,13 @@ public class DBService(IConfigService configService) : IDBService
} }
public async Task AddMedia(string folder, long media_id, long post_id, string link, string? directory, public async Task AddMedia(string folder, long mediaId, long postId, string link, string? directory,
string? filename, long? size, string api_type, string media_type, bool preview, bool downloaded, string? filename, long? size, string apiType, string mediaType, bool preview, bool downloaded,
DateTime? created_at) DateTime? createdAt)
{ {
try try
{ {
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open(); connection.Open();
await EnsureCreatedAtColumnExists(connection, "medias"); await EnsureCreatedAtColumnExists(connection, "medias");
StringBuilder sql = new("SELECT COUNT(*) FROM medias WHERE media_id=@media_id"); StringBuilder sql = new("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
@ -412,15 +406,15 @@ public class DBService(IConfigService configService) : IDBService
sql.Append(" and api_type=@api_type"); sql.Append(" and api_type=@api_type");
} }
using SqliteCommand cmd = new(sql.ToString(), connection); await using SqliteCommand cmd = new(sql.ToString(), connection);
cmd.Parameters.AddWithValue("@media_id", media_id); cmd.Parameters.AddWithValue("@media_id", mediaId);
cmd.Parameters.AddWithValue("@api_type", api_type); cmd.Parameters.AddWithValue("@api_type", apiType);
int count = Convert.ToInt32(cmd.ExecuteScalar()); int count = Convert.ToInt32(cmd.ExecuteScalar());
if (count == 0) if (count == 0)
{ {
// If the record doesn't exist, insert a new one // If the record doesn't exist, insert a new one
using SqliteCommand insertCmd = new( await using SqliteCommand insertCmd = new(
$"INSERT INTO medias(media_id, post_id, link, directory, filename, size, api_type, media_type, preview, downloaded, created_at, record_created_at) VALUES({media_id}, {post_id}, '{link}', '{directory ?? "NULL"}', '{filename ?? "NULL"}', {size?.ToString() ?? "NULL"}, '{api_type}', '{media_type}', {Convert.ToInt32(preview)}, {Convert.ToInt32(downloaded)}, '{created_at?.ToString("yyyy-MM-dd HH:mm:ss")}', '{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}')", $"INSERT INTO medias(media_id, post_id, link, directory, filename, size, api_type, media_type, preview, downloaded, created_at, record_created_at) VALUES({mediaId}, {postId}, '{link}', '{directory ?? "NULL"}', '{filename ?? "NULL"}', {size?.ToString() ?? "NULL"}, '{apiType}', '{mediaType}', {Convert.ToInt32(preview)}, {Convert.ToInt32(downloaded)}, '{createdAt?.ToString("yyyy-MM-dd HH:mm:ss")}', '{DateTime.Now:yyyy-MM-dd HH:mm:ss}')",
connection); connection);
await insertCmd.ExecuteNonQueryAsync(); await insertCmd.ExecuteNonQueryAsync();
} }
@ -441,12 +435,10 @@ public class DBService(IConfigService configService) : IDBService
} }
public async Task<bool> CheckDownloaded(string folder, long media_id, string api_type) public async Task<bool> CheckDownloaded(string folder, long mediaId, string apiType)
{ {
try try
{ {
bool downloaded;
await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
StringBuilder sql = new("SELECT downloaded FROM medias WHERE media_id=@media_id"); StringBuilder sql = new("SELECT downloaded FROM medias WHERE media_id=@media_id");
if (configService.CurrentConfig.DownloadDuplicatedMedia) if (configService.CurrentConfig.DownloadDuplicatedMedia)
@ -456,9 +448,9 @@ public class DBService(IConfigService configService) : IDBService
connection.Open(); connection.Open();
await using SqliteCommand cmd = new(sql.ToString(), connection); await using SqliteCommand cmd = new(sql.ToString(), connection);
cmd.Parameters.AddWithValue("@media_id", media_id); cmd.Parameters.AddWithValue("@media_id", mediaId);
cmd.Parameters.AddWithValue("@api_type", api_type); cmd.Parameters.AddWithValue("@api_type", apiType);
downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync()); bool downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync());
return downloaded; return downloaded;
} }
@ -480,10 +472,10 @@ public class DBService(IConfigService configService) : IDBService
} }
public async Task UpdateMedia(string folder, long media_id, string api_type, string directory, string filename, public async Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename,
long size, bool downloaded, DateTime created_at) long size, bool downloaded, DateTime createdAt)
{ {
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open(); connection.Open();
// Construct the update command // Construct the update command
@ -496,33 +488,30 @@ public class DBService(IConfigService configService) : IDBService
} }
// Create a new command object // Create a new command object
using SqliteCommand command = new(sql.ToString(), connection); await using SqliteCommand command = new(sql.ToString(), connection);
// Add parameters to the command object // Add parameters to the command object
command.Parameters.AddWithValue("@directory", directory); command.Parameters.AddWithValue("@directory", directory);
command.Parameters.AddWithValue("@filename", filename); command.Parameters.AddWithValue("@filename", filename);
command.Parameters.AddWithValue("@size", size); command.Parameters.AddWithValue("@size", size);
command.Parameters.AddWithValue("@downloaded", downloaded ? 1 : 0); command.Parameters.AddWithValue("@downloaded", downloaded ? 1 : 0);
command.Parameters.AddWithValue("@created_at", created_at); command.Parameters.AddWithValue("@created_at", createdAt);
command.Parameters.AddWithValue("@media_id", media_id); command.Parameters.AddWithValue("@media_id", mediaId);
command.Parameters.AddWithValue("@api_type", api_type); command.Parameters.AddWithValue("@api_type", apiType);
// Execute the command // Execute the command
await command.ExecuteNonQueryAsync(); await command.ExecuteNonQueryAsync();
} }
public async Task<long> GetStoredFileSize(string folder, long media_id, string api_type) public async Task<long> GetStoredFileSize(string folder, long mediaId, string apiType)
{ {
long size; await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db")) connection.Open();
{ await using SqliteCommand cmd = new("SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type",
connection.Open(); connection);
using SqliteCommand cmd = new("SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", cmd.Parameters.AddWithValue("@media_id", mediaId);
connection); cmd.Parameters.AddWithValue("@api_type", apiType);
cmd.Parameters.AddWithValue("@media_id", media_id); long size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
cmd.Parameters.AddWithValue("@api_type", api_type);
size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
}
return size; return size;
} }
@ -530,10 +519,9 @@ public class DBService(IConfigService configService) : IDBService
public async Task<DateTime?> GetMostRecentPostDate(string folder) public async Task<DateTime?> GetMostRecentPostDate(string folder)
{ {
DateTime? mostRecentDate = null; DateTime? mostRecentDate = null;
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db")) await using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
{ connection.Open();
connection.Open(); await using SqliteCommand cmd = new(@"
using SqliteCommand cmd = new(@"
SELECT SELECT
MIN(created_at) AS created_at MIN(created_at) AS created_at
FROM ( FROM (
@ -549,11 +537,10 @@ public class DBService(IConfigService configService) : IDBService
ON P.post_id = m.post_id ON P.post_id = m.post_id
WHERE m.downloaded = 0 WHERE m.downloaded = 0
)", connection); )", connection);
object? scalarValue = await cmd.ExecuteScalarAsync(); object? scalarValue = await cmd.ExecuteScalarAsync();
if (scalarValue != null && scalarValue != DBNull.Value) if (scalarValue != null && scalarValue != DBNull.Value)
{ {
mostRecentDate = Convert.ToDateTime(scalarValue); mostRecentDate = Convert.ToDateTime(scalarValue);
}
} }
return mostRecentDate; return mostRecentDate;
@ -561,8 +548,8 @@ public class DBService(IConfigService configService) : IDBService
private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName) private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName)
{ {
using SqliteCommand cmd = new($"PRAGMA table_info({tableName});", connection); await using SqliteCommand cmd = new($"PRAGMA table_info({tableName});", connection);
using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
bool columnExists = false; bool columnExists = false;
while (await reader.ReadAsync()) while (await reader.ReadAsync())
@ -576,7 +563,7 @@ public class DBService(IConfigService configService) : IDBService
if (!columnExists) if (!columnExists)
{ {
using SqliteCommand alterCmd = new($"ALTER TABLE {tableName} ADD COLUMN record_created_at TIMESTAMP;", await using SqliteCommand alterCmd = new($"ALTER TABLE {tableName} ADD COLUMN record_created_at TIMESTAMP;",
connection); connection);
await alterCmd.ExecuteNonQueryAsync(); await alterCmd.ExecuteNonQueryAsync();
} }

View File

@ -10,10 +10,10 @@ using UserEntities = OF_DL.Models.Entities.Users;
namespace OF_DL.Services; namespace OF_DL.Services;
public class DownloadOrchestrationService( public class DownloadOrchestrationService(
IAPIService apiService, IApiService apiService,
IConfigService configService, IConfigService configService,
IDownloadService downloadService, IDownloadService downloadService,
IDBService dbService) : IDownloadOrchestrationService IDbService dbService) : IDownloadOrchestrationService
{ {
public List<long> PaidPostIds { get; } = new(); public List<long> PaidPostIds { get; } = new();
@ -85,7 +85,7 @@ public class DownloadOrchestrationService(
} }
} }
await dbService.CreateUsersDB(result.Users); await dbService.CreateUsersDb(result.Users);
return result; return result;
} }
@ -113,7 +113,7 @@ public class DownloadOrchestrationService(
Log.Debug($"Created folder for {username}"); Log.Debug($"Created folder for {username}");
} }
await dbService.CreateDB(path); await dbService.CreateDb(path);
} }
public async Task<CreatorDownloadResult> DownloadCreatorContentAsync( public async Task<CreatorDownloadResult> DownloadCreatorContentAsync(
@ -346,7 +346,7 @@ public class DownloadOrchestrationService(
} }
await apiService.GetUserInfo($"/users/{user.Key}"); await apiService.GetUserInfo($"/users/{user.Key}");
await dbService.CreateDB(path); await dbService.CreateDb(path);
} }
string basePath = !string.IsNullOrEmpty(config.DownloadPath) string basePath = !string.IsNullOrEmpty(config.DownloadPath)

View File

@ -19,9 +19,9 @@ namespace OF_DL.Services;
public class DownloadService( public class DownloadService(
IAuthService authService, IAuthService authService,
IConfigService configService, IConfigService configService,
IDBService dbService, IDbService dbService,
IFileNameService fileNameService, IFileNameService fileNameService,
IAPIService apiService) IApiService apiService)
: IDownloadService : IDownloadService
{ {
private TaskCompletionSource<bool> _completionSource = new(); private TaskCompletionSource<bool> _completionSource = new();
@ -67,7 +67,7 @@ public class DownloadService(
Directory.CreateDirectory(folder + subFolder); Directory.CreateDirectory(folder + subFolder);
} }
List<string> md5Hashes = CalculateFolderMD5(folder + subFolder); List<string> md5Hashes = CalculateFolderMd5(folder + subFolder);
Uri uri = new(url); Uri uri = new(url);
string destinationPath = $"{folder}{subFolder}/"; string destinationPath = $"{folder}{subFolder}/";
@ -205,7 +205,7 @@ public class DownloadService(
} }
private async Task OnFFMPEGDownloadComplete(string tempFilename, DateTime lastModified, string folder, string path, private async Task OnFFMPEGDownloadComplete(string tempFilename, DateTime lastModified, string folder, string path,
string customFileName, string filename, long media_id, string api_type, IProgressReporter progressReporter) string customFileName, string filename, long mediaId, string apiType, IProgressReporter progressReporter)
{ {
try try
{ {
@ -223,16 +223,9 @@ public class DownloadService(
long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName) long fileSizeInBytes = new FileInfo(!string.IsNullOrEmpty(customFileName)
? folder + path + "/" + customFileName + ".mp4" ? folder + path + "/" + customFileName + ".mp4"
: tempFilename).Length; : tempFilename).Length;
if (configService.CurrentConfig.ShowScrapeSize) progressReporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? fileSizeInBytes : 1);
{
progressReporter.ReportProgress(fileSizeInBytes);
}
else
{
progressReporter.ReportProgress(1);
}
await dbService.UpdateMedia(folder, media_id, api_type, folder + path, await dbService.UpdateMedia(folder, mediaId, apiType, folder + path,
!string.IsNullOrEmpty(customFileName) ? customFileName + ".mp4" : filename + "_source.mp4", !string.IsNullOrEmpty(customFileName) ? customFileName + ".mp4" : filename + "_source.mp4",
fileSizeInBytes, true, lastModified); fileSizeInBytes, true, lastModified);
} }
@ -266,32 +259,27 @@ public class DownloadService(
_completionSource.TrySetResult(false); _completionSource.TrySetResult(false);
} }
private static List<string> CalculateFolderMD5(string folder) private static List<string> CalculateFolderMd5(string folder)
{ {
List<string> md5Hashes = new(); List<string> md5Hashes = [];
if (Directory.Exists(folder)) if (!Directory.Exists(folder))
{ {
string[] files = Directory.GetFiles(folder); return md5Hashes;
foreach (string file in files)
{
md5Hashes.Add(CalculateMD5(file));
}
} }
string[] files = Directory.GetFiles(folder);
md5Hashes.AddRange(files.Select(CalculateMd5));
return md5Hashes; return md5Hashes;
} }
private static string CalculateMD5(string filePath) private static string CalculateMd5(string filePath)
{ {
using (MD5 md5 = MD5.Create()) using MD5 md5 = MD5.Create();
{ using FileStream stream = File.OpenRead(filePath);
using (FileStream stream = File.OpenRead(filePath)) byte[] hash = md5.ComputeHash(stream);
{ return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
byte[] hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
} }
/// <summary> /// <summary>
@ -299,17 +287,17 @@ public class DownloadService(
/// <param name="path"></param> /// <param name="path"></param>
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="folder"></param> /// <param name="folder"></param>
/// <param name="media_id"></param> /// <param name="mediaId"></param>
/// <param name="api_type"></param> /// <param name="apiType"></param>
/// <param name="progressReporter"></param> /// <param name="progressReporter"></param>
/// <param name="serverFileName"></param> /// <param name="serverFileName"></param>
/// <param name="resolvedFileName"></param> /// <param name="resolvedFileName"></param>
/// <returns></returns> /// <returns></returns>
protected async Task<bool> CreateDirectoriesAndDownloadMedia(string path, private async Task<bool> CreateDirectoriesAndDownloadMedia(string path,
string url, string url,
string folder, string folder,
long media_id, long mediaId,
string api_type, string apiType,
IProgressReporter progressReporter, IProgressReporter progressReporter,
string serverFileName, string serverFileName,
string resolvedFileName) string resolvedFileName)
@ -325,7 +313,7 @@ public class DownloadService(
path = UpdatePathBasedOnExtension(folder, path, extension); path = UpdatePathBasedOnExtension(folder, path, extension);
return await ProcessMediaDownload(folder, media_id, api_type, url, path, serverFileName, resolvedFileName, return await ProcessMediaDownload(folder, mediaId, apiType, url, path, serverFileName, resolvedFileName,
extension, progressReporter); extension, progressReporter);
} }
catch (Exception ex) catch (Exception ex)
@ -461,12 +449,12 @@ public class DownloadService(
if (uri.Host == "cdn3.onlyfans.com" && uri.LocalPath.Contains("/dash/files")) if (uri.Host == "cdn3.onlyfans.com" && uri.LocalPath.Contains("/dash/files"))
{ {
string[] messageUrlParsed = url.Split(','); string[] messageUrlParsed = url.Split(',');
string mpdURL = messageUrlParsed[0]; string mpdUrl = messageUrlParsed[0];
string policy = messageUrlParsed[1]; string policy = messageUrlParsed[1];
string signature = messageUrlParsed[2]; string signature = messageUrlParsed[2];
string kvp = messageUrlParsed[3]; string kvp = messageUrlParsed[3];
mpdURL = mpdURL.Replace(".mpd", "_source.mp4"); mpdUrl = mpdUrl.Replace(".mpd", "_source.mp4");
using HttpClient client = new(); using HttpClient client = new();
client.DefaultRequestHeaders.Add("Cookie", client.DefaultRequestHeaders.Add("Cookie",
@ -474,7 +462,7 @@ public class DownloadService(
client.DefaultRequestHeaders.Add("User-Agent", authService.CurrentAuth.UserAgent); client.DefaultRequestHeaders.Add("User-Agent", authService.CurrentAuth.UserAgent);
using HttpResponseMessage response = using HttpResponseMessage response =
await client.GetAsync(mpdURL, HttpCompletionOption.ResponseHeadersRead); await client.GetAsync(mpdUrl, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
fileSize = response.Content.Headers.ContentLength ?? 0; fileSize = response.Content.Headers.ContentLength ?? 0;
@ -500,7 +488,7 @@ public class DownloadService(
return fileSize; return fileSize;
} }
public static async Task<DateTime> GetDRMVideoLastModified(string url, Auth auth) public static async Task<DateTime> GetDrmVideoLastModified(string url, Auth auth)
{ {
string[] messageUrlParsed = url.Split(','); string[] messageUrlParsed = url.Split(',');
string mpdUrl = messageUrlParsed[0]; string mpdUrl = messageUrlParsed[0];
@ -532,15 +520,12 @@ public class DownloadService(
using HttpClient client = new(); using HttpClient client = new();
using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
if (response.Content.Headers.LastModified != null) return DateTime.Now;
{
return response.Content.Headers.LastModified.Value.DateTime;
}
} }
return DateTime.Now; return response.Content.Headers.LastModified?.DateTime ?? DateTime.Now;
} }
/// <summary> /// <summary>
@ -636,8 +621,8 @@ public class DownloadService(
/// Handles new media by downloading and updating the database. /// Handles new media by downloading and updating the database.
/// </summary> /// </summary>
/// <param name="folder"></param> /// <param name="folder"></param>
/// <param name="media_id"></param> /// <param name="mediaId"></param>
/// <param name="api_type"></param> /// <param name="apiType"></param>
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="path"></param> /// <param name="path"></param>
/// <param name="serverFilename"></param> /// <param name="serverFilename"></param>
@ -646,8 +631,8 @@ public class DownloadService(
/// <param name="progressReporter"></param> /// <param name="progressReporter"></param>
/// <returns>A Task resulting in a boolean indicating whether the media is newly downloaded or not.</returns> /// <returns>A Task resulting in a boolean indicating whether the media is newly downloaded or not.</returns>
private async Task<bool> HandleNewMedia(string folder, private async Task<bool> HandleNewMedia(string folder,
long media_id, long mediaId,
string api_type, string apiType,
string url, string url,
string path, string path,
string serverFilename, string serverFilename,
@ -696,32 +681,18 @@ public class DownloadService(
fileSizeInBytes = GetLocalFileSize(finalPath); fileSizeInBytes = GetLocalFileSize(finalPath);
lastModified = File.GetLastWriteTime(finalPath); lastModified = File.GetLastWriteTime(finalPath);
if (configService.CurrentConfig.ShowScrapeSize) progressReporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? fileSizeInBytes : 1);
{
progressReporter.ReportProgress(fileSizeInBytes);
}
else
{
progressReporter.ReportProgress(1);
}
status = false; status = false;
} }
// Handle the case where the file has been downloaded in the past with a custom filename. // Handle the case where the file has been downloaded in the past with a custom filename.
//but it has downloaded outsite of this application so it doesn't exist in the database // but it has downloaded outside of this application so it doesn't exist in the database
// this is a bit improbable but we should check for that. // this is a bit improbable but we should check for that.
else if (File.Exists(fullPathWithTheNewFileName)) else if (File.Exists(fullPathWithTheNewFileName))
{ {
fileSizeInBytes = GetLocalFileSize(fullPathWithTheNewFileName); fileSizeInBytes = GetLocalFileSize(fullPathWithTheNewFileName);
lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName); lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName);
if (configService.CurrentConfig.ShowScrapeSize) progressReporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? fileSizeInBytes : 1);
{
progressReporter.ReportProgress(fileSizeInBytes);
}
else
{
progressReporter.ReportProgress(1);
}
status = false; status = false;
} }
@ -733,9 +704,9 @@ public class DownloadService(
} }
//finaly check which filename we should use. Custom or the server one. //finaly check which filename we should use. Custom or the server one.
//if a custom is used, then the servefilename will be different from the resolved filename. //if a custom is used, then the serverFilename will be different from the resolved filename.
string finalName = serverFilename == resolvedFilename ? serverFilename : resolvedFilename; string finalName = serverFilename == resolvedFilename ? serverFilename : resolvedFilename;
await dbService.UpdateMedia(folder, media_id, api_type, folder + path, finalName + extension, fileSizeInBytes, await dbService.UpdateMedia(folder, mediaId, apiType, folder + path, finalName + extension, fileSizeInBytes,
true, lastModified); true, lastModified);
return status; return status;
} }
@ -745,16 +716,16 @@ public class DownloadService(
/// Handles media that has been previously downloaded and updates the task accordingly. /// Handles media that has been previously downloaded and updates the task accordingly.
/// </summary> /// </summary>
/// <param name="folder"></param> /// <param name="folder"></param>
/// <param name="media_id"></param> /// <param name="mediaId"></param>
/// <param name="api_type"></param> /// <param name="apiType"></param>
/// <param name="progressReporter"></param> /// <param name="progressReporter"></param>
/// <returns>A boolean indicating whether the media is newly downloaded or not.</returns> /// <returns>A boolean indicating whether the media is newly downloaded or not.</returns>
private async Task<bool> HandlePreviouslyDownloadedMediaAsync(string folder, long media_id, string api_type, private async Task<bool> HandlePreviouslyDownloadedMediaAsync(string folder, long mediaId, string apiType,
IProgressReporter progressReporter) IProgressReporter progressReporter)
{ {
if (configService.CurrentConfig.ShowScrapeSize) if (configService.CurrentConfig.ShowScrapeSize)
{ {
long size = await dbService.GetStoredFileSize(folder, media_id, api_type); long size = await dbService.GetStoredFileSize(folder, mediaId, apiType);
progressReporter.ReportProgress(size); progressReporter.ReportProgress(size);
} }
else else
@ -791,11 +762,12 @@ public class DownloadService(
Stream body = await response.Content.ReadAsStreamAsync(); Stream body = await response.Content.ReadAsStreamAsync();
// Wrap the body stream with the ThrottledStream to limit read rate. // Wrap the body stream with the ThrottledStream to limit read rate.
using (ThrottledStream throttledStream = new(body, await using (ThrottledStream throttledStream = new(body,
configService.CurrentConfig.DownloadLimitInMbPerSec * 1_000_000, configService.CurrentConfig.DownloadLimitInMbPerSec * 1_000_000,
configService.CurrentConfig.LimitDownloadRate)) configService.CurrentConfig.LimitDownloadRate))
{ {
using FileStream fileStream = new(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 16384, await using FileStream fileStream = new(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None,
16384,
true); true);
byte[] buffer = new byte[16384]; byte[] buffer = new byte[16384];
int read; int read;
@ -854,7 +826,7 @@ public class DownloadService(
return totalFileSize; return totalFileSize;
} }
public async Task<bool> DownloadMedia(string url, string folder, long media_id, string api_type, public async Task<bool> DownloadMedia(string url, string folder, long mediaId, string apiType,
IProgressReporter progressReporter, string path, IProgressReporter progressReporter, string path,
string? filenameFormat, object? postInfo, object? postMedia, string? filenameFormat, object? postInfo, object? postMedia,
object? author, Dictionary<string, long> users) object? author, Dictionary<string, long> users)
@ -864,12 +836,12 @@ public class DownloadService(
string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, postInfo, postMedia, author, string resolvedFilename = await GenerateCustomFileName(filename, filenameFormat, postInfo, postMedia, author,
folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal); folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnOriginal);
return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, progressReporter, return await CreateDirectoriesAndDownloadMedia(path, url, folder, mediaId, apiType, progressReporter,
filename, resolvedFilename); filename, resolvedFilename);
} }
public async Task<bool> DownloadDRMVideo(string policy, string signature, string kvp, string url, public async Task<bool> DownloadDrmVideo(string policy, string signature, string kvp, string url,
string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, string decryptionKey, string folder, DateTime lastModified, long mediaId, string apiType,
IProgressReporter progressReporter, string path, IProgressReporter progressReporter, string path,
string? filenameFormat, object? postInfo, object? postMedia, string? filenameFormat, object? postInfo, object? postMedia,
object? author, Dictionary<string, long> users) object? author, Dictionary<string, long> users)
@ -902,14 +874,14 @@ public class DownloadService(
string customFileName = await GenerateCustomFileName(filename, filenameFormat, postInfo, postMedia, author, string customFileName = await GenerateCustomFileName(filename, filenameFormat, postInfo, postMedia, author,
folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnEmpty); folder.Split("/")[^1], users, fileNameService, CustomFileNameOption.ReturnEmpty);
if (!await dbService.CheckDownloaded(folder, media_id, api_type)) if (!await dbService.CheckDownloaded(folder, mediaId, apiType))
{ {
if (!string.IsNullOrEmpty(customFileName) if (!string.IsNullOrEmpty(customFileName)
? !File.Exists(folder + path + "/" + customFileName + ".mp4") ? !File.Exists(folder + path + "/" + customFileName + ".mp4")
: !File.Exists(folder + path + "/" + filename + "_source.mp4")) : !File.Exists(folder + path + "/" + filename + "_source.mp4"))
{ {
return await DownloadDrmMedia(authService.CurrentAuth.UserAgent, policy, signature, kvp, return await DownloadDrmMedia(authService.CurrentAuth.UserAgent, policy, signature, kvp,
authService.CurrentAuth.Cookie, url, decryptionKey, folder, lastModified, media_id, api_type, authService.CurrentAuth.Cookie, url, decryptionKey, folder, lastModified, mediaId, apiType,
progressReporter, customFileName, filename, path); progressReporter, customFileName, filename, path);
} }
@ -918,7 +890,7 @@ public class DownloadService(
: folder + path + "/" + filename + "_source.mp4").Length; : folder + path + "/" + filename + "_source.mp4").Length;
ReportProgress(progressReporter, fileSizeInBytes); ReportProgress(progressReporter, fileSizeInBytes);
await dbService.UpdateMedia(folder, media_id, api_type, folder + path, await dbService.UpdateMedia(folder, mediaId, apiType, folder + path,
!string.IsNullOrEmpty(customFileName) ? customFileName + ".mp4" : filename + "_source.mp4", !string.IsNullOrEmpty(customFileName) ? customFileName + ".mp4" : filename + "_source.mp4",
fileSizeInBytes, true, lastModified); fileSizeInBytes, true, lastModified);
} }
@ -946,15 +918,15 @@ public class DownloadService(
return false; return false;
} }
long size = await dbService.GetStoredFileSize(folder, media_id, api_type); long size = await dbService.GetStoredFileSize(folder, mediaId, apiType);
await dbService.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", await dbService.UpdateMedia(folder, mediaId, apiType, folder + path, customFileName + ".mp4",
size, true, lastModified); size, true, lastModified);
} }
} }
if (configService.CurrentConfig.ShowScrapeSize) if (configService.CurrentConfig.ShowScrapeSize)
{ {
long size = await dbService.GetStoredFileSize(folder, media_id, api_type); long size = await dbService.GetStoredFileSize(folder, mediaId, apiType);
progressReporter.ReportProgress(size); progressReporter.ReportProgress(size);
} }
else else
@ -982,26 +954,17 @@ public class DownloadService(
return false; return false;
} }
private void ReportProgress(IProgressReporter reporter, long sizeOrCount) private void ReportProgress(IProgressReporter reporter, long sizeOrCount) =>
{ reporter.ReportProgress(configService.CurrentConfig.ShowScrapeSize ? sizeOrCount : 1);
if (configService.CurrentConfig.ShowScrapeSize)
{
reporter.ReportProgress(sizeOrCount);
}
else
{
reporter.ReportProgress(1);
}
}
public async Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo( public async Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo(
string mpdUrl, string policy, string signature, string kvp, string mpdUrl, string policy, string signature, string kvp,
string mediaId, string contentId, string drmType, string mediaId, string contentId, string drmType,
bool clientIdBlobMissing, bool devicePrivateKeyMissing) bool clientIdBlobMissing, bool devicePrivateKeyMissing)
{ {
string pssh = await apiService.GetDRMMPDPSSH(mpdUrl, policy, signature, kvp); string pssh = await apiService.GetDrmMpdPssh(mpdUrl, policy, signature, kvp);
DateTime lastModified = await apiService.GetDRMMPDLastModified(mpdUrl, policy, signature, kvp); DateTime lastModified = await apiService.GetDrmMpdLastModified(mpdUrl, policy, signature, kvp);
Dictionary<string, string> drmHeaders = Dictionary<string, string> drmHeaders =
apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/{drmType}/{contentId}", apiService.GetDynamicHeaders($"/api2/v2/users/media/{mediaId}/drm/{drmType}/{contentId}",
"?type=widevine"); "?type=widevine");
@ -1009,8 +972,8 @@ public class DownloadService(
$"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/{drmType}/{contentId}?type=widevine"; $"https://onlyfans.com/api2/v2/users/media/{mediaId}/drm/{drmType}/{contentId}?type=widevine";
string decryptionKey = clientIdBlobMissing || devicePrivateKeyMissing string decryptionKey = clientIdBlobMissing || devicePrivateKeyMissing
? await apiService.GetDecryptionKeyOFDL(drmHeaders, licenseUrl, pssh) ? await apiService.GetDecryptionKeyOfdl(drmHeaders, licenseUrl, pssh)
: await apiService.GetDecryptionKeyCDM(drmHeaders, licenseUrl, pssh); : await apiService.GetDecryptionKeyCdm(drmHeaders, licenseUrl, pssh);
return (decryptionKey, lastModified); return (decryptionKey, lastModified);
} }
@ -1041,10 +1004,10 @@ public class DownloadService(
int oldHighlightsCount = 0; int oldHighlightsCount = 0;
int newHighlightsCount = 0; int newHighlightsCount = 0;
foreach (KeyValuePair<long, string> highlightKVP in highlights) foreach (KeyValuePair<long, string> highlightKvp in highlights)
{ {
bool isNew = bool isNew =
await DownloadMedia(highlightKVP.Value, path, highlightKVP.Key, "Stories", progressReporter, await DownloadMedia(highlightKvp.Value, path, highlightKvp.Key, "Stories", progressReporter,
"/Stories/Free", null, null, null, null, new Dictionary<string, long>()); "/Stories/Free", null, null, null, null, new Dictionary<string, long>());
if (isNew) if (isNew)
{ {
@ -1095,9 +1058,9 @@ public class DownloadService(
int oldStoriesCount = 0; int oldStoriesCount = 0;
int newStoriesCount = 0; int newStoriesCount = 0;
foreach (KeyValuePair<long, string> storyKVP in stories) foreach (KeyValuePair<long, string> storyKvp in stories)
{ {
bool isNew = await DownloadMedia(storyKVP.Value, path, storyKVP.Key, "Stories", progressReporter, bool isNew = await DownloadMedia(storyKvp.Value, path, storyKvp.Key, "Stories", progressReporter,
"/Stories/Free", null, null, null, null, new Dictionary<string, long>()); "/Stories/Free", null, null, null, null, new Dictionary<string, long>());
if (isNew) if (isNew)
{ {
@ -1146,20 +1109,20 @@ public class DownloadService(
int oldArchivedCount = 0; int oldArchivedCount = 0;
int newArchivedCount = 0; int newArchivedCount = 0;
foreach (KeyValuePair<long, string> archivedKVP in archived.ArchivedPosts) foreach (KeyValuePair<long, string> archivedKvp in archived.ArchivedPosts)
{ {
bool isNew; bool isNew;
ArchivedEntities.Medium? mediaInfo = ArchivedEntities.Medium? mediaInfo =
archived.ArchivedPostMedia.FirstOrDefault(m => m.Id == archivedKVP.Key); archived.ArchivedPostMedia.FirstOrDefault(m => m.Id == archivedKvp.Key);
ArchivedEntities.ListItem? postInfo = mediaInfo == null ArchivedEntities.ListItem? postInfo = mediaInfo == null
? null ? null
: archived.ArchivedPostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); : archived.ArchivedPostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true);
string filenameFormat = string filenameFormat =
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? "";
if (archivedKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (archivedKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = archivedKVP.Value.Split(','); string[] parsed = archivedKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = await GetDecryptionInfo(parsed[0], parsed[1], (string decryptionKey, DateTime lastModified)? drmInfo = await GetDecryptionInfo(parsed[0], parsed[1],
parsed[2], parsed[3], parsed[2], parsed[3],
parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1168,14 +1131,14 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, archivedKVP.Key, "Posts", drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, archivedKvp.Key, "Posts",
progressReporter, "/Archived/Posts/Free/Videos", filenameFormat, progressReporter, "/Archived/Posts/Free/Videos", filenameFormat,
postInfo, mediaInfo, postInfo?.Author, users); postInfo, mediaInfo, postInfo?.Author, users);
} }
else else
{ {
isNew = await DownloadMedia(archivedKVP.Value, path, archivedKVP.Key, "Posts", progressReporter, isNew = await DownloadMedia(archivedKvp.Value, path, archivedKvp.Key, "Posts", progressReporter,
"/Archived/Posts/Free", filenameFormat, postInfo, mediaInfo, postInfo?.Author, users); "/Archived/Posts/Free", filenameFormat, postInfo, mediaInfo, postInfo?.Author, users);
} }
@ -1226,12 +1189,12 @@ public class DownloadService(
int oldMessagesCount = 0; int oldMessagesCount = 0;
int newMessagesCount = 0; int newMessagesCount = 0;
foreach (KeyValuePair<long, string> messageKVP in messages.Messages) foreach (KeyValuePair<long, string> messageKvp in messages.Messages)
{ {
bool isNew; bool isNew;
MessageEntities.Medium? mediaInfo = messages.MessageMedia.FirstOrDefault(m => m.Id == messageKVP.Key); MessageEntities.Medium? mediaInfo = messages.MessageMedia.FirstOrDefault(m => m.Id == messageKvp.Key);
MessageEntities.ListItem? messageInfo = messages.MessageObjects.FirstOrDefault(p => MessageEntities.ListItem? messageInfo = messages.MessageObjects.FirstOrDefault(p =>
p.Media?.Any(m => m.Id == messageKVP.Key) == true); p.Media?.Any(m => m.Id == messageKvp.Key) == true);
string filenameFormat = string filenameFormat =
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? ""; configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? "";
string messagePath = configService.CurrentConfig.FolderPerMessage && messageInfo != null && string messagePath = configService.CurrentConfig.FolderPerMessage && messageInfo != null &&
@ -1239,9 +1202,9 @@ public class DownloadService(
? $"/Messages/Free/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}" ? $"/Messages/Free/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}"
: "/Messages/Free"; : "/Messages/Free";
if (messageKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (messageKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = messageKVP.Value.Split(','); string[] parsed = messageKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = await GetDecryptionInfo(parsed[0], parsed[1], (string decryptionKey, DateTime lastModified)? drmInfo = await GetDecryptionInfo(parsed[0], parsed[1],
parsed[2], parsed[3], parsed[2], parsed[3],
parsed[4], parsed[5], "message", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "message", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1250,14 +1213,14 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, messageKVP.Key, "Messages", drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, messageKvp.Key, "Messages",
progressReporter, messagePath + "/Videos", filenameFormat, progressReporter, messagePath + "/Videos", filenameFormat,
messageInfo, mediaInfo, messageInfo?.FromUser, users); messageInfo, mediaInfo, messageInfo?.FromUser, users);
} }
else else
{ {
isNew = await DownloadMedia(messageKVP.Value, path, messageKVP.Key, "Messages", progressReporter, isNew = await DownloadMedia(messageKvp.Value, path, messageKvp.Key, "Messages", progressReporter,
messagePath, filenameFormat, messageInfo, mediaInfo, messageInfo?.FromUser, users); messagePath, filenameFormat, messageInfo, mediaInfo, messageInfo?.FromUser, users);
} }
@ -1335,7 +1298,7 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, kvpEntry.Key, "Messages", drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, kvpEntry.Key, "Messages",
progressReporter, paidMsgPath + "/Videos", filenameFormat, progressReporter, paidMsgPath + "/Videos", filenameFormat,
messageInfo, mediaInfo, messageInfo?.FromUser, users); messageInfo, mediaInfo, messageInfo?.FromUser, users);
@ -1415,7 +1378,7 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, kvpEntry.Key, "Streams", drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, kvpEntry.Key, "Streams",
progressReporter, streamPath + "/Videos", filenameFormat, progressReporter, streamPath + "/Videos", filenameFormat,
streamInfo, mediaInfo, streamInfo?.Author, users); streamInfo, mediaInfo, streamInfo?.Author, users);
@ -1471,10 +1434,10 @@ public class DownloadService(
int oldCount = 0, newCount = 0; int oldCount = 0, newCount = 0;
foreach (KeyValuePair<long, string> postKVP in posts.Posts) foreach (KeyValuePair<long, string> postKvp in posts.Posts)
{ {
bool isNew; bool isNew;
PostEntities.Medium? mediaInfo = posts.PostMedia.FirstOrDefault(m => m.Id == postKVP.Key); PostEntities.Medium? mediaInfo = posts.PostMedia.FirstOrDefault(m => m.Id == postKvp.Key);
PostEntities.ListItem? postInfo = mediaInfo == null PostEntities.ListItem? postInfo = mediaInfo == null
? null ? null
: posts.PostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); : posts.PostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true);
@ -1484,9 +1447,9 @@ public class DownloadService(
? $"/Posts/Free/{postInfo.Id} {postInfo.PostedAt:yyyy-MM-dd HH-mm-ss}" ? $"/Posts/Free/{postInfo.Id} {postInfo.PostedAt:yyyy-MM-dd HH-mm-ss}"
: "/Posts/Free"; : "/Posts/Free";
if (postKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (postKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = postKVP.Value.Split(','); string[] parsed = postKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = await GetDecryptionInfo(parsed[0], parsed[1], (string decryptionKey, DateTime lastModified)? drmInfo = await GetDecryptionInfo(parsed[0], parsed[1],
parsed[2], parsed[3], parsed[2], parsed[3],
parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1495,14 +1458,14 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, postKVP.Key, "Posts", drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, postKvp.Key, "Posts",
progressReporter, postPath + "/Videos", filenameFormat, progressReporter, postPath + "/Videos", filenameFormat,
postInfo, mediaInfo, postInfo?.Author, users); postInfo, mediaInfo, postInfo?.Author, users);
} }
else else
{ {
isNew = await DownloadMedia(postKVP.Value, path, postKVP.Key, "Posts", progressReporter, isNew = await DownloadMedia(postKvp.Value, path, postKvp.Key, "Posts", progressReporter,
postPath, filenameFormat, postInfo, mediaInfo, postInfo?.Author, users); postPath, filenameFormat, postInfo, mediaInfo, postInfo?.Author, users);
} }
@ -1551,13 +1514,13 @@ public class DownloadService(
int oldCount = 0, newCount = 0; int oldCount = 0, newCount = 0;
foreach (KeyValuePair<long, string> postKVP in purchasedPosts.PaidPosts) foreach (KeyValuePair<long, string> postKvp in purchasedPosts.PaidPosts)
{ {
bool isNew; bool isNew;
MessageEntities.Medium? mediaInfo = MessageEntities.Medium? mediaInfo =
purchasedPosts.PaidPostMedia.FirstOrDefault(m => m.Id == postKVP.Key); purchasedPosts.PaidPostMedia.FirstOrDefault(m => m.Id == postKvp.Key);
PurchasedEntities.ListItem? postInfo = PurchasedEntities.ListItem? postInfo =
purchasedPosts.PaidPostObjects.FirstOrDefault(p => p.Media?.Any(m => m.Id == postKVP.Key) == true); purchasedPosts.PaidPostObjects.FirstOrDefault(p => p.Media?.Any(m => m.Id == postKvp.Key) == true);
string filenameFormat = string filenameFormat =
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? "";
string paidPostPath = configService.CurrentConfig.FolderPerPaidPost && postInfo != null && string paidPostPath = configService.CurrentConfig.FolderPerPaidPost && postInfo != null &&
@ -1565,9 +1528,9 @@ public class DownloadService(
? $"/Posts/Paid/{postInfo.Id} {postInfo.PostedAt.Value:yyyy-MM-dd HH-mm-ss}" ? $"/Posts/Paid/{postInfo.Id} {postInfo.PostedAt.Value:yyyy-MM-dd HH-mm-ss}"
: "/Posts/Paid"; : "/Posts/Paid";
if (postKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (postKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = postKVP.Value.Split(','); string[] parsed = postKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = await GetDecryptionInfo(parsed[0], parsed[1], (string decryptionKey, DateTime lastModified)? drmInfo = await GetDecryptionInfo(parsed[0], parsed[1],
parsed[2], parsed[3], parsed[2], parsed[3],
parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1576,14 +1539,14 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, postKVP.Key, "Posts", drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, postKvp.Key, "Posts",
progressReporter, paidPostPath + "/Videos", filenameFormat, progressReporter, paidPostPath + "/Videos", filenameFormat,
postInfo, mediaInfo, postInfo?.FromUser, users); postInfo, mediaInfo, postInfo?.FromUser, users);
} }
else else
{ {
isNew = await DownloadMedia(postKVP.Value, path, postKVP.Key, "Posts", progressReporter, isNew = await DownloadMedia(postKvp.Value, path, postKvp.Key, "Posts", progressReporter,
paidPostPath, filenameFormat, postInfo, mediaInfo, postInfo?.FromUser, users); paidPostPath, filenameFormat, postInfo, mediaInfo, postInfo?.FromUser, users);
} }
@ -1622,14 +1585,14 @@ public class DownloadService(
int oldCount = 0, newCount = 0; int oldCount = 0, newCount = 0;
foreach (KeyValuePair<long, string> purchasedPostKVP in purchasedPosts.PaidPosts) foreach (KeyValuePair<long, string> purchasedPostKvp in purchasedPosts.PaidPosts)
{ {
bool isNew; bool isNew;
MessageEntities.Medium? mediaInfo = MessageEntities.Medium? mediaInfo =
purchasedPosts?.PaidPostMedia?.FirstOrDefault(m => m.Id == purchasedPostKVP.Key); purchasedPosts?.PaidPostMedia?.FirstOrDefault(m => m.Id == purchasedPostKvp.Key);
PurchasedEntities.ListItem? postInfo = mediaInfo != null PurchasedEntities.ListItem? postInfo = mediaInfo != null
? purchasedPosts?.PaidPostObjects?.FirstOrDefault(p => ? purchasedPosts?.PaidPostObjects?.FirstOrDefault(p =>
p.Media?.Any(m => m.Id == purchasedPostKVP.Key) == true) p.Media?.Any(m => m.Id == purchasedPostKvp.Key) == true)
: null; : null;
string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username)
.PaidPostFileNameFormat ?? ""; .PaidPostFileNameFormat ?? "";
@ -1638,9 +1601,9 @@ public class DownloadService(
? $"/Posts/Paid/{postInfo.Id} {postInfo.PostedAt.Value:yyyy-MM-dd HH-mm-ss}" ? $"/Posts/Paid/{postInfo.Id} {postInfo.PostedAt.Value:yyyy-MM-dd HH-mm-ss}"
: "/Posts/Paid"; : "/Posts/Paid";
if (purchasedPostKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (purchasedPostKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = purchasedPostKVP.Value.Split(','); string[] parsed = purchasedPostKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = (string decryptionKey, DateTime lastModified)? drmInfo =
await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3], await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3],
parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1649,15 +1612,15 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, purchasedPostKVP.Key, drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, purchasedPostKvp.Key,
"Posts", progressReporter, paidPostPath + "/Videos", filenameFormat, "Posts", progressReporter, paidPostPath + "/Videos", filenameFormat,
postInfo, mediaInfo, postInfo?.FromUser, users); postInfo, mediaInfo, postInfo?.FromUser, users);
} }
else else
{ {
isNew = await DownloadMedia(purchasedPostKVP.Value, path, isNew = await DownloadMedia(purchasedPostKvp.Value, path,
purchasedPostKVP.Key, "Posts", progressReporter, purchasedPostKvp.Key, "Posts", progressReporter,
paidPostPath, filenameFormat, postInfo, mediaInfo, postInfo?.FromUser, users); paidPostPath, filenameFormat, postInfo, mediaInfo, postInfo?.FromUser, users);
} }
@ -1696,14 +1659,14 @@ public class DownloadService(
int oldCount = 0, newCount = 0; int oldCount = 0, newCount = 0;
foreach (KeyValuePair<long, string> paidMessageKVP in paidMessageCollection.PaidMessages) foreach (KeyValuePair<long, string> paidMessageKvp in paidMessageCollection.PaidMessages)
{ {
bool isNew; bool isNew;
MessageEntities.Medium? mediaInfo = MessageEntities.Medium? mediaInfo =
paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.Id == paidMessageKVP.Key); paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.Id == paidMessageKvp.Key);
PurchasedEntities.ListItem? messageInfo = PurchasedEntities.ListItem? messageInfo =
paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => paidMessageCollection.PaidMessageObjects.FirstOrDefault(p =>
p.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); p.Media?.Any(m => m.Id == paidMessageKvp.Key) == true);
string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username)
.PaidMessageFileNameFormat ?? ""; .PaidMessageFileNameFormat ?? "";
string paidMsgPath = configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null && string paidMsgPath = configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null &&
@ -1711,9 +1674,9 @@ public class DownloadService(
? $"/Messages/Paid/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}" ? $"/Messages/Paid/{messageInfo.Id} {messageInfo.CreatedAt.Value:yyyy-MM-dd HH-mm-ss}"
: "/Messages/Paid"; : "/Messages/Paid";
if (paidMessageKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (paidMessageKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = paidMessageKVP.Value.Split(','); string[] parsed = paidMessageKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = (string decryptionKey, DateTime lastModified)? drmInfo =
await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3], await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3],
parsed[4], parsed[5], "message", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "message", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1722,15 +1685,15 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, paidMessageKVP.Key, drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, paidMessageKvp.Key,
"Messages", progressReporter, paidMsgPath + "/Videos", filenameFormat, "Messages", progressReporter, paidMsgPath + "/Videos", filenameFormat,
messageInfo, mediaInfo, messageInfo?.FromUser, users); messageInfo, mediaInfo, messageInfo?.FromUser, users);
} }
else else
{ {
isNew = await DownloadMedia(paidMessageKVP.Value, path, isNew = await DownloadMedia(paidMessageKvp.Value, path,
paidMessageKVP.Key, "Messages", progressReporter, paidMessageKvp.Key, "Messages", progressReporter,
paidMsgPath, filenameFormat, messageInfo, mediaInfo, messageInfo?.FromUser, users); paidMsgPath, filenameFormat, messageInfo, mediaInfo, messageInfo?.FromUser, users);
} }
@ -1769,9 +1732,9 @@ public class DownloadService(
int oldCount = 0, newCount = 0; int oldCount = 0, newCount = 0;
foreach (KeyValuePair<long, string> postKVP in post.SinglePosts) foreach (KeyValuePair<long, string> postKvp in post.SinglePosts)
{ {
PostEntities.Medium? mediaInfo = post.SinglePostMedia.FirstOrDefault(m => m.Id == postKVP.Key); PostEntities.Medium? mediaInfo = post.SinglePostMedia.FirstOrDefault(m => m.Id == postKvp.Key);
PostEntities.SinglePost? postInfo = mediaInfo == null PostEntities.SinglePost? postInfo = mediaInfo == null
? null ? null
: post.SinglePostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); : post.SinglePostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true);
@ -1782,9 +1745,9 @@ public class DownloadService(
: "/Posts/Free"; : "/Posts/Free";
bool isNew; bool isNew;
if (postKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (postKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = postKVP.Value.Split(','); string[] parsed = postKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = (string decryptionKey, DateTime lastModified)? drmInfo =
await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3], await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3],
parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "post", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1793,8 +1756,8 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, postKVP.Key, "Posts", drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, postKvp.Key, "Posts",
progressReporter, postPath + "/Videos", filenameFormat, progressReporter, postPath + "/Videos", filenameFormat,
postInfo, mediaInfo, postInfo?.Author, users); postInfo, mediaInfo, postInfo?.Author, users);
} }
@ -1802,8 +1765,8 @@ public class DownloadService(
{ {
try try
{ {
isNew = await DownloadMedia(postKVP.Value, path, isNew = await DownloadMedia(postKvp.Value, path,
postKVP.Key, "Posts", progressReporter, postKvp.Key, "Posts", progressReporter,
postPath, filenameFormat, postInfo, mediaInfo, postInfo?.Author, users); postPath, filenameFormat, postInfo, mediaInfo, postInfo?.Author, users);
} }
catch catch
@ -1845,14 +1808,14 @@ public class DownloadService(
// Download preview messages // Download preview messages
if (singlePaidMessageCollection.PreviewSingleMessages.Count > 0) if (singlePaidMessageCollection.PreviewSingleMessages.Count > 0)
{ {
foreach (KeyValuePair<long, string> paidMessageKVP in singlePaidMessageCollection.PreviewSingleMessages) foreach (KeyValuePair<long, string> paidMessageKvp in singlePaidMessageCollection.PreviewSingleMessages)
{ {
MessageEntities.Medium? mediaInfo = MessageEntities.Medium? mediaInfo =
singlePaidMessageCollection.PreviewSingleMessageMedia.FirstOrDefault(m => singlePaidMessageCollection.PreviewSingleMessageMedia.FirstOrDefault(m =>
m.Id == paidMessageKVP.Key); m.Id == paidMessageKvp.Key);
MessageEntities.SingleMessage? messageInfo = MessageEntities.SingleMessage? messageInfo =
singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p => singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p =>
p.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); p.Media?.Any(m => m.Id == paidMessageKvp.Key) == true);
string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username)
.PaidMessageFileNameFormat ?? ""; .PaidMessageFileNameFormat ?? "";
string previewMsgPath = configService.CurrentConfig.FolderPerMessage && messageInfo != null && string previewMsgPath = configService.CurrentConfig.FolderPerMessage && messageInfo != null &&
@ -1861,9 +1824,9 @@ public class DownloadService(
: "/Messages/Free"; : "/Messages/Free";
bool isNew; bool isNew;
if (paidMessageKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (paidMessageKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = paidMessageKVP.Value.Split(','); string[] parsed = paidMessageKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = (string decryptionKey, DateTime lastModified)? drmInfo =
await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3], await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3],
parsed[4], parsed[5], "message", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "message", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1872,15 +1835,15 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, paidMessageKVP.Key, drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, paidMessageKvp.Key,
"Messages", progressReporter, previewMsgPath + "/Videos", filenameFormat, "Messages", progressReporter, previewMsgPath + "/Videos", filenameFormat,
messageInfo, mediaInfo, messageInfo?.FromUser, users); messageInfo, mediaInfo, messageInfo?.FromUser, users);
} }
else else
{ {
isNew = await DownloadMedia(paidMessageKVP.Value, path, isNew = await DownloadMedia(paidMessageKvp.Value, path,
paidMessageKVP.Key, "Messages", progressReporter, paidMessageKvp.Key, "Messages", progressReporter,
previewMsgPath, filenameFormat, messageInfo, mediaInfo, messageInfo?.FromUser, users); previewMsgPath, filenameFormat, messageInfo, mediaInfo, messageInfo?.FromUser, users);
} }
@ -1898,14 +1861,14 @@ public class DownloadService(
// Download actual paid messages // Download actual paid messages
if (singlePaidMessageCollection.SingleMessages.Count > 0) if (singlePaidMessageCollection.SingleMessages.Count > 0)
{ {
foreach (KeyValuePair<long, string> paidMessageKVP in singlePaidMessageCollection.SingleMessages) foreach (KeyValuePair<long, string> paidMessageKvp in singlePaidMessageCollection.SingleMessages)
{ {
MessageEntities.Medium? mediaInfo = MessageEntities.Medium? mediaInfo =
singlePaidMessageCollection.SingleMessageMedia.FirstOrDefault(m => singlePaidMessageCollection.SingleMessageMedia.FirstOrDefault(m =>
m.Id == paidMessageKVP.Key); m.Id == paidMessageKvp.Key);
MessageEntities.SingleMessage? messageInfo = MessageEntities.SingleMessage? messageInfo =
singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p => singlePaidMessageCollection.SingleMessageObjects.FirstOrDefault(p =>
p.Media?.Any(m => m.Id == paidMessageKVP.Key) == true); p.Media?.Any(m => m.Id == paidMessageKvp.Key) == true);
string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username)
.PaidMessageFileNameFormat ?? ""; .PaidMessageFileNameFormat ?? "";
string singlePaidMsgPath = configService.CurrentConfig.FolderPerPaidMessage && string singlePaidMsgPath = configService.CurrentConfig.FolderPerPaidMessage &&
@ -1915,9 +1878,9 @@ public class DownloadService(
: "/Messages/Paid"; : "/Messages/Paid";
bool isNew; bool isNew;
if (paidMessageKVP.Value.Contains("cdn3.onlyfans.com/dash/files")) if (paidMessageKvp.Value.Contains("cdn3.onlyfans.com/dash/files"))
{ {
string[] parsed = paidMessageKVP.Value.Split(','); string[] parsed = paidMessageKvp.Value.Split(',');
(string decryptionKey, DateTime lastModified)? drmInfo = (string decryptionKey, DateTime lastModified)? drmInfo =
await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3], await GetDecryptionInfo(parsed[0], parsed[1], parsed[2], parsed[3],
parsed[4], parsed[5], "message", clientIdBlobMissing, devicePrivateKeyMissing); parsed[4], parsed[5], "message", clientIdBlobMissing, devicePrivateKeyMissing);
@ -1926,15 +1889,15 @@ public class DownloadService(
continue; continue;
} }
isNew = await DownloadDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], isNew = await DownloadDrmVideo(parsed[1], parsed[2], parsed[3], parsed[0],
drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, paidMessageKVP.Key, drmInfo.Value.decryptionKey, path, drmInfo.Value.lastModified, paidMessageKvp.Key,
"Messages", progressReporter, singlePaidMsgPath + "/Videos", filenameFormat, "Messages", progressReporter, singlePaidMsgPath + "/Videos", filenameFormat,
messageInfo, mediaInfo, messageInfo?.FromUser, users); messageInfo, mediaInfo, messageInfo?.FromUser, users);
} }
else else
{ {
isNew = await DownloadMedia(paidMessageKVP.Value, path, isNew = await DownloadMedia(paidMessageKvp.Value, path,
paidMessageKVP.Key, "Messages", progressReporter, paidMessageKvp.Key, "Messages", progressReporter,
singlePaidMsgPath, filenameFormat, messageInfo, mediaInfo, messageInfo?.FromUser, users); singlePaidMsgPath, filenameFormat, messageInfo, mediaInfo, messageInfo?.FromUser, users);
} }

View File

@ -27,7 +27,7 @@ public class FileNameService(IAuthService authService) : IFileNameService
if (fileProperty != null && drmProperty != null && propertyName == "mediaCreatedAt") if (fileProperty != null && drmProperty != null && propertyName == "mediaCreatedAt")
{ {
string? mpdurl = GetNestedPropertyValue(media, "Files.Drm.Manifest.Dash") as string; string? mpdUrl = GetNestedPropertyValue(media, "Files.Drm.Manifest.Dash") as string;
string? policy = string? policy =
GetNestedPropertyValue(media, "Files.Drm.Signature.Dash.CloudFrontPolicy") as string; GetNestedPropertyValue(media, "Files.Drm.Signature.Dash.CloudFrontPolicy") as string;
string? signature = string? signature =
@ -35,7 +35,7 @@ public class FileNameService(IAuthService authService) : IFileNameService
string? kvp = string? kvp =
GetNestedPropertyValue(media, "Files.Drm.Signature.Dash.CloudFrontKeyPairId") as string; GetNestedPropertyValue(media, "Files.Drm.Signature.Dash.CloudFrontKeyPairId") as string;
if (string.IsNullOrEmpty(mpdurl) || string.IsNullOrEmpty(policy) || if (string.IsNullOrEmpty(mpdUrl) || string.IsNullOrEmpty(policy) ||
string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(kvp) || string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(kvp) ||
authService.CurrentAuth == null) authService.CurrentAuth == null)
{ {
@ -43,7 +43,7 @@ public class FileNameService(IAuthService authService) : IFileNameService
} }
DateTime lastModified = DateTime lastModified =
await DownloadService.GetDRMVideoLastModified(string.Join(",", mpdurl, policy, signature, kvp), await DownloadService.GetDrmVideoLastModified(string.Join(",", mpdUrl, policy, signature, kvp),
authService.CurrentAuth); authService.CurrentAuth);
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd")); values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
continue; continue;

View File

@ -9,27 +9,31 @@ using UserEntities = OF_DL.Models.Entities.Users;
namespace OF_DL.Services; namespace OF_DL.Services;
public interface IAPIService public interface IApiService
{ {
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<string> GetDRMMPDPSSH(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<Dictionary<string, long>?> GetLists(string endpoint); Task<Dictionary<string, long>?> GetLists(string endpoint);
Task<List<string>?> GetListUsers(string endpoint); Task<List<string>?> GetListUsers(string endpoint);
Task<Dictionary<long, string>?> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username, string folder,
List<long> paid_post_ids); List<long> paidPostIds);
Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username,
List<long> paid_post_ids, List<long> paidPostIds,
IStatusReporter statusReporter); IStatusReporter statusReporter);
Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paid_post_ids, Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paidPostIds,
IStatusReporter statusReporter); IStatusReporter statusReporter);
Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder); Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder);
Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder, List<long> paid_post_ids, Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder, List<long> paidPostIds,
IStatusReporter statusReporter); IStatusReporter statusReporter);
Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder, Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder,
@ -41,15 +45,21 @@ public interface IAPIService
IStatusReporter statusReporter); IStatusReporter statusReporter);
Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder); Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder);
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users); Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users);
Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder,
Dictionary<string, long> users); Dictionary<string, long> users);
Task<UserEntities.User?> GetUserInfo(string endpoint); Task<UserEntities.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); Task<Dictionary<string, long>?> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
Task<Dictionary<string, long>?> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions); 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);
} }

View File

@ -1,27 +0,0 @@
namespace OF_DL.Services;
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 AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived,
DateTime created_at);
Task AddStory(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived,
DateTime created_at);
Task CreateDB(string folder);
Task CreateUsersDB(Dictionary<string, long> users);
Task CheckUsername(KeyValuePair<string, long> user, string path);
Task AddMedia(string folder, long media_id, long post_id, string link, string? directory, string? filename,
long? size, string api_type, string media_type, bool preview, bool downloaded, DateTime? created_at);
Task UpdateMedia(string folder, long media_id, string api_type, string directory, string filename, long size,
bool downloaded, DateTime created_at);
Task<long> GetStoredFileSize(string folder, long media_id, string api_type);
Task<bool> CheckDownloaded(string folder, long media_id, string api_type);
Task<DateTime?> GetMostRecentPostDate(string folder);
}

View File

@ -0,0 +1,31 @@
namespace OF_DL.Services;
public interface IDbService
{
Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
DateTime createdAt, long userId);
Task AddPost(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
DateTime createdAt);
Task AddStory(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
DateTime createdAt);
Task CreateDb(string folder);
Task CreateUsersDb(Dictionary<string, long> users);
Task CheckUsername(KeyValuePair<string, long> user, string path);
Task AddMedia(string folder, long mediaId, long postId, string link, string? directory, string? filename,
long? size, string apiType, string mediaType, bool preview, bool downloaded, DateTime? createdAt);
Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename, long size,
bool downloaded, DateTime createdAt);
Task<long> GetStoredFileSize(string folder, long mediaId, string apiType);
Task<bool> CheckDownloaded(string folder, long mediaId, string apiType);
Task<DateTime?> GetMostRecentPostDate(string folder);
}

View File

@ -11,22 +11,22 @@ public interface IDownloadService
{ {
Task<long> CalculateTotalFileSize(List<string> urls); Task<long> CalculateTotalFileSize(List<string> urls);
Task<bool> ProcessMediaDownload(string folder, long media_id, string api_type, string url, string path, Task<bool> ProcessMediaDownload(string folder, long mediaId, string apiType, string url, string path,
string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter); string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter);
Task<bool> DownloadMedia(string url, string folder, long media_id, string api_type, Task<bool> DownloadMedia(string url, string folder, long mediaId, string apiType,
IProgressReporter progressReporter, string path, IProgressReporter progressReporter, string path,
string? filenameFormat, object? postInfo, object? postMedia, string? filenameFormat, object? postInfo, object? postMedia,
object? author, Dictionary<string, long> users); object? author, Dictionary<string, long> users);
Task<bool> DownloadDRMVideo(string policy, string signature, string kvp, string url, Task<bool> DownloadDrmVideo(string policy, string signature, string kvp, string url,
string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, string decryptionKey, string folder, DateTime lastModified, long mediaId, string apiType,
IProgressReporter progressReporter, string path, IProgressReporter progressReporter, string path,
string? filenameFormat, object? postInfo, object? postMedia, string? filenameFormat, object? postInfo, object? postMedia,
object? author, Dictionary<string, long> users); object? author, Dictionary<string, long> users);
Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo( Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo(
string mpdURL, string policy, string signature, string kvp, string mpdUrl, string policy, string signature, string kvp,
string mediaId, string contentId, string drmType, string mediaId, string contentId, string drmType,
bool clientIdBlobMissing, bool devicePrivateKeyMissing); bool clientIdBlobMissing, bool devicePrivateKeyMissing);

View File

@ -6,6 +6,8 @@ namespace OF_DL.Services;
public interface ILoggingService public interface ILoggingService
{ {
LoggingLevelSwitch LevelSwitch { get; } LoggingLevelSwitch LevelSwitch { get; }
void UpdateLoggingLevel(LoggingLevel newLevel); void UpdateLoggingLevel(LoggingLevel newLevel);
LoggingLevel GetCurrentLoggingLevel(); LoggingLevel GetCurrentLoggingLevel();
} }

View File

@ -221,7 +221,7 @@ public class StartupService(IConfigService configService, IAuthService authServi
return File.Exists(path); return File.Exists(path);
} }
public static string? GetFullPath(string filename) private static string? GetFullPath(string filename)
{ {
if (File.Exists(filename)) if (File.Exists(filename))
{ {
@ -229,15 +229,7 @@ public class StartupService(IConfigService configService, IAuthService authServi
} }
string pathEnv = Environment.GetEnvironmentVariable("PATH") ?? ""; string pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
foreach (string path in pathEnv.Split(Path.PathSeparator)) return pathEnv.Split(Path.PathSeparator).Select(path => Path.Combine(path, filename))
{ .FirstOrDefault(File.Exists);
string fullPath = Path.Combine(path, filename);
if (File.Exists(fullPath))
{
return fullPath;
}
}
return null;
} }
} }

View File

@ -110,8 +110,8 @@ public class Program(IServiceProvider serviceProvider)
services.AddSingleton(loggingService); services.AddSingleton(loggingService);
services.AddSingleton(configService); services.AddSingleton(configService);
services.AddSingleton<IAuthService, AuthService>(); services.AddSingleton<IAuthService, AuthService>();
services.AddSingleton<IAPIService, ApiService>(); services.AddSingleton<IApiService, ApiService>();
services.AddSingleton<IDBService, DBService>(); services.AddSingleton<IDbService, DbService>();
services.AddSingleton<IDownloadService, DownloadService>(); services.AddSingleton<IDownloadService, DownloadService>();
services.AddSingleton<IFileNameService, FileNameService>(); services.AddSingleton<IFileNameService, FileNameService>();
services.AddSingleton<IStartupService, StartupService>(); services.AddSingleton<IStartupService, StartupService>();
@ -403,8 +403,8 @@ public class Program(IServiceProvider serviceProvider)
AnsiConsole.Markup($"[red]Folder for {Markup.Escape(username)} already created\n[/]"); AnsiConsole.Markup($"[red]Folder for {Markup.Escape(username)} already created\n[/]");
} }
IDBService dbService = serviceProvider.GetRequiredService<IDBService>(); IDbService dbService = serviceProvider.GetRequiredService<IDbService>();
await dbService.CreateDB(path); await dbService.CreateDb(path);
await orchestrationService.DownloadSinglePostAsync(username, postId, path, users, await orchestrationService.DownloadSinglePostAsync(username, postId, path, users,
startupResult.ClientIdBlobMissing, startupResult.DevicePrivateKeyMissing, eventHandler); startupResult.ClientIdBlobMissing, startupResult.DevicePrivateKeyMissing, eventHandler);
@ -475,8 +475,8 @@ public class Program(IServiceProvider serviceProvider)
Log.Debug($"Folder for {username} already created"); Log.Debug($"Folder for {username} already created");
} }
IDBService dbService = serviceProvider.GetRequiredService<IDBService>(); IDbService dbService = serviceProvider.GetRequiredService<IDbService>();
await dbService.CreateDB(path); await dbService.CreateDb(path);
await orchestrationService.DownloadSinglePaidMessageAsync(username, messageId, path, users, await orchestrationService.DownloadSinglePaidMessageAsync(username, messageId, path, users,
startupResult.ClientIdBlobMissing, startupResult.DevicePrivateKeyMissing, eventHandler); startupResult.ClientIdBlobMissing, startupResult.DevicePrivateKeyMissing, eventHandler);
@ -488,7 +488,7 @@ public class Program(IServiceProvider serviceProvider)
{ {
IConfigService configService = serviceProvider.GetRequiredService<IConfigService>(); IConfigService configService = serviceProvider.GetRequiredService<IConfigService>();
IAuthService authService = serviceProvider.GetRequiredService<IAuthService>(); IAuthService authService = serviceProvider.GetRequiredService<IAuthService>();
IAPIService apiService = serviceProvider.GetRequiredService<IAPIService>(); IApiService apiService = serviceProvider.GetRequiredService<IApiService>();
ILoggingService loggingService = serviceProvider.GetRequiredService<ILoggingService>(); ILoggingService loggingService = serviceProvider.GetRequiredService<ILoggingService>();
bool hasSelectedUsers = false; bool hasSelectedUsers = false;