Compare commits

...

38 Commits

Author SHA1 Message Date
e6a3eea243 Rebase fixes 2025-12-15 17:28:22 +01:00
4d4df29bf4 Added total new media counts context to "Scrape Completed" log 2025-12-15 17:23:43 +01:00
7ee5fae714 Added logging of which NonInteractive mode it's running 2025-12-15 17:23:43 +01:00
a467e6f8b7 Added "Log.CloseAndFlush" call before every Environment.Exit 2025-12-15 17:23:43 +01:00
23f98a4c8e More logging tweaks 2025-12-15 17:23:42 +01:00
ea976bad70 Tweaked progress output, to create task for each model 2025-12-15 17:23:42 +01:00
6eb7560a54 Fixed missing LogContext enrichment 2025-12-15 17:23:42 +01:00
dc5c2ba093 Fixed parameters to ensure no null values are passed incorrectly 2025-12-15 17:23:42 +01:00
31921115ec Added update all UserInfo mode 2025-12-15 17:23:12 +01:00
1565f668ae Added user info table and update it for each user 2025-12-15 17:23:12 +01:00
63c9d5c6f5 Added additional info logging with new media counts per model 2025-12-15 17:22:41 +01:00
6d9d46585b Enabled logging to Seq. 2025-12-15 17:21:23 +01:00
b24d0150ed Added safe-guard against paid content returned for other models 2025-12-15 17:21:22 +01:00
50191f19c8 Fixed lookup for Paid Posts and Messages, due to API changes 2025-12-15 17:20:46 +01:00
253abb536a Extended "output blocked" to also include expired in separate file 2025-12-15 17:20:46 +01:00
cb5c7f19b7 Updated blocked user lookup with progress status 2025-12-15 17:20:45 +01:00
95c883a937 Updated non-interactive list user lookup.
Tweaked order to fully replace users before updating DB.
2025-12-15 17:20:45 +01:00
17292fe867 Debug logging in BuildHeaderAndExecuteRequests 2025-12-15 17:19:05 +01:00
ae86df7b6d Tweaked publishing script 2025-12-15 17:19:04 +01:00
32e336adc9 Added logic to reset chat read state after downloading messages 2025-12-15 17:19:04 +01:00
d109004d7f Updated subscription lookup to match OF website. 2025-12-15 17:19:04 +01:00
5faa949e0e Added logic to save list of blocked users. 2025-12-15 17:17:44 +01:00
30552fda87 HttpClient tweaks 2025-12-15 17:17:44 +01:00
bc006cb3b6 Added earningId to Subscribe model 2025-12-15 17:17:44 +01:00
9213bcce98 Improved DB connection creation with delayed retry, and connection caching 2025-12-15 17:17:43 +01:00
b30475c719 Extended command line args for NonInteractive 2025-12-15 17:17:43 +01:00
13f63613fc Added exiting if other process is detected, to avoid overlapping runs 2025-12-15 17:17:43 +01:00
03a787c457 Added "x of y" count to "Scraping Data For" console outputs. 2025-12-15 17:17:42 +01:00
5adf749a11 Config and project tweaks, plus publish script 2025-12-15 17:17:17 +01:00
41a8c98a63 Fixed async usage. 2025-12-15 17:17:17 +01:00
167d6640e3 Move DisableBrowserAuth to Auth 2025-12-14 18:47:18 +00:00
e9786f2341 Merge pull request 'Add additional logging for ffmpeg and prevent NRE errors from obscuring the actual errors from ffmpeg.' (#97) from ffmpeg_additional_logging into master
Reviewed-on: sim0n00ps/OF-DL#97
2025-12-14 15:39:10 +00:00
Melithine
0eae466368 Add additional logging for ffmpeg and prevent NRE errors from obscuring the actual errors from ffmpeg. 2025-12-13 19:55:59 -08:00
616aaef1c8 Merge pull request 'Add doc site and Discord invite after the startup banner.' (#89) from header_update into master
Reviewed-on: sim0n00ps/OF-DL#89
Reviewed-by: whimsical-c4lic0 <whimsical-c4lic0@noreply.localhost>
2025-12-13 19:30:56 +00:00
34f055b00e Merge pull request 'Fix 32-bit integers' (#94) from whimsical-c4lic0/OF-DL:fix-32-bit-integers into master
Reviewed-on: sim0n00ps/OF-DL#94
Reviewed-by: melithine <melithine@noreply.localhost>
2025-12-13 19:30:27 +00:00
e7e1556b3c Convert all sizes from int to long 2025-12-13 02:57:58 -06:00
74def34f96 Convert all ids from int to long 2025-12-13 02:52:15 -06:00
Melithine
19730436d4 Add doc site and Discord invite after the startup banner. 2025-12-10 07:32:26 -08:00
31 changed files with 1314 additions and 472 deletions

View File

@ -5,5 +5,12 @@ root = true
charset = utf-8
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{sln,csproj,xml,json,config}]
indent_size = 2
indent_style = space
tab_width = 2

4
.gitignore vendored
View File

@ -370,4 +370,6 @@ FodyWeavers.xsd
!.gitea-actions/**/node_modules/
# venv
venv/
venv/
Publish/

View File

@ -17,7 +17,7 @@ namespace OF_DL.Entities.Archived
public Counters counters { get; set; }
public class Author
{
public int id { get; set; }
public long id { get; set; }
public string _view { get; set; }
}
@ -64,7 +64,7 @@ namespace OF_DL.Entities.Archived
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public List<object> sources { get; set; }
}
@ -73,7 +73,7 @@ namespace OF_DL.Entities.Archived
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Thumb
@ -81,7 +81,7 @@ namespace OF_DL.Entities.Archived
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Hls
@ -105,7 +105,7 @@ namespace OF_DL.Entities.Archived
public class LinkedPost
{
public string responseType { get; set; }
public int? id { get; set; }
public long? id { get; set; }
public DateTime? postedAt { get; set; }
public string postedAtPrecise { get; set; }
public object expiredAt { get; set; }

View File

@ -0,0 +1,7 @@
namespace OF_DL.Entities.Chats
{
public class ChatCollection
{
public Dictionary<int, Chats.Chat> Chats { get; set; } = [];
}
}

View 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; }
}
}
}

View File

@ -107,6 +107,12 @@ namespace OF_DL.Entities
// When enabled, post/message text is stored as-is without XML stripping.
[ToggleableConfig]
public bool DisableTextSanitization { get; set; } = false;
public string[] NonInteractiveSpecificUsers { get; set; } = [];
public string[] NonInteractiveSpecificLists { get; set; } = [];
public bool OutputBlockedUsers { get; set; }
public bool UpdateAllUserInfo { get; set; }
}
public class CreatorConfig : IFileNameFormatConfig

View File

@ -9,10 +9,10 @@ namespace OF_DL.Entities.Highlights
{
public class HighlightMedia
{
public int id { get; set; }
public int userId { get; set; }
public long id { get; set; }
public long userId { get; set; }
public string title { get; set; }
public int coverStoryId { get; set; }
public long coverStoryId { get; set; }
public string cover { get; set; }
public int storiesCount { get; set; }
public DateTime? createdAt { get; set; }
@ -30,7 +30,7 @@ namespace OF_DL.Entities.Highlights
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public List<object> sources { get; set; }
}
@ -50,7 +50,7 @@ namespace OF_DL.Entities.Highlights
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public Sources sources { get; set; }
}
@ -60,7 +60,7 @@ namespace OF_DL.Entities.Highlights
public int width { get; set; }
public int height { get; set; }
public int duration { get; set; }
public int size { get; set; }
public long size { get; set; }
public Sources sources { get; set; }
}
@ -80,14 +80,14 @@ namespace OF_DL.Entities.Highlights
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public Sources sources { get; set; }
}
public class Story
{
public int id { get; set; }
public int userId { get; set; }
public long id { get; set; }
public long userId { get; set; }
public bool isWatched { get; set; }
public bool isReady { get; set; }
public List<Medium> media { get; set; }
@ -102,7 +102,7 @@ namespace OF_DL.Entities.Highlights
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
}
}

View File

@ -12,10 +12,10 @@ namespace OF_DL.Entities.Highlights
public bool hasMore { get; set; }
public class List
{
public int id { get; set; }
public int userId { get; set; }
public long id { get; set; }
public long userId { get; set; }
public string title { get; set; }
public int coverStoryId { get; set; }
public long coverStoryId { get; set; }
public string cover { get; set; }
public int storiesCount { get; set; }
public DateTime? createdAt { get; set; }

View File

@ -34,7 +34,7 @@ namespace OF_DL.Entities.Lists
public class User
{
public int? id { get; set; }
public long? id { get; set; }
public string _view { get; set; }
}
}

