forked from sim0n00ps/OF-DL
Fix remaining compiler warnings
This commit is contained in:
parent
85c9bc1f57
commit
4a218a3efe
@ -384,13 +384,13 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
string endpoint,
|
||||
string? username,
|
||||
string folder,
|
||||
List<long> paid_post_ids)
|
||||
List<long> paidPostIds)
|
||||
{
|
||||
Log.Debug($"Calling GetMedia - {username}");
|
||||
|
||||
try
|
||||
{
|
||||
Dictionary<long, string> return_urls = new();
|
||||
Dictionary<long, string> returnUrls = new();
|
||||
const int postLimit = 50;
|
||||
int limit = 5;
|
||||
int offset = 0;
|
||||
@ -419,7 +419,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
if (string.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
Log.Warning("GetMedia returned empty response for {Endpoint}", endpoint);
|
||||
return return_urls;
|
||||
return returnUrls;
|
||||
}
|
||||
|
||||
if (mediatype == MediaType.Stories)
|
||||
@ -485,9 +485,9 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (medium.CanView && !return_urls.ContainsKey(medium.Id))
|
||||
if (medium.CanView && !returnUrls.ContainsKey(medium.Id))
|
||||
{
|
||||
return_urls.Add(medium.Id, mediaUrl);
|
||||
returnUrls.Add(medium.Id, mediaUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -538,22 +538,22 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string highlight_id in highlightIds)
|
||||
foreach (string highlightId in highlightIds)
|
||||
{
|
||||
Dictionary<string, string> highlight_headers =
|
||||
GetDynamicHeaders("/api2/v2/stories/highlights/" + highlight_id, "");
|
||||
Dictionary<string, string> highlightHeaders =
|
||||
GetDynamicHeaders("/api2/v2/stories/highlights/" + highlightId, "");
|
||||
|
||||
HttpClient highlight_client = GetHttpClient();
|
||||
HttpClient highlightClient = GetHttpClient();
|
||||
|
||||
HttpRequestMessage highlight_request = new(HttpMethod.Get,
|
||||
$"https://onlyfans.com/api2/v2/stories/highlights/{highlight_id}");
|
||||
HttpRequestMessage highlightRequest = new(HttpMethod.Get,
|
||||
$"https://onlyfans.com/api2/v2/stories/highlights/{highlightId}");
|
||||
|
||||
foreach (KeyValuePair<string, string> keyValuePair in highlight_headers)
|
||||
foreach (KeyValuePair<string, string> keyValuePair in highlightHeaders)
|
||||
{
|
||||
highlight_request.Headers.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
highlightRequest.Headers.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
using HttpResponseMessage highlightResponse = await highlight_client.SendAsync(highlight_request);
|
||||
using HttpResponseMessage highlightResponse = await highlightClient.SendAsync(highlightRequest);
|
||||
highlightResponse.EnsureSuccessStatusCode();
|
||||
string highlightBody = await highlightResponse.Content.ReadAsStringAsync();
|
||||
HighlightDtos.HighlightMediaDto? highlightMediaDto =
|
||||
@ -608,9 +608,9 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!return_urls.ContainsKey(medium.Id) && !string.IsNullOrEmpty(storyUrl))
|
||||
if (!returnUrls.ContainsKey(medium.Id) && !string.IsNullOrEmpty(storyUrl))
|
||||
{
|
||||
return_urls.Add(medium.Id, storyUrl);
|
||||
returnUrls.Add(medium.Id, storyUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -618,7 +618,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
}
|
||||
}
|
||||
|
||||
return return_urls;
|
||||
return returnUrls;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -640,7 +640,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
|
||||
public async Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder,
|
||||
string username,
|
||||
List<long> paid_post_ids, IStatusReporter statusReporter)
|
||||
List<long> paidPostIds, IStatusReporter statusReporter)
|
||||
{
|
||||
Log.Debug($"Calling GetPaidPosts - {username}");
|
||||
|
||||
@ -726,7 +726,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
{
|
||||
if (!previewids.Contains(medium.Id))
|
||||
{
|
||||
paid_post_ids.Add(medium.Id);
|
||||
paidPostIds.Add(medium.Id);
|
||||
}
|
||||
|
||||
if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages)
|
||||
@ -834,7 +834,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
}
|
||||
|
||||
|
||||
public async Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paid_post_ids,
|
||||
public async Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paidPostIds,
|
||||
IStatusReporter statusReporter)
|
||||
{
|
||||
Log.Debug($"Calling GetPosts - {endpoint}");
|
||||
@ -978,7 +978,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
|
||||
if (medium.CanView && medium.Files?.Drm == null)
|
||||
{
|
||||
bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id));
|
||||
bool has = paidPostIds.Any(cus => cus.Equals(medium.Id));
|
||||
if (!has && !string.IsNullOrEmpty(fullUrl))
|
||||
{
|
||||
if (!postCollection.Posts.ContainsKey(medium.Id))
|
||||
@ -1004,7 +1004,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
TryGetDrmInfo(medium.Files, out string manifestDash, out string cloudFrontPolicy,
|
||||
out string cloudFrontSignature, out string cloudFrontKeyPairId))
|
||||
{
|
||||
bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id));
|
||||
bool has = paidPostIds.Any(cus => cus.Equals(medium.Id));
|
||||
if (!has && !postCollection.Posts.ContainsKey(medium.Id))
|
||||
{
|
||||
await dbService.AddMedia(folder, medium.Id, post.Id, manifestDash, null, null, null,
|
||||
@ -1197,7 +1197,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
}
|
||||
|
||||
public async Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder,
|
||||
List<long> paid_post_ids,
|
||||
List<long> paidPostIds,
|
||||
IStatusReporter statusReporter)
|
||||
{
|
||||
Log.Debug($"Calling GetStreams - {endpoint}");
|
||||
@ -1311,7 +1311,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
|
||||
if (medium.CanView && medium.Files?.Drm == null)
|
||||
{
|
||||
bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id));
|
||||
bool has = paidPostIds.Any(cus => cus.Equals(medium.Id));
|
||||
if (!has && !string.IsNullOrEmpty(fullUrl))
|
||||
{
|
||||
if (!streamsCollection.Streams.ContainsKey(medium.Id))
|
||||
@ -1327,7 +1327,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
TryGetDrmInfo(medium.Files, out string manifestDash, out string cloudFrontPolicy,
|
||||
out string cloudFrontSignature, out string cloudFrontKeyPairId))
|
||||
{
|
||||
bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id));
|
||||
bool has = paidPostIds.Any(cus => cus.Equals(medium.Id));
|
||||
if (!has && !streamsCollection.Streams.ContainsKey(medium.Id))
|
||||
{
|
||||
await dbService.AddMedia(folder, medium.Id, stream.Id, manifestDash, null, null, null,
|
||||
@ -1451,7 +1451,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
}
|
||||
}
|
||||
|
||||
await dbService.AddPost(folder, archive.Id, archive.Text != null ? archive.Text : "",
|
||||
await dbService.AddPost(folder, archive.Id, archive.Text ?? "",
|
||||
archive.Price ?? "0",
|
||||
archive.Price != null && archive.IsOpened, archive.IsArchived, archive.PostedAt);
|
||||
archivedCollection.ArchivedPostObjects.Add(archive);
|
||||
@ -1567,7 +1567,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
break;
|
||||
}
|
||||
|
||||
getParams["id"] = newMessages.List[newMessages.List.Count - 1].Id.ToString();
|
||||
getParams["id"] = newMessages.List[^1].Id.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1601,8 +1601,8 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
{
|
||||
DateTime createdAt = list.CreatedAt ?? DateTime.Now;
|
||||
await dbService.AddMessage(folder, list.Id, list.Text ?? "", list.Price ?? "0",
|
||||
list.CanPurchaseReason == "opened" ? true :
|
||||
list.CanPurchaseReason != "opened" ? false : (bool?)null ?? false, false,
|
||||
list.CanPurchaseReason == "opened" ||
|
||||
(list.CanPurchaseReason == "opened" && ((bool?)null ?? false)), false,
|
||||
createdAt,
|
||||
list.FromUser?.Id ?? int.MinValue);
|
||||
messageCollection.MessageObjects.Add(list);
|
||||
@ -2907,24 +2907,19 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
throw new Exception("Auth service is missing required fields");
|
||||
}
|
||||
|
||||
string? pssh;
|
||||
|
||||
HttpClient client = new();
|
||||
HttpRequestMessage request = new(HttpMethod.Get, mpdUrl);
|
||||
request.Headers.Add("user-agent", currentAuth.UserAgent);
|
||||
request.Headers.Add("Accept", "*/*");
|
||||
request.Headers.Add("Cookie",
|
||||
$"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {currentAuth.Cookie};");
|
||||
using (HttpResponseMessage response = await client.SendAsync(request))
|
||||
{
|
||||
using HttpResponseMessage response = await client.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string body = await response.Content.ReadAsStringAsync();
|
||||
XNamespace ns = "urn:mpeg:dash:schema:mpd:2011";
|
||||
XNamespace cenc = "urn:mpeg:cenc:2013";
|
||||
XDocument xmlDoc = XDocument.Parse(body);
|
||||
IEnumerable<XElement> psshElements = xmlDoc.Descendants(cenc + "pssh");
|
||||
pssh = psshElements.ElementAt(1).Value;
|
||||
}
|
||||
string pssh = psshElements.ElementAt(1).Value;
|
||||
|
||||
return pssh;
|
||||
}
|
||||
@ -2956,8 +2951,6 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
|
||||
try
|
||||
{
|
||||
DateTime lastmodified;
|
||||
|
||||
Auth? currentAuth = authService.CurrentAuth;
|
||||
if (currentAuth == null || currentAuth.UserAgent == null || currentAuth.Cookie == null)
|
||||
{
|
||||
@ -2970,14 +2963,12 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
request.Headers.Add("Accept", "*/*");
|
||||
request.Headers.Add("Cookie",
|
||||
$"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {currentAuth.Cookie};");
|
||||
using (HttpResponseMessage response =
|
||||
await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
|
||||
{
|
||||
using HttpResponseMessage response =
|
||||
await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
response.EnsureSuccessStatusCode();
|
||||
lastmodified = response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now;
|
||||
DateTime lastmodified = response.Content.Headers.LastModified?.LocalDateTime ?? DateTime.Now;
|
||||
|
||||
Log.Debug($"Last modified: {lastmodified}");
|
||||
}
|
||||
|
||||
return lastmodified;
|
||||
}
|
||||
@ -3061,14 +3052,14 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
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)
|
||||
{
|
||||
Log.Debug("Calling GetDecryptionKeyCDM");
|
||||
|
||||
try
|
||||
{
|
||||
byte[] resp1 = await PostData(licenceURL, drmHeaders, [0x08, 0x04]);
|
||||
byte[] resp1 = await PostData(licenceUrl, drmHeaders, [0x08, 0x04]);
|
||||
string certDataB64 = Convert.ToBase64String(resp1);
|
||||
CDMApi cdm = new();
|
||||
byte[]? challenge = cdm.GetChallenge(pssh, certDataB64);
|
||||
@ -3077,7 +3068,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
throw new Exception("Failed to get challenge from CDM");
|
||||
}
|
||||
|
||||
byte[] resp2 = await PostData(licenceURL, drmHeaders, challenge);
|
||||
byte[] resp2 = await PostData(licenceUrl, drmHeaders, challenge);
|
||||
string licenseB64 = Convert.ToBase64String(resp2);
|
||||
Log.Debug("resp1: {Resp1}", resp1);
|
||||
Log.Debug("certDataB64: {CertDataB64}", certDataB64);
|
||||
@ -3156,7 +3147,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
diff.TotalSeconds; // This gives the number of seconds. If you need milliseconds, use diff.TotalMilliseconds
|
||||
}
|
||||
|
||||
public static bool IsStringOnlyDigits(string input) => input.All(char.IsDigit);
|
||||
private static bool IsStringOnlyDigits(string input) => input.All(char.IsDigit);
|
||||
|
||||
|
||||
private HttpClient GetHttpClient()
|
||||
@ -3277,7 +3268,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
}
|
||||
|
||||
|
||||
public async Task<Dictionary<string, long>?> GetAllSubscriptions(Dictionary<string, string> getParams,
|
||||
private async Task<Dictionary<string, long>?> GetAllSubscriptions(Dictionary<string, string> getParams,
|
||||
string endpoint, bool includeRestricted)
|
||||
{
|
||||
try
|
||||
@ -3350,7 +3341,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? GetDynamicRules()
|
||||
private static string? GetDynamicRules()
|
||||
{
|
||||
Log.Debug("Calling GetDynamicRules");
|
||||
try
|
||||
|
||||
@ -94,7 +94,7 @@ public class DownloadOrchestrationService(
|
||||
{
|
||||
long listId = lists[listName];
|
||||
List<string> listUsernames = await apiService.GetListUsers($"/lists/{listId}/users") ?? [];
|
||||
return allUsers.Where(x => listUsernames.Contains(x.Key)).Distinct()
|
||||
return allUsers.Where(x => listUsernames.Contains(x.Key))
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using FFmpeg.NET;
|
||||
using FFmpeg.NET.Events;
|
||||
using OF_DL.Models;
|
||||
@ -31,7 +30,7 @@ public class DownloadService(
|
||||
{
|
||||
try
|
||||
{
|
||||
string path = "/Profile";
|
||||
const string path = "/Profile";
|
||||
|
||||
if (!Directory.Exists(folder + path))
|
||||
{
|
||||
@ -61,7 +60,7 @@ public class DownloadService(
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadProfileImage(string url, string folder, string subFolder, string username)
|
||||
private static async Task DownloadProfileImage(string url, string folder, string subFolder, string username)
|
||||
{
|
||||
if (!Directory.Exists(folder + subFolder))
|
||||
{
|
||||
@ -85,7 +84,7 @@ public class DownloadService(
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] hash = md5.ComputeHash(memoryStream);
|
||||
byte[] hash = await md5.ComputeHashAsync(memoryStream);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
if (!md5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
|
||||
{
|
||||
@ -94,7 +93,7 @@ public class DownloadService(
|
||||
? response.Content.Headers.LastModified.Value.LocalDateTime.ToString("dd-MM-yyyy")
|
||||
: DateTime.Now.ToString("dd-MM-yyyy"));
|
||||
|
||||
using (FileStream fileStream = File.Create(destinationPath))
|
||||
await using (FileStream fileStream = File.Create(destinationPath))
|
||||
{
|
||||
await memoryStream.CopyToAsync(fileStream);
|
||||
}
|
||||
@ -104,9 +103,9 @@ public class DownloadService(
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> DownloadDrmMedia(string user_agent, string policy, string signature, string kvp,
|
||||
string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id,
|
||||
string api_type, IProgressReporter progressReporter, string customFileName, string filename, string path)
|
||||
private async Task<bool> DownloadDrmMedia(string userAgent, string policy, string signature, string kvp,
|
||||
string sess, string url, string decryptionKey, string folder, DateTime lastModified, long mediaId,
|
||||
string apiType, IProgressReporter progressReporter, string customFileName, string filename, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -116,35 +115,12 @@ public class DownloadService(
|
||||
string decKey = "";
|
||||
if (pos1 >= 0)
|
||||
{
|
||||
decKey = decryptionKey.Substring(pos1 + 1);
|
||||
decKey = decryptionKey[(pos1 + 1)..];
|
||||
}
|
||||
|
||||
int streamIndex = 0;
|
||||
string tempFilename = $"{folder}{path}/{filename}_source.mp4";
|
||||
|
||||
//int? streamIndex = await GetVideoStreamIndexFromMpd(url, policy, signature, kvp, downloadConfig.DownloadVideoResolution);
|
||||
|
||||
//if (streamIndex == null)
|
||||
// throw new Exception($"Could not find video stream for resolution {downloadConfig.DownloadVideoResolution}");
|
||||
|
||||
//string tempFilename;
|
||||
|
||||
//switch (downloadConfig.DownloadVideoResolution)
|
||||
//{
|
||||
// case VideoResolution.source:
|
||||
// tempFilename = $"{folder}{path}/{filename}_source.mp4";
|
||||
// break;
|
||||
// case VideoResolution._240:
|
||||
// tempFilename = $"{folder}{path}/{filename}_240.mp4";
|
||||
// break;
|
||||
// case VideoResolution._720:
|
||||
// tempFilename = $"{folder}{path}/{filename}_720.mp4";
|
||||
// break;
|
||||
// default:
|
||||
// tempFilename = $"{folder}{path}/{filename}_source.mp4";
|
||||
// break;
|
||||
//}
|
||||
|
||||
// Configure ffmpeg log level and optional report file location
|
||||
bool ffmpegDebugLogging = Log.IsEnabled(LogEventLevel.Debug);
|
||||
|
||||
@ -187,7 +163,7 @@ public class DownloadService(
|
||||
$"{logLevelArgs} " +
|
||||
$"-cenc_decryption_key {decKey} " +
|
||||
$"-headers \"{cookieHeader}\" " +
|
||||
$"-user_agent \"{user_agent}\" " +
|
||||
$"-user_agent \"{userAgent}\" " +
|
||||
"-referer \"https://onlyfans.com\" " +
|
||||
"-rw_timeout 20000000 " +
|
||||
"-reconnect 1 -reconnect_streamed 1 -reconnect_on_network_error 1 -reconnect_delay_max 10 " +
|
||||
@ -205,7 +181,7 @@ public class DownloadService(
|
||||
{
|
||||
_completionSource.TrySetResult(true);
|
||||
await OnFFMPEGDownloadComplete(tempFilename, lastModified, folder, path, customFileName, filename,
|
||||
media_id, api_type, progressReporter);
|
||||
mediaId, apiType, progressReporter);
|
||||
};
|
||||
await ffmpeg.ExecuteAsync(parameters, CancellationToken.None);
|
||||
|
||||
@ -287,59 +263,7 @@ public class DownloadService(
|
||||
Log.Error("FFmpeg failed. Input={Input} Output={Output} ExitCode={ExitCode} Message={Message} Inner={Inner}",
|
||||
input, output, exitCode, message, inner);
|
||||
|
||||
_completionSource?.TrySetResult(false);
|
||||
}
|
||||
|
||||
private async Task<int?> GetVideoStreamIndexFromMpd(string mpdUrl, string policy, string signature, string kvp,
|
||||
VideoResolution resolution)
|
||||
{
|
||||
if (authService.CurrentAuth == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpClient client = new();
|
||||
HttpRequestMessage request = new(HttpMethod.Get, mpdUrl);
|
||||
request.Headers.Add("user-agent", authService.CurrentAuth.UserAgent);
|
||||
request.Headers.Add("Accept", "*/*");
|
||||
request.Headers.Add("Cookie",
|
||||
$"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {authService.CurrentAuth.Cookie};");
|
||||
using (HttpResponseMessage response = await client.SendAsync(request))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
string body = await response.Content.ReadAsStringAsync();
|
||||
XDocument doc = XDocument.Parse(body);
|
||||
XNamespace ns = "urn:mpeg:dash:schema:mpd:2011";
|
||||
// XNamespace cenc = "urn:mpeg:cenc:2013";
|
||||
XElement? videoAdaptationSet = doc
|
||||
.Descendants(ns + "AdaptationSet")
|
||||
.FirstOrDefault(e => (string?)e.Attribute("mimeType") == "video/mp4");
|
||||
|
||||
if (videoAdaptationSet == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string targetHeight = resolution switch
|
||||
{
|
||||
VideoResolution._240 => "240",
|
||||
VideoResolution._720 => "720",
|
||||
VideoResolution.source => "1280",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(resolution))
|
||||
};
|
||||
|
||||
List<XElement> representations = videoAdaptationSet.Elements(ns + "Representation").ToList();
|
||||
|
||||
for (int i = 0; i < representations.Count; i++)
|
||||
{
|
||||
if ((string?)representations[i].Attribute("height") == targetHeight)
|
||||
{
|
||||
return i; // this is the index FFmpeg will use for `-map 0:v:{i}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
_completionSource.TrySetResult(false);
|
||||
}
|
||||
|
||||
private static List<string> CalculateFolderMD5(string folder)
|
||||
@ -900,42 +824,31 @@ public class DownloadService(
|
||||
long totalFileSize = 0;
|
||||
if (urls.Count > 250)
|
||||
{
|
||||
int batchSize = 250;
|
||||
const int batchSize = 250;
|
||||
|
||||
List<Task<long>> tasks = new();
|
||||
List<Task<long>> tasks = [];
|
||||
|
||||
for (int i = 0; i < urls.Count; i += batchSize)
|
||||
{
|
||||
List<string> batchUrls = urls.Skip(i).Take(batchSize).ToList();
|
||||
|
||||
IEnumerable<Task<long>> batchTasks = batchUrls.Select(GetFileSizeAsync);
|
||||
Task<long>[] batchTasks = batchUrls.Select(GetFileSizeAsync).ToArray();
|
||||
tasks.AddRange(batchTasks);
|
||||
|
||||
await Task.WhenAll(batchTasks);
|
||||
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
|
||||
long[] fileSizes = await Task.WhenAll(tasks);
|
||||
foreach (long fileSize in fileSizes)
|
||||
{
|
||||
totalFileSize += fileSize;
|
||||
}
|
||||
totalFileSize += fileSizes.Sum();
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Task<long>> tasks = new();
|
||||
|
||||
foreach (string url in urls)
|
||||
{
|
||||
tasks.Add(GetFileSizeAsync(url));
|
||||
}
|
||||
List<Task<long>> tasks = [];
|
||||
tasks.AddRange(urls.Select(GetFileSizeAsync));
|
||||
|
||||
long[] fileSizes = await Task.WhenAll(tasks);
|
||||
foreach (long fileSize in fileSizes)
|
||||
{
|
||||
totalFileSize += fileSize;
|
||||
}
|
||||
totalFileSize += fileSizes.Sum();
|
||||
}
|
||||
|
||||
return totalFileSize;
|
||||
|
||||
@ -6,6 +6,7 @@ using OF_DL.Helpers;
|
||||
using OF_DL.Models;
|
||||
using OF_DL.Models.OfdlApi;
|
||||
using Serilog;
|
||||
using static Newtonsoft.Json.JsonConvert;
|
||||
using WidevineConstants = OF_DL.Widevine.Constants;
|
||||
|
||||
namespace OF_DL.Services;
|
||||
@ -30,11 +31,11 @@ public class StartupService(IConfigService configService, IAuthService authServi
|
||||
// FFmpeg detection
|
||||
DetectFfmpeg(result);
|
||||
|
||||
if (result.FfmpegFound)
|
||||
if (result.FfmpegFound && result.FfmpegPath != null)
|
||||
{
|
||||
// Escape backslashes for Windows
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||
result.FfmpegPath!.Contains(@":\") &&
|
||||
result.FfmpegPath.Contains(@":\") &&
|
||||
!result.FfmpegPath.Contains(@":\\"))
|
||||
{
|
||||
result.FfmpegPath = result.FfmpegPath.Replace(@"\", @"\\");
|
||||
@ -42,7 +43,7 @@ public class StartupService(IConfigService configService, IAuthService authServi
|
||||
}
|
||||
|
||||
// Get FFmpeg version
|
||||
result.FfmpegVersion = await GetFfmpegVersionAsync(result.FfmpegPath!);
|
||||
result.FfmpegVersion = await GetFfmpegVersionAsync(result.FfmpegPath);
|
||||
}
|
||||
|
||||
// Widevine device checks
|
||||
@ -51,40 +52,27 @@ public class StartupService(IConfigService configService, IAuthService authServi
|
||||
result.DevicePrivateKeyMissing = !File.Exists(Path.Join(WidevineConstants.DEVICES_FOLDER,
|
||||
WidevineConstants.DEVICE_NAME, "device_private_key"));
|
||||
|
||||
if (!result.ClientIdBlobMissing)
|
||||
{
|
||||
Log.Debug("device_client_id_blob found");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("device_client_id_blob missing");
|
||||
}
|
||||
|
||||
if (!result.DevicePrivateKeyMissing)
|
||||
{
|
||||
Log.Debug("device_private_key found");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("device_private_key missing");
|
||||
}
|
||||
Log.Debug("device_client_id_blob {Status}", result.ClientIdBlobMissing ? "missing" : "found");
|
||||
Log.Debug("device_private_key {Status}", result.DevicePrivateKeyMissing ? " missing" : "found");
|
||||
|
||||
// rules.json validation
|
||||
if (File.Exists("rules.json"))
|
||||
if (!File.Exists("rules.json"))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result.RulesJsonExists = true;
|
||||
try
|
||||
{
|
||||
JsonConvert.DeserializeObject<DynamicRules>(File.ReadAllText("rules.json"));
|
||||
DeserializeObject<DynamicRules>(await File.ReadAllTextAsync("rules.json"));
|
||||
Log.Debug("Rules.json: ");
|
||||
Log.Debug(JsonConvert.SerializeObject(File.ReadAllText("rules.json"), Formatting.Indented));
|
||||
Log.Debug(SerializeObject(await File.ReadAllTextAsync("rules.json"), Formatting.Indented));
|
||||
result.RulesJsonValid = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result.RulesJsonError = e.Message;
|
||||
Log.Error("rules.json processing failed.", e.Message);
|
||||
}
|
||||
Log.Error("rules.json processing failed. {ErrorMessage}", e.Message);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -100,7 +88,7 @@ public class StartupService(IConfigService configService, IAuthService authServi
|
||||
result.LocalVersion = Assembly.GetEntryAssembly()?.GetName().Version;
|
||||
|
||||
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30));
|
||||
string? latestReleaseTag = null;
|
||||
string? latestReleaseTag;
|
||||
|
||||
try
|
||||
{
|
||||
@ -121,18 +109,18 @@ public class StartupService(IConfigService configService, IAuthService authServi
|
||||
}
|
||||
|
||||
result.LatestVersion = new Version(latestReleaseTag.Replace("OFDLV", ""));
|
||||
int versionComparison = result.LocalVersion!.CompareTo(result.LatestVersion);
|
||||
int? versionComparison = result.LocalVersion?.CompareTo(result.LatestVersion);
|
||||
result.IsUpToDate = versionComparison >= 0;
|
||||
|
||||
Log.Debug("Detected client running version " +
|
||||
$"{result.LocalVersion.Major}.{result.LocalVersion.Minor}.{result.LocalVersion.Build}");
|
||||
$"{result.LocalVersion?.Major}.{result.LocalVersion?.Minor}.{result.LocalVersion?.Build}");
|
||||
Log.Debug("Latest release version " +
|
||||
$"{result.LatestVersion.Major}.{result.LatestVersion.Minor}.{result.LatestVersion.Build}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result.CheckFailed = true;
|
||||
Log.Error("Error checking latest release on GitHub.", e.Message);
|
||||
Log.Error("Error checking latest release on GitHub. {Message}", e.Message);
|
||||
}
|
||||
#else
|
||||
Log.Debug("Running in Debug/Local mode. Version check skipped.");
|
||||
@ -207,7 +195,7 @@ public class StartupService(IConfigService configService, IAuthService authServi
|
||||
if (firstLine.StartsWith("ffmpeg version"))
|
||||
{
|
||||
int versionStart = "ffmpeg version ".Length;
|
||||
int copyrightIndex = firstLine.IndexOf(" Copyright");
|
||||
int copyrightIndex = firstLine.IndexOf(" Copyright", StringComparison.Ordinal);
|
||||
return copyrightIndex > versionStart
|
||||
? firstLine.Substring(versionStart, copyrightIndex - versionStart)
|
||||
: firstLine.Substring(versionStart);
|
||||
|
||||
@ -61,9 +61,7 @@ public class ThrottledStream : Stream
|
||||
TimeSpan sleep = targetTime - actualTime;
|
||||
if (sleep > TimeSpan.Zero)
|
||||
{
|
||||
using AutoResetEvent waitHandle = new(false);
|
||||
scheduler.Sleep(sleep).GetAwaiter().OnCompleted(() => waitHandle.Set());
|
||||
waitHandle.WaitOne();
|
||||
scheduler.Sleep(sleep).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -264,23 +264,24 @@ public class CDM
|
||||
Serializer.Serialize(memoryStream, session.Device.ClientID);
|
||||
byte[] data = Padding.AddPKCS7Padding(memoryStream.ToArray(), 16);
|
||||
|
||||
using AesCryptoServiceProvider aesProvider = new()
|
||||
{
|
||||
BlockSize = 128, Padding = PaddingMode.PKCS7, Mode = CipherMode.CBC
|
||||
};
|
||||
aesProvider.GenerateKey();
|
||||
aesProvider.GenerateIV();
|
||||
using Aes aes = Aes.Create();
|
||||
aes.BlockSize = 128;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
|
||||
aes.GenerateKey();
|
||||
aes.GenerateIV();
|
||||
|
||||
using MemoryStream mstream = new();
|
||||
using CryptoStream cryptoStream = new(mstream, aesProvider.CreateEncryptor(aesProvider.Key, aesProvider.IV),
|
||||
using CryptoStream cryptoStream = new(mstream, aes.CreateEncryptor(aes.Key, aes.IV),
|
||||
CryptoStreamMode.Write);
|
||||
cryptoStream.Write(data, 0, data.Length);
|
||||
encryptedClientIdProto.EncryptedClientId = mstream.ToArray();
|
||||
|
||||
using RSACryptoServiceProvider RSA = new();
|
||||
RSA.ImportRSAPublicKey(session.ServiceCertificate.DeviceCertificate.PublicKey, out int _);
|
||||
encryptedClientIdProto.EncryptedPrivacyKey = RSA.Encrypt(aesProvider.Key, RSAEncryptionPadding.OaepSHA1);
|
||||
encryptedClientIdProto.EncryptedClientIdIv = aesProvider.IV;
|
||||
encryptedClientIdProto.EncryptedPrivacyKey = RSA.Encrypt(aes.Key, RSAEncryptionPadding.OaepSHA1);
|
||||
encryptedClientIdProto.EncryptedClientIdIv = aes.IV;
|
||||
encryptedClientIdProto.ServiceId =
|
||||
Encoding.UTF8.GetString(session.ServiceCertificate.DeviceCertificate.ServiceId);
|
||||
encryptedClientIdProto.ServiceCertificateSerialNumber =
|
||||
@ -397,27 +398,24 @@ public class CDM
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] keyId;
|
||||
byte[] encryptedKey = key.Key;
|
||||
byte[] iv = key.Iv;
|
||||
keyId = key.Id;
|
||||
byte[] keyId = key.Id;
|
||||
if (keyId == null)
|
||||
{
|
||||
keyId = Encoding.ASCII.GetBytes(key.Type.ToString());
|
||||
}
|
||||
|
||||
byte[] decryptedKey;
|
||||
|
||||
using MemoryStream mstream = new();
|
||||
|
||||
using AesCryptoServiceProvider aesProvider = new();
|
||||
aesProvider.Mode = CipherMode.CBC;
|
||||
aesProvider.Padding = PaddingMode.PKCS7;
|
||||
using Aes aes = Aes.Create();
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
|
||||
using CryptoStream cryptoStream = new(mstream, aesProvider.CreateDecryptor(session.DerivedKeys.Enc, iv),
|
||||
using CryptoStream cryptoStream = new(mstream, aes.CreateDecryptor(session.DerivedKeys.Enc, iv),
|
||||
CryptoStreamMode.Write);
|
||||
cryptoStream.Write(encryptedKey, 0, encryptedKey.Length);
|
||||
decryptedKey = mstream.ToArray();
|
||||
byte[] decryptedKey = mstream.ToArray();
|
||||
|
||||
List<string> permissions = [];
|
||||
if (type == "OperatorSession")
|
||||
|
||||
@ -12,35 +12,55 @@ public class SpectreDownloadEventHandler : IDownloadEventHandler
|
||||
{
|
||||
public async Task<T> WithStatusAsync<T>(string statusMessage, Func<IStatusReporter, Task<T>> work)
|
||||
{
|
||||
T result = default!;
|
||||
TaskCompletionSource<T> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
await AnsiConsole.Status()
|
||||
.StartAsync($"[red]{Markup.Escape(statusMessage)}[/]",
|
||||
async ctx =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SpectreStatusReporter reporter = new(ctx);
|
||||
result = await work(reporter);
|
||||
T result = await work(reporter);
|
||||
tcs.TrySetResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
public async Task<T> WithProgressAsync<T>(string description, long maxValue, bool showSize,
|
||||
Func<IProgressReporter, Task<T>> work)
|
||||
{
|
||||
T result = default!;
|
||||
TaskCompletionSource<T> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
await AnsiConsole.Progress()
|
||||
.Columns(GetProgressColumns(showSize))
|
||||
.StartAsync(async ctx =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ProgressTask task = ctx.AddTask($"[red]{Markup.Escape(description)}[/]", false);
|
||||
task.MaxValue = maxValue;
|
||||
task.StartTask();
|
||||
|
||||
SpectreProgressReporter progressReporter = new(task);
|
||||
result = await work(progressReporter);
|
||||
T result = await work(progressReporter);
|
||||
tcs.TrySetResult(result);
|
||||
|
||||
task.StopTask();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
public void OnContentFound(string contentType, int mediaCount, int objectCount) =>
|
||||
|
||||
@ -219,6 +219,7 @@ public class Program(IServiceProvider serviceProvider)
|
||||
Console.WriteLine("\nPress any key to exit.");
|
||||
Console.ReadKey();
|
||||
}
|
||||
|
||||
Environment.Exit(2);
|
||||
}
|
||||
}
|
||||
@ -542,7 +543,7 @@ public class Program(IServiceProvider serviceProvider)
|
||||
}
|
||||
}
|
||||
|
||||
selectedUsers = users.Where(x => listUsernames.Contains($"{x.Key}")).Distinct()
|
||||
selectedUsers = users.Where(x => listUsernames.Contains($"{x.Key}"))
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
AnsiConsole.Markup(string.Format("[red]Downloading from List(s): {0}[/]",
|
||||
string.Join(", ", listSelection)));
|
||||
@ -787,6 +788,7 @@ public class Program(IServiceProvider serviceProvider)
|
||||
AnsiConsole.MarkupLine("[red]Press any key to exit.[/]");
|
||||
Console.ReadKey();
|
||||
}
|
||||
|
||||
Environment.Exit(2);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user