Compare commits
29 Commits
1564e298bd
...
8ce53b5be9
Author | SHA1 | Date | |
---|---|---|---|
8ce53b5be9 | |||
637bac5f61 | |||
8fd17feba5 | |||
3cc3c49b0f | |||
3ac8360a09 | |||
fed6fc77a8 | |||
e723680a96 | |||
233de7c9e5 | |||
a893653be1 | |||
2e8789897e | |||
fd2c6ad6cd | |||
5488d9f80c | |||
3a944c112d | |||
5e433f6568 | |||
cd60d3092d | |||
b36ecd4f5b | |||
f5ca6d8eb2 | |||
69be3607a0 | |||
0a34f81510 | |||
![]() |
8106f690e0 | ||
![]() |
442cc646d6 | ||
![]() |
e77f8abff6 | ||
ec751480e1 | |||
8578c40c20 | |||
b12ef22406 | |||
6a42dbe53e | |||
44890f51ee | |||
7f2849e5fd | |||
![]() |
21d0e37bda |
@ -5,5 +5,12 @@ root = true
|
|||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
end_of_line = crlf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{sln,csproj,xml,json,config}]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
tab_width = 2
|
||||||
|
36
.gitea/workflows/publish-docs.yml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: Publish docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'OFDLV*'
|
||||||
|
paths:
|
||||||
|
- 'docs/**'
|
||||||
|
- '.gitea/workflows/publish-docs.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Install MkDocs
|
||||||
|
run: |
|
||||||
|
pip install mkdocs-material
|
||||||
|
|
||||||
|
- name: Build site
|
||||||
|
run: |
|
||||||
|
mkdocs build --clean
|
||||||
|
|
||||||
|
- name: Deploy site
|
||||||
|
run: |
|
||||||
|
sudo rm -rf /var/www/mkdocs/*
|
||||||
|
sudo cp -r site/* /var/www/mkdocs/
|
||||||
|
sudo chown -R www-data:www-data /var/www/mkdocs/
|
7
.gitignore
vendored
@ -367,4 +367,9 @@ FodyWeavers.xsd
|
|||||||
/OF DL/device_private_key
|
/OF DL/device_private_key
|
||||||
|
|
||||||
# Allow node_modules inside custom actions
|
# Allow node_modules inside custom actions
|
||||||
!.gitea-actions/**/node_modules/
|
!.gitea-actions/**/node_modules/
|
||||||
|
|
||||||
|
# venv
|
||||||
|
venv/
|
||||||
|
|
||||||
|
Publish/
|
||||||
|
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
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -103,6 +103,11 @@ namespace OF_DL.Entities
|
|||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public VideoResolution DownloadVideoResolution { get; set; } = VideoResolution.source;
|
public VideoResolution DownloadVideoResolution { get; set; } = VideoResolution.source;
|
||||||
|
|
||||||
|
public string[] NonInteractiveSpecificUsers { get; set; } = [];
|
||||||
|
public string[] NonInteractiveSpecificLists { get; set; } = [];
|
||||||
|
|
||||||
|
public bool OutputBlockedUsers { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreatorConfig : IFileNameFormatConfig
|
public class CreatorConfig : IFileNameFormatConfig
|
||||||
|
9
OF DL/Entities/LatestReleaseAPIResponse.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class LatestReleaseAPIResponse
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "tag_name")]
|
||||||
|
public string TagName { get; set; } = "";
|
||||||
|
}
|
@ -98,6 +98,7 @@ namespace OF_DL.Entities
|
|||||||
public object id { get; set; }
|
public object id { get; set; }
|
||||||
public int? userId { get; set; }
|
public int? userId { get; set; }
|
||||||
public int? subscriberId { get; set; }
|
public int? subscriberId { get; set; }
|
||||||
|
public long? earningId { get; set; }
|
||||||
public DateTime? date { get; set; }
|
public DateTime? date { get; set; }
|
||||||
public int? duration { get; set; }
|
public int? duration { get; set; }
|
||||||
public DateTime? startDate { get; set; }
|
public DateTime? startDate { get; set; }
|
||||||
|
@ -2,6 +2,7 @@ using Newtonsoft.Json;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OF_DL.Entities;
|
using OF_DL.Entities;
|
||||||
using OF_DL.Entities.Archived;
|
using OF_DL.Entities.Archived;
|
||||||
|
using OF_DL.Entities.Chats;
|
||||||
using OF_DL.Entities.Highlights;
|
using OF_DL.Entities.Highlights;
|
||||||
using OF_DL.Entities.Lists;
|
using OF_DL.Entities.Lists;
|
||||||
using OF_DL.Entities.Messages;
|
using OF_DL.Entities.Messages;
|
||||||
@ -29,6 +30,8 @@ public class APIHelper : IAPIHelper
|
|||||||
private readonly IDBHelper m_DBHelper;
|
private readonly IDBHelper m_DBHelper;
|
||||||
private readonly IDownloadConfig downloadConfig;
|
private readonly IDownloadConfig downloadConfig;
|
||||||
private readonly Auth auth;
|
private readonly Auth auth;
|
||||||
|
private HttpClient httpClient = new();
|
||||||
|
|
||||||
private static DateTime? cachedDynamicRulesExpiration;
|
private static DateTime? cachedDynamicRulesExpiration;
|
||||||
private static DynamicRules? cachedDynamicRules;
|
private static DynamicRules? cachedDynamicRules;
|
||||||
|
|
||||||
@ -116,11 +119,11 @@ 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)
|
||||||
{
|
{
|
||||||
Log.Debug("Calling BuildHeaderAndExecuteRequests");
|
Log.Debug("Calling BuildHeaderAndExecuteRequests");
|
||||||
|
|
||||||
HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint);
|
HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint, method);
|
||||||
using var response = await client.SendAsync(request);
|
using var response = await client.SendAsync(request);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
string body = await response.Content.ReadAsStringAsync();
|
string body = await response.Content.ReadAsStringAsync();
|
||||||
@ -131,15 +134,20 @@ public class APIHelper : IAPIHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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");
|
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);
|
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}");
|
Log.Debug($"Full request URL: {Constants.API_URL}{endpoint}{queryParams}");
|
||||||
|
|
||||||
@ -164,18 +172,16 @@ public class APIHelper : IAPIHelper
|
|||||||
return input.All(char.IsDigit);
|
return input.All(char.IsDigit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpClient GetHttpClient(IDownloadConfig? config = null)
|
||||||
private static HttpClient GetHttpClient(IDownloadConfig? config = null)
|
|
||||||
{
|
{
|
||||||
var client = new HttpClient();
|
httpClient ??= new HttpClient();
|
||||||
if (config?.Timeout != null && config.Timeout > 0)
|
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>
|
/// <summary>
|
||||||
/// this one is used during initialization only
|
/// this one is used during initialization only
|
||||||
/// if the config option is not available then no modificatiotns will be done on the getParams
|
/// if the config option is not available then no modificatiotns will be done on the getParams
|
||||||
@ -302,7 +308,7 @@ public class APIHelper : IAPIHelper
|
|||||||
|
|
||||||
Log.Debug("Calling GetAllSubscrptions");
|
Log.Debug("Calling GetAllSubscrptions");
|
||||||
|
|
||||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, httpClient);
|
||||||
|
|
||||||
subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body);
|
subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body);
|
||||||
if (subscriptions != null && subscriptions.hasMore)
|
if (subscriptions != null && subscriptions.hasMore)
|
||||||
@ -312,7 +318,7 @@ public class APIHelper : IAPIHelper
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Subscriptions newSubscriptions = new();
|
Subscriptions newSubscriptions = new();
|
||||||
string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, httpClient);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(loopbody) && (!loopbody.Contains("[]") || loopbody.Trim() != "[]"))
|
if (!string.IsNullOrEmpty(loopbody) && (!loopbody.Contains("[]") || loopbody.Trim() != "[]"))
|
||||||
{
|
{
|
||||||
@ -334,11 +340,14 @@ public class APIHelper : IAPIHelper
|
|||||||
|
|
||||||
foreach (Subscriptions.List subscription in subscriptions.list)
|
foreach (Subscriptions.List subscription in subscriptions.list)
|
||||||
{
|
{
|
||||||
if ((!(subscription.isRestricted ?? false) || ((subscription.isRestricted ?? false) && includeRestricted))
|
if (users.ContainsKey(subscription.username))
|
||||||
&& !users.ContainsKey(subscription.username))
|
continue;
|
||||||
{
|
|
||||||
|
bool isRestricted = subscription.isRestricted ?? false;
|
||||||
|
bool isRestrictedButAllowed = isRestricted && includeRestricted;
|
||||||
|
|
||||||
|
if (!isRestricted || isRestrictedButAllowed)
|
||||||
users.Add(subscription.username, subscription.id);
|
users.Add(subscription.username, subscription.id);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return users;
|
return users;
|
||||||
@ -373,7 +382,6 @@ public class APIHelper : IAPIHelper
|
|||||||
|
|
||||||
public async Task<Dictionary<string, int>?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config)
|
public async Task<Dictionary<string, int>?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config)
|
||||||
{
|
{
|
||||||
|
|
||||||
Dictionary<string, string> getParams = new()
|
Dictionary<string, string> getParams = new()
|
||||||
{
|
{
|
||||||
{ "offset", "0" },
|
{ "offset", "0" },
|
||||||
@ -387,6 +395,20 @@ public class APIHelper : IAPIHelper
|
|||||||
return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config);
|
return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Dictionary<string, int>?> GetBlockedUsers(string endpoint, IDownloadConfig config)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> getParams = new()
|
||||||
|
{
|
||||||
|
{ "offset", "0" },
|
||||||
|
{ "limit", "50" },
|
||||||
|
{ "type", "expired" },
|
||||||
|
{ "format", "infinite"}
|
||||||
|
};
|
||||||
|
|
||||||
|
Log.Debug("Calling GetBlockedUsers");
|
||||||
|
|
||||||
|
return await GetAllSubscriptions(getParams, endpoint, true, config);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Dictionary<string, int>> GetLists(string endpoint, IDownloadConfig config)
|
public async Task<Dictionary<string, int>> GetLists(string endpoint, IDownloadConfig config)
|
||||||
{
|
{
|
||||||
@ -405,7 +427,7 @@ public class APIHelper : IAPIHelper
|
|||||||
Dictionary<string, int> lists = new();
|
Dictionary<string, int> lists = new();
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||||
|
|
||||||
if (body == null)
|
if (body == null)
|
||||||
{
|
{
|
||||||
@ -470,7 +492,7 @@ public class APIHelper : IAPIHelper
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||||
if (body == null)
|
if (body == null)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@ -553,7 +575,7 @@ public class APIHelper : IAPIHelper
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||||
|
|
||||||
|
|
||||||
if (mediatype == MediaType.Stories)
|
if (mediatype == MediaType.Stories)
|
||||||
@ -932,7 +954,7 @@ public class APIHelper : IAPIHelper
|
|||||||
ref getParams,
|
ref getParams,
|
||||||
downloadAsOf);
|
downloadAsOf);
|
||||||
|
|
||||||
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
|
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
|
||||||
posts = JsonConvert.DeserializeObject<Post>(body, m_JsonSerializerSettings);
|
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.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);
|
ctx.Spinner(Spinner.Known.Dots);
|
||||||
@ -1090,7 +1112,7 @@ public class APIHelper : IAPIHelper
|
|||||||
{ "skip_users", "all" }
|
{ "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);
|
singlePost = JsonConvert.DeserializeObject<SinglePost>(body, m_JsonSerializerSettings);
|
||||||
|
|
||||||
if (singlePost != null)
|
if (singlePost != null)
|
||||||
@ -1251,7 +1273,7 @@ public class APIHelper : IAPIHelper
|
|||||||
ref getParams,
|
ref getParams,
|
||||||
config.CustomDate);
|
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);
|
streams = JsonConvert.DeserializeObject<Streams>(body, m_JsonSerializerSettings);
|
||||||
ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]");
|
ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]");
|
||||||
ctx.Spinner(Spinner.Known.Dots);
|
ctx.Spinner(Spinner.Known.Dots);
|
||||||
@ -2134,11 +2156,11 @@ public class APIHelper : IAPIHelper
|
|||||||
{
|
{
|
||||||
JObject user = await GetUserInfoById($"/users/list?x[]={purchase.fromUser.id}");
|
JObject user = await GetUserInfoById($"/users/list?x[]={purchase.fromUser.id}");
|
||||||
|
|
||||||
if(user is null)
|
if (user is null)
|
||||||
{
|
{
|
||||||
if (!config.BypassContentForCreatorsWhoNoLongerExist)
|
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);
|
purchasedTabUsers.Add($"Deleted User - {purchase.fromUser.id}", purchase.fromUser.id);
|
||||||
}
|
}
|
||||||
@ -2188,7 +2210,7 @@ public class APIHelper : IAPIHelper
|
|||||||
{
|
{
|
||||||
if (!config.BypassContentForCreatorsWhoNoLongerExist)
|
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);
|
purchasedTabUsers.Add($"Deleted User - {purchase.author.id}", purchase.author.id);
|
||||||
}
|
}
|
||||||
@ -2585,6 +2607,94 @@ public class APIHelper : IAPIHelper
|
|||||||
return null;
|
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.Add(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)
|
public async Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp)
|
||||||
{
|
{
|
||||||
@ -2703,7 +2813,7 @@ public class APIHelper : IAPIHelper
|
|||||||
|
|
||||||
using var response = await client.SendAsync(request);
|
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();
|
response.EnsureSuccessStatusCode();
|
||||||
var body = await response.Content.ReadAsStringAsync();
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
@ -2844,7 +2954,7 @@ public class APIHelper : IAPIHelper
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpClient client = new HttpClient();
|
HttpClient client = new HttpClient();
|
||||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://raw.githubusercontent.com/deviint/onlyfans-dynamic-rules/main/dynamicRules.json");
|
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://git.ofdl.tools/sim0n00ps/dynamic-rules/raw/branch/main/rules.json");
|
||||||
using var response = client.Send(request);
|
using var response = client.Send(request);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
|
@ -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 Microsoft.Data.Sqlite;
|
||||||
using Serilog;
|
|
||||||
using OF_DL.Entities;
|
using OF_DL.Entities;
|
||||||
|
using Serilog;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace OF_DL.Helpers
|
namespace OF_DL.Helpers
|
||||||
{
|
{
|
||||||
public class DBHelper : IDBHelper
|
public class DBHelper : IDBHelper
|
||||||
{
|
{
|
||||||
|
private static readonly Dictionary<string, SqliteConnection> _connections = [];
|
||||||
|
|
||||||
private readonly IDownloadConfig downloadConfig;
|
private readonly IDownloadConfig downloadConfig;
|
||||||
|
|
||||||
public DBHelper(IDownloadConfig downloadConfig)
|
public DBHelper(IDownloadConfig downloadConfig)
|
||||||
@ -32,9 +28,7 @@ namespace OF_DL.Helpers
|
|||||||
string dbFilePath = $"{folder}/Metadata/user_data.db";
|
string dbFilePath = $"{folder}/Metadata/user_data.db";
|
||||||
|
|
||||||
// connect to the new database file
|
// connect to the new database file
|
||||||
using SqliteConnection connection = new($"Data Source={dbFilePath}");
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={dbFilePath}");
|
||||||
// open the connection
|
|
||||||
connection.Open();
|
|
||||||
|
|
||||||
// create the 'medias' table
|
// 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))
|
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,11 +133,9 @@ namespace OF_DL.Helpers
|
|||||||
{
|
{
|
||||||
try
|
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);
|
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 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))
|
||||||
{
|
{
|
||||||
await cmd.ExecuteNonQueryAsync();
|
await cmd.ExecuteNonQueryAsync();
|
||||||
@ -194,9 +186,7 @@ namespace OF_DL.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
||||||
|
|
||||||
connection.Open();
|
|
||||||
|
|
||||||
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))
|
||||||
{
|
{
|
||||||
@ -247,8 +237,8 @@ namespace OF_DL.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||||
connection.Open();
|
|
||||||
await EnsureCreatedAtColumnExists(connection, "messages");
|
await EnsureCreatedAtColumnExists(connection, "messages");
|
||||||
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection);
|
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection);
|
||||||
cmd.Parameters.AddWithValue("@post_id", post_id);
|
cmd.Parameters.AddWithValue("@post_id", post_id);
|
||||||
@ -286,8 +276,8 @@ namespace OF_DL.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||||
connection.Open();
|
|
||||||
await EnsureCreatedAtColumnExists(connection, "posts");
|
await EnsureCreatedAtColumnExists(connection, "posts");
|
||||||
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection);
|
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection);
|
||||||
cmd.Parameters.AddWithValue("@post_id", post_id);
|
cmd.Parameters.AddWithValue("@post_id", post_id);
|
||||||
@ -324,8 +314,8 @@ namespace OF_DL.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||||
connection.Open();
|
|
||||||
await EnsureCreatedAtColumnExists(connection, "stories");
|
await EnsureCreatedAtColumnExists(connection, "stories");
|
||||||
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM stories WHERE post_id=@post_id", connection);
|
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM stories WHERE post_id=@post_id", connection);
|
||||||
cmd.Parameters.AddWithValue("@post_id", post_id);
|
cmd.Parameters.AddWithValue("@post_id", post_id);
|
||||||
@ -362,8 +352,8 @@ namespace OF_DL.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||||
connection.Open();
|
|
||||||
await EnsureCreatedAtColumnExists(connection, "medias");
|
await EnsureCreatedAtColumnExists(connection, "medias");
|
||||||
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
|
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
|
||||||
if (downloadConfig.DownloadDuplicatedMedia)
|
if (downloadConfig.DownloadDuplicatedMedia)
|
||||||
@ -400,22 +390,21 @@ namespace OF_DL.Helpers
|
|||||||
{
|
{
|
||||||
try
|
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");
|
sql.Append(" and api_type=@api_type");
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return downloaded;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -435,8 +424,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)
|
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");
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||||
connection.Open();
|
|
||||||
|
|
||||||
// Construct the update command
|
// Construct the update command
|
||||||
StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id");
|
StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id");
|
||||||
@ -463,25 +451,21 @@ namespace OF_DL.Helpers
|
|||||||
|
|
||||||
public async Task<long> GetStoredFileSize(string folder, long media_id, string api_type)
|
public async Task<long> GetStoredFileSize(string folder, long media_id, string api_type)
|
||||||
{
|
{
|
||||||
long size;
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||||
using (SqliteConnection connection = new($"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);
|
||||||
connection.Open();
|
cmd.Parameters.AddWithValue("@media_id", media_id);
|
||||||
using SqliteCommand cmd = new($"SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", connection);
|
cmd.Parameters.AddWithValue("@api_type", api_type);
|
||||||
cmd.Parameters.AddWithValue("@media_id", media_id);
|
|
||||||
cmd.Parameters.AddWithValue("@api_type", api_type);
|
long size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
|
||||||
size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
|
|
||||||
}
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DateTime?> GetMostRecentPostDate(string folder)
|
public async Task<DateTime?> GetMostRecentPostDate(string folder)
|
||||||
{
|
{
|
||||||
DateTime? mostRecentDate = null;
|
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
|
||||||
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
|
||||||
{
|
using SqliteCommand cmd = new(@"
|
||||||
connection.Open();
|
|
||||||
using SqliteCommand cmd = new(@"
|
|
||||||
SELECT
|
SELECT
|
||||||
MIN(created_at) AS created_at
|
MIN(created_at) AS created_at
|
||||||
FROM (
|
FROM (
|
||||||
@ -497,13 +481,14 @@ namespace OF_DL.Helpers
|
|||||||
ON P.post_id = m.post_id
|
ON P.post_id = m.post_id
|
||||||
WHERE m.downloaded = 0
|
WHERE m.downloaded = 0
|
||||||
)", connection);
|
)", connection);
|
||||||
var scalarValue = await cmd.ExecuteScalarAsync();
|
|
||||||
if(scalarValue != null && scalarValue != DBNull.Value)
|
var scalarValue = await cmd.ExecuteScalarAsync();
|
||||||
{
|
if (scalarValue != null && scalarValue != DBNull.Value)
|
||||||
mostRecentDate = Convert.ToDateTime(scalarValue);
|
{
|
||||||
}
|
return Convert.ToDateTime(scalarValue);
|
||||||
}
|
}
|
||||||
return mostRecentDate;
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName)
|
private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName)
|
||||||
@ -527,5 +512,35 @@ namespace OF_DL.Helpers
|
|||||||
await alterCmd.ExecuteNonQueryAsync();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -604,28 +604,31 @@ public class DownloadHelper : IDownloadHelper
|
|||||||
decKey = decryptionKey.Substring(pos1 + 1);
|
decKey = decryptionKey.Substring(pos1 + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int? streamIndex = await GetVideoStreamIndexFromMpd(url, policy, signature, kvp, downloadConfig.DownloadVideoResolution);
|
int streamIndex = 0;
|
||||||
|
string tempFilename = $"{folder}{path}/{filename}_source.mp4";
|
||||||
|
|
||||||
if (streamIndex == null)
|
//int? streamIndex = await GetVideoStreamIndexFromMpd(url, policy, signature, kvp, downloadConfig.DownloadVideoResolution);
|
||||||
throw new Exception($"Could not find video stream for resolution {downloadConfig.DownloadVideoResolution}");
|
|
||||||
|
|
||||||
string tempFilename;
|
//if (streamIndex == null)
|
||||||
|
// throw new Exception($"Could not find video stream for resolution {downloadConfig.DownloadVideoResolution}");
|
||||||
|
|
||||||
switch (downloadConfig.DownloadVideoResolution)
|
//string tempFilename;
|
||||||
{
|
|
||||||
case VideoResolution.source:
|
//switch (downloadConfig.DownloadVideoResolution)
|
||||||
tempFilename = $"{folder}{path}/{filename}_source.mp4";
|
//{
|
||||||
break;
|
// case VideoResolution.source:
|
||||||
case VideoResolution._240:
|
// tempFilename = $"{folder}{path}/{filename}_source.mp4";
|
||||||
tempFilename = $"{folder}{path}/{filename}_240.mp4";
|
// break;
|
||||||
break;
|
// case VideoResolution._240:
|
||||||
case VideoResolution._720:
|
// tempFilename = $"{folder}{path}/{filename}_240.mp4";
|
||||||
tempFilename = $"{folder}{path}/{filename}_720.mp4";
|
// break;
|
||||||
break;
|
// case VideoResolution._720:
|
||||||
default:
|
// tempFilename = $"{folder}{path}/{filename}_720.mp4";
|
||||||
tempFilename = $"{folder}{path}/{filename}_source.mp4";
|
// break;
|
||||||
break;
|
// default:
|
||||||
}
|
// tempFilename = $"{folder}{path}/{filename}_source.mp4";
|
||||||
|
// break;
|
||||||
|
//}
|
||||||
|
|
||||||
string parameters = $"-cenc_decryption_key {decKey} -headers \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess} Origin: https://onlyfans.com Referer: https://onlyfans.com User-Agent: {user_agent}\" -y -i \"{url}\" -map 0:v:{streamIndex} -map 0:a? -codec copy \"{tempFilename}\"";
|
string parameters = $"-cenc_decryption_key {decKey} -headers \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess} Origin: https://onlyfans.com Referer: https://onlyfans.com User-Agent: {user_agent}\" -y -i \"{url}\" -map 0:v:{streamIndex} -map 0:a? -codec copy \"{tempFilename}\"";
|
||||||
|
|
||||||
@ -852,7 +855,7 @@ public class DownloadHelper : IDownloadHelper
|
|||||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
MD5 md5 = MD5.Create();
|
MD5 md5 = MD5.Create();
|
||||||
byte[] hash = md5.ComputeHash(memoryStream);
|
byte[] hash = await md5.ComputeHashAsync(memoryStream);
|
||||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
if (!avatarMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
|
if (!avatarMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
|
||||||
{
|
{
|
||||||
@ -895,7 +898,7 @@ public class DownloadHelper : IDownloadHelper
|
|||||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
MD5 md5 = MD5.Create();
|
MD5 md5 = MD5.Create();
|
||||||
byte[] hash = md5.ComputeHash(memoryStream);
|
byte[] hash = await md5.ComputeHashAsync(memoryStream);
|
||||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
if (!headerMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
|
if (!headerMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
|
||||||
{
|
{
|
||||||
|
52
OF DL/Helpers/VersionHelper.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using OF_DL.Entities;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace OF_DL.Helpers;
|
||||||
|
|
||||||
|
public static class VersionHelper
|
||||||
|
{
|
||||||
|
public static string? GetLatestReleaseTag()
|
||||||
|
{
|
||||||
|
Log.Debug("Calling GetLatestReleaseTag");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpClient client = new();
|
||||||
|
HttpRequestMessage request = new(HttpMethod.Get, "https://git.ofdl.tools/api/v1/repos/sim0n00ps/OF-DL/releases/latest");
|
||||||
|
using var response = client.Send(request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Debug("GetLatestReleaseTag did not return a Success Status Code");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = response.Content.ReadAsStringAsync().Result;
|
||||||
|
|
||||||
|
Log.Debug("GetLatestReleaseTag API Response: ");
|
||||||
|
Log.Debug(body);
|
||||||
|
|
||||||
|
var versionCheckResponse = JsonConvert.DeserializeObject<LatestReleaseAPIResponse>(body);
|
||||||
|
|
||||||
|
if (versionCheckResponse == null || versionCheckResponse.TagName == "")
|
||||||
|
{
|
||||||
|
Log.Debug("GetLatestReleaseTag did not return a valid tag name");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return versionCheckResponse.TagName;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -7,8 +7,15 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ApplicationIcon>Icon\download.ico</ApplicationIcon>
|
<ApplicationIcon>Icon\download.ico</ApplicationIcon>
|
||||||
|
<LangVersion>12</LangVersion>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<NoWarn>CS0168;CS0219;CS0472;CS1998;CS8073;CS8600;CS8602;CS8603;CS8604;CS8605;CS8613;CS8618;CS8622;CS8625;CS8629;SYSLIB0021;AsyncFixer01;AsyncFixer02</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Icon\download.ico" />
|
<Content Include="Icon\download.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -19,7 +26,6 @@
|
|||||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.46" />
|
<PackageReference Include="protobuf-net" Version="3.2.46" />
|
||||||
<PackageReference Include="PuppeteerSharp" Version="20.1.3" />
|
<PackageReference Include="PuppeteerSharp" Version="20.1.3" />
|
||||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||||
@ -38,12 +44,15 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="auth.json">
|
<None Update="auth.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
</None>
|
</None>
|
||||||
<None Update="config.conf">
|
<None Update="config.conf">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
</None>
|
</None>
|
||||||
<None Update="rules.json">
|
<None Update="rules.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
291
OF DL/Program.cs
@ -9,7 +9,6 @@ using OF_DL.Entities.Streams;
|
|||||||
using OF_DL.Enumerations;
|
using OF_DL.Enumerations;
|
||||||
using OF_DL.Enumurations;
|
using OF_DL.Enumurations;
|
||||||
using OF_DL.Helpers;
|
using OF_DL.Helpers;
|
||||||
using Octokit;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
@ -22,6 +21,8 @@ using static OF_DL.Entities.Messages.Messages;
|
|||||||
using Akka.Configuration;
|
using Akka.Configuration;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using static Akka.Actor.ProviderSelection;
|
using static Akka.Actor.ProviderSelection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using OF_DL.Entities.Chats;
|
||||||
|
|
||||||
namespace OF_DL;
|
namespace OF_DL;
|
||||||
|
|
||||||
@ -45,15 +46,15 @@ public class Program
|
|||||||
AuthHelper authHelper = new();
|
AuthHelper authHelper = new();
|
||||||
Task setupBrowserTask = authHelper.SetupBrowser(runningInDocker);
|
Task setupBrowserTask = authHelper.SetupBrowser(runningInDocker);
|
||||||
|
|
||||||
Task.Delay(1000).Wait();
|
await Task.Delay(1000);
|
||||||
if (!setupBrowserTask.IsCompleted)
|
if (!setupBrowserTask.IsCompleted)
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLine($"[yellow]Downloading dependencies. Please wait ...[/]");
|
AnsiConsole.MarkupLine($"[yellow]Downloading dependencies. Please wait ...[/]");
|
||||||
}
|
}
|
||||||
setupBrowserTask.Wait();
|
await setupBrowserTask;
|
||||||
|
|
||||||
Task<Auth?> getAuthTask = authHelper.GetAuthFromBrowser();
|
Task<Auth?> getAuthTask = authHelper.GetAuthFromBrowser();
|
||||||
Task.Delay(5000).Wait();
|
await Task.Delay(5000);
|
||||||
if (!getAuthTask.IsCompleted)
|
if (!getAuthTask.IsCompleted)
|
||||||
{
|
{
|
||||||
if (runningInDocker)
|
if (runningInDocker)
|
||||||
@ -118,13 +119,15 @@ public class Program
|
|||||||
|
|
||||||
AnsiConsole.Write(new FigletText("Welcome to OF-DL").Color(Color.Red));
|
AnsiConsole.Write(new FigletText("Welcome to OF-DL").Color(Color.Red));
|
||||||
|
|
||||||
|
ExitIfOtherProcess();
|
||||||
|
|
||||||
//Remove config.json and convert to config.conf
|
//Remove config.json and convert to config.conf
|
||||||
if (File.Exists("config.json"))
|
if (File.Exists("config.json"))
|
||||||
{
|
{
|
||||||
AnsiConsole.Markup("[green]config.json located successfully!\n[/]");
|
AnsiConsole.Markup("[green]config.json located successfully!\n[/]");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string jsonText = File.ReadAllText("config.json");
|
string jsonText = await File.ReadAllTextAsync("config.json");
|
||||||
var jsonConfig = JsonConvert.DeserializeObject<Entities.Config>(jsonText);
|
var jsonConfig = JsonConvert.DeserializeObject<Entities.Config>(jsonText);
|
||||||
|
|
||||||
if (jsonConfig != null)
|
if (jsonConfig != null)
|
||||||
@ -220,7 +223,7 @@ public class Program
|
|||||||
hoconConfig.AppendLine($" LoggingLevel = \"{jsonConfig.LoggingLevel.ToString().ToLower()}\"");
|
hoconConfig.AppendLine($" LoggingLevel = \"{jsonConfig.LoggingLevel.ToString().ToLower()}\"");
|
||||||
hoconConfig.AppendLine("}");
|
hoconConfig.AppendLine("}");
|
||||||
|
|
||||||
File.WriteAllText("config.conf", hoconConfig.ToString());
|
await File.WriteAllTextAsync("config.conf", hoconConfig.ToString());
|
||||||
File.Delete("config.json");
|
File.Delete("config.json");
|
||||||
AnsiConsole.Markup("[green]config.conf created successfully from config.json!\n[/]");
|
AnsiConsole.Markup("[green]config.conf created successfully from config.json!\n[/]");
|
||||||
}
|
}
|
||||||
@ -246,7 +249,7 @@ public class Program
|
|||||||
AnsiConsole.Markup("[green]config.conf located successfully!\n[/]");
|
AnsiConsole.Markup("[green]config.conf located successfully!\n[/]");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string hoconText = File.ReadAllText("config.conf");
|
string hoconText = await File.ReadAllTextAsync("config.conf");
|
||||||
|
|
||||||
var hoconConfig = ConfigurationFactory.ParseString(hoconText);
|
var hoconConfig = ConfigurationFactory.ParseString(hoconText);
|
||||||
|
|
||||||
@ -281,7 +284,7 @@ public class Program
|
|||||||
DownloadDateSelection = Enum.Parse<DownloadDateSelection>(hoconConfig.GetString("Download.DownloadDateSelection"), true),
|
DownloadDateSelection = Enum.Parse<DownloadDateSelection>(hoconConfig.GetString("Download.DownloadDateSelection"), true),
|
||||||
CustomDate = !string.IsNullOrWhiteSpace(hoconConfig.GetString("Download.CustomDate")) ? DateTime.Parse(hoconConfig.GetString("Download.CustomDate")) : null,
|
CustomDate = !string.IsNullOrWhiteSpace(hoconConfig.GetString("Download.CustomDate")) ? DateTime.Parse(hoconConfig.GetString("Download.CustomDate")) : null,
|
||||||
ShowScrapeSize = hoconConfig.GetBoolean("Download.ShowScrapeSize"),
|
ShowScrapeSize = hoconConfig.GetBoolean("Download.ShowScrapeSize"),
|
||||||
DownloadVideoResolution = ParseVideoResolution(hoconConfig.GetString("Download.DownloadVideoResolution")),
|
DownloadVideoResolution = ParseVideoResolution(hoconConfig.GetString("Download.DownloadVideoResolution", "source")),
|
||||||
|
|
||||||
// File Settings
|
// File Settings
|
||||||
PaidPostFileNameFormat = hoconConfig.GetString("File.PaidPostFileNameFormat"),
|
PaidPostFileNameFormat = hoconConfig.GetString("File.PaidPostFileNameFormat"),
|
||||||
@ -474,15 +477,52 @@ public class Program
|
|||||||
|
|
||||||
if (args is not null && args.Length > 0)
|
if (args is not null && args.Length > 0)
|
||||||
{
|
{
|
||||||
const string NON_INTERACTIVE_ARG = "--non-interactive";
|
const string NON_INTERACTIVE_ARG = "--non-interactive";
|
||||||
|
const string SPECIFIC_LISTS_ARG = "--specific-lists";
|
||||||
|
const string SPECIFIC_USERS_ARG = "--specific-users";
|
||||||
|
|
||||||
if (args.Any(a => NON_INTERACTIVE_ARG.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase)))
|
if (args.Any(a => NON_INTERACTIVE_ARG.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
cliNonInteractive = true;
|
AnsiConsole.Markup($"[grey]Non-Interactive Mode enabled through command-line argument![/]\n");
|
||||||
Log.Debug("NonInteractiveMode set via command line");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Debug("Additional arguments:");
|
config.NonInteractiveMode = true;
|
||||||
|
|
||||||
|
int indexOfSpecificListsArg = Array.FindIndex(args, a => a.Contains(SPECIFIC_LISTS_ARG, StringComparison.OrdinalIgnoreCase));
|
||||||
|
int indexOfSpecificUsersArg = Array.FindIndex(args, a => a.Contains(SPECIFIC_USERS_ARG, StringComparison.OrdinalIgnoreCase));
|
||||||
|
char[] separator = [','];
|
||||||
|
|
||||||
|
if (indexOfSpecificListsArg >= 0)
|
||||||
|
{
|
||||||
|
int indexOfListValues = indexOfSpecificListsArg + 1;
|
||||||
|
|
||||||
|
string[] strListValues = args.ElementAtOrDefault(indexOfListValues)?.Split(separator, StringSplitOptions.RemoveEmptyEntries) ?? [];
|
||||||
|
if (strListValues.Length > 0)
|
||||||
|
{
|
||||||
|
config.NonInteractiveSpecificLists = strListValues;
|
||||||
|
config.NonInteractiveModeListName = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexOfSpecificUsersArg >= 0)
|
||||||
|
{
|
||||||
|
int indexOfUserValues = indexOfSpecificUsersArg + 1;
|
||||||
|
string[] strUserValues = args.ElementAtOrDefault(indexOfUserValues)?.Split(separator, StringSplitOptions.RemoveEmptyEntries) ?? [];
|
||||||
|
if (strUserValues.Length > 0)
|
||||||
|
{
|
||||||
|
config.NonInteractiveSpecificUsers = strUserValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const string OUTPUT_BLOCKED_USERS_ARG = "--output-blocked";
|
||||||
|
|
||||||
|
if (args.Any(a => OUTPUT_BLOCKED_USERS_ARG.Equals(a, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
config.NonInteractiveMode = true;
|
||||||
|
config.OutputBlockedUsers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug("Additional arguments:");
|
||||||
foreach (string argument in args)
|
foreach (string argument in args)
|
||||||
{
|
{
|
||||||
Log.Debug(argument);
|
Log.Debug(argument);
|
||||||
@ -515,14 +555,54 @@ public class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !DEBUG
|
try
|
||||||
|
{
|
||||||
|
// Only run the version check if not in DEBUG mode
|
||||||
|
#if !DEBUG
|
||||||
Version localVersion = Assembly.GetEntryAssembly()?.GetName().Version; //Only tested with numeric values.
|
Version localVersion = Assembly.GetEntryAssembly()?.GetName().Version; //Only tested with numeric values.
|
||||||
|
String? latestReleaseTag = VersionHelper.GetLatestReleaseTag();
|
||||||
|
|
||||||
|
if (latestReleaseTag == null)
|
||||||
|
{
|
||||||
|
AnsiConsole.Markup("[yellow]Failed to verify that OF-DL is up-to-date.\n[/]");
|
||||||
|
Log.Error("Failed to get the latest release tag.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Version latestGiteaRelease = new Version(latestReleaseTag.Replace("OFDLV", ""));
|
||||||
|
|
||||||
|
// Compare the Versions
|
||||||
|
int versionComparison = localVersion.CompareTo(latestGiteaRelease);
|
||||||
|
if (versionComparison < 0)
|
||||||
|
{
|
||||||
|
// The version on GitHub is more up to date than this local release.
|
||||||
|
AnsiConsole.Markup("[red]You are running OF-DL version " + $"{localVersion.Major}.{localVersion.Minor}.{localVersion.Build}\n[/]");
|
||||||
|
AnsiConsole.Markup("[red]Please update to the current release, " + $"{latestGiteaRelease.Major}.{latestGiteaRelease.Minor}.{latestGiteaRelease.Build}: [link]https://git.ofdl.tools/sim0n00ps/OF-DL/releases[/]\n[/]");
|
||||||
|
Log.Debug("Detected outdated client running version " + $"{localVersion.Major}.{localVersion.Minor}.{localVersion.Build}");
|
||||||
|
Log.Debug("Latest release version " + $"{latestGiteaRelease.Major}.{latestGiteaRelease.Minor}.{latestGiteaRelease.Build}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This local version is greater than the release version on GitHub.
|
||||||
|
AnsiConsole.Markup("[green]You are running OF-DL version " + $"{localVersion.Major}.{localVersion.Minor}.{localVersion.Build}\n[/]");
|
||||||
|
AnsiConsole.Markup("[green]Latest Release version: " + $"{latestGiteaRelease.Major}.{latestGiteaRelease.Minor}.{latestGiteaRelease.Build}\n[/]");
|
||||||
|
Log.Debug("Detected client running version " + $"{localVersion.Major}.{localVersion.Minor}.{localVersion.Build}");
|
||||||
|
Log.Debug("Latest release version " + $"{latestGiteaRelease.Major}.{latestGiteaRelease.Minor}.{latestGiteaRelease.Build}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
AnsiConsole.Markup("[yellow]Running in Debug/Local mode. Version check skipped.\n[/]");
|
||||||
|
Log.Debug("Running in Debug/Local mode. Version check skipped.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
AnsiConsole.Markup("[red]Error checking latest release on GitHub:\n[/]");
|
||||||
|
Console.WriteLine(e);
|
||||||
|
Log.Error("Error checking latest release on GitHub.", e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
AnsiConsole.Markup("[red]You are running OF-DL version " + $"{localVersion.Major}.{localVersion.Minor}.{localVersion.Build}\n[/]");
|
|
||||||
#else
|
|
||||||
AnsiConsole.Markup("[yellow]Running in Debug/Local mode. Version check skipped.\n[/]");
|
|
||||||
Log.Debug("Running in Debug/Local mode. Version check skipped.");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (File.Exists("auth.json"))
|
if (File.Exists("auth.json"))
|
||||||
{
|
{
|
||||||
@ -541,7 +621,7 @@ public class Program
|
|||||||
Log.Debug("Deleting auth.json");
|
Log.Debug("Deleting auth.json");
|
||||||
File.Delete("auth.json");
|
File.Delete("auth.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cliNonInteractive)
|
if (cliNonInteractive)
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLine($"\n[red]auth.json has invalid JSON syntax. The file can be generated automatically when OF-DL is run in the standard, interactive mode.[/]\n");
|
AnsiConsole.MarkupLine($"\n[red]auth.json has invalid JSON syntax. The file can be generated automatically when OF-DL is run in the standard, interactive mode.[/]\n");
|
||||||
@ -607,9 +687,10 @@ public class Program
|
|||||||
AnsiConsole.Markup("[green]rules.json located successfully!\n[/]");
|
AnsiConsole.Markup("[green]rules.json located successfully!\n[/]");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
JsonConvert.DeserializeObject<DynamicRules>(File.ReadAllText("rules.json"));
|
string rulesJson = await File.ReadAllTextAsync("rules.json");
|
||||||
|
DynamicRules? dynamicRules = JsonConvert.DeserializeObject<DynamicRules>(rulesJson);
|
||||||
Log.Debug($"Rules.json: ");
|
Log.Debug($"Rules.json: ");
|
||||||
Log.Debug(JsonConvert.SerializeObject(File.ReadAllText("rules.json"), Formatting.Indented));
|
Log.Debug(JsonConvert.SerializeObject(dynamicRules, Formatting.Indented));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -773,7 +854,21 @@ public class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
AnsiConsole.Markup($"[green]Logged In successfully as {validate.name} {validate.username}\n[/]");
|
AnsiConsole.Markup($"[green]Logged In successfully as {validate.name} {validate.username}\n[/]");
|
||||||
await DownloadAllData(apiHelper, auth, config);
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (config.OutputBlockedUsers)
|
||||||
|
{
|
||||||
|
await DownloadBlockedUsers(apiHelper, config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await DownloadAllData(apiHelper, auth, config);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DBHelper.CloseAllConnections();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -794,8 +889,29 @@ public class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task DownloadBlockedUsers(APIHelper m_ApiHelper, Entities.Config Config)
|
||||||
|
{
|
||||||
|
const string OUTPUT_FILE = "blocked-users.json";
|
||||||
|
|
||||||
private static async Task DownloadAllData(APIHelper m_ApiHelper, Auth Auth, Entities.Config Config)
|
Log.Debug($"Calling GetBlockedUsers");
|
||||||
|
|
||||||
|
AnsiConsole.Markup($"[red]Getting Blocked Users\n[/]");
|
||||||
|
|
||||||
|
Dictionary<string, int>? blockedUsers = await m_ApiHelper.GetBlockedUsers("/users/blocked", Config);
|
||||||
|
|
||||||
|
if (blockedUsers is null || blockedUsers.Count == 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.Markup($"[red]No Blocked Users found.\n[/]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AnsiConsole.Markup($"[red]Found {blockedUsers.Count} Blocked Users, saving to '{OUTPUT_FILE}'\n[/]");
|
||||||
|
string json = JsonConvert.SerializeObject(blockedUsers, Formatting.Indented);
|
||||||
|
await File.WriteAllTextAsync(OUTPUT_FILE, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task DownloadAllData(APIHelper m_ApiHelper, Auth Auth, Entities.Config Config)
|
||||||
{
|
{
|
||||||
DBHelper dBHelper = new DBHelper(Config);
|
DBHelper dBHelper = new DBHelper(Config);
|
||||||
|
|
||||||
@ -804,13 +920,21 @@ public class Program
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
DateTime startTime = DateTime.Now;
|
DateTime startTime = DateTime.Now;
|
||||||
Dictionary<string, int> users = new();
|
Dictionary<string, int> users = new();
|
||||||
Dictionary<string, int> activeSubs = await m_ApiHelper.GetActiveSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config);
|
|
||||||
|
|
||||||
Log.Debug("Subscriptions: ");
|
Task<Dictionary<string, int>?> taskActive = m_ApiHelper.GetActiveSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config);
|
||||||
|
Task<Dictionary<string, int>?> taskExpired = Config!.IncludeExpiredSubscriptions
|
||||||
|
? m_ApiHelper.GetExpiredSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config)
|
||||||
|
: Task.FromResult<Dictionary<string, int>?>([]);
|
||||||
|
|
||||||
foreach (KeyValuePair<string, int> activeSub in activeSubs)
|
await Task.WhenAll(taskActive, taskExpired);
|
||||||
{
|
|
||||||
|
Dictionary<string, int> subsActive = await taskActive ?? [];
|
||||||
|
Dictionary<string, int> subsExpired = await taskExpired ?? [];
|
||||||
|
|
||||||
|
Log.Debug("Subscriptions: ");
|
||||||
|
foreach (KeyValuePair<string, int> activeSub in subsActive)
|
||||||
|
{
|
||||||
if (!users.ContainsKey(activeSub.Key))
|
if (!users.ContainsKey(activeSub.Key))
|
||||||
{
|
{
|
||||||
users.Add(activeSub.Key, activeSub.Value);
|
users.Add(activeSub.Key, activeSub.Value);
|
||||||
@ -820,11 +944,9 @@ public class Program
|
|||||||
if (Config!.IncludeExpiredSubscriptions)
|
if (Config!.IncludeExpiredSubscriptions)
|
||||||
{
|
{
|
||||||
Log.Debug("Inactive Subscriptions: ");
|
Log.Debug("Inactive Subscriptions: ");
|
||||||
|
foreach (KeyValuePair<string, int> expiredSub in subsExpired)
|
||||||
Dictionary<string, int> expiredSubs = await m_ApiHelper.GetExpiredSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config);
|
{
|
||||||
foreach (KeyValuePair<string, int> expiredSub in expiredSubs)
|
if (!users.ContainsKey(expiredSub.Key))
|
||||||
{
|
|
||||||
if (!users.ContainsKey(expiredSub.Key))
|
|
||||||
{
|
{
|
||||||
users.Add(expiredSub.Key, expiredSub.Value);
|
users.Add(expiredSub.Key, expiredSub.Value);
|
||||||
Log.Debug($"Name: {expiredSub.Key} ID: {expiredSub.Value}");
|
Log.Debug($"Name: {expiredSub.Key} ID: {expiredSub.Value}");
|
||||||
@ -847,15 +969,39 @@ public class Program
|
|||||||
var ignoredUsernames = await m_ApiHelper.GetListUsers($"/lists/{ignoredUsersListId}/users", Config) ?? [];
|
var ignoredUsernames = await m_ApiHelper.GetListUsers($"/lists/{ignoredUsersListId}/users", Config) ?? [];
|
||||||
users = users.Where(x => !ignoredUsernames.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value);
|
users = users.Where(x => !ignoredUsernames.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await dBHelper.CreateUsersDB(users);
|
if (users.Count <= 0)
|
||||||
|
throw new InvalidOperationException("No users found!");
|
||||||
|
|
||||||
|
await dBHelper.CreateUsersDB(users);
|
||||||
KeyValuePair<bool, Dictionary<string, int>> hasSelectedUsersKVP;
|
KeyValuePair<bool, Dictionary<string, int>> hasSelectedUsersKVP;
|
||||||
if(Config.NonInteractiveMode && Config.NonInteractiveModePurchasedTab)
|
if(Config.NonInteractiveMode && Config.NonInteractiveModePurchasedTab)
|
||||||
{
|
{
|
||||||
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, new Dictionary<string, int> { { "PurchasedTab", 0 } });
|
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, new Dictionary<string, int> { { "PurchasedTab", 0 } });
|
||||||
}
|
}
|
||||||
else if (Config.NonInteractiveMode && string.IsNullOrEmpty(Config.NonInteractiveModeListName))
|
else if (Config.NonInteractiveMode && Config.NonInteractiveSpecificLists is not null && Config.NonInteractiveSpecificLists.Length > 0)
|
||||||
|
{
|
||||||
|
HashSet<string> listUsernames = [];
|
||||||
|
foreach (string listName in Config.NonInteractiveSpecificLists)
|
||||||
|
{
|
||||||
|
if (!lists.TryGetValue(listName, out int listId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
List<string> usernames = await m_ApiHelper.GetListUsers($"/lists/{listId}/users", Config);
|
||||||
|
foreach (string user in usernames)
|
||||||
|
listUsernames.Add(user);
|
||||||
|
}
|
||||||
|
users = users.Where(x => listUsernames.Contains(x.Key)).Distinct().ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, users);
|
||||||
|
}
|
||||||
|
else if (Config.NonInteractiveMode && Config.NonInteractiveSpecificUsers is not null && Config.NonInteractiveSpecificUsers.Length > 0)
|
||||||
|
{
|
||||||
|
HashSet<string> usernames = [.. Config.NonInteractiveSpecificUsers];
|
||||||
|
users = users.Where(u => usernames.Contains(u.Key)).ToDictionary(u => u.Key, u => u.Value);
|
||||||
|
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, users);
|
||||||
|
}
|
||||||
|
else if (Config.NonInteractiveMode && string.IsNullOrEmpty(Config.NonInteractiveModeListName))
|
||||||
{
|
{
|
||||||
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, users);
|
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, users);
|
||||||
}
|
}
|
||||||
@ -985,9 +1131,11 @@ public class Program
|
|||||||
Log.Debug($"Download path: {p}");
|
Log.Debug($"Download path: {p}");
|
||||||
|
|
||||||
List<PurchasedTabCollection> purchasedTabCollections = await m_ApiHelper.GetPurchasedTab("/posts/paid", p, Config, users);
|
List<PurchasedTabCollection> purchasedTabCollections = await m_ApiHelper.GetPurchasedTab("/posts/paid", p, Config, users);
|
||||||
foreach(PurchasedTabCollection purchasedTabCollection in purchasedTabCollections)
|
int userNum = 1;
|
||||||
|
int userCount = purchasedTabCollections.Count;
|
||||||
|
foreach (PurchasedTabCollection purchasedTabCollection in purchasedTabCollections)
|
||||||
{
|
{
|
||||||
AnsiConsole.Markup($"[red]\nScraping Data for {purchasedTabCollection.Username}\n[/]");
|
AnsiConsole.Markup($"[red]\nScraping Data for {purchasedTabCollection.Username} ({userNum++} of {userCount})\n[/]");
|
||||||
string path = "";
|
string path = "";
|
||||||
if (!string.IsNullOrEmpty(Config.DownloadPath))
|
if (!string.IsNullOrEmpty(Config.DownloadPath))
|
||||||
{
|
{
|
||||||
@ -1096,8 +1244,10 @@ public class Program
|
|||||||
}
|
}
|
||||||
else if (hasSelectedUsersKVP.Key && !hasSelectedUsersKVP.Value.ContainsKey("ConfigChanged"))
|
else if (hasSelectedUsersKVP.Key && !hasSelectedUsersKVP.Value.ContainsKey("ConfigChanged"))
|
||||||
{
|
{
|
||||||
//Iterate over each user in the list of users
|
//Iterate over each user in the list of users
|
||||||
foreach (KeyValuePair<string, int> user in hasSelectedUsersKVP.Value)
|
int userNum = 1;
|
||||||
|
int userCount = hasSelectedUsersKVP.Value.Count;
|
||||||
|
foreach (KeyValuePair<string, int> user in hasSelectedUsersKVP.Value)
|
||||||
{
|
{
|
||||||
int paidPostCount = 0;
|
int paidPostCount = 0;
|
||||||
int postCount = 0;
|
int postCount = 0;
|
||||||
@ -1107,7 +1257,7 @@ public class Program
|
|||||||
int highlightsCount = 0;
|
int highlightsCount = 0;
|
||||||
int messagesCount = 0;
|
int messagesCount = 0;
|
||||||
int paidMessagesCount = 0;
|
int paidMessagesCount = 0;
|
||||||
AnsiConsole.Markup($"[red]\nScraping Data for {user.Key}\n[/]");
|
AnsiConsole.Markup($"[red]\nScraping Data for {user.Key} ({userNum++} of {userCount})\n[/]");
|
||||||
|
|
||||||
Log.Debug($"Scraping Data for {user.Key}");
|
Log.Debug($"Scraping Data for {user.Key}");
|
||||||
|
|
||||||
@ -1392,6 +1542,9 @@ public class Program
|
|||||||
{
|
{
|
||||||
Log.Debug($"Calling DownloadMessages - {user.Key}");
|
Log.Debug($"Calling DownloadMessages - {user.Key}");
|
||||||
|
|
||||||
|
AnsiConsole.Markup($"[grey]Getting Unread Chats\n[/]");
|
||||||
|
HashSet<int> unreadChats = await GetUsersWithUnreadChats(downloadContext.ApiHelper, downloadContext.DownloadConfig);
|
||||||
|
|
||||||
MessageCollection messages = new MessageCollection();
|
MessageCollection messages = new MessageCollection();
|
||||||
|
|
||||||
await AnsiConsole.Status()
|
await AnsiConsole.Status()
|
||||||
@ -1399,7 +1552,14 @@ public class Program
|
|||||||
{
|
{
|
||||||
messages = await downloadContext.ApiHelper.GetMessages($"/chats/{user.Value}/messages", path, downloadContext.DownloadConfig!, ctx);
|
messages = await downloadContext.ApiHelper.GetMessages($"/chats/{user.Value}/messages", path, downloadContext.DownloadConfig!, ctx);
|
||||||
});
|
});
|
||||||
int oldMessagesCount = 0;
|
|
||||||
|
if (unreadChats.Contains(user.Value))
|
||||||
|
{
|
||||||
|
AnsiConsole.Markup($"[grey]Restoring unread state\n[/]");
|
||||||
|
await downloadContext.ApiHelper.MarkAsUnread($"/chats/{user.Value}/mark-as-read", downloadContext.DownloadConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
int oldMessagesCount = 0;
|
||||||
int newMessagesCount = 0;
|
int newMessagesCount = 0;
|
||||||
if (messages != null && messages.Messages.Count > 0)
|
if (messages != null && messages.Messages.Count > 0)
|
||||||
{
|
{
|
||||||
@ -1646,7 +1806,7 @@ public class Program
|
|||||||
{
|
{
|
||||||
archived = await downloadContext.ApiHelper.GetArchived($"/users/{user.Value}/posts", path, downloadContext.DownloadConfig!, ctx);
|
archived = await downloadContext.ApiHelper.GetArchived($"/users/{user.Value}/posts", path, downloadContext.DownloadConfig!, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
int oldArchivedCount = 0;
|
int oldArchivedCount = 0;
|
||||||
int newArchivedCount = 0;
|
int newArchivedCount = 0;
|
||||||
if (archived != null && archived.ArchivedPosts.Count > 0)
|
if (archived != null && archived.ArchivedPosts.Count > 0)
|
||||||
@ -1779,7 +1939,7 @@ public class Program
|
|||||||
{
|
{
|
||||||
posts = await downloadContext.ApiHelper.GetPosts($"/users/{user.Value}/posts", path, downloadContext.DownloadConfig!, paid_post_ids, ctx);
|
posts = await downloadContext.ApiHelper.GetPosts($"/users/{user.Value}/posts", path, downloadContext.DownloadConfig!, paid_post_ids, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
int oldPostCount = 0;
|
int oldPostCount = 0;
|
||||||
int newPostCount = 0;
|
int newPostCount = 0;
|
||||||
if (posts == null || posts.Posts.Count <= 0)
|
if (posts == null || posts.Posts.Count <= 0)
|
||||||
@ -2301,7 +2461,7 @@ public class Program
|
|||||||
{
|
{
|
||||||
streams = await downloadContext.ApiHelper.GetStreams($"/users/{user.Value}/posts/streams", path, downloadContext.DownloadConfig!, paid_post_ids, ctx);
|
streams = await downloadContext.ApiHelper.GetStreams($"/users/{user.Value}/posts/streams", path, downloadContext.DownloadConfig!, paid_post_ids, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
int oldStreamsCount = 0;
|
int oldStreamsCount = 0;
|
||||||
int newStreamsCount = 0;
|
int newStreamsCount = 0;
|
||||||
if (streams == null || streams.Streams.Count <= 0)
|
if (streams == null || streams.Streams.Count <= 0)
|
||||||
@ -2924,7 +3084,7 @@ public class Program
|
|||||||
hoconConfig.AppendLine($" LoggingLevel = \"{newConfig.LoggingLevel.ToString().ToLower()}\"");
|
hoconConfig.AppendLine($" LoggingLevel = \"{newConfig.LoggingLevel.ToString().ToLower()}\"");
|
||||||
hoconConfig.AppendLine("}");
|
hoconConfig.AppendLine("}");
|
||||||
|
|
||||||
File.WriteAllText("config.conf", hoconConfig.ToString());
|
await File.WriteAllTextAsync("config.conf", hoconConfig.ToString());
|
||||||
|
|
||||||
string newConfigString = JsonConvert.SerializeObject(newConfig, Formatting.Indented);
|
string newConfigString = JsonConvert.SerializeObject(newConfig, Formatting.Indented);
|
||||||
|
|
||||||
@ -3148,7 +3308,18 @@ public class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ValidateFilePath(string path)
|
private static async Task<HashSet<int>> GetUsersWithUnreadChats(APIHelper apiHelper, IDownloadConfig currentConfig)
|
||||||
|
{
|
||||||
|
ChatCollection chats = await apiHelper.GetChats($"/chats", currentConfig, onlyUnread: true);
|
||||||
|
|
||||||
|
var unreadChats = chats.Chats
|
||||||
|
.Where(c => c.Value.unreadMessagesCount > 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return [.. unreadChats.Select(c => c.Key)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ValidateFilePath(string path)
|
||||||
{
|
{
|
||||||
char[] invalidChars = System.IO.Path.GetInvalidPathChars();
|
char[] invalidChars = System.IO.Path.GetInvalidPathChars();
|
||||||
char[] foundInvalidChars = path.Where(c => invalidChars.Contains(c)).ToArray();
|
char[] foundInvalidChars = path.Where(c => invalidChars.Contains(c)).ToArray();
|
||||||
@ -3251,4 +3422,24 @@ public class Program
|
|||||||
|
|
||||||
return Enum.Parse<VideoResolution>("_" + value, ignoreCase: true);
|
return Enum.Parse<VideoResolution>("_" + value, ignoreCase: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ExitIfOtherProcess()
|
||||||
|
{
|
||||||
|
Assembly entryAssembly = Assembly.GetEntryAssembly();
|
||||||
|
AssemblyName entryAssemblyName = entryAssembly?.GetName();
|
||||||
|
|
||||||
|
if (entryAssemblyName?.Name is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Process thisProcess = Process.GetCurrentProcess();
|
||||||
|
Process[] otherProcesses = [.. Process.GetProcessesByName(entryAssemblyName.Name).Where(p => p.Id != thisProcess.Id)];
|
||||||
|
|
||||||
|
if (otherProcesses.Length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AnsiConsole.Markup($"[green]Other OF DL process detected, exiting..\n[/]");
|
||||||
|
Log.Warning("Other OF DL process detected, exiting..");
|
||||||
|
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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"
|
||||||
|
|
||||||
|
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
|
@ -7,7 +7,7 @@ Scrape all the media from an OnlyFans account
|
|||||||
Join the discord [here](https://discord.com/invite/6bUW8EJ53j)
|
Join the discord [here](https://discord.com/invite/6bUW8EJ53j)
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
Please refer to https://sim0n00ps.github.io/OF-DL/ for instructions on:
|
Please refer to https://docs.ofdl.tools/ for instructions on:
|
||||||
- Requirements
|
- Requirements
|
||||||
- Installing the Program
|
- Installing the Program
|
||||||
- Running the Program
|
- Running the Program
|
||||||
|
2
docs/.gitignore
vendored
@ -18,3 +18,5 @@
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
venv/
|
@ -1 +0,0 @@
|
|||||||
20.16.0
|
|
@ -1,41 +0,0 @@
|
|||||||
# Website
|
|
||||||
|
|
||||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
$ yarn
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local Development
|
|
||||||
|
|
||||||
```
|
|
||||||
$ yarn start
|
|
||||||
```
|
|
||||||
|
|
||||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
```
|
|
||||||
$ yarn build
|
|
||||||
```
|
|
||||||
|
|
||||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
|
||||||
|
|
||||||
### Deployment
|
|
||||||
|
|
||||||
Using SSH:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ USE_SSH=true yarn deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
Not using SSH:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
|
@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
|
||||||
};
|
|
@ -10,18 +10,14 @@ OF DL allows you to log in to your OnlyFans account directly. This simplifies th
|
|||||||
When prompted by the application, log into your OnlyFans account. Do not close the opened window, tab, or navigate away to another webpage.
|
When prompted by the application, log into your OnlyFans account. Do not close the opened window, tab, or navigate away to another webpage.
|
||||||
The new window will close automatically when the authentication process has finished.
|
The new window will close automatically when the authentication process has finished.
|
||||||
|
|
||||||
:::warning
|
!!! warning
|
||||||
|
|
||||||
Some users have reported that "Sign in with Google" has not been working with this authentication method.
|
Some users have reported that "Sign in with Google" has not been working with this authentication method.
|
||||||
If you use the Google sign-in option to log into your OnlyFans account, use one of the [legacy authentication methods](#legacy-methods) described below.
|
If you use the Google sign-in option to log into your OnlyFans account, use one of the [legacy authentication methods](#legacy-methods) described below.
|
||||||
|
|
||||||
:::
|
!!! info
|
||||||
|
|
||||||
:::info
|
If you are using docker, follow the special [authentication instructions documented](/installation/docker) to authenticate OF-DL
|
||||||
|
|
||||||
If you are using docker, follow the special [authentication instructions documented](/docs/installation/docker) to authenticate OF-DL
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Legacy Methods
|
## Legacy Methods
|
||||||
|
|
@ -265,7 +265,7 @@ Default: `""`
|
|||||||
|
|
||||||
Allowed values: Any valid string
|
Allowed values: Any valid string
|
||||||
|
|
||||||
Description: Please refer to [custom filename formats](/docs/config/custom-filename-formats#paidpostfilenameformat) page to see what fields you can use.
|
Description: Please refer to [custom filename formats](/config/custom-filename-formats#paidpostfilenameformat) page to see what fields you can use.
|
||||||
|
|
||||||
## PostFileNameFormat
|
## PostFileNameFormat
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ Default: `""`
|
|||||||
|
|
||||||
Allowed values: Any valid string
|
Allowed values: Any valid string
|
||||||
|
|
||||||
Description: Please refer to the [custom filename formats](/docs/config/custom-filename-formats#postfilenameformat) page to see what fields you can use.
|
Description: Please refer to the [custom filename formats](/config/custom-filename-formats#postfilenameformat) page to see what fields you can use.
|
||||||
|
|
||||||
## PaidMessageFileNameFormat
|
## PaidMessageFileNameFormat
|
||||||
|
|
||||||
@ -285,7 +285,7 @@ Default: `""`
|
|||||||
|
|
||||||
Allowed values: Any valid string
|
Allowed values: Any valid string
|
||||||
|
|
||||||
Description: Please refer to [custom filename formats](/docs/config/custom-filename-formats#paidmessagefilenameformat) page to see what fields you can use.
|
Description: Please refer to [custom filename formats](/config/custom-filename-formats#paidmessagefilenameformat) page to see what fields you can use.
|
||||||
|
|
||||||
## MessageFileNameFormat
|
## MessageFileNameFormat
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ Default: `""`
|
|||||||
|
|
||||||
Allowed values: Any valid string
|
Allowed values: Any valid string
|
||||||
|
|
||||||
Description: Please refer to [custom filename formats](/docs/config/custom-filename-formats#messagefilenameformat) page to see what fields you can use.
|
Description: Please refer to [custom filename formats](/config/custom-filename-formats#messagefilenameformat) page to see what fields you can use.
|
||||||
|
|
||||||
## RenameExistingFilesWhenCustomFormatIsSelected
|
## RenameExistingFilesWhenCustomFormatIsSelected
|
||||||
|
|
||||||
@ -322,7 +322,7 @@ Description: This configuration options allows you to set file name formats for
|
|||||||
This is useful if you want to have different file name formats for different creators. The values set here will override the global values set in the config file
|
This is useful if you want to have different file name formats for different creators. The values set here will override the global values set in the config file
|
||||||
(see [PaidPostFileNameFormat](#paidpostfilenameformat), [PostFileNameFormat](#postfilenameformat),
|
(see [PaidPostFileNameFormat](#paidpostfilenameformat), [PostFileNameFormat](#postfilenameformat),
|
||||||
[PaidMessageFileNAmeFormat](#paidmessagefilenameformat), and [MessageFileNameFormat](#messagefilenameformat)).
|
[PaidMessageFileNAmeFormat](#paidmessagefilenameformat), and [MessageFileNameFormat](#messagefilenameformat)).
|
||||||
For more information on the file name formats, see the [custom filename formats](/docs/config/custom-filename-formats) page.
|
For more information on the file name formats, see the [custom filename formats](/config/custom-filename-formats) page.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```
|
```
|
||||||
@ -435,15 +435,13 @@ Description: If set to `true`, the program will run without any input from the u
|
|||||||
(unless [NonInteractiveModeListName](#noninteractivemodelistname) or [NonInteractiveModePurchasedTab](#noninteractivemodepurchasedtab) are configured).
|
(unless [NonInteractiveModeListName](#noninteractivemodelistname) or [NonInteractiveModePurchasedTab](#noninteractivemodepurchasedtab) are configured).
|
||||||
If set to `false`, the default behaviour will apply, and you will be able to choose an option from the menu.
|
If set to `false`, the default behaviour will apply, and you will be able to choose an option from the menu.
|
||||||
|
|
||||||
:::warning
|
!!! warning
|
||||||
|
|
||||||
If NonInteractiveMode is enabled, you will be unable to authenticate OF-DL using the standard authentication method.
|
If NonInteractiveMode is enabled, you will be unable to authenticate OF-DL using the standard authentication method.
|
||||||
Before you can run OF-DL in NonInteractiveMode, you must either
|
Before you can run OF-DL in NonInteractiveMode, you must either
|
||||||
|
|
||||||
1. Generate an auth.json file by running OF-DL with NonInteractiveMode disabled and authenticating OF-DL using the standard method **OR**
|
1. Generate an auth.json file by running OF-DL with NonInteractiveMode disabled and authenticating OF-DL using the standard method **OR**
|
||||||
2. Generate an auth.json file by using a [legacy authentication method](/docs/config/auth#legacy-methods)
|
2. Generate an auth.json file by using a [legacy authentication method](/config/auth#legacy-methods)
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## NonInteractiveModeListName
|
## NonInteractiveModeListName
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"label": "Configuration",
|
|
||||||
"position": 2,
|
|
||||||
"link": {
|
|
||||||
"type": "generated-index",
|
|
||||||
"description": "Configuration options and information for OF-DL"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"label": "Installation",
|
|
||||||
"position": 1,
|
|
||||||
"link": {
|
|
||||||
"type": "generated-index",
|
|
||||||
"description": "Installation instructions for OF-DL"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
// `@type` JSDoc annotations allow editor autocompletion and type checking
|
|
||||||
// (when paired with `@ts-check`).
|
|
||||||
// There are various equivalent ways to declare your Docusaurus config.
|
|
||||||
// See: https://docusaurus.io/docs/api/docusaurus-config
|
|
||||||
|
|
||||||
import {themes as prismThemes} from 'prism-react-renderer';
|
|
||||||
|
|
||||||
/** @type {import('@docusaurus/types').Config} */
|
|
||||||
const config = {
|
|
||||||
title: 'OF-DL',
|
|
||||||
tagline: 'A media scraper for OnlyFans with DRM video support',
|
|
||||||
favicon: 'img/logo.png',
|
|
||||||
|
|
||||||
// Set the production url of your site here
|
|
||||||
url: 'https://sim0n00ps.github.io',
|
|
||||||
// Set the /<baseUrl>/ pathname under which your site is served
|
|
||||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
|
||||||
baseUrl: '/OF-DL/',
|
|
||||||
|
|
||||||
// GitHub pages deployment config.
|
|
||||||
// If you aren't using GitHub pages, you don't need these.
|
|
||||||
organizationName: 'sim0n00ps', // Usually your GitHub org/user name.
|
|
||||||
projectName: 'OF-DL', // Usually your repo name.
|
|
||||||
|
|
||||||
onBrokenLinks: 'throw',
|
|
||||||
onBrokenMarkdownLinks: 'warn',
|
|
||||||
|
|
||||||
// Even if you don't use internationalization, you can use this field to set
|
|
||||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
|
||||||
// may want to replace "en" with "zh-Hans".
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en',
|
|
||||||
locales: ['en'],
|
|
||||||
},
|
|
||||||
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@docusaurus/preset-classic',
|
|
||||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
|
||||||
({
|
|
||||||
docs: {
|
|
||||||
sidebarPath: './sidebars.js',
|
|
||||||
},
|
|
||||||
blog: false,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
themeConfig:
|
|
||||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
|
||||||
({
|
|
||||||
colorMode: {
|
|
||||||
respectPrefersColorScheme: true,
|
|
||||||
},
|
|
||||||
navbar: {
|
|
||||||
title: 'OF-DL',
|
|
||||||
logo: {
|
|
||||||
alt: 'OF-DL Logo',
|
|
||||||
src: 'img/logo.png',
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
type: 'docSidebar',
|
|
||||||
sidebarId: 'generatedSidebar',
|
|
||||||
position: 'left',
|
|
||||||
label: 'Docs',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://github.com/sim0n00ps/OF-DL',
|
|
||||||
label: 'GitHub',
|
|
||||||
position: 'right',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
style: 'dark',
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
title: 'Docs',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Installation',
|
|
||||||
to: '/docs/installation/windows',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Configuration',
|
|
||||||
to: '/docs/config/auth',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Running the Program',
|
|
||||||
to: '/docs/running-the-program',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Community',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Discord',
|
|
||||||
href: 'https://discord.com/invite/6bUW8EJ53j',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'More',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'GitHub',
|
|
||||||
href: 'https://github.com/sim0n00ps/OF-DL',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
prism: {
|
|
||||||
theme: prismThemes.github,
|
|
||||||
darkTheme: prismThemes.dracula,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
docs/img/logo.ico
Normal file
After Width: | Height: | Size: 109 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
8
docs/index.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Welcome to OF-DL
|
||||||
|
|
||||||
|
C# console app to download all of the media from Onlyfans accounts with DRM video downloading support.
|
||||||
|
|
||||||
|
!!! info "PLEASE READ BEFORE DOWNLOADING"
|
||||||
|
THIS TOOL CANNOT BYPASS PAYWALLS, IT CAN ONLY DOWNLOAD CONTENT YOU HAVE ACCESS TO, PLEASE DO NOT DOWNLOAD THIS TOOL THINKING YOU CAN BYPASS PAYING FOR THINGS!!!!!
|
||||||
|
|
||||||
|
Join the discord [here](https://discord.com/invite/6bUW8EJ53j)
|
@ -18,7 +18,7 @@ To run OF-DL in a docker container, follow these steps:
|
|||||||
Adjust `$HOME/ofdl` as desired (including in the commands below) if you want the files stored elsewhere.
|
Adjust `$HOME/ofdl` as desired (including in the commands below) if you want the files stored elsewhere.
|
||||||
4. Run the following command to start the docker container:
|
4. Run the following command to start the docker container:
|
||||||
```bash
|
```bash
|
||||||
docker run --rm -it -v $HOME/ofdl/data/:/data -v $HOME/ofdl/config/:/config -p 8080:8080 ghcr.io/sim0n00ps/of-dl:latest
|
docker run --rm -it -v $HOME/ofdl/data/:/data -v $HOME/ofdl/config/:/config -p 8080:8080 git.ofdl.tools/sim0n00ps/of-dl:latest
|
||||||
```
|
```
|
||||||
If `config.json` and/or `rules.json` don't exist in the `config` directory, files with default values will be created when you run the docker container.
|
If `config.json` and/or `rules.json` don't exist in the `config` directory, files with default values will be created when you run the docker container.
|
||||||
If you have your own Widevine keys, those files should be placed under `$HOME/ofdl/config/cdm/devices/chrome_1610/`.
|
If you have your own Widevine keys, those files should be placed under `$HOME/ofdl/config/cdm/devices/chrome_1610/`.
|
||||||
@ -29,14 +29,14 @@ To run OF-DL in a docker container, follow these steps:
|
|||||||
When a new version of OF-DL is released, you can download the latest docker image by executing:
|
When a new version of OF-DL is released, you can download the latest docker image by executing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull ghcr.io/sim0n00ps/of-dl:latest
|
docker pull git.ofdl.tools/sim0n00ps/of-dl:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then run the new version of OF-DL by executing the `docker run` command in the [Running OF-DL](#running-of-dl) section above.
|
You can then run the new version of OF-DL by executing the `docker run` command in the [Running OF-DL](#running-of-dl) section above.
|
||||||
|
|
||||||
## Building the Docker Image (Optional)
|
## Building the Docker Image (Optional)
|
||||||
|
|
||||||
Since official docker images are provided for OF-DL through GitHub Container Registry (ghcr.io), you do not need to build the docker image yourself.
|
Since official docker images are provided for OF-DL through Gitea (git.ofdl.tools), you do not need to build the docker image yourself.
|
||||||
If you would like to build the docker image yourself, however, start by cloning the OF-DL repository and opening a terminal in the root directory of the repository.
|
If you would like to build the docker image yourself, however, start by cloning the OF-DL repository and opening a terminal in the root directory of the repository.
|
||||||
Then, execute the following command while replacing `x.x.x` with the current version of OF-DL:
|
Then, execute the following command while replacing `x.x.x` with the current version of OF-DL:
|
||||||
|
|
||||||
@ -45,4 +45,4 @@ VERSION="x.x.x" docker build --build-arg VERSION=$VERSION -t of-dl .
|
|||||||
```
|
```
|
||||||
|
|
||||||
You can then run a container using the image you just built by executing the `docker run` command in the
|
You can then run a container using the image you just built by executing the `docker run` command in the
|
||||||
[Running OF-DL](#running-of-dl) section above while replacing `ghcr.io/sim0n00ps/of-dl:latest` with `of-dl`.
|
[Running OF-DL](#running-of-dl) section above while replacing `git.ofdl.tools/sim0n00ps/of-dl:latest` with `of-dl`.
|
@ -5,7 +5,7 @@ sidebar_position: 3
|
|||||||
# Linux
|
# Linux
|
||||||
|
|
||||||
A Linux release of OF-DL is not available at this time, however you can run OF-DL on Linux using Docker.
|
A Linux release of OF-DL is not available at this time, however you can run OF-DL on Linux using Docker.
|
||||||
Please refer to the [Docker](/docs/installation/docker) page for instructions on how to run OF-DL in a Docker container.
|
Please refer to the [Docker](/installation/docker) page for instructions on how to run OF-DL in a Docker container.
|
||||||
If you do not have Docker installed, you can download it from [here](https://docs.docker.com/desktop/install/linux-install/).
|
If you do not have Docker installed, you can download it from [here](https://docs.docker.com/desktop/install/linux-install/).
|
||||||
If you would like to run OF-DL natively on Linux, you can build it from source by following the instructions below.
|
If you would like to run OF-DL natively on Linux, you can build it from source by following the instructions below.
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ sudo apt-get install libicu-dev
|
|||||||
- Clone the repo
|
- Clone the repo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/sim0n00ps/OF-DL.git
|
git clone https://git.ofdl.tools/sim0n00ps/OF-DL.git
|
||||||
cd 'OF-DL'
|
cd 'OF-DL'
|
||||||
```
|
```
|
||||||
|
|
@ -5,5 +5,5 @@ sidebar_position: 4
|
|||||||
# macOS
|
# macOS
|
||||||
|
|
||||||
macOS releases of OF-DL are not available at this time, however you can run OF-DL on macOS using Docker.
|
macOS releases of OF-DL are not available at this time, however you can run OF-DL on macOS using Docker.
|
||||||
Please refer to the [Docker](/docs/installation/docker) page for instructions on how to run OF-DL in a Docker container.
|
Please refer to the [Docker](/installation/docker) page for instructions on how to run OF-DL in a Docker container.
|
||||||
If you do not have Docker installed, you can download it from [here](https://docs.docker.com/desktop/install/mac-install/).
|
If you do not have Docker installed, you can download it from [here](https://docs.docker.com/desktop/install/mac-install/).
|
@ -11,11 +11,11 @@ sidebar_position: 1
|
|||||||
You will need to download FFmpeg. You can download it from [here](https://www.gyan.dev/ffmpeg/builds/).
|
You will need to download FFmpeg. You can download it from [here](https://www.gyan.dev/ffmpeg/builds/).
|
||||||
Make sure you download `ffmpeg-release-essentials.zip`. Unzip it anywhere on your computer. You only need `ffmpeg.exe`, and you can ignore the rest.
|
Make sure you download `ffmpeg-release-essentials.zip`. Unzip it anywhere on your computer. You only need `ffmpeg.exe`, and you can ignore the rest.
|
||||||
Move `ffmpeg.exe` to the same folder as `OF DL.exe` (downloaded in the installation steps below). If you choose to move `ffmpeg.exe` to a different folder,
|
Move `ffmpeg.exe` to the same folder as `OF DL.exe` (downloaded in the installation steps below). If you choose to move `ffmpeg.exe` to a different folder,
|
||||||
you will need to specify the path to `ffmpeg.exe` in the config file (see the `FFmpegPath` [config option](/docs/config/configuration#ffmpegpath)).
|
you will need to specify the path to `ffmpeg.exe` in the config file (see the `FFmpegPath` [config option](/config/configuration#ffmpegpath)).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Navigate to the OF-DL [releases page](https://github.com/sim0n00ps/OF-DL/releases), and download the latest release zip file. The zip file will be named `OFDLVx.x.x.zip` where `x.x.x` is the version number.
|
1. Navigate to the OF-DL [releases page](https://git.ofdl.tools/sim0n00ps/OF-DL/releases), and download the latest release zip file. The zip file will be named `OFDLVx.x.x.zip` where `x.x.x` is the version number.
|
||||||
2. Unzip the downloaded file. The destination folder can be anywhere on your computer, preferably somewhere where you want to download content to/already have content downloaded.
|
2. Unzip the downloaded file. The destination folder can be anywhere on your computer, preferably somewhere where you want to download content to/already have content downloaded.
|
||||||
3. Your folder should contain a folder named `cdm` as well as the following files:
|
3. Your folder should contain a folder named `cdm` as well as the following files:
|
||||||
- OF DL.exe
|
- OF DL.exe
|
15550
docs/package-lock.json
generated
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "of-dl",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"docusaurus": "docusaurus",
|
|
||||||
"start": "docusaurus start",
|
|
||||||
"build": "docusaurus build",
|
|
||||||
"swizzle": "docusaurus swizzle",
|
|
||||||
"deploy": "docusaurus deploy",
|
|
||||||
"clear": "docusaurus clear",
|
|
||||||
"serve": "docusaurus serve",
|
|
||||||
"write-translations": "docusaurus write-translations",
|
|
||||||
"write-heading-ids": "docusaurus write-heading-ids"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@docusaurus/core": "3.4.0",
|
|
||||||
"@docusaurus/preset-classic": "3.4.0",
|
|
||||||
"@mdx-js/react": "^3.0.0",
|
|
||||||
"clsx": "^2.0.0",
|
|
||||||
"prism-react-renderer": "^2.3.0",
|
|
||||||
"react": "^18.0.0",
|
|
||||||
"react-dom": "^18.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@docusaurus/module-type-aliases": "3.4.0",
|
|
||||||
"@docusaurus/types": "3.4.0"
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.5%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 3 chrome version",
|
|
||||||
"last 3 firefox version",
|
|
||||||
"last 5 safari version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ sidebar_position: 3
|
|||||||
|
|
||||||
# Running the Program
|
# Running the Program
|
||||||
|
|
||||||
Once you are happy you have filled everything in [auth.json](/docs/config/auth) correctly, you can double click OF-DL.exe and you should see a command prompt window appear, it should look something like this:
|
Once you are happy you have filled everything in [auth.json](/config/auth) correctly, you can double click OF-DL.exe and you should see a command prompt window appear, it should look something like this:
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Creating a sidebar enables you to:
|
|
||||||
- create an ordered group of docs
|
|
||||||
- render a sidebar for each doc of that group
|
|
||||||
- provide next/previous navigation
|
|
||||||
|
|
||||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
|
||||||
|
|
||||||
Create as many sidebars as you want.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
|
||||||
const sidebars = {
|
|
||||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
|
||||||
generatedSidebar: [{type: 'autogenerated', dirName: '.'}],
|
|
||||||
|
|
||||||
// But you can create a sidebar manually
|
|
||||||
/*
|
|
||||||
tutorialSidebar: [
|
|
||||||
'intro',
|
|
||||||
'hello',
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Tutorial',
|
|
||||||
items: ['tutorial-basics/create-a-document'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sidebars;
|
|
@ -1,39 +0,0 @@
|
|||||||
import clsx from 'clsx';
|
|
||||||
import Link from '@docusaurus/Link';
|
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
||||||
import Layout from '@theme/Layout';
|
|
||||||
|
|
||||||
import Heading from '@theme/Heading';
|
|
||||||
import styles from './index.module.css';
|
|
||||||
|
|
||||||
function HomepageHeader() {
|
|
||||||
const {siteConfig} = useDocusaurusContext();
|
|
||||||
return (
|
|
||||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
|
||||||
<div className="container">
|
|
||||||
<Heading as="h1" className="hero__title">
|
|
||||||
{siteConfig.title}
|
|
||||||
</Heading>
|
|
||||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
|
||||||
<div className={styles.buttons}>
|
|
||||||
<Link
|
|
||||||
className="button button--secondary button--lg"
|
|
||||||
to="docs/installation/windows">
|
|
||||||
Installation
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
const {siteConfig} = useDocusaurusContext();
|
|
||||||
return (
|
|
||||||
<Layout
|
|
||||||
title={siteConfig.title}
|
|
||||||
description={siteConfig.tagline}>
|
|
||||||
<HomepageHeader />
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
|
||||||
* and scoped locally.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.heroBanner {
|
|
||||||
padding: 4rem 0;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 996px) {
|
|
||||||
.heroBanner {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
0
docs/static/.nojekyll
vendored
BIN
docs/static/img/logo.png
vendored
Before Width: | Height: | Size: 769 B |
2
excludes.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
excludes.txt
|
||||||
|
rules.json
|
46
mkdocs.yml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
site_name: OF-DL Docs
|
||||||
|
site_url: https://docs.ofdl.tools
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
features:
|
||||||
|
- navigation.tabs
|
||||||
|
- navigation.top
|
||||||
|
- navigation.instant
|
||||||
|
- navigation.expand
|
||||||
|
- navigation.sections
|
||||||
|
- navigation.tracking
|
||||||
|
- navigation.search.highlight
|
||||||
|
- navigation.search.suggest
|
||||||
|
- navigation.search.share
|
||||||
|
- navigation.search.suggest
|
||||||
|
- navigation.search.share
|
||||||
|
- navigation.search.suggest
|
||||||
|
- navigation.search.share
|
||||||
|
language: en
|
||||||
|
palette:
|
||||||
|
- scheme: default
|
||||||
|
toggle:
|
||||||
|
icon: material/toggle-switch-off-outline
|
||||||
|
name: Switch to dark mode
|
||||||
|
primary: dark-blue
|
||||||
|
accent: white
|
||||||
|
- scheme: slate
|
||||||
|
toggle:
|
||||||
|
icon: material/toggle-switch
|
||||||
|
name: Switch to light mode
|
||||||
|
primary: dark-blue
|
||||||
|
accent: white
|
||||||
|
font:
|
||||||
|
text: Roboto
|
||||||
|
code: Roboto Mono
|
||||||
|
logo: img/logo.ico
|
||||||
|
favicon: img/logo.ico
|
||||||
|
markdown_extensions:
|
||||||
|
- admonition
|
||||||
|
- pymdownx.details
|
||||||
|
- pymdownx.superfences
|
||||||
|
extra:
|
||||||
|
social:
|
||||||
|
- icon: fontawesome/brands/discord
|
||||||
|
link: https://discord.com/invite/6bUW8EJ53j
|
||||||
|
copyright: "© 2025 OF-DL. All rights reserved."
|