View File

@ -14,7 +14,7 @@ namespace OF_DL.Entities.Lists
public string header { get; set; }
public HeaderSize headerSize { get; set; }
public HeaderThumbs headerThumbs { get; set; }
public int? id { get; set; }
public long? id { get; set; }
public string name { get; set; }
public string username { get; set; }
public bool? canLookStory { get; set; }
@ -92,7 +92,7 @@ namespace OF_DL.Entities.Lists
public class Subscribe
{
public object id { get; set; }
public int? userId { get; set; }
public long? userId { get; set; }
public int? subscriberId { get; set; }
public DateTime? date { get; set; }
public int? duration { get; set; }
@ -160,7 +160,7 @@ namespace OF_DL.Entities.Lists
public class SubscriptionBundle
{
public int? id { get; set; }
public long? id { get; set; }
public string? discount { get; set; }
public string? duration { get; set; }
public string? price { get; set; }

View File

@ -43,7 +43,7 @@ namespace OF_DL.Entities.Messages
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public List<object> sources { get; set; }
}
@ -52,7 +52,7 @@ namespace OF_DL.Entities.Messages
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Thumb
@ -60,12 +60,12 @@ namespace OF_DL.Entities.Messages
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class FromUser
{
public int? id { get; set; }
public long? id { get; set; }
public string _view { get; set; }
}

View File

@ -17,7 +17,7 @@ namespace OF_DL.Entities.Messages
public string header { get; set; }
public HeaderSize headerSize { get; set; }
public HeaderThumbs headerThumbs { get; set; }
public int? id { get; set; }
public long? id { get; set; }
public string name { get; set; }
public string username { get; set; }
public bool canLookStory { get; set; }
@ -81,7 +81,7 @@ namespace OF_DL.Entities.Messages
{
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class SingleMessage

View File

@ -20,7 +20,7 @@ public class Post
public string tailMarker { get; set; }
public class Author
{
public int id { get; set; }
public long id { get; set; }
public string _view { get; set; }
}
@ -56,7 +56,7 @@ public class Post
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public List<object> sources { get; set; }
}
@ -65,7 +65,7 @@ public class Post
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Thumb
@ -73,7 +73,7 @@ public class Post
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Hls

View File

@ -12,7 +12,7 @@ namespace OF_DL.Entities.Post
public class SinglePost
{
public string responseType { get; set; }
public int id { get; set; }
public long id { get; set; }
public DateTime postedAt { get; set; }
public string postedAtPrecise { get; set; }
public object expiredAt { get; set; }
@ -67,7 +67,7 @@ namespace OF_DL.Entities.Post
public List<object> preview { get; set; }
public class Author
{
public int id { get; set; }
public long id { get; set; }
public string _view { get; set; }
}
@ -85,7 +85,7 @@ namespace OF_DL.Entities.Post
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public List<object> sources { get; set; }
}
@ -94,7 +94,7 @@ namespace OF_DL.Entities.Post
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Thumb
@ -102,7 +102,7 @@ namespace OF_DL.Entities.Post
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Info
@ -134,7 +134,7 @@ namespace OF_DL.Entities.Post
{
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public string url { get; set; }
}
@ -143,7 +143,7 @@ namespace OF_DL.Entities.Post
public string source { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public int duration { get; set; }
}

View File

@ -13,17 +13,14 @@ namespace OF_DL.Entities.Purchased
public List<List> list { get; set; }
public bool hasMore { get; set; }
public class FromUser
{
public int id { get; set; }
public long id { get; set; }
public string _view { get; set; }
}
public class Author
{
public int id { get; set; }
public long id { get; set; }
public string _view { get; set; }
}
@ -81,10 +78,5 @@ namespace OF_DL.Entities.Purchased
public string hls { get; set; }
public string dash { get; set; }
}
}
}

View File

@ -9,8 +9,8 @@ namespace OF_DL.Entities.Stories
{
public class Stories
{
public int id { get; set; }
public int userId { get; set; }
public long id { get; set; }
public long userId { get; set; }
public bool isWatched { get; set; }
public bool isReady { get; set; }
public List<Medium> media { get; set; }
@ -31,7 +31,7 @@ namespace OF_DL.Entities.Stories
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public List<object> sources { get; set; }
}
@ -51,7 +51,7 @@ namespace OF_DL.Entities.Stories
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public Sources sources { get; set; }
}
@ -61,7 +61,7 @@ namespace OF_DL.Entities.Stories
public int width { get; set; }
public int height { get; set; }
public int duration { get; set; }
public int size { get; set; }
public long size { get; set; }
public Sources sources { get; set; }
}
@ -81,7 +81,7 @@ namespace OF_DL.Entities.Stories
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public Sources sources { get; set; }
}
@ -90,7 +90,7 @@ namespace OF_DL.Entities.Stories
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
}
}

View File

@ -17,7 +17,7 @@ namespace OF_DL.Entities.Streams
public Counters counters { get; set; }
public class Author
{
public int id { get; set; }
public long id { get; set; }
public string _view { get; set; }
}
@ -46,7 +46,7 @@ namespace OF_DL.Entities.Streams
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public List<object> sources { get; set; }
}
@ -55,7 +55,7 @@ namespace OF_DL.Entities.Streams
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Thumb
@ -63,7 +63,7 @@ namespace OF_DL.Entities.Streams
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
}
public class Info
@ -154,7 +154,7 @@ namespace OF_DL.Entities.Streams
{
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public string url { get; set; }
}
@ -163,7 +163,7 @@ namespace OF_DL.Entities.Streams
public string source { get; set; }
public int width { get; set; }
public int height { get; set; }
public int size { get; set; }
public long size { get; set; }
public int duration { get; set; }
}

View File

