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
16 changed files with 720 additions and 784 deletions
Showing only changes of commit 487de58274 - Show all commits

View File

@ -37,7 +37,7 @@ public class Padding
return result; return result;
} }
public static byte[]? AddPSSPadding(byte[] hash) public static byte[] AddPssPadding(byte[] hash)
{ {
int modBits = 2048; int modBits = 2048;
int hLen = 20; int hLen = 20;
@ -49,10 +49,11 @@ public class Padding
lmask = (lmask >> 1) | 0x80; lmask = (lmask >> 1) | 0x80;
} }
if (emLen < hLen + hLen + 2) // Commented out since the condition will always be false while emLen = 256 and hLen = 20
{ // if (emLen < hLen + hLen + 2)
return null; // {
} // return null;
// }
byte[] salt = new byte[hLen]; byte[] salt = new byte[hLen];
new Random().NextBytes(salt); new Random().NextBytes(salt);
@ -102,7 +103,7 @@ public class Padding
db[i] = (byte)(maskedDB[i] ^ dbMask[i]); db[i] = (byte)(maskedDB[i] ^ dbMask[i]);
} }
int onePos = BitConverter.ToString(db[hLen..]).Replace("-", "").IndexOf("01") / 2; int onePos = BitConverter.ToString(db[hLen..]).Replace("-", "").IndexOf("01", StringComparison.Ordinal) / 2;
byte[] unpadded = db[(hLen + onePos + 1)..]; byte[] unpadded = db[(hLen + onePos + 1)..];
return unpadded; return unpadded;

View File

@ -115,22 +115,22 @@ public class Config : IFileNameFormatConfig
if (CreatorConfigs.TryGetValue(username, out CreatorConfig? creatorConfig)) if (CreatorConfigs.TryGetValue(username, out CreatorConfig? creatorConfig))
{ {
if (creatorConfig?.PaidPostFileNameFormat != null) if (creatorConfig.PaidPostFileNameFormat != null)
{ {
combinedFilenameFormatConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat; combinedFilenameFormatConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat;
} }
if (creatorConfig?.PostFileNameFormat != null) if (creatorConfig.PostFileNameFormat != null)
{ {
combinedFilenameFormatConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat; combinedFilenameFormatConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat;
} }
if (creatorConfig?.PaidMessageFileNameFormat != null) if (creatorConfig.PaidMessageFileNameFormat != null)
{ {
combinedFilenameFormatConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat; combinedFilenameFormatConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat;
} }
if (creatorConfig?.MessageFileNameFormat != null) if (creatorConfig.MessageFileNameFormat != null)
{ {
combinedFilenameFormatConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat; combinedFilenameFormatConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat;
} }

View File

@ -1,5 +1,4 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using OF_DL.Models.Dtos.Archived;
namespace OF_DL.Models.Dtos.Common; namespace OF_DL.Models.Dtos.Common;

File diff suppressed because it is too large Load Diff

View File

@ -136,6 +136,11 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
return; return;
} }
if (string.IsNullOrWhiteSpace(CurrentAuth.Cookie))
{
return;
}
string pattern = @"(auth_id=\d+)|(sess=[^;]+)"; string pattern = @"(auth_id=\d+)|(sess=[^;]+)";
MatchCollection matches = Regex.Matches(CurrentAuth.Cookie, pattern); MatchCollection matches = Regex.Matches(CurrentAuth.Cookie, pattern);
@ -176,7 +181,7 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService
} }
} }
private async Task<Auth?> GetAuthFromBrowser(bool isDocker = false) private async Task<Auth?> GetAuthFromBrowser()
{ {
try try
{ {

View File

@ -44,7 +44,7 @@ public class ConfigService(ILoggingService loggingService) : IConfigService
} }
// Check for command-line arguments // Check for command-line arguments
if (args != null && args.Length > 0) if (args.Length > 0)
{ {
const string NON_INTERACTIVE_ARG = "--non-interactive"; const string NON_INTERACTIVE_ARG = "--non-interactive";
if (args.Any(a => a.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase))) if (args.Any(a => a.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase)))
@ -71,12 +71,6 @@ public class ConfigService(ILoggingService loggingService) : IConfigService
public async Task SaveConfigurationAsync(string filePath = "config.conf") public async Task SaveConfigurationAsync(string filePath = "config.conf")
{ {
if (CurrentConfig == null)
{
Log.Warning("Attempted to save null config to file");
return;
}
try try
{ {
string hoconConfig = BuildHoconFromConfig(CurrentConfig); string hoconConfig = BuildHoconFromConfig(CurrentConfig);
@ -409,7 +403,13 @@ public class ConfigService(ILoggingService loggingService) : IConfigService
ToggleableConfigAttribute? attr = propInfo.GetCustomAttribute<ToggleableConfigAttribute>(); ToggleableConfigAttribute? attr = propInfo.GetCustomAttribute<ToggleableConfigAttribute>();
if (attr != null) if (attr != null)
{ {
result.Add((propInfo.Name, (bool)propInfo.GetValue(CurrentConfig)!)); bool? value = (bool?)propInfo.GetValue(CurrentConfig);
if (value == null)
{
continue;
}
result.Add((propInfo.Name, value.Value));
} }
} }
@ -427,10 +427,16 @@ public class ConfigService(ILoggingService loggingService) : IConfigService
if (attr != null) if (attr != null)
{ {
bool newValue = selectedNames.Contains(propInfo.Name); bool newValue = selectedNames.Contains(propInfo.Name);
bool oldValue = (bool)propInfo.GetValue(CurrentConfig)!; bool? oldValue = (bool?)propInfo.GetValue(CurrentConfig);
if (oldValue == null)
{
continue;
}
propInfo.SetValue(newConfig, newValue); propInfo.SetValue(newConfig, newValue);
if (newValue != oldValue) if (newValue != oldValue.Value)
{ {
configChanged = true; configChanged = true;
} }

View File

@ -219,17 +219,17 @@ public class DBService(IConfigService configService) : IDBService
connection)) connection))
{ {
checkCmd.Parameters.AddWithValue("@userId", user.Value); checkCmd.Parameters.AddWithValue("@userId", user.Value);
using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync()) await using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync())
{ {
if (reader.Read()) if (reader.Read())
{ {
long storedUserId = reader.GetInt64(0);
string storedUsername = reader.GetString(1); string storedUsername = reader.GetString(1);
if (storedUsername != user.Key) if (storedUsername != user.Key)
{ {
using (SqliteCommand updateCmd = await using (SqliteCommand updateCmd =
new("UPDATE users SET username = @newUsername WHERE user_id = @userId;", connection)) new("UPDATE users SET username = @newUsername WHERE user_id = @userId;",
connection))
{ {
updateCmd.Parameters.AddWithValue("@newUsername", user.Key); updateCmd.Parameters.AddWithValue("@newUsername", user.Key);
updateCmd.Parameters.AddWithValue("@userId", user.Value); updateCmd.Parameters.AddWithValue("@userId", user.Value);
@ -278,13 +278,13 @@ public class DBService(IConfigService configService) : IDBService
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 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", post_id);
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value); insertCmd.Parameters.AddWithValue("@message_text", message_text);
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value); insertCmd.Parameters.AddWithValue("@price", price);
insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
insertCmd.Parameters.AddWithValue("@created_at", created_at); insertCmd.Parameters.AddWithValue("@created_at", created_at);
@ -328,8 +328,8 @@ public class DBService(IConfigService configService) : IDBService
"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", post_id);
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value); insertCmd.Parameters.AddWithValue("@message_text", message_text);
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value); insertCmd.Parameters.AddWithValue("@price", price);
insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
insertCmd.Parameters.AddWithValue("@created_at", created_at); insertCmd.Parameters.AddWithValue("@created_at", created_at);
@ -372,8 +372,8 @@ public class DBService(IConfigService configService) : IDBService
"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", post_id);
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value); insertCmd.Parameters.AddWithValue("@message_text", message_text);
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value); insertCmd.Parameters.AddWithValue("@price", price);
insertCmd.Parameters.AddWithValue("@is_paid", is_paid); insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
insertCmd.Parameters.AddWithValue("@is_archived", is_archived); insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
insertCmd.Parameters.AddWithValue("@created_at", created_at); insertCmd.Parameters.AddWithValue("@created_at", created_at);
@ -445,10 +445,9 @@ public class DBService(IConfigService configService) : IDBService
{ {
try try
{ {
bool downloaded = false; bool downloaded;
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,11 +455,10 @@ public class DBService(IConfigService configService) : IDBService
} }
connection.Open(); connection.Open();
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", media_id);
cmd.Parameters.AddWithValue("@api_type", api_type); cmd.Parameters.AddWithValue("@api_type", api_type);
downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync()); downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync());
}
return downloaded; return downloaded;
} }

