forked from sim0n00ps/OF-DL
Compare commits
1 Commits
master
...
dev-before
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ea0c5f525 |
@ -5,5 +5,12 @@ root = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{sln,csproj,xml,json,config}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
tab_width = 2
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -370,4 +370,6 @@ FodyWeavers.xsd
|
||||
!.gitea-actions/**/node_modules/
|
||||
|
||||
# venv
|
||||
venv/
|
||||
venv/
|
||||
|
||||
Publish/
|
||||
|
||||
7
OF DL/Entities/Chats/ChatCollection.cs
Normal file
7
OF DL/Entities/Chats/ChatCollection.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace OF_DL.Entities.Chats
|
||||
{
|
||||
public class ChatCollection
|
||||
{
|
||||
public Dictionary<int, Chats.Chat> Chats { get; set; } = [];
|
||||
}
|
||||
}
|
||||
20
OF DL/Entities/Chats/Chats.cs
Normal file
20
OF DL/Entities/Chats/Chats.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace OF_DL.Entities.Chats
|
||||
{
|
||||
public class Chats
|
||||
{
|
||||
public List<Chat> list { get; set; }
|
||||
public bool hasMore { get; set; }
|
||||
public int nextOffset { get; set; }
|
||||
|
||||
public class Chat
|
||||
{
|
||||
public User withUser { get; set; }
|
||||
public int unreadMessagesCount { get; set; }
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public int id { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,6 +107,12 @@ namespace OF_DL.Entities
|
||||
// When enabled, post/message text is stored as-is without XML stripping.
|
||||
[ToggleableConfig]
|
||||
public bool DisableTextSanitization { get; set; } = false;
|
||||
|
||||
public string[] NonInteractiveSpecificUsers { get; set; } = [];
|
||||
public string[] NonInteractiveSpecificLists { get; set; } = [];
|
||||
|
||||
public bool OutputBlockedUsers { get; set; }
|
||||
public bool UpdateAllUserInfo { get; set; }
|
||||
}
|
||||
|
||||
public class CreatorConfig : IFileNameFormatConfig
|
||||
|
||||
@ -98,6 +98,7 @@ namespace OF_DL.Entities
|
||||
public object id { get; set; }
|
||||
public long? userId { get; set; }
|
||||
public int? subscriberId { get; set; }
|
||||
public long? earningId { get; set; }
|
||||
public DateTime? date { get; set; }
|
||||
public int? duration { get; set; }
|
||||
public DateTime? startDate { get; set; }
|
||||
|
||||
@ -2,6 +2,7 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OF_DL.Entities;
|
||||
using OF_DL.Entities.Archived;
|
||||
using OF_DL.Entities.Chats;
|
||||
using OF_DL.Entities.Highlights;
|
||||
using OF_DL.Entities.Lists;
|
||||
using OF_DL.Entities.Messages;
|
||||
@ -13,6 +14,7 @@ using OF_DL.Enumerations;
|
||||
using OF_DL.Enumurations;
|
||||
using Serilog;
|
||||
using Spectre.Console;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@ -25,10 +27,15 @@ namespace OF_DL.Helpers;
|
||||
|
||||
public class APIHelper : IAPIHelper
|
||||
{
|
||||
private const int MAX_RETRIES = 10;
|
||||
private const int DELAY_BEFORE_RETRY = 1000;
|
||||
|
||||
private static readonly JsonSerializerSettings m_JsonSerializerSettings;
|
||||
private readonly IDBHelper m_DBHelper;
|
||||
private readonly IDownloadConfig downloadConfig;
|
||||
private readonly Auth auth;
|
||||
private HttpClient httpClient = new();
|
||||
|
||||
private static DateTime? cachedDynamicRulesExpiration;
|
||||
private static DynamicRules? cachedDynamicRules;
|
||||
private const int MaxAttempts = 30;
|
||||
@ -118,30 +125,58 @@ public class APIHelper : IAPIHelper
|
||||
}
|
||||
|
||||
|
||||
private async Task<string?> BuildHeaderAndExecuteRequests(Dictionary<string, string> getParams, string endpoint, HttpClient client)
|
||||
private async Task<string?> BuildHeaderAndExecuteRequests(Dictionary<string, string> getParams, string endpoint, HttpClient client, HttpMethod? method = null, int retryCount = 0)
|
||||
{
|
||||
Log.Debug("Calling BuildHeaderAndExecuteRequests");
|
||||
Log.Debug("Calling BuildHeaderAndExecuteRequests -- Attempt number: {AttemptNumber}", retryCount + 1);
|
||||
|
||||
HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint);
|
||||
using var response = await client.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string body = await response.Content.ReadAsStringAsync();
|
||||
try
|
||||
{
|
||||
HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint, method);
|
||||
|
||||
Log.Debug(body);
|
||||
Debug.WriteLine($"Executing {request.Method.Method.ToUpper()} request: {request.RequestUri}\r\n\t{GetParamsString(getParams)}");
|
||||
|
||||
return body;
|
||||
using var response = await client.SendAsync(request);
|
||||
|
||||
if (Debugger.IsAttached && !response.IsSuccessStatusCode)
|
||||
Debugger.Break();
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
string body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
Log.Debug(body);
|
||||
|
||||
return body;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
if (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests && retryCount < MAX_RETRIES)
|
||||
{
|
||||
await Task.Delay(DELAY_BEFORE_RETRY);
|
||||
return await BuildHeaderAndExecuteRequests(getParams, endpoint, client, method, ++retryCount);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
static string GetParamsString(Dictionary<string, string> getParams)
|
||||
=> string.Join(" | ", getParams.Select(kv => $"{kv.Key}={kv.Value}"));
|
||||
}
|
||||
|
||||
|
||||
private async Task<HttpRequestMessage> BuildHttpRequestMessage(Dictionary<string, string> getParams, string endpoint)
|
||||
private async Task<HttpRequestMessage> BuildHttpRequestMessage(Dictionary<string, string> getParams, string endpoint, HttpMethod? method = null)
|
||||
{
|
||||
Log.Debug("Calling BuildHttpRequestMessage");
|
||||
|
||||
string queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
method ??= HttpMethod.Get;
|
||||
|
||||
string queryParams = "";
|
||||
|
||||
if (getParams.Any())
|
||||
queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
|
||||
Dictionary<string, string> headers = GetDynamicHeaders($"/api2/v2{endpoint}", queryParams);
|
||||
|
||||
HttpRequestMessage request = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{queryParams}");
|
||||
HttpRequestMessage request = new(method, $"{Constants.API_URL}{endpoint}{queryParams}");
|
||||
|
||||
Log.Debug($"Full request URL: {Constants.API_URL}{endpoint}{queryParams}");
|
||||
|
||||
@ -166,18 +201,16 @@ public class APIHelper : IAPIHelper
|
||||
return input.All(char.IsDigit);
|
||||
}
|
||||
|
||||
|
||||
private static HttpClient GetHttpClient(IDownloadConfig? config = null)
|
||||
private HttpClient GetHttpClient(IDownloadConfig? config = null)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
httpClient ??= new HttpClient();
|
||||
if (config?.Timeout != null && config.Timeout > 0)
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(config.Timeout.Value);
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(config.Timeout.Value);
|
||||
}
|
||||
return client;
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// this one is used during initialization only
|
||||
/// if the config option is not available then no modificatiotns will be done on the getParams
|
||||
@ -215,7 +248,7 @@ public class APIHelper : IAPIHelper
|
||||
}
|
||||
|
||||
|
||||
public async Task<User?> GetUserInfo(string endpoint)
|
||||
public async Task<User?> GetUserInfo(string username, string endpoint)
|
||||
{
|
||||
Log.Debug($"Calling GetUserInfo: {endpoint}");
|
||||
|
||||
@ -242,6 +275,10 @@ public class APIHelper : IAPIHelper
|
||||
response.EnsureSuccessStatusCode();
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
user = JsonConvert.DeserializeObject<Entities.User>(body, m_JsonSerializerSettings);
|
||||
|
||||
if (user is not null && !endpoint.EndsWith("/me"))
|
||||
await m_DBHelper.UpdateUserInfo(username, user);
|
||||
|
||||
return user;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -300,47 +337,44 @@ public class APIHelper : IAPIHelper
|
||||
try
|
||||
{
|
||||
Dictionary<string, long> users = new();
|
||||
Subscriptions subscriptions = new();
|
||||
|
||||
int limit = 25;
|
||||
int offset = 0;
|
||||
|
||||
getParams["limit"] = limit.ToString();
|
||||
getParams["offset"] = offset.ToString();
|
||||
|
||||
Log.Debug("Calling GetAllSubscrptions");
|
||||
|
||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
||||
|
||||
subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body);
|
||||
if (subscriptions != null && subscriptions.hasMore)
|
||||
while (true)
|
||||
{
|
||||
getParams["offset"] = subscriptions.list.Count.ToString();
|
||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, httpClient);
|
||||
|
||||
while (true)
|
||||
if (string.IsNullOrWhiteSpace(body))
|
||||
break;
|
||||
|
||||
Subscriptions? subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body, m_JsonSerializerSettings);
|
||||
|
||||
if (subscriptions?.list is null)
|
||||
break;
|
||||
|
||||
foreach (Subscriptions.List item in subscriptions.list)
|
||||
{
|
||||
Subscriptions newSubscriptions = new();
|
||||
string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
||||
if (users.ContainsKey(item.username))
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(loopbody) && (!loopbody.Contains("[]") || loopbody.Trim() != "[]"))
|
||||
{
|
||||
newSubscriptions = JsonConvert.DeserializeObject<Subscriptions>(loopbody, m_JsonSerializerSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
bool isRestricted = item.isRestricted ?? false;
|
||||
bool isRestrictedButAllowed = isRestricted && includeRestricted;
|
||||
|
||||
subscriptions.list.AddRange(newSubscriptions.list);
|
||||
if (!newSubscriptions.hasMore)
|
||||
{
|
||||
break;
|
||||
}
|
||||
getParams["offset"] = subscriptions.list.Count.ToString();
|
||||
if (!isRestricted || isRestrictedButAllowed)
|
||||
users.Add(item.username, item.id);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Subscriptions.List subscription in subscriptions.list)
|
||||
{
|
||||
if ((!(subscription.isRestricted ?? false) || ((subscription.isRestricted ?? false) && includeRestricted))
|
||||
&& !users.ContainsKey(subscription.username))
|
||||
{
|
||||
users.Add(subscription.username, subscription.id);
|
||||
}
|
||||
if (!subscriptions.hasMore)
|
||||
break;
|
||||
|
||||
offset += limit;
|
||||
getParams["offset"] = offset.ToString();
|
||||
}
|
||||
|
||||
return users;
|
||||
@ -363,23 +397,20 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
Dictionary<string, string> getParams = new()
|
||||
{
|
||||
{ "offset", "0" },
|
||||
{ "limit", "50" },
|
||||
{ "type", "active" },
|
||||
{ "format", "infinite"}
|
||||
};
|
||||
|
||||
Log.Debug("Calling GetActiveSubscriptions");
|
||||
|
||||
return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config);
|
||||
}
|
||||
|
||||
|
||||
public async Task<Dictionary<string, long>?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config)
|
||||
{
|
||||
|
||||
Dictionary<string, string> getParams = new()
|
||||
{
|
||||
{ "offset", "0" },
|
||||
{ "limit", "50" },
|
||||
{ "type", "expired" },
|
||||
{ "format", "infinite"}
|
||||
};
|
||||
@ -389,6 +420,86 @@ public class APIHelper : IAPIHelper
|
||||
return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, long>?> GetUsersWithProgress(string typeDisplay, string endpoint, StatusContext ctx, string? typeParam, bool offsetByCount)
|
||||
{
|
||||
int limit = 50;
|
||||
int offset = 0;
|
||||
bool includeRestricted = true;
|
||||
|
||||
Dictionary<string, string> getParams = new()
|
||||
{
|
||||
["format"] = "infinite",
|
||||
["limit"] = limit.ToString(),
|
||||
["offset"] = offset.ToString()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(typeParam))
|
||||
getParams["type"] = typeParam;
|
||||
|
||||
try
|
||||
{
|
||||
Dictionary<string, long> users = [];
|
||||
|
||||
Log.Debug("Calling GetUsersWithProgress");
|
||||
|
||||
bool isLastLoop = false;
|
||||
while (true)
|
||||
{
|
||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, httpClient);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(body))
|
||||
break;
|
||||
|
||||
Subscriptions? subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body, m_JsonSerializerSettings);
|
||||
|
||||
if (subscriptions?.list is null)
|
||||
break;
|
||||
|
||||
foreach (Subscriptions.List item in subscriptions.list)
|
||||
{
|
||||
if (users.ContainsKey(item.username))
|
||||
continue;
|
||||
|
||||
bool isRestricted = item.isRestricted ?? false;
|
||||
bool isRestrictedButAllowed = isRestricted && includeRestricted;
|
||||
|
||||
if (!isRestricted || isRestrictedButAllowed)
|
||||
users.Add(item.username, item.id);
|
||||
}
|
||||
|
||||
ctx.Status($"[red]Getting {typeDisplay} Users\n[/] [red]Found {users.Count}[/]");
|
||||
ctx.Spinner(Spinner.Known.Dots);
|
||||
ctx.SpinnerStyle(Style.Parse("blue"));
|
||||
|
||||
if (isLastLoop)
|
||||
break;
|
||||
|
||||
if (!subscriptions.hasMore || subscriptions.list.Count == 0)
|
||||
isLastLoop = true;
|
||||
|
||||
offset += offsetByCount
|
||||
? subscriptions.list.Count
|
||||
: limit;
|
||||
|
||||
getParams["offset"] = offset.ToString();
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
||||
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Console.WriteLine("\nInner Exception:");
|
||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, long>> GetLists(string endpoint, IDownloadConfig config)
|
||||
{
|
||||
@ -407,7 +518,7 @@ public class APIHelper : IAPIHelper
|
||||
Dictionary<string, long> lists = new();
|
||||
while (true)
|
||||
{
|
||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
|
||||
if (body == null)
|
||||
{
|
||||
@ -472,7 +583,7 @@ public class APIHelper : IAPIHelper
|
||||
|
||||
while (true)
|
||||
{
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
if (body == null)
|
||||
{
|
||||
break;
|
||||
@ -516,6 +627,66 @@ public class APIHelper : IAPIHelper
|
||||
}
|
||||
|
||||
|
||||
public async Task<Dictionary<string, long>?> GetUsersFromList(string endpoint, bool includeRestricted, IDownloadConfig config)
|
||||
{
|
||||
var model = new { list = new[] { new { id = int.MaxValue, username = string.Empty, isRestricted = false, isBlocked = false } }, nextOffset = 0, hasMore = false };
|
||||
|
||||
Log.Debug($"Calling GetUsersFromList - {endpoint}");
|
||||
|
||||
int limit = 50;
|
||||
int offset = 0;
|
||||
|
||||
Dictionary<string, string> getParams = new()
|
||||
{
|
||||
{ "offset", offset.ToString() },
|
||||
{ "limit", limit.ToString() },
|
||||
{ "format", "infinite" }
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
Dictionary<string, long> users = [];
|
||||
|
||||
while (true)
|
||||
{
|
||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(body))
|
||||
break;
|
||||
|
||||
var data = JsonConvert.DeserializeAnonymousType(body, model, m_JsonSerializerSettings);
|
||||
|
||||
if (data is null)
|
||||
break;
|
||||
|
||||
foreach (var item in data.list)
|
||||
{
|
||||
if (users.ContainsKey(item.username))
|
||||
continue;
|
||||
|
||||
bool isRestricted = item.isRestricted;
|
||||
bool isRestrictedButAllowed = isRestricted && includeRestricted;
|
||||
|
||||
if (!isRestricted || isRestrictedButAllowed)
|
||||
users.Add(item.username, item.id);
|
||||
}
|
||||
|
||||
if (!data.hasMore)
|
||||
break;
|
||||
|
||||
|
||||
offset += data.nextOffset;
|
||||
getParams["offset"] = offset.ToString();
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Dictionary<long, string>> GetMedia(MediaType mediatype,
|
||||
string endpoint,
|
||||
string? username,
|
||||
@ -557,7 +728,7 @@ public class APIHelper : IAPIHelper
|
||||
break;
|
||||
}
|
||||
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
|
||||
|
||||
if (mediatype == MediaType.Stories)
|
||||
@ -730,7 +901,7 @@ public class APIHelper : IAPIHelper
|
||||
}
|
||||
|
||||
|
||||
public async Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx)
|
||||
public async Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, long userId, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx)
|
||||
{
|
||||
Log.Debug($"Calling GetPaidPosts - {username}");
|
||||
|
||||
@ -739,13 +910,13 @@ public class APIHelper : IAPIHelper
|
||||
Purchased paidPosts = new();
|
||||
PaidPostCollection paidPostCollection = new();
|
||||
int post_limit = 50;
|
||||
int offset = 0;
|
||||
Dictionary<string, string> getParams = new()
|
||||
{
|
||||
{ "limit", post_limit.ToString() },
|
||||
{ "skip_users", "all" },
|
||||
{ "order", "publish_date_desc" },
|
||||
{ "format", "infinite" },
|
||||
{ "author", username }
|
||||
{ "author", username },
|
||||
};
|
||||
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
@ -755,9 +926,10 @@ public class APIHelper : IAPIHelper
|
||||
ctx.SpinnerStyle(Style.Parse("blue"));
|
||||
if (paidPosts != null && paidPosts.hasMore)
|
||||
{
|
||||
getParams["offset"] = paidPosts.list.Count.ToString();
|
||||
while (true)
|
||||
{
|
||||
offset += post_limit;
|
||||
getParams["offset"] = offset.ToString();
|
||||
|
||||
Purchased newPaidPosts = new();
|
||||
|
||||
@ -772,7 +944,6 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
break;
|
||||
}
|
||||
getParams["offset"] = Convert.ToString(Convert.ToInt32(getParams["offset"]) + post_limit);
|
||||
}
|
||||
|
||||
}
|
||||
@ -781,6 +952,9 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
if (purchase.responseType == "post" && purchase.media != null && purchase.media.Count > 0)
|
||||
{
|
||||
if (purchase.fromUser.id != userId)
|
||||
continue; // Ensures only posts from current model are included
|
||||
|
||||
List<long> previewids = new();
|
||||
if (purchase.previews != null)
|
||||
{
|
||||
@ -938,7 +1112,7 @@ public class APIHelper : IAPIHelper
|
||||
ref getParams,
|
||||
downloadAsOf);
|
||||
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
posts = JsonConvert.DeserializeObject<Post>(body, m_JsonSerializerSettings);
|
||||
ctx.Status($"[red]Getting Posts (this may take a long time, depending on the number of Posts the creator has)\n[/] [red]Found {posts.list.Count}[/]");
|
||||
ctx.Spinner(Spinner.Known.Dots);
|
||||
@ -1096,7 +1270,7 @@ public class APIHelper : IAPIHelper
|
||||
{ "skip_users", "all" }
|
||||
};
|
||||
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
singlePost = JsonConvert.DeserializeObject<SinglePost>(body, m_JsonSerializerSettings);
|
||||
|
||||
if (singlePost != null)
|
||||
@ -1153,7 +1327,7 @@ public class APIHelper : IAPIHelper
|
||||
}
|
||||
break;
|
||||
case VideoResolution._240:
|
||||
if(medium.videoSources != null)
|
||||
if (medium.videoSources != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(medium.videoSources._240))
|
||||
{
|
||||
@ -1252,7 +1426,7 @@ public class APIHelper : IAPIHelper
|
||||
ref getParams,
|
||||
config.CustomDate);
|
||||
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
streams = JsonConvert.DeserializeObject<Streams>(body, m_JsonSerializerSettings);
|
||||
ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]");
|
||||
ctx.Spinner(Spinner.Known.Dots);
|
||||
@ -1526,7 +1700,7 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
{ "limit", post_limit.ToString() },
|
||||
{ "order", "desc" },
|
||||
{ "skip_users", "all" }
|
||||
{ "skip_users", "all" },
|
||||
};
|
||||
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
@ -1536,9 +1710,10 @@ public class APIHelper : IAPIHelper
|
||||
ctx.SpinnerStyle(Style.Parse("blue"));
|
||||
if (messages.hasMore)
|
||||
{
|
||||
getParams["id"] = messages.list[^1].id.ToString();
|
||||
while (true)
|
||||
{
|
||||
getParams["id"] = messages.list[^1].id.ToString();
|
||||
|
||||
Messages newmessages = new();
|
||||
|
||||
var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
@ -1552,7 +1727,6 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
break;
|
||||
}
|
||||
getParams["id"] = newmessages.list[newmessages.list.Count - 1].id.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1880,7 +2054,7 @@ public class APIHelper : IAPIHelper
|
||||
}
|
||||
|
||||
|
||||
public async Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, IDownloadConfig config, StatusContext ctx)
|
||||
public async Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, long userId, IDownloadConfig config, StatusContext ctx)
|
||||
{
|
||||
Log.Debug($"Calling GetPaidMessages - {username}");
|
||||
|
||||
@ -1889,13 +2063,14 @@ public class APIHelper : IAPIHelper
|
||||
Purchased paidMessages = new();
|
||||
PaidMessageCollection paidMessageCollection = new();
|
||||
int post_limit = 50;
|
||||
int offset = 0;
|
||||
Dictionary<string, string> getParams = new()
|
||||
{
|
||||
{ "limit", post_limit.ToString() },
|
||||
{ "order", "publish_date_desc" },
|
||||
{ "skip_users", "all" },
|
||||
{ "format", "infinite" },
|
||||
{ "offset", offset.ToString() },
|
||||
{ "author", username },
|
||||
{ "skip_users", "all" }
|
||||
};
|
||||
|
||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
@ -1905,9 +2080,11 @@ public class APIHelper : IAPIHelper
|
||||
ctx.SpinnerStyle(Style.Parse("blue"));
|
||||
if (paidMessages != null && paidMessages.hasMore)
|
||||
{
|
||||
getParams["offset"] = paidMessages.list.Count.ToString();
|
||||
while (true)
|
||||
{
|
||||
offset += post_limit;
|
||||
getParams["offset"] = offset.ToString();
|
||||
|
||||
string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
Purchased newpaidMessages = new();
|
||||
Dictionary<string, string> loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams);
|
||||
@ -1919,12 +2096,14 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
looprequest.Headers.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
using (var loopresponse = await loopclient.SendAsync(looprequest))
|
||||
{
|
||||
loopresponse.EnsureSuccessStatusCode();
|
||||
var loopbody = await loopresponse.Content.ReadAsStringAsync();
|
||||
newpaidMessages = JsonConvert.DeserializeObject<Purchased>(loopbody, m_JsonSerializerSettings);
|
||||
}
|
||||
|
||||
paidMessages.list.AddRange(newpaidMessages.list);
|
||||
ctx.Status($"[red]Getting Paid Messages\n[/] [red]Found {paidMessages.list.Count}[/]");
|
||||
ctx.Spinner(Spinner.Known.Dots);
|
||||
@ -1933,16 +2112,21 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
break;
|
||||
}
|
||||
getParams["offset"] = Convert.ToString(Convert.ToInt32(getParams["offset"]) + post_limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (paidMessages.list != null && paidMessages.list.Count > 0)
|
||||
{
|
||||
long ownUserId = Convert.ToInt64(auth.USER_ID);
|
||||
long[] validUserIds = [ownUserId, userId];
|
||||
|
||||
foreach (Purchased.List purchase in paidMessages.list.Where(p => p.responseType == "message").OrderByDescending(p => p.postedAt ?? p.createdAt))
|
||||
{
|
||||
if (!config.IgnoreOwnMessages || purchase.fromUser.id != Convert.ToInt32(auth.USER_ID))
|
||||
if (!config.IgnoreOwnMessages || purchase.fromUser.id != ownUserId)
|
||||
{
|
||||
if (!validUserIds.Contains(purchase.fromUser.id))
|
||||
continue; // Ensures only messages from current model (or self) are included
|
||||
|
||||
if (purchase.postedAt != null)
|
||||
{
|
||||
await m_DBHelper.AddMessage(folder, purchase.id, purchase.text != null ? purchase.text : string.Empty, purchase.price != null ? purchase.price : "0", true, false, purchase.postedAt.Value, purchase.fromUser.id);
|
||||
@ -2190,11 +2374,11 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
JObject user = await GetUserInfoById($"/users/list?x[]={purchase.fromUser.id}");
|
||||
|
||||
if(user is null)
|
||||
if (user is null)
|
||||
{
|
||||
if (!config.BypassContentForCreatorsWhoNoLongerExist)
|
||||
{
|
||||
if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}"))
|
||||
if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}"))
|
||||
{
|
||||
purchasedTabUsers.Add($"Deleted User - {purchase.fromUser.id}", purchase.fromUser.id);
|
||||
}
|
||||
@ -2244,7 +2428,7 @@ public class APIHelper : IAPIHelper
|
||||
{
|
||||
if (!config.BypassContentForCreatorsWhoNoLongerExist)
|
||||
{
|
||||
if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}"))
|
||||
if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}"))
|
||||
{
|
||||
purchasedTabUsers.Add($"Deleted User - {purchase.author.id}", purchase.author.id);
|
||||
}
|
||||
@ -2647,6 +2831,94 @@ public class APIHelper : IAPIHelper
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<ChatCollection> GetChats(string endpoint, IDownloadConfig config, bool onlyUnread)
|
||||
{
|
||||
Log.Debug($"Calling GetChats - {endpoint}");
|
||||
|
||||
try
|
||||
{
|
||||
Chats chats = new();
|
||||
ChatCollection collection = new();
|
||||
|
||||
int limit = 60;
|
||||
Dictionary<string, string> getParams = new()
|
||||
{
|
||||
{ "limit", $"{limit}" },
|
||||
{ "offset", "0" },
|
||||
{ "skip_users", "all" },
|
||||
{ "order", "recent" }
|
||||
};
|
||||
|
||||
if (onlyUnread)
|
||||
getParams["filter"] = "unread";
|
||||
|
||||
string body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
chats = JsonConvert.DeserializeObject<Chats>(body, m_JsonSerializerSettings);
|
||||
|
||||
if (chats.hasMore)
|
||||
{
|
||||
getParams["offset"] = $"{chats.nextOffset}";
|
||||
|
||||
while (true)
|
||||
{
|
||||
string loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||
Chats newChats = JsonConvert.DeserializeObject<Chats>(loopbody, m_JsonSerializerSettings);
|
||||
|
||||
chats.list.AddRange(newChats.list);
|
||||
|
||||
if (!newChats.hasMore)
|
||||
break;
|
||||
|
||||
getParams["offset"] = $"{newChats.nextOffset}";
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Chats.Chat chat in chats.list)
|
||||
collection.Chats.TryAdd(chat.withUser.id, chat);
|
||||
|
||||
return collection;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
||||
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Console.WriteLine("\nInner Exception:");
|
||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task MarkAsUnread(string endpoint, IDownloadConfig config)
|
||||
{
|
||||
Log.Debug($"Calling MarkAsUnread - {endpoint}");
|
||||
|
||||
try
|
||||
{
|
||||
var result = new { success = false };
|
||||
|
||||
string body = await BuildHeaderAndExecuteRequests([], endpoint, GetHttpClient(config), HttpMethod.Delete);
|
||||
result = JsonConvert.DeserializeAnonymousType(body, result);
|
||||
|
||||
if (result?.success != true)
|
||||
Console.WriteLine($"Failed to mark chat as unread! Endpoint: {endpoint}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
||||
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Console.WriteLine("\nInner Exception:");
|
||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp)
|
||||
{
|
||||
@ -2762,7 +3034,7 @@ public class APIHelper : IAPIHelper
|
||||
|
||||
using var response = await client.SendAsync(request);
|
||||
|
||||
Log.Debug($"CDRM Project Response (Attempt {attempt}): {response.Content.ReadAsStringAsync().Result}");
|
||||
Log.Debug($"CDRM Project Response (Attempt {attempt}): {await response.Content.ReadAsStringAsync()}");
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OF_DL.Enumurations;
|
||||
using System.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Serilog;
|
||||
using OF_DL.Entities;
|
||||
using Serilog;
|
||||
using System.Text;
|
||||
|
||||
namespace OF_DL.Helpers
|
||||
{
|
||||
public class DBHelper : IDBHelper
|
||||
{
|
||||
private static readonly Dictionary<string, SqliteConnection> _connections = [];
|
||||
|
||||
private readonly IDownloadConfig downloadConfig;
|
||||
|
||||
public DBHelper(IDownloadConfig downloadConfig)
|
||||
@ -32,9 +28,7 @@ namespace OF_DL.Helpers
|
||||
string dbFilePath = $"{folder}/Metadata/user_data.db";
|
||||
|
||||
// connect to the new database file
|
||||
using SqliteConnection connection = new($"Data Source={dbFilePath}");
|
||||
// open the connection
|
||||
connection.Open();
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={dbFilePath}");
|
||||
|
||||
// create the 'medias' table
|
||||
using (SqliteCommand cmd = 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));", connection))
|
||||
@ -139,39 +133,44 @@ namespace OF_DL.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
||||
Log.Debug("Database data source: " + connection.DataSource);
|
||||
|
||||
connection.Open();
|
||||
|
||||
using (SqliteCommand cmd = 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));", connection))
|
||||
using (SqliteCommand cmdUsers = 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));", connection))
|
||||
{
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
await cmdUsers.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
using (SqliteCommand cmdInfo = new("CREATE TABLE IF NOT EXISTS user_info (user_id INTEGER NOT NULL, name VARCHAR NOT NULL, about VARCHAR NULL, expires_on TIMESTAMP NULL, photo_count INT NOT NULL, video_count INT NOT NULL, PRIMARY KEY(user_id));", connection))
|
||||
{
|
||||
await cmdInfo.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
using (SqliteCommand cmdInfo = new("CREATE TABLE IF NOT EXISTS user_info_blob (user_id INTEGER NOT NULL, name VARCHAR NOT NULL, blob TEXT NULL, PRIMARY KEY(user_id));", connection))
|
||||
{
|
||||
await cmdInfo.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
Log.Debug("Adding missing creators");
|
||||
foreach (KeyValuePair<string, long> user in users)
|
||||
{
|
||||
using (SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection))
|
||||
using SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection);
|
||||
checkCmd.Parameters.AddWithValue("@userId", user.Value);
|
||||
|
||||
using var reader = await checkCmd.ExecuteReaderAsync();
|
||||
|
||||
if (!reader.Read())
|
||||
{
|
||||
checkCmd.Parameters.AddWithValue("@userId", user.Value);
|
||||
using (var reader = await checkCmd.ExecuteReaderAsync())
|
||||
{
|
||||
if (!reader.Read())
|
||||
{
|
||||
using (SqliteCommand insertCmd = new($"INSERT INTO users (user_id, username) VALUES (@userId, @username);", connection))
|
||||
{
|
||||
insertCmd.Parameters.AddWithValue("@userId", user.Value);
|
||||
insertCmd.Parameters.AddWithValue("@username", user.Key);
|
||||
await insertCmd.ExecuteNonQueryAsync();
|
||||
Log.Debug("Inserted new creator: " + user.Key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("Creator " + user.Key + " already exists");
|
||||
}
|
||||
}
|
||||
using SqliteCommand insertCmd = new($"INSERT INTO users (user_id, username) VALUES (@userId, @username);", connection);
|
||||
insertCmd.Parameters.AddWithValue("@userId", user.Value);
|
||||
insertCmd.Parameters.AddWithValue("@username", user.Key);
|
||||
|
||||
await insertCmd.ExecuteNonQueryAsync();
|
||||
Log.Debug("Inserted new creator: " + user.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("Creator " + user.Key + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,9 +193,7 @@ namespace OF_DL.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
||||
|
||||
connection.Open();
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
||||
|
||||
using (SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection))
|
||||
{
|
||||
@ -243,12 +240,92 @@ namespace OF_DL.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, long>> GetUsers()
|
||||
{
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
||||
using SqliteCommand cmd = new("SELECT user_id, username FROM users", connection);
|
||||
using SqliteDataReader reader = cmd.ExecuteReader();
|
||||
|
||||
Dictionary<string, long> result = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
long userId = reader.GetInt64(0);
|
||||
string username = reader.GetString(1);
|
||||
|
||||
result[username] = userId;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task UpdateUserInfo(string username, User? user)
|
||||
{
|
||||
if (user?.id is null)
|
||||
return;
|
||||
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
||||
Log.Debug("Database data source: " + connection.DataSource);
|
||||
|
||||
await UpdateAsync();
|
||||
await UpdateBlobAsync();
|
||||
|
||||
async Task UpdateAsync()
|
||||
{
|
||||
using SqliteCommand cmdInfo = new(
|
||||
"INSERT OR REPLACE INTO user_info (user_id, name, about, expires_on, photo_count, video_count) " +
|
||||
"VALUES (@userId, @name, @about, @expiresOn, @photoCount, @videoCount);",
|
||||
connection
|
||||
);
|
||||
|
||||
cmdInfo.Parameters.AddWithValue("@userId", user.id);
|
||||
cmdInfo.Parameters.AddWithValue("@name", user.name ?? user.username ?? username);
|
||||
cmdInfo.Parameters.AddWithValue("@about", user.about);
|
||||
cmdInfo.Parameters.AddWithValue("@expiresOn", user.subscribedByExpireDate);
|
||||
cmdInfo.Parameters.AddWithValue("@photoCount", user.photosCount ?? 0);
|
||||
cmdInfo.Parameters.AddWithValue("@videoCount", user.videosCount ?? 0);
|
||||
|
||||
try
|
||||
{
|
||||
await cmdInfo.ExecuteNonQueryAsync();
|
||||
Log.Debug("Inserted or updated creator info: {Username:l}", user.username);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to update User Info for: {Username:l}", user.username);
|
||||
}
|
||||
}
|
||||
|
||||
async Task UpdateBlobAsync()
|
||||
{
|
||||
using SqliteCommand cmdInfo = new(
|
||||
"INSERT OR REPLACE INTO user_info_blob (user_id, name, blob) " +
|
||||
"VALUES (@userId, @name, @blob);",
|
||||
connection
|
||||
);
|
||||
|
||||
cmdInfo.Parameters.AddWithValue("@userId", user.id);
|
||||
cmdInfo.Parameters.AddWithValue("@name", user.name ?? user.username ?? username);
|
||||
cmdInfo.Parameters.AddWithValue("@blob", Newtonsoft.Json.JsonConvert.SerializeObject(user));
|
||||
|
||||
try
|
||||
{
|
||||
await cmdInfo.ExecuteNonQueryAsync();
|
||||
Log.Debug("Inserted or updated creator blob: {Username:l}", user.username);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to update User Info Blob for: {Username:l}", user.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, long user_id)
|
||||
{
|
||||
try
|
||||
{
|
||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||
connection.Open();
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||
|
||||
await EnsureCreatedAtColumnExists(connection, "messages");
|
||||
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection);
|
||||
cmd.Parameters.AddWithValue("@post_id", post_id);
|
||||
@ -286,8 +363,8 @@ namespace OF_DL.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||
connection.Open();
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||
|
||||
await EnsureCreatedAtColumnExists(connection, "posts");
|
||||
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection);
|
||||
cmd.Parameters.AddWithValue("@post_id", post_id);
|
||||
@ -324,8 +401,8 @@ namespace OF_DL.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||
connection.Open();
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||
|
||||
await EnsureCreatedAtColumnExists(connection, "stories");
|
||||
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM stories WHERE post_id=@post_id", connection);
|
||||
cmd.Parameters.AddWithValue("@post_id", post_id);
|
||||
@ -362,8 +439,8 @@ namespace OF_DL.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||
connection.Open();
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||
|
||||
await EnsureCreatedAtColumnExists(connection, "medias");
|
||||
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
|
||||
if (downloadConfig.DownloadDuplicatedMedia)
|
||||
@ -400,22 +477,21 @@ namespace OF_DL.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
bool downloaded = false;
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||
|
||||
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
||||
StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id");
|
||||
if (downloadConfig.DownloadDuplicatedMedia)
|
||||
{
|
||||
StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id");
|
||||
if(downloadConfig.DownloadDuplicatedMedia)
|
||||
{
|
||||
sql.Append(" and api_type=@api_type");
|
||||
}
|
||||
|
||||
connection.Open();
|
||||
using SqliteCommand cmd = new (sql.ToString(), connection);
|
||||
cmd.Parameters.AddWithValue("@media_id", media_id);
|
||||
cmd.Parameters.AddWithValue("@api_type", api_type);
|
||||
downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync());
|
||||
sql.Append(" and api_type=@api_type");
|
||||
}
|
||||
|
||||
connection.Open();
|
||||
using SqliteCommand cmd = new(sql.ToString(), connection);
|
||||
cmd.Parameters.AddWithValue("@media_id", media_id);
|
||||
cmd.Parameters.AddWithValue("@api_type", api_type);
|
||||
|
||||
bool downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync());
|
||||
|
||||
return downloaded;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -435,8 +511,7 @@ namespace OF_DL.Helpers
|
||||
|
||||
public async Task UpdateMedia(string folder, long media_id, string api_type, string directory, string filename, long size, bool downloaded, DateTime created_at)
|
||||
{
|
||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||
connection.Open();
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||
|
||||
// Construct the update command
|
||||
StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id");
|
||||
@ -463,25 +538,21 @@ namespace OF_DL.Helpers
|
||||
|
||||
public async Task<long> GetStoredFileSize(string folder, long media_id, string api_type)
|
||||
{
|
||||
long size;
|
||||
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
||||
{
|
||||
connection.Open();
|
||||
using SqliteCommand cmd = new($"SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", connection);
|
||||
cmd.Parameters.AddWithValue("@media_id", media_id);
|
||||
cmd.Parameters.AddWithValue("@api_type", api_type);
|
||||
size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
|
||||
}
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||
|
||||
using SqliteCommand cmd = new($"SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", connection);
|
||||
cmd.Parameters.AddWithValue("@media_id", media_id);
|
||||
cmd.Parameters.AddWithValue("@api_type", api_type);
|
||||
|
||||
long size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
|
||||
return size;
|
||||
}
|
||||
|
||||
public async Task<DateTime?> GetMostRecentPostDate(string folder)
|
||||
{
|
||||
DateTime? mostRecentDate = null;
|
||||
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
||||
{
|
||||
connection.Open();
|
||||
using SqliteCommand cmd = new(@"
|
||||
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||
|
||||
using SqliteCommand cmd = new(@"
|
||||
SELECT
|
||||
MIN(created_at) AS created_at
|
||||
FROM (
|
||||
@ -497,13 +568,14 @@ namespace OF_DL.Helpers
|
||||
ON P.post_id = m.post_id
|
||||
WHERE m.downloaded = 0
|
||||
)", connection);
|
||||
var scalarValue = await cmd.ExecuteScalarAsync();
|
||||
if(scalarValue != null && scalarValue != DBNull.Value)
|
||||
{
|
||||
mostRecentDate = Convert.ToDateTime(scalarValue);
|
||||
}
|
||||
|
||||
var scalarValue = await cmd.ExecuteScalarAsync();
|
||||
if (scalarValue != null && scalarValue != DBNull.Value)
|
||||
{
|
||||
return Convert.ToDateTime(scalarValue);
|
||||
}
|
||||
return mostRecentDate;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName)
|
||||
@ -527,5 +599,35 @@ namespace OF_DL.Helpers
|
||||
await alterCmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public static void CloseAllConnections()
|
||||
{
|
||||
foreach (SqliteConnection cn in _connections.Values)
|
||||
{
|
||||
cn?.Close();
|
||||
cn?.Dispose();
|
||||
}
|
||||
|
||||
_connections.Clear();
|
||||
}
|
||||
|
||||
private static async Task<SqliteConnection> GetAndOpenConnectionAsync(string connectionString, int numberOfRetries = 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
SqliteConnection connection = new(connectionString);
|
||||
connection.Open();
|
||||
|
||||
return connection;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (--numberOfRetries <= 0)
|
||||
throw;
|
||||
|
||||
await Task.Delay(300);
|
||||
return await GetAndOpenConnectionAsync(connectionString, numberOfRetries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -921,7 +921,7 @@ public class DownloadHelper : IDownloadHelper
|
||||
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 (!avatarMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
|
||||
{
|
||||
@ -964,7 +964,7 @@ public class DownloadHelper : IDownloadHelper
|
||||
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 (!headerMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
|
||||
{
|
||||
|
||||
@ -18,17 +18,18 @@ namespace OF_DL.Helpers
|
||||
Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp);
|
||||
Task<Dictionary<string, long>> GetLists(string endpoint, IDownloadConfig config);
|
||||
Task<List<string>> GetListUsers(string endpoint, IDownloadConfig config);
|
||||
Task<Dictionary<string, long>?> GetUsersFromList(string endpoint, bool includeRestricted, IDownloadConfig config);
|
||||
Task<Dictionary<long, string>> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, IDownloadConfig config, List<long> paid_post_ids);
|
||||
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
|
||||
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, long userId, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
|
||||
Task<PostCollection> GetPosts(string endpoint, string folder, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
|
||||
Task<SinglePostCollection> GetPost(string endpoint, string folder, IDownloadConfig config);
|
||||
Task<StreamsCollection> GetStreams(string endpoint, string folder, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
|
||||
Task<ArchivedCollection> GetArchived(string endpoint, string folder, IDownloadConfig config, StatusContext ctx);
|
||||
Task<MessageCollection> GetMessages(string endpoint, string folder, IDownloadConfig config, StatusContext ctx);
|
||||
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, IDownloadConfig config, StatusContext ctx);
|
||||
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, long userId, IDownloadConfig config, StatusContext ctx);
|
||||
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary<string, long> users);
|
||||
Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary<string, long> users);
|
||||
Task<User> GetUserInfo(string endpoint);
|
||||
Task<User> GetUserInfo(string username, string endpoint);
|
||||
Task<JObject> GetUserInfoById(string endpoint);
|
||||
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);
|
||||
Task<Dictionary<string, long>> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
using OF_DL.Entities;
|
||||
|
||||
namespace OF_DL.Helpers
|
||||
{
|
||||
public interface IDBHelper
|
||||
@ -13,5 +15,6 @@ namespace OF_DL.Helpers
|
||||
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);
|
||||
Task UpdateUserInfo(string username, User? user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,15 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationIcon>Icon\download.ico</ApplicationIcon>
|
||||
<LangVersion>12</LangVersion>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>CS0168;CS0219;CS0472;CS1998;CS8073;CS8600;CS8602;CS8603;CS8604;CS8605;CS8613;CS8618;CS8622;CS8625;CS8629;SYSLIB0021;AsyncFixer01;AsyncFixer02</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Icon\download.ico" />
|
||||
</ItemGroup>
|
||||
@ -24,6 +31,7 @@
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0"/>
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageReference Include="xFFmpeg.NET" Version="7.2.0" />
|
||||
</ItemGroup>
|
||||
@ -37,12 +45,15 @@
|
||||
<ItemGroup>
|
||||
<None Update="auth.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
<None Update="config.conf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
<None Update="rules.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
663
OF DL/Program.cs
663
OF DL/Program.cs
File diff suppressed because it is too large
Load Diff
33
Publish_OF-DL.bat
Normal file
33
Publish_OF-DL.bat
Normal file
@ -0,0 +1,33 @@
|
||||
@ECHO OFF
|
||||
|
||||
ECHO.
|
||||
|
||||
ECHO ==============================
|
||||
ECHO == Cleaning Output ===========
|
||||
ECHO ==============================
|
||||
dotnet clean ".\OF DL\OF DL.csproj" -v minimal
|
||||
DEL /Q /F ".\Publish"
|
||||
|
||||
ECHO.
|
||||
|
||||
ECHO ==============================
|
||||
ECHO == Publishing OF-DL ==========
|
||||
ECHO ==============================
|
||||
dotnet publish ".\OF DL\OF DL.csproj" -o ".\Publish" -c Debug
|
||||
|
||||
ECHO.
|
||||
|
||||
ECHO ==============================
|
||||
ECHO == Copy to network drive? ====
|
||||
ECHO ==============================
|
||||
CHOICE /C yn /m "Copy published files to network drive? "
|
||||
|
||||
IF %ERRORLEVEL%==1 (GOTO Copy) ELSE (GOTO Exit)
|
||||
|
||||
:Copy
|
||||
xcopy .\Publish\* p:\_Utils\OF_DL /I /Y /Q /EXCLUDE:.\excludes.txt
|
||||
|
||||
:Exit
|
||||
ECHO.
|
||||
ECHO.
|
||||
PAUSE
|
||||
2
excludes.txt
Normal file
2
excludes.txt
Normal file
@ -0,0 +1,2 @@
|
||||
excludes.txt
|
||||
rules.json
|
||||
Loading…
x
Reference in New Issue
Block a user