@ -36,7 +36,7 @@ namespace OF_DL.Entities
public string header { get; set; }
public HeaderSize headerSize { get; set; }
public HeaderThumbs headerThumbs { get; set; }
public int id { get; set; }
public long id { get; set; }
public string name { get; set; }
public string username { get; set; }
public bool? canLookStory { get; set; }
@ -96,8 +96,9 @@ namespace OF_DL.Entities
public class Subscribe
{
public object id { get; set; }
public int? userId { get; set; }
public long? userId { get; set; }
public int? subscriberId { get; set; }
public long? earningId { get; set; }
public DateTime? date { get; set; }
public int? duration { get; set; }
public DateTime? startDate { get; set; }

View File

@ -14,7 +14,7 @@ namespace OF_DL.Entities
public string? header { get; set; }
public HeaderSize headerSize { get; set; }
public HeaderThumbs headerThumbs { get; set; }
public int? id { get; set; }
public long? id { get; set; }
public string name { get; set; }
public string username { get; set; }
public bool? canLookStory { get; set; }
@ -124,7 +124,7 @@ namespace OF_DL.Entities
public class Subscribe
{
public long? id { get; set; }
public int? userId { get; set; }
public long? userId { get; set; }
public int? subscriberId { get; set; }
public DateTime? date { get; set; }
public int? duration { get; set; }

View File

@ -2,6 +2,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OF_DL.Entities;
using OF_DL.Entities.Archived;
using OF_DL.Entities.Chats;
using OF_DL.Entities.Highlights;
using OF_DL.Entities.Lists;
using OF_DL.Entities.Messages;
@ -13,6 +14,7 @@ using OF_DL.Enumerations;
using OF_DL.Enumurations;
using Serilog;
using Spectre.Console;
using System.Diagnostics;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
@ -25,10 +27,15 @@ namespace OF_DL.Helpers;
public class APIHelper : IAPIHelper
{
private const int MAX_RETRIES = 10;
private const int DELAY_BEFORE_RETRY = 1000;
private static readonly JsonSerializerSettings m_JsonSerializerSettings;
private readonly IDBHelper m_DBHelper;
private readonly IDownloadConfig downloadConfig;
private readonly Auth auth;
private HttpClient httpClient = new();
private static DateTime? cachedDynamicRulesExpiration;
private static DynamicRules? cachedDynamicRules;
private const int MaxAttempts = 30;
@ -118,30 +125,58 @@ public class APIHelper : IAPIHelper
}
private async Task<string?> BuildHeaderAndExecuteRequests(Dictionary<string, string> getParams, string endpoint, HttpClient client)
private async Task<string?> BuildHeaderAndExecuteRequests(Dictionary<string, string> getParams, string endpoint, HttpClient client, HttpMethod? method = null, int retryCount = 0)
{
Log.Debug("Calling BuildHeaderAndExecuteRequests");
Log.Debug("Calling BuildHeaderAndExecuteRequests -- Attempt number: {AttemptNumber}", retryCount + 1);
HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint);
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
try
{
HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint, method);
Log.Debug(body);
Debug.WriteLine($"Executing {request.Method.Method.ToUpper()} request: {request.RequestUri}\r\n\t{GetParamsString(getParams)}");
return body;
using var response = await client.SendAsync(request);
if (Debugger.IsAttached && !response.IsSuccessStatusCode)
Debugger.Break();
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
Log.Debug(body);
return body;
}
catch (HttpRequestException ex)
{
if (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests && retryCount < MAX_RETRIES)
{
await Task.Delay(DELAY_BEFORE_RETRY);
return await BuildHeaderAndExecuteRequests(getParams, endpoint, client, method, ++retryCount);
}
throw;
}
static string GetParamsString(Dictionary<string, string> getParams)
=> string.Join(" | ", getParams.Select(kv => $"{kv.Key}={kv.Value}"));
}
private async Task<HttpRequestMessage> BuildHttpRequestMessage(Dictionary<string, string> getParams, string endpoint)
private async Task<HttpRequestMessage> BuildHttpRequestMessage(Dictionary<string, string> getParams, string endpoint, HttpMethod? method = null)
{
Log.Debug("Calling BuildHttpRequestMessage");
string queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
method ??= HttpMethod.Get;
string queryParams = "";
if (getParams.Any())
queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
Dictionary<string, string> headers = GetDynamicHeaders($"/api2/v2{endpoint}", queryParams);
HttpRequestMessage request = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{queryParams}");
HttpRequestMessage request = new(method, $"{Constants.API_URL}{endpoint}{queryParams}");
Log.Debug($"Full request URL: {Constants.API_URL}{endpoint}{queryParams}");
@ -166,18 +201,16 @@ public class APIHelper : IAPIHelper
return input.All(char.IsDigit);
}
private static HttpClient GetHttpClient(IDownloadConfig? config = null)
private HttpClient GetHttpClient(IDownloadConfig? config = null)
{
var client = new HttpClient();
httpClient ??= new HttpClient();
if (config?.Timeout != null && config.Timeout > 0)
{
client.Timeout = TimeSpan.FromSeconds(config.Timeout.Value);
httpClient.Timeout = TimeSpan.FromSeconds(config.Timeout.Value);
}
return client;
return httpClient;
}
/// <summary>
/// this one is used during initialization only
/// if the config option is not available then no modificatiotns will be done on the getParams
@ -215,7 +248,7 @@ public class APIHelper : IAPIHelper
}
public async Task<User?> GetUserInfo(string endpoint)
public async Task<User?> GetUserInfo(string username, string endpoint)
{
Log.Debug($"Calling GetUserInfo: {endpoint}");
@ -242,6 +275,10 @@ public class APIHelper : IAPIHelper
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
user = JsonConvert.DeserializeObject<Entities.User>(body, m_JsonSerializerSettings);
if (user is not null && !endpoint.EndsWith("/me"))
await m_DBHelper.UpdateUserInfo(username, user);
return user;
}
catch (Exception ex)
@ -295,52 +332,49 @@ public class APIHelper : IAPIHelper
}
public async Task<Dictionary<string, int>?> GetAllSubscriptions(Dictionary<string, string> getParams, string endpoint, bool includeRestricted, IDownloadConfig config)
public async Task<Dictionary<string, long>?> GetAllSubscriptions(Dictionary<string, string> getParams, string endpoint, bool includeRestricted, IDownloadConfig config)
{
try
{
Dictionary<string, int> users = new();
Subscriptions subscriptions = new();
Dictionary<string, long> users = new();
int limit = 25;
int offset = 0;
getParams["limit"] = limit.ToString();
getParams["offset"] = offset.ToString();
Log.Debug("Calling GetAllSubscrptions");
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body);
if (subscriptions != null && subscriptions.hasMore)
while (true)
{
getParams["offset"] = subscriptions.list.Count.ToString();
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, httpClient);
while (true)
if (string.IsNullOrWhiteSpace(body))
break;
Subscriptions? subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body, m_JsonSerializerSettings);
if (subscriptions?.list is null)
break;
foreach (Subscriptions.List item in subscriptions.list)
{
Subscriptions newSubscriptions = new();
string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
if (users.ContainsKey(item.username))
continue;
if (!string.IsNullOrEmpty(loopbody) && (!loopbody.Contains("[]") || loopbody.Trim() != "[]"))
{
newSubscriptions = JsonConvert.DeserializeObject<Subscriptions>(loopbody, m_JsonSerializerSettings);
}
else
{
break;
}
bool isRestricted = item.isRestricted ?? false;
bool isRestrictedButAllowed = isRestricted && includeRestricted;
subscriptions.list.AddRange(newSubscriptions.list);
if (!newSubscriptions.hasMore)
{
break;
}
getParams["offset"] = subscriptions.list.Count.ToString();
if (!isRestricted || isRestrictedButAllowed)
users.Add(item.username, item.id);
}
}
foreach (Subscriptions.List subscription in subscriptions.list)
{
if ((!(subscription.isRestricted ?? false) || ((subscription.isRestricted ?? false) && includeRestricted))
&& !users.ContainsKey(subscription.username))
{
users.Add(subscription.username, subscription.id);
}
if (!subscriptions.hasMore)
break;
offset += limit;
getParams["offset"] = offset.ToString();
}
return users;
@ -359,27 +393,24 @@ public class APIHelper : IAPIHelper
return null;
}
public async Task<Dictionary<string, int>?> GetActiveSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config)
public async Task<Dictionary<string, long>?> GetActiveSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config)
{
Dictionary<string, string> getParams = new()
{
{ "offset", "0" },
{ "limit", "50" },
{ "type", "active" },
{ "format", "infinite"}
};
Log.Debug("Calling GetActiveSubscriptions");
return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config);
}
public async Task<Dictionary<string, int>?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config)
public async Task<Dictionary<string, long>?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config)
{
Dictionary<string, string> getParams = new()
{
{ "offset", "0" },
{ "limit", "50" },
{ "type", "expired" },
{ "format", "infinite"}
};
@ -389,8 +420,88 @@ public class APIHelper : IAPIHelper
return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config);
}
public async Task<Dictionary<string, long>?> GetUsersWithProgress(string typeDisplay, string endpoint, StatusContext ctx, string? typeParam, bool offsetByCount)
{
int limit = 50;
int offset = 0;
bool includeRestricted = true;
public async Task<Dictionary<string, int>> GetLists(string endpoint, IDownloadConfig config)
Dictionary<string, string> getParams = new()
{
["format"] = "infinite",
["limit"] = limit.ToString(),
["offset"] = offset.ToString()
};
if (!string.IsNullOrWhiteSpace(typeParam))
getParams["type"] = typeParam;
try
{
Dictionary<string, long> users = [];
Log.Debug("Calling GetUsersWithProgress");
bool isLastLoop = false;
while (true)
{
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, httpClient);
if (string.IsNullOrWhiteSpace(body))
break;
Subscriptions? subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body, m_JsonSerializerSettings);
if (subscriptions?.list is null)
break;
foreach (Subscriptions.List item in subscriptions.list)
{
if (users.ContainsKey(item.username))
continue;
bool isRestricted = item.isRestricted ?? false;
bool isRestrictedButAllowed = isRestricted && includeRestricted;
if (!isRestricted || isRestrictedButAllowed)
users.Add(item.username, item.id);
}
ctx.Status($"[red]Getting {typeDisplay} Users\n[/] [red]Found {users.Count}[/]");
ctx.Spinner(Spinner.Known.Dots);
ctx.SpinnerStyle(Style.Parse("blue"));
if (isLastLoop)
break;
if (!subscriptions.hasMore || subscriptions.list.Count == 0)
isLastLoop = true;
offset += offsetByCount
? subscriptions.list.Count
: limit;
getParams["offset"] = offset.ToString();
}
return users;
}
catch (Exception ex)
{
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
if (ex.InnerException != null)
{
Console.WriteLine("\nInner Exception:");
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
}
}
return null;
}
public async Task<Dictionary<string, long>> GetLists(string endpoint, IDownloadConfig config)
{
Log.Debug("Calling GetLists");
@ -404,10 +515,10 @@ public class APIHelper : IAPIHelper
{ "limit", "50" },
{ "format", "infinite" }
};
Dictionary<string, int> lists = new();
Dictionary<string, long> lists = new();
while (true)
{
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
if (body == null)
{
@ -472,7 +583,7 @@ public class APIHelper : IAPIHelper
while (true)
{
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
if (body == null)
{
break;
@ -516,6 +627,66 @@ public class APIHelper : IAPIHelper
}
public async Task<Dictionary<string, long>?> GetUsersFromList(string endpoint, bool includeRestricted, IDownloadConfig config)
{
var model = new { list = new[] { new { id = int.MaxValue, username = string.Empty, isRestricted = false, isBlocked = false } }, nextOffset = 0, hasMore = false };
Log.Debug($"Calling GetUsersFromList - {endpoint}");
int limit = 50;
int offset = 0;
Dictionary<string, string> getParams = new()
{
{ "offset", offset.ToString() },
{ "limit", limit.ToString() },
{ "format", "infinite" }
};
try
{
Dictionary<string, long> users = [];
while (true)
{
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
if (string.IsNullOrWhiteSpace(body))
break;
var data = JsonConvert.DeserializeAnonymousType(body, model, m_JsonSerializerSettings);
if (data is null)
break;
foreach (var item in data.list)
{
if (users.ContainsKey(item.username))
continue;
bool isRestricted = item.isRestricted;
bool isRestrictedButAllowed = isRestricted && includeRestricted;
if (!isRestricted || isRestrictedButAllowed)
users.Add(item.username, item.id);
}
if (!data.hasMore)
break;
offset += data.nextOffset;
getParams["offset"] = offset.ToString();
}
return users;
}
catch (Exception ex)
{
throw;
}
}
public async Task<Dictionary<long, string>> GetMedia(MediaType mediatype,
string endpoint,
string? username,
@ -557,7 +728,7 @@ public class APIHelper : IAPIHelper
break;
}
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
if (mediatype == MediaType.Stories)
@ -730,7 +901,7 @@ public class APIHelper : IAPIHelper
}
public async Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx)
public async Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, long userId, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx)
{
Log.Debug($"Calling GetPaidPosts - {username}");
@ -739,13 +910,13 @@ public class APIHelper : IAPIHelper
Purchased paidPosts = new();
PaidPostCollection paidPostCollection = new();
int post_limit = 50;
int offset = 0;
Dictionary<string, string> getParams = new()
{
{ "limit", post_limit.ToString() },
{ "skip_users", "all" },
{ "order", "publish_date_desc" },
{ "format", "infinite" },
{ "author", username }
{ "author", username },
};
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
@ -755,9 +926,10 @@ public class APIHelper : IAPIHelper
ctx.SpinnerStyle(Style.Parse("blue"));
if (paidPosts != null && paidPosts.hasMore)
{
getParams["offset"] = paidPosts.list.Count.ToString();
while (true)
{
offset += post_limit;
getParams["offset"] = offset.ToString();
Purchased newPaidPosts = new();
@ -772,7 +944,6 @@ public class APIHelper : IAPIHelper
{
break;
}
getParams["offset"] = Convert.ToString(Convert.ToInt32(getParams["offset"]) + post_limit);
}
}
@ -781,6 +952,9 @@ public class APIHelper : IAPIHelper
{
if (purchase.responseType == "post" && purchase.media != null && purchase.media.Count > 0)
{
if (purchase.fromUser.id != userId)
continue; // Ensures only posts from current model are included
List<long> previewids = new();
if (purchase.previews != null)
{
@ -938,7 +1112,7 @@ public class APIHelper : IAPIHelper
ref getParams,
downloadAsOf);
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
posts = JsonConvert.DeserializeObject<Post>(body, m_JsonSerializerSettings);
ctx.Status($"[red]Getting Posts (this may take a long time, depending on the number of Posts the creator has)\n[/] [red]Found {posts.list.Count}[/]");
ctx.Spinner(Spinner.Known.Dots);
@ -1096,7 +1270,7 @@ public class APIHelper : IAPIHelper
{ "skip_users", "all" }
};
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
singlePost = JsonConvert.DeserializeObject<SinglePost>(body, m_JsonSerializerSettings);
if (singlePost != null)
@ -1156,7 +1330,7 @@ public class APIHelper : IAPIHelper
}
break;
case VideoResolution._240:
if(medium.videoSources != null)
if (medium.videoSources != null)
{
if (!string.IsNullOrEmpty(medium.videoSources._240))
{
@ -1258,7 +1432,7 @@ public class APIHelper : IAPIHelper
ref getParams,
config.CustomDate);
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
streams = JsonConvert.DeserializeObject<Streams>(body, m_JsonSerializerSettings);
ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]");
ctx.Spinner(Spinner.Known.Dots);
@ -1532,7 +1706,7 @@ public class APIHelper : IAPIHelper
{
{ "limit", post_limit.ToString() },
{ "order", "desc" },
{ "skip_users", "all" }
{ "skip_users", "all" },
};
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
@ -1542,9 +1716,10 @@ public class APIHelper : IAPIHelper
ctx.SpinnerStyle(Style.Parse("blue"));
if (messages.hasMore)
{
getParams["id"] = messages.list[^1].id.ToString();
while (true)
{
getParams["id"] = messages.list[^1].id.ToString();
Messages newmessages = new();
var loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
@ -1558,7 +1733,6 @@ public class APIHelper : IAPIHelper
{
break;
}
getParams["id"] = newmessages.list[newmessages.list.Count - 1].id.ToString();
}
}
@ -1834,7 +2008,7 @@ public class APIHelper : IAPIHelper
}
public async Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, IDownloadConfig config, StatusContext ctx)
public async Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, long userId, IDownloadConfig config, StatusContext ctx)
{
Log.Debug($"Calling GetPaidMessages - {username}");
@ -1843,13 +2017,14 @@ public class APIHelper : IAPIHelper
Purchased paidMessages = new();
PaidMessageCollection paidMessageCollection = new();
int post_limit = 50;
int offset = 0;
Dictionary<string, string> getParams = new()
{
{ "limit", post_limit.ToString() },
{ "order", "publish_date_desc" },
{ "skip_users", "all" },
{ "format", "infinite" },
{ "offset", offset.ToString() },
{ "author", username },
{ "skip_users", "all" }
};
var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
@ -1859,9 +2034,11 @@ public class APIHelper : IAPIHelper
ctx.SpinnerStyle(Style.Parse("blue"));
if (paidMessages != null && paidMessages.hasMore)
{
getParams["offset"] = paidMessages.list.Count.ToString();
while (true)
{
offset += post_limit;
getParams["offset"] = offset.ToString();
string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
Purchased newpaidMessages = new();
Dictionary<string, string> loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams);
@ -1873,12 +2050,14 @@ public class APIHelper : IAPIHelper
{
looprequest.Headers.Add(keyValuePair.Key, keyValuePair.Value);
}
using (var loopresponse = await loopclient.SendAsync(looprequest))
{
loopresponse.EnsureSuccessStatusCode();
var loopbody = await loopresponse.Content.ReadAsStringAsync();
newpaidMessages = JsonConvert.DeserializeObject<Purchased>(loopbody, m_JsonSerializerSettings);
}
paidMessages.list.AddRange(newpaidMessages.list);
ctx.Status($"[red]Getting Paid Messages\n[/] [red]Found {paidMessages.list.Count}[/]");
ctx.Spinner(Spinner.Known.Dots);
@ -1887,16 +2066,21 @@ public class APIHelper : IAPIHelper
{
break;
}
getParams["offset"] = Convert.ToString(Convert.ToInt32(getParams["offset"]) + post_limit);
}
}
if (paidMessages.list != null && paidMessages.list.Count > 0)
{
long ownUserId = Convert.ToInt64(auth.USER_ID);
long[] validUserIds = [ownUserId, userId];
foreach (Purchased.List purchase in paidMessages.list.Where(p => p.responseType == "message").OrderByDescending(p => p.postedAt ?? p.createdAt))
{
if (!config.IgnoreOwnMessages || purchase.fromUser.id != Convert.ToInt32(auth.USER_ID))
if (!config.IgnoreOwnMessages || purchase.fromUser.id != ownUserId)
{
if (!validUserIds.Contains(purchase.fromUser.id))
continue; // Ensures only messages from current model (or self) are included
if (purchase.postedAt != null)
{
await m_DBHelper.AddMessage(folder, purchase.id, purchase.text != null ? purchase.text : string.Empty, purchase.price != null ? purchase.price : "0", true, false, purchase.postedAt.Value, purchase.fromUser.id);
@ -2067,13 +2251,13 @@ public class APIHelper : IAPIHelper
return null;
}
public async Task<Dictionary<string, int>> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary<string, int> users)
public async Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary<string, long> users)
{
Log.Debug($"Calling GetPurchasedTabUsers - {endpoint}");
try
{
Dictionary<string, int> purchasedTabUsers = new();
Dictionary<string, long> purchasedTabUsers = new();
Purchased purchased = new();
int post_limit = 50;
Dictionary<string, string> getParams = new()
@ -2144,11 +2328,11 @@ public class APIHelper : IAPIHelper
{
JObject user = await GetUserInfoById($"/users/list?x[]={purchase.fromUser.id}");
if(user is null)
if (user is null)
{
if (!config.BypassContentForCreatorsWhoNoLongerExist)
{
if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}"))
if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}"))
{
purchasedTabUsers.Add($"Deleted User - {purchase.fromUser.id}", purchase.fromUser.id);
}
@ -2198,7 +2382,7 @@ public class APIHelper : IAPIHelper
{
if (!config.BypassContentForCreatorsWhoNoLongerExist)
{
if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}"))
if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}"))
{
purchasedTabUsers.Add($"Deleted User - {purchase.author.id}", purchase.author.id);
}
@ -2240,7 +2424,7 @@ public class APIHelper : IAPIHelper
return null;
}
public async Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary<string, int> users)
public async Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary<string, long> users)
{
Log.Debug($"Calling GetPurchasedTab - {endpoint}");
@ -2596,6 +2780,94 @@ public class APIHelper : IAPIHelper
return null;
}
public async Task<ChatCollection> GetChats(string endpoint, IDownloadConfig config, bool onlyUnread)
{
Log.Debug($"Calling GetChats - {endpoint}");
try
{
Chats chats = new();
ChatCollection collection = new();
int limit = 60;
Dictionary<string, string> getParams = new()
{
{ "limit", $"{limit}" },
{ "offset", "0" },
{ "skip_users", "all" },
{ "order", "recent" }
};
if (onlyUnread)
getParams["filter"] = "unread";
string body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
chats = JsonConvert.DeserializeObject<Chats>(body, m_JsonSerializerSettings);
if (chats.hasMore)
{
getParams["offset"] = $"{chats.nextOffset}";
while (true)
{
string loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config));
Chats newChats = JsonConvert.DeserializeObject<Chats>(loopbody, m_JsonSerializerSettings);
chats.list.AddRange(newChats.list);
if (!newChats.hasMore)
break;
getParams["offset"] = $"{newChats.nextOffset}";
}
}
foreach (Chats.Chat chat in chats.list)
collection.Chats.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)
{
@ -2711,7 +2983,7 @@ public class APIHelper : IAPIHelper
using var response = await client.SendAsync(request);
Log.Debug($"CDRM Project Response (Attempt {attempt}): {response.Content.ReadAsStringAsync().Result}");
Log.Debug($"CDRM Project Response (Attempt {attempt}): {await response.Content.ReadAsStringAsync()}");
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();

View File

@ -1,18 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OF_DL.Enumurations;
using System.IO;
using Microsoft.Data.Sqlite;
using Serilog;
using OF_DL.Entities;
using Serilog;
using System.Text;
namespace OF_DL.Helpers
{
public class DBHelper : IDBHelper
{
private static readonly Dictionary<string, SqliteConnection> _connections = [];
private readonly IDownloadConfig downloadConfig;
public DBHelper(IDownloadConfig downloadConfig)
@ -32,9 +28,7 @@ namespace OF_DL.Helpers
string dbFilePath = $"{folder}/Metadata/user_data.db";
// connect to the new database file
using SqliteConnection connection = new($"Data Source={dbFilePath}");
// open the connection
connection.Open();
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={dbFilePath}");
// create the 'medias' table
using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS medias (id INTEGER NOT NULL, media_id INTEGER, post_id INTEGER NOT NULL, link VARCHAR, directory VARCHAR, filename VARCHAR, size INTEGER, api_type VARCHAR, media_type VARCHAR, preview INTEGER, linked VARCHAR, downloaded INTEGER, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(media_id));", connection))
@ -135,43 +129,43 @@ namespace OF_DL.Helpers
}
}
public async Task CreateUsersDB(Dictionary<string, int> users)
public async Task CreateUsersDB(Dictionary<string, long> users)
{
try
{
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
Log.Debug("Database data source: " + connection.DataSource);
connection.Open();
using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS users (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, PRIMARY KEY(id), UNIQUE(username));", connection))
using (SqliteCommand cmdUsers = new("CREATE TABLE IF NOT EXISTS users (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, PRIMARY KEY(id), UNIQUE(username));", connection))
{
await cmd.ExecuteNonQueryAsync();
await cmdUsers.ExecuteNonQueryAsync();
}
using (SqliteCommand cmdInfo = new("CREATE TABLE IF NOT EXISTS user_info (user_id INTEGER NOT NULL, name VARCHAR NOT NULL, about VARCHAR NULL, expires_on TIMESTAMP NULL, photo_count INT NOT NULL, video_count INT NOT NULL, PRIMARY KEY(user_id));", connection))
{
await cmdInfo.ExecuteNonQueryAsync();
}
Log.Debug("Adding missing creators");
foreach (KeyValuePair<string, int> user in users)
foreach (KeyValuePair<string, long> user in users)
{
using (SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection))
using SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection);
checkCmd.Parameters.AddWithValue("@userId", user.Value);
using var reader = await checkCmd.ExecuteReaderAsync();
if (!reader.Read())
{
checkCmd.Parameters.AddWithValue("@userId", user.Value);
using (var reader = await checkCmd.ExecuteReaderAsync())
{
if (!reader.Read())
{
using (SqliteCommand insertCmd = new($"INSERT INTO users (user_id, username) VALUES (@userId, @username);", connection))
{
insertCmd.Parameters.AddWithValue("@userId", user.Value);
insertCmd.Parameters.AddWithValue("@username", user.Key);
await insertCmd.ExecuteNonQueryAsync();
Log.Debug("Inserted new creator: " + user.Key);
}
}
else
{
Log.Debug("Creator " + user.Key + " already exists");
}
}
using SqliteCommand insertCmd = new($"INSERT INTO users (user_id, username) VALUES (@userId, @username);", connection);
insertCmd.Parameters.AddWithValue("@userId", user.Value);
insertCmd.Parameters.AddWithValue("@username", user.Key);
await insertCmd.ExecuteNonQueryAsync();
Log.Debug("Inserted new creator: " + user.Key);
}
else
{
Log.Debug("Creator " + user.Key + " already exists");
}
}
@ -190,13 +184,11 @@ namespace OF_DL.Helpers
}
}
public async Task CheckUsername(KeyValuePair<string, int> user, string path)
public async Task CheckUsername(KeyValuePair<string, long> user, string path)
{
try
{
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
connection.Open();
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
using (SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection))
{
@ -243,12 +235,64 @@ namespace OF_DL.Helpers
}
}
public async Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, int user_id)
public async Task<Dictionary<string, long>> GetUsers()
{
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
using SqliteCommand cmd = new("SELECT user_id, username FROM users", connection);
using SqliteDataReader reader = cmd.ExecuteReader();
Dictionary<string, long> result = new(StringComparer.OrdinalIgnoreCase);
while (reader.Read())
{
long userId = reader.GetInt64(0);
string username = reader.GetString(1);
result[username] = userId;
}
return result;
}
public async Task UpdateUserInfo(string username, User? user)
{
if (user?.id is null)
return;
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db");
Log.Debug("Database data source: " + connection.DataSource);
//"SELECT name, about, expires_on, photo_count, video_count FROM user_info WHERE user_id = @userId"
using SqliteCommand cmdInfo = new(
"INSERT OR REPLACE INTO user_info (user_id, name, about, expires_on, photo_count, video_count) " +
"VALUES (@userId, @name, @about, @expiresOn, @photoCount, @videoCount);",
connection
);
cmdInfo.Parameters.AddWithValue("@userId", user.id);
cmdInfo.Parameters.AddWithValue("@name", user.name ?? user.username ?? username);
cmdInfo.Parameters.AddWithValue("@about", user.about);
cmdInfo.Parameters.AddWithValue("@expiresOn", user.subscribedByExpireDate);
cmdInfo.Parameters.AddWithValue("@photoCount", user.photosCount ?? 0);
cmdInfo.Parameters.AddWithValue("@videoCount", user.videosCount ?? 0);
try
{
int rowCount = await cmdInfo.ExecuteNonQueryAsync();
Log.Debug("Inserted or updated creator info: {Username:l}", user.username);
}
catch (Exception ex)
{
Log.Warning(ex, "Failed to update User Info for: {Username:l}", user.username);
}
}
public async Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, long user_id)
{
try
{
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open();
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
await EnsureCreatedAtColumnExists(connection, "messages");
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection);
cmd.Parameters.AddWithValue("@post_id", post_id);
@ -286,8 +330,8 @@ namespace OF_DL.Helpers
{
try
{
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open();
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
await EnsureCreatedAtColumnExists(connection, "posts");
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection);
cmd.Parameters.AddWithValue("@post_id", post_id);
@ -324,8 +368,8 @@ namespace OF_DL.Helpers
{
try
{
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open();
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
await EnsureCreatedAtColumnExists(connection, "stories");
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM stories WHERE post_id=@post_id", connection);
cmd.Parameters.AddWithValue("@post_id", post_id);
@ -362,8 +406,8 @@ namespace OF_DL.Helpers
{
try
{
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open();
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
await EnsureCreatedAtColumnExists(connection, "medias");
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
if (downloadConfig.DownloadDuplicatedMedia)
@ -400,22 +444,21 @@ namespace OF_DL.Helpers
{
try
{
bool downloaded = false;
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id");
if (downloadConfig.DownloadDuplicatedMedia)
{
StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id");
if(downloadConfig.DownloadDuplicatedMedia)
{
sql.Append(" and api_type=@api_type");
}
connection.Open();
using SqliteCommand cmd = new (sql.ToString(), connection);
cmd.Parameters.AddWithValue("@media_id", media_id);
cmd.Parameters.AddWithValue("@api_type", api_type);
downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync());
sql.Append(" and api_type=@api_type");
}
connection.Open();
using SqliteCommand cmd = new(sql.ToString(), connection);
cmd.Parameters.AddWithValue("@media_id", media_id);
cmd.Parameters.AddWithValue("@api_type", api_type);
bool downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync());
return downloaded;
}
catch (Exception ex)
@ -435,8 +478,7 @@ namespace OF_DL.Helpers
public async Task UpdateMedia(string folder, long media_id, string api_type, string directory, string filename, long size, bool downloaded, DateTime created_at)
{
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
connection.Open();
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
// Construct the update command
StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id");
@ -463,25 +505,21 @@ namespace OF_DL.Helpers
public async Task<long> GetStoredFileSize(string folder, long media_id, string api_type)
{
long size;
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
{
connection.Open();
using SqliteCommand cmd = new($"SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", connection);
cmd.Parameters.AddWithValue("@media_id", media_id);
cmd.Parameters.AddWithValue("@api_type", api_type);
size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
}
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
using SqliteCommand cmd = new($"SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", connection);
cmd.Parameters.AddWithValue("@media_id", media_id);
cmd.Parameters.AddWithValue("@api_type", api_type);
long size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
return size;
}
public async Task<DateTime?> GetMostRecentPostDate(string folder)
{
DateTime? mostRecentDate = null;
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
{
connection.Open();
using SqliteCommand cmd = new(@"
SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db");
using SqliteCommand cmd = new(@"
SELECT
MIN(created_at) AS created_at
FROM (
@ -495,15 +533,16 @@ namespace OF_DL.Helpers
FROM posts AS P
INNER JOIN medias AS m
ON P.post_id = m.post_id
WHERE m.downloaded = 0
WHERE m.downloaded = 0
)", connection);
var scalarValue = await cmd.ExecuteScalarAsync();
if(scalarValue != null && scalarValue != DBNull.Value)
{
mostRecentDate = Convert.ToDateTime(scalarValue);
}
var scalarValue = await cmd.ExecuteScalarAsync();
if (scalarValue != null && scalarValue != DBNull.Value)
{
return Convert.ToDateTime(scalarValue);
}
return mostRecentDate;
return null;
}
private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName)
@ -527,5 +566,35 @@ namespace OF_DL.Helpers
await alterCmd.ExecuteNonQueryAsync();
}
}
public static void CloseAllConnections()
{
foreach (SqliteConnection cn in _connections.Values)
{
cn?.Close();
cn?.Dispose();
}
_connections.Clear();
}
private static async Task<SqliteConnection> GetAndOpenConnectionAsync(string connectionString, int numberOfRetries = 2)
{
try
{
SqliteConnection connection = new(connectionString);
connection.Open();
return connection;
}
catch (Exception)
{
if (--numberOfRetries <= 0)
throw;
await Task.Delay(300);
return await GetAndOpenConnectionAsync(connectionString, numberOfRetries);
}
}
}
}

View File

@ -77,9 +77,9 @@ public class DownloadHelper : IDownloadHelper
try
{
string customFileName = string.Empty;
if (!Directory.Exists(folder + path))
if (!Directory.Exists(folder + path))
{
Directory.CreateDirectory(folder + path);
Directory.CreateDirectory(folder + path);
}
string extension = Path.GetExtension(url.Split("?")[0]);
@ -164,7 +164,7 @@ public class DownloadHelper : IDownloadHelper
object? postMedia,
object? author,
string username,
Dictionary<string, int> users,
Dictionary<string, long> users,
IFileNameHelper fileNameHelper,
CustomFileNameOption option)
{
@ -628,9 +628,38 @@ public class DownloadHelper : IDownloadHelper
// 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}\"";
// Configure ffmpeg log level and optional report file location
bool ffmpegDebugLogging = Log.IsEnabled(Serilog.Events.LogEventLevel.Debug);
string logLevelArgs = ffmpegDebugLogging || downloadConfig.LoggingLevel is LoggingLevel.Verbose or LoggingLevel.Debug
? "-loglevel debug -report"
: downloadConfig.LoggingLevel switch
{
LoggingLevel.Information => "-loglevel info",
LoggingLevel.Warning => "-loglevel warning",
LoggingLevel.Error => "-loglevel error",
LoggingLevel.Fatal => "-loglevel fatal",
_ => string.Empty
};
if (logLevelArgs.Contains("-report", StringComparison.OrdinalIgnoreCase))
{
// Direct ffmpeg report files into the same logs directory Serilog uses (relative to current working directory)
string logDir = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "logs"));
Directory.CreateDirectory(logDir);
string ffReportPath = Path.Combine(logDir, "ffmpeg-%p-%t.log"); // ffmpeg will replace %p/%t
Environment.SetEnvironmentVariable("FFREPORT", $"file={ffReportPath}:level=32");
Log.Debug("FFREPORT enabled at: {FFREPORT} (cwd: {Cwd})", Environment.GetEnvironmentVariable("FFREPORT"), Environment.CurrentDirectory);
}
else
{
Environment.SetEnvironmentVariable("FFREPORT", null);
Log.Debug("FFREPORT disabled (cwd: {Cwd})", Environment.CurrentDirectory);
}
string parameters = $"{logLevelArgs} -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}\"".Trim();
Log.Debug($"Calling FFMPEG with Parameters: {parameters}");
@ -661,7 +690,7 @@ public class DownloadHelper : IDownloadHelper
#endregion
#region normal posts
public async Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, int> users)
public async Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, long> users)
{
string path;
if (downloadConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && postInfo?.postedAt is not null)
@ -679,7 +708,7 @@ public class DownloadHelper : IDownloadHelper
return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename);
}
public async Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary<string, int> users)
public async Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary<string, long> users)
{
string path;
if (downloadConfig.FolderPerPost && postInfo != null && postInfo?.id is not null && postInfo?.postedAt is not null)
@ -697,7 +726,7 @@ public class DownloadHelper : IDownloadHelper
return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename);
}
public async Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, int> users)
public async Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users)
{
string path;
if (downloadConfig.FolderPerPost && streamInfo != null && streamInfo?.id is not null && streamInfo?.postedAt is not null)
@ -717,7 +746,7 @@ public class DownloadHelper : IDownloadHelper
}
public async Task<bool> DownloadMessageMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary<string, int> users)
public async Task<bool> DownloadMessageMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary<string, long> users)
{
string path;
if (downloadConfig.FolderPerMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null)
@ -735,7 +764,7 @@ public class DownloadHelper : IDownloadHelper
}
public async Task<bool> DownloadArchivedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Archived.List? messageInfo, Archived.Medium? messageMedia, Archived.Author? author, Dictionary<string, int> users)
public async Task<bool> DownloadArchivedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Archived.List? messageInfo, Archived.Medium? messageMedia, Archived.Author? author, Dictionary<string, long> users)
{
string path = "/Archived/Posts/Free";
Uri uri = new(url);
@ -754,7 +783,7 @@ public class DownloadHelper : IDownloadHelper
return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, filename);
}
public async Task<bool> DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary<string, int> users)
public async Task<bool> DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users)
{
string path;
if (downloadConfig.FolderPerPaidMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null)
@ -771,7 +800,7 @@ public class DownloadHelper : IDownloadHelper
return await CreateDirectoriesAndDownloadMedia(path, url, folder, media_id, api_type, task, filename, resolvedFilename);
}
public async Task<bool> DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, int> users)
public async Task<bool> DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users)
{
string path;
if (downloadConfig.FolderPerPaidMessage && messageInfo != null && messageInfo?.id is not null && messageInfo?.createdAt is not null)
@ -797,7 +826,7 @@ public class DownloadHelper : IDownloadHelper
Purchased.List? messageInfo,
Medium? messageMedia,
Purchased.FromUser? fromUser,
Dictionary<string, int> users)
Dictionary<string, long> users)
{
string path;
if (downloadConfig.FolderPerPaidPost && messageInfo != null && messageInfo?.id is not null && messageInfo?.postedAt is not null)
@ -819,19 +848,19 @@ public class DownloadHelper : IDownloadHelper
{
try
{
string path = $"/Profile";
string path = $"/Profile";
if (!Directory.Exists(folder + path))
if (!Directory.Exists(folder + path))
{
Directory.CreateDirectory(folder + path);
Directory.CreateDirectory(folder + path);
}
if (!string.IsNullOrEmpty(avatarUrl))
{
string avatarpath = $"{path}/Avatars";
if (!Directory.Exists(folder + avatarpath))
if (!Directory.Exists(folder + avatarpath))
{
Directory.CreateDirectory(folder + avatarpath);
Directory.CreateDirectory(folder + avatarpath);
}
List<string> avatarMD5Hashes = WidevineClient.Utils.CalculateFolderMD5(folder + avatarpath);
@ -855,7 +884,7 @@ public class DownloadHelper : IDownloadHelper
memoryStream.Seek(0, SeekOrigin.Begin);
MD5 md5 = MD5.Create();
byte[] hash = md5.ComputeHash(memoryStream);
byte[] hash = await md5.ComputeHashAsync(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
if (!avatarMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
{
@ -872,9 +901,9 @@ public class DownloadHelper : IDownloadHelper
if (!string.IsNullOrEmpty(headerUrl))
{
string headerpath = $"{path}/Headers";
if (!Directory.Exists(folder + headerpath))
if (!Directory.Exists(folder + headerpath))
{
Directory.CreateDirectory(folder + headerpath);
Directory.CreateDirectory(folder + headerpath);
}
List<string> headerMD5Hashes = WidevineClient.Utils.CalculateFolderMD5(folder + headerpath);
@ -898,7 +927,7 @@ public class DownloadHelper : IDownloadHelper
memoryStream.Seek(0, SeekOrigin.Begin);
MD5 md5 = MD5.Create();
byte[] hash = md5.ComputeHash(memoryStream);
byte[] hash = await md5.ComputeHashAsync(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
if (!headerMD5Hashes.Contains(BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant()))
{
@ -965,12 +994,22 @@ public class DownloadHelper : IDownloadHelper
private void OnError(object sender, ConversionErrorEventArgs e)
{
Log.Debug("[{0} => {1}]: Error: {2}\n{3}", e.Input.Name, e.Output.Name, e.Exception.ExitCode, e.Exception.InnerException);
// Guard all fields to avoid NullReference exceptions from FFmpeg.NET
var input = e?.Input?.Name ?? "<none>";
var output = e?.Output?.Name ?? "<none>";
var exitCode = e?.Exception?.ExitCode.ToString() ?? "<unknown>";
var message = e?.Exception?.Message ?? "<no message>";
var inner = e?.Exception?.InnerException?.Message ?? "<no inner>";
Log.Error("FFmpeg failed. Input={Input} Output={Output} ExitCode={ExitCode} Message={Message} Inner={Inner}",
input, output, exitCode, message, inner);
_completionSource?.TrySetResult(false);
}
#region drm posts
public async Task<bool> DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary<string, int> users)
public async Task<bool> DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary<string, long> users)
{
try
{
@ -986,9 +1025,9 @@ public class DownloadHelper : IDownloadHelper
{
path = "/Messages/Free/Videos";
}
if (!Directory.Exists(folder + path))
if (!Directory.Exists(folder + path))
{
Directory.CreateDirectory(folder + path);
Directory.CreateDirectory(folder + path);
}
@ -1078,7 +1117,7 @@ public class DownloadHelper : IDownloadHelper
}
public async Task<bool> DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary<string, int> users)
public async Task<bool> DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users)
{
try
{
@ -1094,9 +1133,9 @@ public class DownloadHelper : IDownloadHelper
{
path = "/Messages/Paid/Videos";
}
if (!Directory.Exists(folder + path))
if (!Directory.Exists(folder + path))
{
Directory.CreateDirectory(folder + path);
Directory.CreateDirectory(folder + path);
}
if (!string.IsNullOrEmpty(filenameFormat) && messageInfo != null && messageMedia != null)
@ -1184,7 +1223,7 @@ public class DownloadHelper : IDownloadHelper
return false;
}
public async Task<bool> DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, int> users)
public async Task<bool> DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users)
{
try
{
@ -1291,7 +1330,7 @@ public class DownloadHelper : IDownloadHelper
}
public async Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, int> users)
public async Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, long> users)
{
try
{
@ -1307,9 +1346,9 @@ public class DownloadHelper : IDownloadHelper
{
path = "/Posts/Free/Videos";
}
if (!Directory.Exists(folder + path))
if (!Directory.Exists(folder + path))
{
Directory.CreateDirectory(folder + path);
Directory.CreateDirectory(folder + path);
}
if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null)
@ -1396,7 +1435,7 @@ public class DownloadHelper : IDownloadHelper
}
return false;
}
public async Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary<string, int> users)
public async Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary<string, long> users)
{
try
{
@ -1412,9 +1451,9 @@ public class DownloadHelper : IDownloadHelper
{
path = "/Posts/Free/Videos";
}
if (!Directory.Exists(folder + path))
if (!Directory.Exists(folder + path))
{
Directory.CreateDirectory(folder + path);
Directory.CreateDirectory(folder + path);
}
if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null)
@ -1475,7 +1514,7 @@ public class DownloadHelper : IDownloadHelper
await m_DBHelper.UpdateMedia(folder, media_id, api_type, folder + path, customFileName + ".mp4", size, true, lastModified);
}
}
if (downloadConfig.ShowScrapeSize)
{
long size = await m_DBHelper.GetStoredFileSize(folder, media_id, api_type);
@ -1501,7 +1540,7 @@ public class DownloadHelper : IDownloadHelper
}
return false;
}
public async Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, int> users)
public async Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users)
{
try
{
@ -1607,7 +1646,7 @@ public class DownloadHelper : IDownloadHelper
return false;
}
public async Task<bool> DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? postInfo, Medium? postMedia, Purchased.FromUser? fromUser, Dictionary<string, int> users)
public async Task<bool> DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Purchased.List? postInfo, Medium? postMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users)
{
try
{
@ -1623,9 +1662,9 @@ public class DownloadHelper : IDownloadHelper
{
path = "/Posts/Paid/Videos";
}
if (!Directory.Exists(folder + path))
if (!Directory.Exists(folder + path))
{
Directory.CreateDirectory(folder + path);
Directory.CreateDirectory(folder + path);
}
@ -1715,7 +1754,7 @@ public class DownloadHelper : IDownloadHelper
}
public async Task<bool> DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Archived.List? postInfo, Archived.Medium? postMedia, Archived.Author? author, Dictionary<string, int> users)
public async Task<bool> DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, Archived.List? postInfo, Archived.Medium? postMedia, Archived.Author? author, Dictionary<string, long> users)
{
try
{
@ -1723,9 +1762,9 @@ public class DownloadHelper : IDownloadHelper
Uri uri = new(url);
string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0];
string path = "/Archived/Posts/Free/Videos";
if (!Directory.Exists(folder + path))
if (!Directory.Exists(folder + path))
{
Directory.CreateDirectory(folder + path);
Directory.CreateDirectory(folder + path);
}
if (!string.IsNullOrEmpty(filenameFormat) && postInfo != null && postMedia != null)

View File

@ -18,7 +18,7 @@ namespace OF_DL.Helpers
this.auth = auth;
}
public async Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, int> users = null)
public async Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, long> users = null)
{
Dictionary<string, string> values = new();
Type type1 = obj1.GetType();
@ -36,7 +36,7 @@ namespace OF_DL.Helpers
{
drmProperty = GetNestedPropertyValue(obj2, "files.drm");
}
if(fileProperty != null && drmProperty != null && propertyName == "mediaCreatedAt")
{
object mpdurl = GetNestedPropertyValue(obj2, "files.drm.manifest.dash");
@ -66,7 +66,7 @@ namespace OF_DL.Helpers
continue;
}
}
}
PropertyInfo? property = Array.Find(properties2, p => p.Name.Equals(propertyName.Replace("media", ""), StringComparison.OrdinalIgnoreCase));
if (property != null)