View File

@ -144,9 +144,9 @@ public class DownloadOrchestrationService(
counts.PaidPostCount = await DownloadContentTypeAsync("Paid Posts", counts.PaidPostCount = await DownloadContentTypeAsync("Paid Posts",
async statusReporter => async statusReporter =>
await apiService.GetPaidPosts("/posts/paid/post", path, username, PaidPostIds, statusReporter), await apiService.GetPaidPosts("/posts/paid/post", path, username, PaidPostIds, statusReporter),
posts => posts?.PaidPosts?.Count ?? 0, posts => posts.PaidPosts.Count,
posts => posts?.PaidPostObjects?.Count ?? 0, posts => posts.PaidPostObjects.Count,
posts => posts?.PaidPosts?.Values?.ToList(), posts => posts.PaidPosts.Values.ToList(),
async (posts, reporter) => await downloadService.DownloadPaidPosts(username, userId, path, users, async (posts, reporter) => await downloadService.DownloadPaidPosts(username, userId, path, users,
clientIdBlobMissing, devicePrivateKeyMissing, posts, reporter), clientIdBlobMissing, devicePrivateKeyMissing, posts, reporter),
eventHandler); eventHandler);
@ -161,9 +161,9 @@ public class DownloadOrchestrationService(
counts.PostCount = await DownloadContentTypeAsync("Posts", counts.PostCount = await DownloadContentTypeAsync("Posts",
async statusReporter => async statusReporter =>
await apiService.GetPosts($"/users/{userId}/posts", path, PaidPostIds, statusReporter), await apiService.GetPosts($"/users/{userId}/posts", path, PaidPostIds, statusReporter),
posts => posts?.Posts?.Count ?? 0, posts => posts.Posts.Count,
posts => posts?.PostObjects?.Count ?? 0, posts => posts.PostObjects.Count,
posts => posts?.Posts?.Values?.ToList(), posts => posts.Posts.Values.ToList(),
async (posts, reporter) => await downloadService.DownloadFreePosts(username, userId, path, users, async (posts, reporter) => await downloadService.DownloadFreePosts(username, userId, path, users,
clientIdBlobMissing, devicePrivateKeyMissing, posts, reporter), clientIdBlobMissing, devicePrivateKeyMissing, posts, reporter),
eventHandler); eventHandler);
@ -174,9 +174,9 @@ public class DownloadOrchestrationService(
counts.ArchivedCount = await DownloadContentTypeAsync("Archived Posts", counts.ArchivedCount = await DownloadContentTypeAsync("Archived Posts",
async statusReporter => async statusReporter =>
await apiService.GetArchived($"/users/{userId}/posts", path, statusReporter), await apiService.GetArchived($"/users/{userId}/posts", path, statusReporter),
archived => archived?.ArchivedPosts?.Count ?? 0, archived => archived.ArchivedPosts.Count,
archived => archived?.ArchivedPostObjects?.Count ?? 0, archived => archived.ArchivedPostObjects.Count,
archived => archived?.ArchivedPosts?.Values?.ToList(), archived => archived.ArchivedPosts.Values.ToList(),
async (archived, reporter) => await downloadService.DownloadArchived(username, userId, path, users, async (archived, reporter) => await downloadService.DownloadArchived(username, userId, path, users,
clientIdBlobMissing, devicePrivateKeyMissing, archived, reporter), clientIdBlobMissing, devicePrivateKeyMissing, archived, reporter),
eventHandler); eventHandler);
@ -187,9 +187,9 @@ public class DownloadOrchestrationService(
counts.StreamsCount = await DownloadContentTypeAsync("Streams", counts.StreamsCount = await DownloadContentTypeAsync("Streams",
async statusReporter => async statusReporter =>
await apiService.GetStreams($"/users/{userId}/posts/streams", path, PaidPostIds, statusReporter), await apiService.GetStreams($"/users/{userId}/posts/streams", path, PaidPostIds, statusReporter),
streams => streams?.Streams?.Count ?? 0, streams => streams.Streams.Count,
streams => streams?.StreamObjects?.Count ?? 0, streams => streams.StreamObjects.Count,
streams => streams?.Streams?.Values?.ToList(), streams => streams.Streams.Values.ToList(),
async (streams, reporter) => await downloadService.DownloadStreams(username, userId, path, users, async (streams, reporter) => await downloadService.DownloadStreams(username, userId, path, users,
clientIdBlobMissing, devicePrivateKeyMissing, streams, reporter), clientIdBlobMissing, devicePrivateKeyMissing, streams, reporter),
eventHandler); eventHandler);
@ -256,9 +256,9 @@ public class DownloadOrchestrationService(
counts.MessagesCount = await DownloadContentTypeAsync("Messages", counts.MessagesCount = await DownloadContentTypeAsync("Messages",
async statusReporter => async statusReporter =>
await apiService.GetMessages($"/chats/{userId}/messages", path, statusReporter), await apiService.GetMessages($"/chats/{userId}/messages", path, statusReporter),
messages => messages.Messages?.Count ?? 0, messages => messages.Messages.Count,
messages => messages.MessageObjects?.Count ?? 0, messages => messages.MessageObjects.Count,
messages => messages?.Messages.Values.ToList(), messages => messages.Messages.Values.ToList(),
async (messages, reporter) => await downloadService.DownloadMessages(username, userId, path, users, async (messages, reporter) => await downloadService.DownloadMessages(username, userId, path, users,
clientIdBlobMissing, devicePrivateKeyMissing, messages, reporter), clientIdBlobMissing, devicePrivateKeyMissing, messages, reporter),
eventHandler); eventHandler);
@ -269,9 +269,9 @@ public class DownloadOrchestrationService(
counts.PaidMessagesCount = await DownloadContentTypeAsync("Paid Messages", counts.PaidMessagesCount = await DownloadContentTypeAsync("Paid Messages",
async statusReporter => async statusReporter =>
await apiService.GetPaidMessages("/posts/paid/chat", path, username, statusReporter), await apiService.GetPaidMessages("/posts/paid/chat", path, username, statusReporter),
paidMessages => paidMessages?.PaidMessages?.Count ?? 0, paidMessages => paidMessages.PaidMessages.Count,
paidMessages => paidMessages?.PaidMessageObjects?.Count ?? 0, paidMessages => paidMessages.PaidMessageObjects.Count,
paidMessages => paidMessages?.PaidMessages?.Values?.ToList(), paidMessages => paidMessages.PaidMessages.Values.ToList(),
async (paidMessages, reporter) => await downloadService.DownloadPaidMessages(username, path, users, async (paidMessages, reporter) => await downloadService.DownloadPaidMessages(username, path, users,
clientIdBlobMissing, devicePrivateKeyMissing, paidMessages, reporter), clientIdBlobMissing, devicePrivateKeyMissing, paidMessages, reporter),
eventHandler); eventHandler);
@ -368,7 +368,7 @@ public class DownloadOrchestrationService(
int paidMessagesCount = 0; int paidMessagesCount = 0;
// Download paid posts // Download paid posts
if (purchasedTabCollection.PaidPosts?.PaidPosts?.Count > 0) if (purchasedTabCollection.PaidPosts.PaidPosts.Count > 0)
{ {
eventHandler.OnContentFound("Paid Posts", eventHandler.OnContentFound("Paid Posts",
purchasedTabCollection.PaidPosts.PaidPosts.Count, purchasedTabCollection.PaidPosts.PaidPosts.Count,
@ -396,7 +396,7 @@ public class DownloadOrchestrationService(
} }
// Download paid messages // Download paid messages
if (purchasedTabCollection.PaidMessages?.PaidMessages?.Count > 0) if (purchasedTabCollection.PaidMessages.PaidMessages.Count > 0)
{ {
eventHandler.OnContentFound("Paid Messages", eventHandler.OnContentFound("Paid Messages",
purchasedTabCollection.PaidMessages.PaidMessages.Count, purchasedTabCollection.PaidMessages.PaidMessages.Count,

View File

@ -25,7 +25,7 @@ public class DownloadService(
IAPIService apiService) IAPIService apiService)
: IDownloadService : IDownloadService
{ {
private TaskCompletionSource<bool> _completionSource; private TaskCompletionSource<bool> _completionSource = new();
public async Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username) public async Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username)
{ {
@ -201,7 +201,7 @@ public class DownloadService(
Engine ffmpeg = new(configService.CurrentConfig.FFmpegPath); Engine ffmpeg = new(configService.CurrentConfig.FFmpegPath);
ffmpeg.Error += OnError; ffmpeg.Error += OnError;
ffmpeg.Complete += async (sender, args) => ffmpeg.Complete += async (_, _) =>
{ {
_completionSource.TrySetResult(true); _completionSource.TrySetResult(true);
await OnFFMPEGDownloadComplete(tempFilename, lastModified, folder, path, customFileName, filename, await OnFFMPEGDownloadComplete(tempFilename, lastModified, folder, path, customFileName, filename,
@ -275,14 +275,14 @@ public class DownloadService(
} }
} }
private void OnError(object sender, ConversionErrorEventArgs e) private void OnError(object? sender, ConversionErrorEventArgs e)
{ {
// Guard all fields to avoid NullReference exceptions from FFmpeg.NET // Guard all fields to avoid NullReference exceptions from FFmpeg.NET
string input = e?.Input?.Name ?? "<none>"; string input = e.Input?.Name ?? "<none>";
string output = e?.Output?.Name ?? "<none>"; string output = e.Output?.Name ?? "<none>";
string exitCode = e?.Exception?.ExitCode.ToString() ?? "<unknown>"; string exitCode = e.Exception?.ExitCode.ToString() ?? "<unknown>";
string message = e?.Exception?.Message ?? "<no message>"; string message = e.Exception?.Message ?? "<no message>";
string inner = e?.Exception?.InnerException?.Message ?? "<no inner>"; string inner = e.Exception?.InnerException?.Message ?? "<no inner>";
Log.Error("FFmpeg failed. Input={Input} Output={Output} ExitCode={ExitCode} Message={Message} Inner={Inner}", Log.Error("FFmpeg failed. Input={Input} Output={Output} ExitCode={ExitCode} Message={Message} Inner={Inner}",
input, output, exitCode, message, inner); input, output, exitCode, message, inner);
@ -310,7 +310,7 @@ public class DownloadService(
string body = await response.Content.ReadAsStringAsync(); string body = await response.Content.ReadAsStringAsync();
XDocument doc = XDocument.Parse(body); XDocument doc = XDocument.Parse(body);
XNamespace ns = "urn:mpeg:dash:schema:mpd:2011"; XNamespace ns = "urn:mpeg:dash:schema:mpd:2011";
XNamespace cenc = "urn:mpeg:cenc:2013"; // XNamespace cenc = "urn:mpeg:cenc:2013";
XElement? videoAdaptationSet = doc XElement? videoAdaptationSet = doc
.Descendants(ns + "AdaptationSet") .Descendants(ns + "AdaptationSet")
.FirstOrDefault(e => (string?)e.Attribute("mimeType") == "video/mp4"); .FirstOrDefault(e => (string?)e.Attribute("mimeType") == "video/mp4");
@ -392,7 +392,6 @@ public class DownloadService(
{ {
try try
{ {
string customFileName = "";
if (!Directory.Exists(folder + path)) if (!Directory.Exists(folder + path))
{ {
Directory.CreateDirectory(folder + path); Directory.CreateDirectory(folder + path);
@ -580,19 +579,19 @@ public class DownloadService(
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];
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",
$"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.Cookie}"); $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.Cookie}");
client.DefaultRequestHeaders.Add("User-Agent", auth.UserAgent); client.DefaultRequestHeaders.Add("User-Agent", auth.UserAgent);
using HttpResponseMessage response = await client.GetAsync(mpdURL, HttpCompletionOption.ResponseHeadersRead); using HttpResponseMessage response = await client.GetAsync(mpdUrl, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
if (response.Content.Headers.LastModified != null) if (response.Content.Headers.LastModified != null)
@ -624,8 +623,8 @@ public class DownloadService(
/// Processes the download and database update of media. /// Processes the download and database update of media.
/// </summary> /// </summary>
/// <param name="folder">The folder where the media is stored.</param> /// <param name="folder">The folder where the media is stored.</param>
/// <param name="media_id">The ID of the media.</param> /// <param name="mediaId">The ID of the media.</param>
/// <param name="api_type"></param> /// <param name="apiType"></param>
/// <param name="url">The URL from where to download the media.</param> /// <param name="url">The URL from where to download the media.</param>
/// <param name="path">The relative path to the media.</param> /// <param name="path">The relative path to the media.</param>
/// <param name="serverFilename"></param> /// <param name="serverFilename"></param>
@ -634,8 +633,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>
public async Task<bool> ProcessMediaDownload(string folder, public async Task<bool> ProcessMediaDownload(string folder,
long media_id, long mediaId,
string api_type, string apiType,
string url, string url,
string path, string path,
string serverFilename, string serverFilename,
@ -645,11 +644,11 @@ public class DownloadService(
{ {
try try
{ {
if (!await dbService.CheckDownloaded(folder, media_id, api_type)) if (!await dbService.CheckDownloaded(folder, mediaId, apiType))
{ {
return await HandleNewMedia(folder, return await HandleNewMedia(folder,
media_id, mediaId,
api_type, apiType,
url, url,
path, path,
serverFilename, serverFilename,
@ -658,11 +657,11 @@ public class DownloadService(
progressReporter); progressReporter);
} }
bool status = await HandlePreviouslyDownloadedMediaAsync(folder, media_id, api_type, progressReporter); bool status = await HandlePreviouslyDownloadedMediaAsync(folder, mediaId, apiType, progressReporter);
if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected && if (configService.CurrentConfig.RenameExistingFilesWhenCustomFormatIsSelected &&
serverFilename != resolvedFilename) serverFilename != resolvedFilename)
{ {
await HandleRenamingOfExistingFilesAsync(folder, media_id, api_type, path, serverFilename, await HandleRenamingOfExistingFilesAsync(folder, mediaId, apiType, path, serverFilename,
resolvedFilename, extension); resolvedFilename, extension);
} }
@ -677,9 +676,9 @@ public class DownloadService(
} }
private async Task<bool> HandleRenamingOfExistingFilesAsync(string folder, private async Task HandleRenamingOfExistingFilesAsync(string folder,
long media_id, long mediaId,
string api_type, string apiType,
string path, string path,
string serverFilename, string serverFilename,
string resolvedFilename, string resolvedFilename,
@ -689,7 +688,7 @@ public class DownloadService(
string fullPathWithTheNewFileName = $"{folder}{path}/{resolvedFilename}{extension}"; string fullPathWithTheNewFileName = $"{folder}{path}/{resolvedFilename}{extension}";
if (!File.Exists(fullPathWithTheServerFileName)) if (!File.Exists(fullPathWithTheServerFileName))
{ {
return false; return;
} }
try try
@ -699,14 +698,13 @@ public class DownloadService(
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"An error occurred: {ex.Message}"); Console.WriteLine($"An error occurred: {ex.Message}");
return false; return;
} }
long size = await dbService.GetStoredFileSize(folder, media_id, api_type); long size = await dbService.GetStoredFileSize(folder, mediaId, apiType);
DateTime lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName); DateTime lastModified = File.GetLastWriteTime(fullPathWithTheNewFileName);
await dbService.UpdateMedia(folder, media_id, api_type, folder + path, resolvedFilename + extension, size, true, await dbService.UpdateMedia(folder, mediaId, apiType, folder + path, resolvedFilename + extension, size, true,
lastModified); lastModified);
return true;
} }
@ -1084,17 +1082,13 @@ public class DownloadService(
} }
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);
if (pssh == null)
{
return null;
}
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");
@ -1220,7 +1214,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadArchived - {username}"); Log.Debug($"Calling DownloadArchived - {username}");
if (archived == null || archived.ArchivedPosts.Count == 0) if (archived.ArchivedPosts.Count == 0)
{ {
Log.Debug("Found 0 Archived Posts"); Log.Debug("Found 0 Archived Posts");
return new DownloadResult return new DownloadResult
@ -1244,8 +1238,9 @@ public class DownloadService(
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 = ArchivedEntities.ListItem? postInfo = mediaInfo == null
archived.ArchivedPostObjects.FirstOrDefault(p => p?.Media?.Contains(mediaInfo) == true); ? null
: archived.ArchivedPostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true);
string filenameFormat = string filenameFormat =
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? "";
@ -1300,7 +1295,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadMessages - {username}"); Log.Debug($"Calling DownloadMessages - {username}");
if (messages == null || messages.Messages.Count == 0) if (messages.Messages.Count == 0)
{ {
Log.Debug("Found 0 Messages"); Log.Debug("Found 0 Messages");
return new DownloadResult return new DownloadResult
@ -1323,11 +1318,11 @@ public class DownloadService(
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 &&
messageInfo?.Id is not null && messageInfo?.CreatedAt is not null messageInfo.Id != 0 && messageInfo.CreatedAt is not null
? $"/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";
@ -1383,7 +1378,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadPaidMessages - {username}"); Log.Debug($"Calling DownloadPaidMessages - {username}");
if (paidMessageCollection == null || paidMessageCollection.PaidMessages.Count == 0) if (paidMessageCollection.PaidMessages.Count == 0)
{ {
Log.Debug("Found 0 Paid Messages"); Log.Debug("Found 0 Paid Messages");
return new DownloadResult return new DownloadResult
@ -1408,11 +1403,11 @@ public class DownloadService(
MessageEntities.Medium? mediaInfo = MessageEntities.Medium? mediaInfo =
paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.Id == kvpEntry.Key); paidMessageCollection.PaidMessageMedia.FirstOrDefault(m => m.Id == kvpEntry.Key);
PurchasedEntities.ListItem? messageInfo = paidMessageCollection.PaidMessageObjects.FirstOrDefault(p => PurchasedEntities.ListItem? messageInfo = paidMessageCollection.PaidMessageObjects.FirstOrDefault(p =>
p?.Media?.Any(m => m.Id == kvpEntry.Key) == true); p.Media?.Any(m => m.Id == kvpEntry.Key) == true);
string filenameFormat = string filenameFormat =
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? ""; configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).MessageFileNameFormat ?? "";
string paidMsgPath = configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null && string paidMsgPath = configService.CurrentConfig.FolderPerPaidMessage && messageInfo != null &&
messageInfo?.Id is not null && messageInfo?.CreatedAt is not null messageInfo.Id != 0 && messageInfo.CreatedAt is not null
? $"/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";
@ -1465,7 +1460,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadStreams - {username}"); Log.Debug($"Calling DownloadStreams - {username}");
if (streams == null || streams.Streams.Count == 0) if (streams.Streams.Count == 0)
{ {
Log.Debug("Found 0 Streams"); Log.Debug("Found 0 Streams");
return new DownloadResult return new DownloadResult
@ -1487,12 +1482,12 @@ public class DownloadService(
{ {
bool isNew; bool isNew;
StreamEntities.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.Id == kvpEntry.Key); StreamEntities.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.Id == kvpEntry.Key);
StreamEntities.ListItem? streamInfo = StreamEntities.ListItem? streamInfo = mediaInfo == null
streams.StreamObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); ? null
: streams.StreamObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true);
string filenameFormat = string filenameFormat =
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? "";
string streamPath = configService.CurrentConfig.FolderPerPost && streamInfo != null && string streamPath = configService.CurrentConfig.FolderPerPost && streamInfo != null && streamInfo.Id != 0
streamInfo?.Id is not null && streamInfo?.PostedAt is not null
? $"/Posts/Free/{streamInfo.Id} {streamInfo.PostedAt:yyyy-MM-dd HH-mm-ss}" ? $"/Posts/Free/{streamInfo.Id} {streamInfo.PostedAt:yyyy-MM-dd HH-mm-ss}"
: "/Posts/Free"; : "/Posts/Free";
@ -1546,7 +1541,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadFreePosts - {username}"); Log.Debug($"Calling DownloadFreePosts - {username}");
if (posts == null || posts.Posts.Count == 0) if (posts.Posts.Count == 0)
{ {
Log.Debug("Found 0 Posts"); Log.Debug("Found 0 Posts");
return new DownloadResult return new DownloadResult
@ -1566,13 +1561,13 @@ public class DownloadService(
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 = PostEntities.ListItem? postInfo = mediaInfo == null
posts.PostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true); ? null
: posts.PostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true);
string filenameFormat = string filenameFormat =
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? ""; configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? "";
string postPath = configService.CurrentConfig.FolderPerPost && postInfo != null && string postPath = configService.CurrentConfig.FolderPerPost && postInfo != null && postInfo.Id != 0
postInfo?.Id is not null && postInfo?.PostedAt is not null
? $"/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";
@ -1625,7 +1620,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadPaidPosts - {username}"); Log.Debug($"Calling DownloadPaidPosts - {username}");
if (purchasedPosts == null || purchasedPosts.PaidPosts.Count == 0) if (purchasedPosts.PaidPosts.Count == 0)
{ {
Log.Debug("Found 0 Paid Posts"); Log.Debug("Found 0 Paid Posts");
return new DownloadResult return new DownloadResult
@ -1649,11 +1644,11 @@ public class DownloadService(
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 &&
postInfo?.Id is not null && postInfo?.PostedAt is not null postInfo.Id != 0 && postInfo.PostedAt is not null
? $"/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";
@ -1706,7 +1701,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadPaidPostsPurchasedTab - {username}"); Log.Debug($"Calling DownloadPaidPostsPurchasedTab - {username}");
if (purchasedPosts == null || purchasedPosts.PaidPosts.Count == 0) if (purchasedPosts.PaidPosts.Count == 0)
{ {
Log.Debug("Found 0 Paid Posts"); Log.Debug("Found 0 Paid Posts");
return new DownloadResult { TotalCount = 0, MediaType = "Paid Posts", Success = true }; return new DownloadResult { TotalCount = 0, MediaType = "Paid Posts", Success = true };
@ -1721,12 +1716,12 @@ public class DownloadService(
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 ?? "";
string paidPostPath = configService.CurrentConfig.FolderPerPaidPost && postInfo != null && string paidPostPath = configService.CurrentConfig.FolderPerPaidPost && postInfo != null &&
postInfo?.Id is not null && postInfo?.PostedAt is not null postInfo.Id != 0 && postInfo.PostedAt is not null
? $"/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";
@ -1766,7 +1761,7 @@ public class DownloadService(
Log.Debug($"Paid Posts Already Downloaded: {oldCount} New Paid Posts Downloaded: {newCount}"); Log.Debug($"Paid Posts Already Downloaded: {oldCount} New Paid Posts Downloaded: {newCount}");
return new DownloadResult return new DownloadResult
{ {
TotalCount = purchasedPosts.PaidPosts.Count, TotalCount = purchasedPosts?.PaidPosts.Count ?? 0,
NewDownloads = newCount, NewDownloads = newCount,
ExistingDownloads = oldCount, ExistingDownloads = oldCount,
MediaType = "Paid Posts", MediaType = "Paid Posts",
@ -1780,7 +1775,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadPaidMessagesPurchasedTab - {username}"); Log.Debug($"Calling DownloadPaidMessagesPurchasedTab - {username}");
if (paidMessageCollection == null || paidMessageCollection.PaidMessages.Count == 0) if (paidMessageCollection.PaidMessages.Count == 0)
{ {
Log.Debug("Found 0 Paid Messages"); Log.Debug("Found 0 Paid Messages");
return new DownloadResult { TotalCount = 0, MediaType = "Paid Messages", Success = true }; return new DownloadResult { TotalCount = 0, MediaType = "Paid Messages", Success = true };
@ -1795,11 +1790,11 @@ public class DownloadService(
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 &&
messageInfo?.Id is not null && messageInfo?.CreatedAt is not null messageInfo.Id != 0 && messageInfo.CreatedAt is not null
? $"/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";
@ -1853,7 +1848,7 @@ public class DownloadService(
{ {
Log.Debug($"Calling DownloadSinglePost - {username}"); Log.Debug($"Calling DownloadSinglePost - {username}");
if (post == null || post.SinglePosts.Count == 0) if (post.SinglePosts.Count == 0)
{ {
Log.Debug("Couldn't find post"); Log.Debug("Couldn't find post");
return new DownloadResult { TotalCount = 0, MediaType = "Posts", Success = true }; return new DownloadResult { TotalCount = 0, MediaType = "Posts", Success = true };
@ -1864,12 +1859,12 @@ public class DownloadService(
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 = PostEntities.SinglePost? postInfo = mediaInfo == null
post.SinglePostObjects.FirstOrDefault(p => p?.Media?.Contains(mediaInfo) == true); ? null
: post.SinglePostObjects.FirstOrDefault(p => p.Media?.Contains(mediaInfo) == true);
string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username) string filenameFormat = configService.CurrentConfig.GetCreatorFileNameFormatConfig(username)
.PostFileNameFormat ?? ""; .PostFileNameFormat ?? "";
string postPath = configService.CurrentConfig.FolderPerPost && postInfo != null && string postPath = configService.CurrentConfig.FolderPerPost && postInfo != null && postInfo.Id != 0
postInfo?.Id is not null && postInfo?.PostedAt is not null
? $"/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";
@ -1930,12 +1925,7 @@ public class DownloadService(
PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection, PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection,
IProgressReporter progressReporter) IProgressReporter progressReporter)
{ {
Log.Debug($"Calling DownloadSinglePaidMessage - {username}"); Log.Debug("Calling DownloadSinglePaidMessage - {Username}", username);
if (singlePaidMessageCollection == null)
{
return new DownloadResult { TotalCount = 0, MediaType = "Paid Messages", Success = true };
}
int totalNew = 0, totalOld = 0; int totalNew = 0, totalOld = 0;
@ -1949,11 +1939,11 @@ public class DownloadService(
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 &&
messageInfo?.Id is not null && messageInfo?.CreatedAt is not null messageInfo.Id != 0 && messageInfo.CreatedAt is not null
? $"/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";
@ -2002,12 +1992,12 @@ public class DownloadService(
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 &&
messageInfo != null && messageInfo?.Id is not null && messageInfo != null && messageInfo.Id != 0 &&
messageInfo?.CreatedAt is not null messageInfo.CreatedAt is not null
? $"/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";

View File

@ -38,7 +38,7 @@ public class StartupService(IConfigService configService, IAuthService authServi
!result.FfmpegPath.Contains(@":\\")) !result.FfmpegPath.Contains(@":\\"))
{ {
result.FfmpegPath = result.FfmpegPath.Replace(@"\", @"\\"); result.FfmpegPath = result.FfmpegPath.Replace(@"\", @"\\");
configService.CurrentConfig!.FFmpegPath = result.FfmpegPath; configService.CurrentConfig.FFmpegPath = result.FfmpegPath;
} }
// Get FFmpeg version // Get FFmpeg version
@ -144,7 +144,7 @@ public class StartupService(IConfigService configService, IAuthService authServi
private void DetectFfmpeg(StartupResult result) private void DetectFfmpeg(StartupResult result)
{ {
if (!string.IsNullOrEmpty(configService.CurrentConfig!.FFmpegPath) && if (!string.IsNullOrEmpty(configService.CurrentConfig.FFmpegPath) &&
ValidateFilePath(configService.CurrentConfig.FFmpegPath)) ValidateFilePath(configService.CurrentConfig.FFmpegPath))
{ {
result.FfmpegFound = true; result.FfmpegFound = true;

View File

@ -67,7 +67,7 @@ public class ThrottledStream : Stream
} }
} }
protected async Task ThrottleAsync(int bytes) private async Task ThrottleAsync(int bytes)
{ {
if (!shouldThrottle) if (!shouldThrottle)
{ {

View File

@ -21,6 +21,7 @@ internal static class XmlUtils
} }
catch catch
{ {
// ignored
} }
return ""; return "";

View File

@ -112,7 +112,7 @@ public class CDM
{ {
//needed for HBO Max //needed for HBO Max
PSSHBox psshBox = PSSHBox.FromByteArray(initData); PsshBox psshBox = PsshBox.FromByteArray(initData);
cencHeader = Serializer.Deserialize<WidevineCencHeader>(new MemoryStream(psshBox.Data)); cencHeader = Serializer.Deserialize<WidevineCencHeader>(new MemoryStream(psshBox.Data));
} }
catch catch
@ -278,7 +278,7 @@ public class CDM
encryptedClientIdProto.EncryptedClientId = mstream.ToArray(); encryptedClientIdProto.EncryptedClientId = mstream.ToArray();
using RSACryptoServiceProvider RSA = new(); using RSACryptoServiceProvider RSA = new();
RSA.ImportRSAPublicKey(session.ServiceCertificate.DeviceCertificate.PublicKey, out int bytesRead); RSA.ImportRSAPublicKey(session.ServiceCertificate.DeviceCertificate.PublicKey, out int _);
encryptedClientIdProto.EncryptedPrivacyKey = RSA.Encrypt(aesProvider.Key, RSAEncryptionPadding.OaepSHA1); encryptedClientIdProto.EncryptedPrivacyKey = RSA.Encrypt(aesProvider.Key, RSAEncryptionPadding.OaepSHA1);
encryptedClientIdProto.EncryptedClientIdIv = aesProvider.IV; encryptedClientIdProto.EncryptedClientIdIv = aesProvider.IV;
encryptedClientIdProto.ServiceId = encryptedClientIdProto.ServiceId =

View File

@ -29,5 +29,13 @@ public class CDMApi
CDM.ProvideLicense(SessionId, Convert.FromBase64String(licenseB64)); CDM.ProvideLicense(SessionId, Convert.FromBase64String(licenseB64));
} }
public List<ContentKey> GetKeys() => CDM.GetKeys(SessionId); public List<ContentKey> GetKeys()
{
if (SessionId == null)
{
throw new Exception("No session ID set. Could not get keys");
}
return CDM.GetKeys(SessionId);
}
} }

View File

@ -43,6 +43,10 @@ public class CDMDevice
using StreamReader reader = File.OpenText(privateKeyPath); using StreamReader reader = File.OpenText(privateKeyPath);
DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject(); DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
} }
else
{
throw new Exception("No device private key found");
}
if (vmpBytes != null) if (vmpBytes != null)
{ {

View File

@ -1,34 +1,34 @@
namespace OF_DL.Widevine; namespace OF_DL.Widevine;
internal class PSSHBox internal class PsshBox
{ {
private static readonly byte[] PSSH_HEADER = new byte[] { 0x70, 0x73, 0x73, 0x68 }; private static readonly byte[] s_psshHeader = [0x70, 0x73, 0x73, 0x68];
private PSSHBox(List<byte[]> kids, byte[] data) private PsshBox(List<byte[]> kids, byte[] data)
{ {
KIDs = kids; KIDs = kids;
Data = data; Data = data;
} }
public List<byte[]> KIDs { get; set; } = new(); public List<byte[]> KIDs { get; set; }
public byte[] Data { get; set; } public byte[] Data { get; set; }
public static PSSHBox FromByteArray(byte[] psshbox) public static PsshBox FromByteArray(byte[] psshbox)
{ {
using MemoryStream stream = new(psshbox); using MemoryStream stream = new(psshbox);
stream.Seek(4, SeekOrigin.Current); stream.Seek(4, SeekOrigin.Current);
byte[] header = new byte[4]; byte[] header = new byte[4];
stream.Read(header, 0, 4); stream.ReadExactly(header, 0, 4);
if (!header.SequenceEqual(PSSH_HEADER)) if (!header.SequenceEqual(s_psshHeader))
{ {
throw new Exception("Not a pssh box"); throw new Exception("Not a pssh box");
} }
stream.Seek(20, SeekOrigin.Current); stream.Seek(20, SeekOrigin.Current);
byte[] kidCountBytes = new byte[4]; byte[] kidCountBytes = new byte[4];
stream.Read(kidCountBytes, 0, 4); stream.ReadExactly(kidCountBytes, 0, 4);
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
@ -41,12 +41,12 @@ internal class PSSHBox
for (int i = 0; i < kidCount; i++) for (int i = 0; i < kidCount; i++)
{ {
byte[] kid = new byte[16]; byte[] kid = new byte[16];
stream.Read(kid); stream.ReadExactly(kid);
kids.Add(kid); kids.Add(kid);
} }
byte[] dataLengthBytes = new byte[4]; byte[] dataLengthBytes = new byte[4];
stream.Read(dataLengthBytes); stream.ReadExactly(dataLengthBytes);
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
@ -57,12 +57,12 @@ internal class PSSHBox
if (dataLength == 0) if (dataLength == 0)
{ {
return new PSSHBox(kids, []); return new PsshBox(kids, []);
} }
byte[] data = new byte[dataLength]; byte[] data = new byte[dataLength];
stream.Read(data); stream.ReadExactly(data);
return new PSSHBox(kids, data); return new PsshBox(kids, data);
} }
} }