View File

@ -16,23 +16,24 @@ namespace OF_DL.Helpers
Task<string> GetDecryptionKeyCDM(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
Task<DateTime> GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp);
Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp);
Task<Dictionary<string, int>> GetLists(string endpoint, IDownloadConfig config);
Task<Dictionary<string, long>> GetLists(string endpoint, IDownloadConfig config);
Task<List<string>> GetListUsers(string endpoint, IDownloadConfig config);
Task<Dictionary<string, long>?> GetUsersFromList(string endpoint, bool includeRestricted, IDownloadConfig config);
Task<Dictionary<long, string>> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, IDownloadConfig config, List<long> paid_post_ids);
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, long userId, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
Task<PostCollection> GetPosts(string endpoint, string folder, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
Task<SinglePostCollection> GetPost(string endpoint, string folder, IDownloadConfig config);
Task<StreamsCollection> GetStreams(string endpoint, string folder, IDownloadConfig config, List<long> paid_post_ids, StatusContext ctx);
Task<ArchivedCollection> GetArchived(string endpoint, string folder, IDownloadConfig config, StatusContext ctx);
Task<MessageCollection> GetMessages(string endpoint, string folder, IDownloadConfig config, StatusContext ctx);
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, IDownloadConfig config, StatusContext ctx);
Task<Dictionary<string, int>> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary<string, int> users);
Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary<string, int> users);
Task<User> GetUserInfo(string endpoint);
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, long userId, IDownloadConfig config, StatusContext ctx);
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, IDownloadConfig config, Dictionary<string, long> users);
Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, IDownloadConfig config, Dictionary<string, long> users);
Task<User> GetUserInfo(string username, string endpoint);
Task<JObject> GetUserInfoById(string endpoint);
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);
Task<Dictionary<string, int>> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config);
Task<Dictionary<string, int>> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config);
Task<Dictionary<string, long>> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config);
Task<Dictionary<string, long>> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions, IDownloadConfig config);
Task<string> GetDecryptionKeyOFDL(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
}
}

View File

@ -1,17 +1,20 @@
using OF_DL.Entities;
namespace OF_DL.Helpers
{
public interface IDBHelper
{
Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, int user_id);
Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, long user_id);
Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at);
Task AddStory(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at);
Task CreateDB(string folder);
Task CreateUsersDB(Dictionary<string, int> users);
Task CheckUsername(KeyValuePair<string, int> user, string path);
Task CreateUsersDB(Dictionary<string, long> users);
Task CheckUsername(KeyValuePair<string, long> user, string path);
Task AddMedia(string folder, long media_id, long post_id, string link, string? directory, string? filename, long? size, string api_type, string media_type, bool preview, bool downloaded, DateTime? created_at);
Task UpdateMedia(string folder, long media_id, string api_type, string directory, string filename, long size, bool downloaded, DateTime created_at);
Task<long> GetStoredFileSize(string folder, long media_id, string api_type);
Task<bool> CheckDownloaded(string folder, long media_id, string api_type);
Task<DateTime?> GetMostRecentPostDate(string folder);
Task UpdateUserInfo(string username, User? user);
}
}

View File

@ -12,25 +12,25 @@ namespace OF_DL.Helpers
public interface IDownloadHelper
{
Task<long> CalculateTotalFileSize(List<string> urls);
Task<bool> DownloadArchivedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Archived.List messageInfo, Archived.Medium messageMedia, Archived.Author author, Dictionary<string, int> users);
Task<bool> DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Archived.List postInfo, Archived.Medium postMedia, Archived.Author author, Dictionary<string, int> users);
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary<string, int> users);
Task<bool> DownloadArchivedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Archived.List messageInfo, Archived.Medium messageMedia, Archived.Author author, Dictionary<string, long> users);
Task<bool> DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Archived.List postInfo, Archived.Medium postMedia, Archived.Author author, Dictionary<string, long> users);
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary<string, long> users);
Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username);
Task<bool> DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Messages.List messageInfo, Messages.Medium messageMedia, Messages.FromUser fromUser, Dictionary<string, int> users);
Task<bool> DownloadMessageMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Messages.List messageInfo, Messages.Medium messageMedia, Messages.FromUser fromUser, Dictionary<string, int> users);
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Post.List postInfo, Post.Medium postMedia, Post.Author author, Dictionary<string, int> users);
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, int> users);
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary<string, int> users);
Task<bool> DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, int> users);
Task<bool> DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Messages.List messageInfo, Messages.Medium messageMedia, Messages.FromUser fromUser, Dictionary<string, long> users);
Task<bool> DownloadMessageMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Messages.List messageInfo, Messages.Medium messageMedia, Messages.FromUser fromUser, Dictionary<string, long> users);
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Post.List postInfo, Post.Medium postMedia, Post.Author author, Dictionary<string, long> users);
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, long> users);
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary<string, long> users);
Task<bool> DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, long> users);
Task<bool> DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, int> users);
Task<bool> DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, int> users);
Task<bool> DownloadSinglePurchasedMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users);
Task<bool> DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, long> users);
Task<bool> DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, int> users);
Task<bool> DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List postInfo, Medium postMedia, Purchased.FromUser fromUser, Dictionary<string, int> users);
Task<bool> DownloadPurchasedPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, int> users);
Task<bool> DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users);
Task<bool> DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List postInfo, Medium postMedia, Purchased.FromUser fromUser, Dictionary<string, long> users);
Task<bool> DownloadPurchasedPostMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string filenameFormat, Purchased.List messageInfo, Medium messageMedia, Purchased.FromUser fromUser, Dictionary<string, long> users);
Task<bool> DownloadStoryMedia(string url, string folder, long media_id, string api_type, ProgressTask task);
Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, int> users);
Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Streams.List streamInfo, Streams.Medium streamMedia, Streams.Author author, Dictionary<string, int> users);
Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, ProgressTask task, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users);
Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, ProgressTask task, string filenameFormat, Streams.List streamInfo, Streams.Medium streamMedia, Streams.Author author, Dictionary<string, long> users);
}
}

View File

@ -3,6 +3,6 @@ namespace OF_DL.Helpers
public interface IFileNameHelper
{
Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values);
Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, int> users = null);
Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, long> users = null);
}
}

View File

@ -7,8 +7,15 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ApplicationIcon>Icon\download.ico</ApplicationIcon>
<LangVersion>12</LangVersion>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
</PropertyGroup>
<PropertyGroup>
<NoWarn>CS0168;CS0219;CS0472;CS1998;CS8073;CS8600;CS8602;CS8603;CS8604;CS8605;CS8613;CS8618;CS8622;CS8625;CS8629;SYSLIB0021;AsyncFixer01;AsyncFixer02</NoWarn>
</PropertyGroup>
<ItemGroup>
<Content Include="Icon\download.ico" />
</ItemGroup>
@ -24,6 +31,7 @@
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0"/>
<PackageReference Include="System.Reactive" Version="6.0.1" />
<PackageReference Include="xFFmpeg.NET" Version="7.2.0" />
</ItemGroup>
@ -37,12 +45,15 @@
<ItemGroup>
<None Update="auth.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
<None Update="config.conf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
<None Update="rules.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
</ItemGroup>

File diff suppressed because it is too large Load Diff

33
Publish_OF-DL.bat Normal file
View File

@ -0,0 +1,33 @@
@ECHO OFF
ECHO.
ECHO ==============================
ECHO == Cleaning Output ===========
ECHO ==============================
dotnet clean ".\OF DL\OF DL.csproj" -v minimal
DEL /Q /F ".\Publish"
ECHO.
ECHO ==============================
ECHO == Publishing OF-DL ==========
ECHO ==============================
dotnet publish ".\OF DL\OF DL.csproj" -o ".\Publish" -c Debug
ECHO.
ECHO ==============================
ECHO == Copy to network drive? ====
ECHO ==============================
CHOICE /C yn /m "Copy published files to network drive? "
IF %ERRORLEVEL%==1 (GOTO Copy) ELSE (GOTO Exit)
:Copy
xcopy .\Publish\* p:\_Utils\OF_DL /I /Y /Q /EXCLUDE:.\excludes.txt
:Exit
ECHO.
ECHO.
PAUSE

2
excludes.txt Normal file
View File

@ -0,0 +1,2 @@
excludes.txt
rules.json