forked from sim0n00ps/OF-DL
		
	Compare commits
	
		
			10 Commits
		
	
	
		
			9f4dbccc02
			...
			ddbc98b12c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ddbc98b12c | |||
| 5effa47fac | |||
| 499f13dba9 | |||
| f2dfcd45b0 | |||
| c65b0e0001 | |||
| 117f6d4905 | |||
| 049965b0fb | |||
| cb5f901396 | |||
| 3d2a5fd282 | |||
| 788dbcc2c6 | 
							
								
								
									
										7
									
								
								OF DL/Entities/Chats/ChatCollection.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								OF DL/Entities/Chats/ChatCollection.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | namespace OF_DL.Entities.Chats | ||||||
|  | { | ||||||
|  |     public class ChatCollection | ||||||
|  |     { | ||||||
|  |         public Dictionary<int, Chats.Chat> Chats { get; set; } = []; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								OF DL/Entities/Chats/Chats.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								OF DL/Entities/Chats/Chats.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | namespace OF_DL.Entities.Chats | ||||||
|  | { | ||||||
|  |     public class Chats | ||||||
|  |     { | ||||||
|  |         public List<Chat> list { get; set; } | ||||||
|  |         public bool hasMore { get; set; } | ||||||
|  |         public int nextOffset { get; set; } | ||||||
|  | 
 | ||||||
|  |         public class Chat | ||||||
|  |         { | ||||||
|  |             public User withUser { get; set; } | ||||||
|  |             public int unreadMessagesCount { get; set; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public class User | ||||||
|  |         { | ||||||
|  |             public int id { get; set; } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -103,6 +103,11 @@ namespace OF_DL.Entities | |||||||
| 
 | 
 | ||||||
|         [JsonConverter(typeof(StringEnumConverter))] |         [JsonConverter(typeof(StringEnumConverter))] | ||||||
|         public VideoResolution DownloadVideoResolution { get; set; } = VideoResolution.source; |         public VideoResolution DownloadVideoResolution { get; set; } = VideoResolution.source; | ||||||
|  | 
 | ||||||
|  |         public string[] NonInteractiveSpecificUsers { get; set; } = []; | ||||||
|  |         public string[] NonInteractiveSpecificLists { get; set; } = []; | ||||||
|  | 
 | ||||||
|  |         public bool OutputBlockedUsers { get; set; } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public class CreatorConfig : IFileNameFormatConfig |     public class CreatorConfig : IFileNameFormatConfig | ||||||
|  | |||||||
| @ -98,6 +98,7 @@ namespace OF_DL.Entities | |||||||
|             public object id { get; set; } |             public object id { get; set; } | ||||||
|             public int? userId { get; set; } |             public int? userId { get; set; } | ||||||
|             public int? subscriberId { get; set; } |             public int? subscriberId { get; set; } | ||||||
|  |             public long? earningId { get; set; } | ||||||
|             public DateTime? date { get; set; } |             public DateTime? date { get; set; } | ||||||
|             public int? duration { get; set; } |             public int? duration { get; set; } | ||||||
|             public DateTime? startDate { get; set; } |             public DateTime? startDate { get; set; } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ using Newtonsoft.Json; | |||||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||||
| using OF_DL.Entities; | using OF_DL.Entities; | ||||||
| using OF_DL.Entities.Archived; | using OF_DL.Entities.Archived; | ||||||
|  | using OF_DL.Entities.Chats; | ||||||
| using OF_DL.Entities.Highlights; | using OF_DL.Entities.Highlights; | ||||||
| using OF_DL.Entities.Lists; | using OF_DL.Entities.Lists; | ||||||
| using OF_DL.Entities.Messages; | using OF_DL.Entities.Messages; | ||||||
| @ -29,6 +30,8 @@ public class APIHelper : IAPIHelper | |||||||
|     private readonly IDBHelper m_DBHelper; |     private readonly IDBHelper m_DBHelper; | ||||||
|     private readonly IDownloadConfig downloadConfig; |     private readonly IDownloadConfig downloadConfig; | ||||||
|     private readonly Auth auth; |     private readonly Auth auth; | ||||||
|  |     private HttpClient httpClient = new(); | ||||||
|  | 
 | ||||||
|     private static DateTime? cachedDynamicRulesExpiration; |     private static DateTime? cachedDynamicRulesExpiration; | ||||||
|     private static DynamicRules? cachedDynamicRules; |     private static DynamicRules? cachedDynamicRules; | ||||||
| 
 | 
 | ||||||
| @ -116,11 +119,11 @@ public class APIHelper : IAPIHelper | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private async Task<string?> BuildHeaderAndExecuteRequests(Dictionary<string, string> getParams, string endpoint, HttpClient client) |     private async Task<string?> BuildHeaderAndExecuteRequests(Dictionary<string, string> getParams, string endpoint, HttpClient client, HttpMethod? method = null) | ||||||
|     { |     { | ||||||
|         Log.Debug("Calling BuildHeaderAndExecuteRequests"); |         Log.Debug("Calling BuildHeaderAndExecuteRequests"); | ||||||
| 
 | 
 | ||||||
|         HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint); |         HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint, method); | ||||||
|         using var response = await client.SendAsync(request); |         using var response = await client.SendAsync(request); | ||||||
|         response.EnsureSuccessStatusCode(); |         response.EnsureSuccessStatusCode(); | ||||||
|         string body = await response.Content.ReadAsStringAsync(); |         string body = await response.Content.ReadAsStringAsync(); | ||||||
| @ -131,15 +134,20 @@ public class APIHelper : IAPIHelper | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private async Task<HttpRequestMessage> BuildHttpRequestMessage(Dictionary<string, string> getParams, string endpoint) |     private async Task<HttpRequestMessage> BuildHttpRequestMessage(Dictionary<string, string> getParams, string endpoint, HttpMethod? method = null) | ||||||
|     { |     { | ||||||
|         Log.Debug("Calling BuildHttpRequestMessage"); |         Log.Debug("Calling BuildHttpRequestMessage"); | ||||||
| 
 | 
 | ||||||
|         string queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); |         method ??= HttpMethod.Get; | ||||||
|  | 
 | ||||||
|  |         string queryParams = ""; | ||||||
|  | 
 | ||||||
|  |         if (getParams.Any()) | ||||||
|  |             queryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); | ||||||
| 
 | 
 | ||||||
|         Dictionary<string, string> headers = GetDynamicHeaders($"/api2/v2{endpoint}", queryParams); |         Dictionary<string, string> headers = GetDynamicHeaders($"/api2/v2{endpoint}", queryParams); | ||||||
| 
 | 
 | ||||||
|         HttpRequestMessage request = new(HttpMethod.Get, $"{Constants.API_URL}{endpoint}{queryParams}"); |         HttpRequestMessage request = new(method, $"{Constants.API_URL}{endpoint}{queryParams}"); | ||||||
| 
 | 
 | ||||||
|         Log.Debug($"Full request URL: {Constants.API_URL}{endpoint}{queryParams}"); |         Log.Debug($"Full request URL: {Constants.API_URL}{endpoint}{queryParams}"); | ||||||
| 
 | 
 | ||||||
| @ -164,18 +172,16 @@ public class APIHelper : IAPIHelper | |||||||
|         return input.All(char.IsDigit); |         return input.All(char.IsDigit); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     private HttpClient GetHttpClient(IDownloadConfig? config = null) | ||||||
|     private static HttpClient GetHttpClient(IDownloadConfig? config = null) |  | ||||||
|     { |     { | ||||||
|         var client = new HttpClient(); |         httpClient ??= new HttpClient(); | ||||||
|         if (config?.Timeout != null && config.Timeout > 0) |         if (config?.Timeout != null && config.Timeout > 0) | ||||||
|         { |         { | ||||||
|             client.Timeout = TimeSpan.FromSeconds(config.Timeout.Value); |             httpClient.Timeout = TimeSpan.FromSeconds(config.Timeout.Value); | ||||||
|         } |         } | ||||||
|         return client; |         return httpClient; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// this one is used during initialization only |     /// this one is used during initialization only | ||||||
|     /// if the config option is not available then no modificatiotns will be done on the getParams |     /// if the config option is not available then no modificatiotns will be done on the getParams | ||||||
| @ -302,7 +308,7 @@ public class APIHelper : IAPIHelper | |||||||
| 
 | 
 | ||||||
|             Log.Debug("Calling GetAllSubscrptions"); |             Log.Debug("Calling GetAllSubscrptions"); | ||||||
| 
 | 
 | ||||||
|             string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); |             string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, httpClient); | ||||||
| 
 | 
 | ||||||
|             subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body); |             subscriptions = JsonConvert.DeserializeObject<Subscriptions>(body); | ||||||
|             if (subscriptions != null && subscriptions.hasMore) |             if (subscriptions != null && subscriptions.hasMore) | ||||||
| @ -312,7 +318,7 @@ public class APIHelper : IAPIHelper | |||||||
|                 while (true) |                 while (true) | ||||||
|                 { |                 { | ||||||
|                     Subscriptions newSubscriptions = new(); |                     Subscriptions newSubscriptions = new(); | ||||||
|                     string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); |                     string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, httpClient); | ||||||
| 
 | 
 | ||||||
|                     if (!string.IsNullOrEmpty(loopbody) && (!loopbody.Contains("[]") || loopbody.Trim() != "[]")) |                     if (!string.IsNullOrEmpty(loopbody) && (!loopbody.Contains("[]") || loopbody.Trim() != "[]")) | ||||||
|                     { |                     { | ||||||
| @ -334,11 +340,14 @@ public class APIHelper : IAPIHelper | |||||||
| 
 | 
 | ||||||
|             foreach (Subscriptions.List subscription in subscriptions.list) |             foreach (Subscriptions.List subscription in subscriptions.list) | ||||||
|             { |             { | ||||||
|                 if ((!(subscription.isRestricted ?? false) || ((subscription.isRestricted ?? false) && includeRestricted)) |                 if (users.ContainsKey(subscription.username)) | ||||||
|                     && !users.ContainsKey(subscription.username)) |                     continue; | ||||||
|                 { | 
 | ||||||
|  |                 bool isRestricted = subscription.isRestricted ?? false; | ||||||
|  |                 bool isRestrictedButAllowed = isRestricted && includeRestricted; | ||||||
|  | 
 | ||||||
|  |                 if (!isRestricted || isRestrictedButAllowed) | ||||||
|                     users.Add(subscription.username, subscription.id); |                     users.Add(subscription.username, subscription.id); | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return users; |             return users; | ||||||
| @ -373,7 +382,6 @@ public class APIHelper : IAPIHelper | |||||||
| 
 | 
 | ||||||
|     public async Task<Dictionary<string, int>?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config) |     public async Task<Dictionary<string, int>?> GetExpiredSubscriptions(string endpoint, bool includeRestricted, IDownloadConfig config) | ||||||
|     { |     { | ||||||
| 
 |  | ||||||
|         Dictionary<string, string> getParams = new() |         Dictionary<string, string> getParams = new() | ||||||
|         { |         { | ||||||
|             { "offset", "0" }, |             { "offset", "0" }, | ||||||
| @ -387,6 +395,20 @@ public class APIHelper : IAPIHelper | |||||||
|         return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config); |         return await GetAllSubscriptions(getParams, endpoint, includeRestricted, config); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public async Task<Dictionary<string, int>?> GetBlockedUsers(string endpoint, IDownloadConfig config) | ||||||
|  |     { | ||||||
|  |         Dictionary<string, string> getParams = new() | ||||||
|  |         { | ||||||
|  |             { "offset", "0" }, | ||||||
|  |             { "limit", "50" }, | ||||||
|  |             { "type", "expired" }, | ||||||
|  |             { "format", "infinite"} | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         Log.Debug("Calling GetBlockedUsers"); | ||||||
|  | 
 | ||||||
|  |         return await GetAllSubscriptions(getParams, endpoint, true, config); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public async Task<Dictionary<string, int>> GetLists(string endpoint, IDownloadConfig config) |     public async Task<Dictionary<string, int>> GetLists(string endpoint, IDownloadConfig config) | ||||||
|     { |     { | ||||||
| @ -405,7 +427,7 @@ public class APIHelper : IAPIHelper | |||||||
|             Dictionary<string, int> lists = new(); |             Dictionary<string, int> lists = new(); | ||||||
|             while (true) |             while (true) | ||||||
|             { |             { | ||||||
|                 string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); |                 string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); | ||||||
| 
 | 
 | ||||||
|                 if (body == null) |                 if (body == null) | ||||||
|                 { |                 { | ||||||
| @ -470,7 +492,7 @@ public class APIHelper : IAPIHelper | |||||||
| 
 | 
 | ||||||
|             while (true) |             while (true) | ||||||
|             { |             { | ||||||
|                 var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); |                 var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); | ||||||
|                 if (body == null) |                 if (body == null) | ||||||
|                 { |                 { | ||||||
|                     break; |                     break; | ||||||
| @ -553,7 +575,7 @@ public class APIHelper : IAPIHelper | |||||||
|                     break; |                     break; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); |             var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|             if (mediatype == MediaType.Stories) |             if (mediatype == MediaType.Stories) | ||||||
| @ -932,7 +954,7 @@ public class APIHelper : IAPIHelper | |||||||
|                 ref getParams, |                 ref getParams, | ||||||
|                 downloadAsOf); |                 downloadAsOf); | ||||||
| 
 | 
 | ||||||
|             var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); |             var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); | ||||||
|             posts = JsonConvert.DeserializeObject<Post>(body, m_JsonSerializerSettings); |             posts = JsonConvert.DeserializeObject<Post>(body, m_JsonSerializerSettings); | ||||||
|             ctx.Status($"[red]Getting Posts (this may take a long time, depending on the number of Posts the creator has)\n[/] [red]Found {posts.list.Count}[/]"); |             ctx.Status($"[red]Getting Posts (this may take a long time, depending on the number of Posts the creator has)\n[/] [red]Found {posts.list.Count}[/]"); | ||||||
|             ctx.Spinner(Spinner.Known.Dots); |             ctx.Spinner(Spinner.Known.Dots); | ||||||
| @ -1090,7 +1112,7 @@ public class APIHelper : IAPIHelper | |||||||
|                 { "skip_users", "all" } |                 { "skip_users", "all" } | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); |             var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); | ||||||
|             singlePost = JsonConvert.DeserializeObject<SinglePost>(body, m_JsonSerializerSettings); |             singlePost = JsonConvert.DeserializeObject<SinglePost>(body, m_JsonSerializerSettings); | ||||||
| 
 | 
 | ||||||
|             if (singlePost != null) |             if (singlePost != null) | ||||||
| @ -1251,7 +1273,7 @@ public class APIHelper : IAPIHelper | |||||||
|                 ref getParams, |                 ref getParams, | ||||||
|                 config.CustomDate); |                 config.CustomDate); | ||||||
| 
 | 
 | ||||||
|             var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); |             var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); | ||||||
|             streams = JsonConvert.DeserializeObject<Streams>(body, m_JsonSerializerSettings); |             streams = JsonConvert.DeserializeObject<Streams>(body, m_JsonSerializerSettings); | ||||||
|             ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]"); |             ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]"); | ||||||
|             ctx.Spinner(Spinner.Known.Dots); |             ctx.Spinner(Spinner.Known.Dots); | ||||||
| @ -2134,11 +2156,11 @@ public class APIHelper : IAPIHelper | |||||||
|                         { |                         { | ||||||
|                             JObject user = await GetUserInfoById($"/users/list?x[]={purchase.fromUser.id}"); |                             JObject user = await GetUserInfoById($"/users/list?x[]={purchase.fromUser.id}"); | ||||||
| 
 | 
 | ||||||
|                             if(user is null) |                             if (user is null) | ||||||
|                             { |                             { | ||||||
|                                 if (!config.BypassContentForCreatorsWhoNoLongerExist) |                                 if (!config.BypassContentForCreatorsWhoNoLongerExist) | ||||||
|                                 { |                                 { | ||||||
|                                     if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}")) |                                     if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.fromUser.id}")) | ||||||
|                                     { |                                     { | ||||||
|                                         purchasedTabUsers.Add($"Deleted User - {purchase.fromUser.id}", purchase.fromUser.id); |                                         purchasedTabUsers.Add($"Deleted User - {purchase.fromUser.id}", purchase.fromUser.id); | ||||||
|                                     } |                                     } | ||||||
| @ -2188,7 +2210,7 @@ public class APIHelper : IAPIHelper | |||||||
|                             { |                             { | ||||||
|                                 if (!config.BypassContentForCreatorsWhoNoLongerExist) |                                 if (!config.BypassContentForCreatorsWhoNoLongerExist) | ||||||
|                                 { |                                 { | ||||||
|                                     if(!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}")) |                                     if (!purchasedTabUsers.ContainsKey($"Deleted User - {purchase.author.id}")) | ||||||
|                                     { |                                     { | ||||||
|                                         purchasedTabUsers.Add($"Deleted User - {purchase.author.id}", purchase.author.id); |                                         purchasedTabUsers.Add($"Deleted User - {purchase.author.id}", purchase.author.id); | ||||||
|                                     } |                                     } | ||||||
| @ -2585,6 +2607,94 @@ public class APIHelper : IAPIHelper | |||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public async Task<ChatCollection> GetChats(string endpoint, IDownloadConfig config, bool onlyUnread) | ||||||
|  |     { | ||||||
|  |         Log.Debug($"Calling GetChats - {endpoint}"); | ||||||
|  | 
 | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             Chats chats = new(); | ||||||
|  |             ChatCollection collection = new(); | ||||||
|  | 
 | ||||||
|  |             int limit = 60; | ||||||
|  |             Dictionary<string, string> getParams = new() | ||||||
|  |             { | ||||||
|  |                 { "limit", $"{limit}" }, | ||||||
|  |                 { "offset", "0" }, | ||||||
|  |                 { "skip_users", "all" }, | ||||||
|  |                 { "order", "recent" } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             if (onlyUnread) | ||||||
|  |                 getParams["filter"] = "unread"; | ||||||
|  | 
 | ||||||
|  |             string body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); | ||||||
|  |             chats = JsonConvert.DeserializeObject<Chats>(body, m_JsonSerializerSettings); | ||||||
|  | 
 | ||||||
|  |             if (chats.hasMore) | ||||||
|  |             { | ||||||
|  |                 getParams["offset"] = $"{chats.nextOffset}"; | ||||||
|  | 
 | ||||||
|  |                 while (true) | ||||||
|  |                 { | ||||||
|  |                     string loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); | ||||||
|  |                     Chats newChats = JsonConvert.DeserializeObject<Chats>(loopbody, m_JsonSerializerSettings); | ||||||
|  | 
 | ||||||
|  |                     chats.list.AddRange(newChats.list); | ||||||
|  | 
 | ||||||
|  |                     if (!newChats.hasMore) | ||||||
|  |                         break; | ||||||
|  | 
 | ||||||
|  |                     getParams["offset"] = $"{newChats.nextOffset}"; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             foreach (Chats.Chat chat in chats.list) | ||||||
|  |                 collection.Chats.Add(chat.withUser.id, chat); | ||||||
|  | 
 | ||||||
|  |             return collection; | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); | ||||||
|  |             Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); | ||||||
|  |             if (ex.InnerException != null) | ||||||
|  |             { | ||||||
|  |                 Console.WriteLine("\nInner Exception:"); | ||||||
|  |                 Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); | ||||||
|  |                 Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async Task MarkAsUnread(string endpoint, IDownloadConfig config) | ||||||
|  |     { | ||||||
|  |         Log.Debug($"Calling MarkAsUnread - {endpoint}"); | ||||||
|  | 
 | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             var result = new { success = false }; | ||||||
|  | 
 | ||||||
|  |             string body = await BuildHeaderAndExecuteRequests([], endpoint, GetHttpClient(config), HttpMethod.Delete); | ||||||
|  |             result = JsonConvert.DeserializeAnonymousType(body, result); | ||||||
|  | 
 | ||||||
|  |             if (result?.success != true) | ||||||
|  |                 Console.WriteLine($"Failed to mark chat as unread! Endpoint: {endpoint}"); | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); | ||||||
|  |             Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); | ||||||
|  |             if (ex.InnerException != null) | ||||||
|  |             { | ||||||
|  |                 Console.WriteLine("\nInner Exception:"); | ||||||
|  |                 Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); | ||||||
|  |                 Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public async Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp) |     public async Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp) | ||||||
|     { |     { | ||||||
|  | |||||||
| @ -1,18 +1,14 @@ | |||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Text; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| using OF_DL.Enumurations; |  | ||||||
| using System.IO; |  | ||||||
| using Microsoft.Data.Sqlite; | using Microsoft.Data.Sqlite; | ||||||
| using Serilog; |  | ||||||
| using OF_DL.Entities; | using OF_DL.Entities; | ||||||
|  | using Serilog; | ||||||
|  | using System.Text; | ||||||
| 
 | 
 | ||||||
| namespace OF_DL.Helpers | namespace OF_DL.Helpers | ||||||
| { | { | ||||||
|     public class DBHelper : IDBHelper |     public class DBHelper : IDBHelper | ||||||
|     { |     { | ||||||
|  |         private static readonly Dictionary<string, SqliteConnection> _connections = []; | ||||||
|  | 
 | ||||||
|         private readonly IDownloadConfig downloadConfig; |         private readonly IDownloadConfig downloadConfig; | ||||||
| 
 | 
 | ||||||
|         public DBHelper(IDownloadConfig downloadConfig) |         public DBHelper(IDownloadConfig downloadConfig) | ||||||
| @ -32,9 +28,7 @@ namespace OF_DL.Helpers | |||||||
|                 string dbFilePath = $"{folder}/Metadata/user_data.db"; |                 string dbFilePath = $"{folder}/Metadata/user_data.db"; | ||||||
| 
 | 
 | ||||||
|                 // connect to the new database file |                 // connect to the new database file | ||||||
|                 using SqliteConnection connection = new($"Data Source={dbFilePath}"); |                 SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={dbFilePath}"); | ||||||
|                 // open the connection |  | ||||||
|                 connection.Open(); |  | ||||||
| 
 | 
 | ||||||
|                 // create the 'medias' table |                 // create the 'medias' table | ||||||
|                 using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS medias (id INTEGER NOT NULL, media_id INTEGER, post_id INTEGER NOT NULL, link VARCHAR, directory VARCHAR, filename VARCHAR, size INTEGER, api_type VARCHAR, media_type VARCHAR, preview INTEGER, linked VARCHAR, downloaded INTEGER, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(media_id));", connection)) |                 using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS medias (id INTEGER NOT NULL, media_id INTEGER, post_id INTEGER NOT NULL, link VARCHAR, directory VARCHAR, filename VARCHAR, size INTEGER, api_type VARCHAR, media_type VARCHAR, preview INTEGER, linked VARCHAR, downloaded INTEGER, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(media_id));", connection)) | ||||||
| @ -139,11 +133,9 @@ namespace OF_DL.Helpers | |||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db"); |                 SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db"); | ||||||
|                 Log.Debug("Database data source: " + connection.DataSource); |                 Log.Debug("Database data source: " + connection.DataSource); | ||||||
| 
 | 
 | ||||||
|                 connection.Open(); |  | ||||||
| 
 |  | ||||||
|                 using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS users (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, PRIMARY KEY(id), UNIQUE(username));", connection)) |                 using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS users (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, PRIMARY KEY(id), UNIQUE(username));", connection)) | ||||||
|                 { |                 { | ||||||
|                     await cmd.ExecuteNonQueryAsync(); |                     await cmd.ExecuteNonQueryAsync(); | ||||||
| @ -194,9 +186,7 @@ namespace OF_DL.Helpers | |||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db"); |                 SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={Directory.GetCurrentDirectory()}/users.db"); | ||||||
| 
 |  | ||||||
|                 connection.Open(); |  | ||||||
| 
 | 
 | ||||||
|                 using (SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection)) |                 using (SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection)) | ||||||
|                 { |                 { | ||||||
| @ -247,8 +237,8 @@ namespace OF_DL.Helpers | |||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); |                 SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db"); | ||||||
|                 connection.Open(); | 
 | ||||||
|                 await EnsureCreatedAtColumnExists(connection, "messages"); |                 await EnsureCreatedAtColumnExists(connection, "messages"); | ||||||
|                 using SqliteCommand cmd = new($"SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection); |                 using SqliteCommand cmd = new($"SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection); | ||||||
|                 cmd.Parameters.AddWithValue("@post_id", post_id); |                 cmd.Parameters.AddWithValue("@post_id", post_id); | ||||||
| @ -286,8 +276,8 @@ namespace OF_DL.Helpers | |||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); |                 SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db"); | ||||||
|                 connection.Open(); | 
 | ||||||
|                 await EnsureCreatedAtColumnExists(connection, "posts"); |                 await EnsureCreatedAtColumnExists(connection, "posts"); | ||||||
|                 using SqliteCommand cmd = new($"SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection); |                 using SqliteCommand cmd = new($"SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection); | ||||||
|                 cmd.Parameters.AddWithValue("@post_id", post_id); |                 cmd.Parameters.AddWithValue("@post_id", post_id); | ||||||
| @ -324,8 +314,8 @@ namespace OF_DL.Helpers | |||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); |                 SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db"); | ||||||
|                 connection.Open(); | 
 | ||||||
|                 await EnsureCreatedAtColumnExists(connection, "stories"); |                 await EnsureCreatedAtColumnExists(connection, "stories"); | ||||||
|                 using SqliteCommand cmd = new($"SELECT COUNT(*) FROM stories WHERE post_id=@post_id", connection); |                 using SqliteCommand cmd = new($"SELECT COUNT(*) FROM stories WHERE post_id=@post_id", connection); | ||||||
|                 cmd.Parameters.AddWithValue("@post_id", post_id); |                 cmd.Parameters.AddWithValue("@post_id", post_id); | ||||||
| @ -362,8 +352,8 @@ namespace OF_DL.Helpers | |||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); |                 SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db"); | ||||||
|                 connection.Open(); | 
 | ||||||
|                 await EnsureCreatedAtColumnExists(connection, "medias"); |                 await EnsureCreatedAtColumnExists(connection, "medias"); | ||||||
|                 StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id"); |                 StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id"); | ||||||
|                 if (downloadConfig.DownloadDuplicatedMedia) |                 if (downloadConfig.DownloadDuplicatedMedia) | ||||||
| @ -400,22 +390,21 @@ namespace OF_DL.Helpers | |||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 bool downloaded = false; |                 SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db"); | ||||||
| 
 | 
 | ||||||
|                 using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db")) |                 StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id"); | ||||||
|  |                 if (downloadConfig.DownloadDuplicatedMedia) | ||||||
|                 { |                 { | ||||||
|                     StringBuilder sql = new StringBuilder("SELECT downloaded FROM medias WHERE media_id=@media_id"); |                     sql.Append(" and api_type=@api_type"); | ||||||
|                     if(downloadConfig.DownloadDuplicatedMedia) |  | ||||||
|                     { |  | ||||||
|                         sql.Append(" and api_type=@api_type"); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     connection.Open(); |  | ||||||
|                     using SqliteCommand cmd = new (sql.ToString(), connection); |  | ||||||
|                     cmd.Parameters.AddWithValue("@media_id", media_id); |  | ||||||
|                     cmd.Parameters.AddWithValue("@api_type", api_type); |  | ||||||
|                     downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync()); |  | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 connection.Open(); | ||||||
|  |                 using SqliteCommand cmd = new(sql.ToString(), connection); | ||||||
|  |                 cmd.Parameters.AddWithValue("@media_id", media_id); | ||||||
|  |                 cmd.Parameters.AddWithValue("@api_type", api_type); | ||||||
|  | 
 | ||||||
|  |                 bool downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync()); | ||||||
|  | 
 | ||||||
|                 return downloaded; |                 return downloaded; | ||||||
|             } |             } | ||||||
|             catch (Exception ex) |             catch (Exception ex) | ||||||
| @ -435,8 +424,7 @@ namespace OF_DL.Helpers | |||||||
| 
 | 
 | ||||||
|         public async Task UpdateMedia(string folder, long media_id, string api_type, string directory, string filename, long size, bool downloaded, DateTime created_at) |         public async Task UpdateMedia(string folder, long media_id, string api_type, string directory, string filename, long size, bool downloaded, DateTime created_at) | ||||||
|         { |         { | ||||||
|             using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"); |             SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db"); | ||||||
|             connection.Open(); |  | ||||||
| 
 | 
 | ||||||
|             // Construct the update command |             // Construct the update command | ||||||
|             StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id"); |             StringBuilder sql = new StringBuilder("UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id"); | ||||||
| @ -463,25 +451,21 @@ namespace OF_DL.Helpers | |||||||
| 
 | 
 | ||||||
|         public async Task<long> GetStoredFileSize(string folder, long media_id, string api_type) |         public async Task<long> GetStoredFileSize(string folder, long media_id, string api_type) | ||||||
|         { |         { | ||||||
|             long size; |             SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db"); | ||||||
|             using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db")) | 
 | ||||||
|             { |             using SqliteCommand cmd = new($"SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", connection); | ||||||
|                 connection.Open(); |             cmd.Parameters.AddWithValue("@media_id", media_id); | ||||||
|                 using SqliteCommand cmd = new($"SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", connection); |             cmd.Parameters.AddWithValue("@api_type", api_type); | ||||||
|                 cmd.Parameters.AddWithValue("@media_id", media_id); | 
 | ||||||
|                 cmd.Parameters.AddWithValue("@api_type", api_type); |             long size = Convert.ToInt64(await cmd.ExecuteScalarAsync()); | ||||||
|                 size = Convert.ToInt64(await cmd.ExecuteScalarAsync()); |  | ||||||
|             } |  | ||||||
|             return size; |             return size; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public async Task<DateTime?> GetMostRecentPostDate(string folder) |         public async Task<DateTime?> GetMostRecentPostDate(string folder) | ||||||
|         { |         { | ||||||
|             DateTime? mostRecentDate = null; |             SqliteConnection connection = await GetAndOpenConnectionAsync($"Data Source={folder}/Metadata/user_data.db"); | ||||||
|             using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db")) | 
 | ||||||
|             { |             using SqliteCommand cmd = new(@"
 | ||||||
|                 connection.Open(); |  | ||||||
|                 using SqliteCommand cmd = new(@"
 |  | ||||||
|                     SELECT |                     SELECT | ||||||
| 	                    MIN(created_at) AS created_at | 	                    MIN(created_at) AS created_at | ||||||
|                     FROM  ( |                     FROM  ( | ||||||
| @ -497,13 +481,14 @@ namespace OF_DL.Helpers | |||||||
| 				                    ON P.post_id = m.post_id | 				                    ON P.post_id = m.post_id | ||||||
| 		                    WHERE m.downloaded = 0  | 		                    WHERE m.downloaded = 0  | ||||||
| 	                      )", connection);
 | 	                      )", connection);
 | ||||||
|                 var scalarValue = await cmd.ExecuteScalarAsync(); | 
 | ||||||
|                 if(scalarValue != null && scalarValue != DBNull.Value) |             var scalarValue = await cmd.ExecuteScalarAsync(); | ||||||
|                 { |             if (scalarValue != null && scalarValue != DBNull.Value) | ||||||
|                     mostRecentDate = Convert.ToDateTime(scalarValue); |             { | ||||||
|                 } |                 return Convert.ToDateTime(scalarValue); | ||||||
|             } |             } | ||||||
|             return mostRecentDate; | 
 | ||||||
|  |             return null; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName) |         private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName) | ||||||
| @ -527,5 +512,35 @@ namespace OF_DL.Helpers | |||||||
|                 await alterCmd.ExecuteNonQueryAsync(); |                 await alterCmd.ExecuteNonQueryAsync(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         public static void CloseAllConnections() | ||||||
|  |         { | ||||||
|  |             foreach (SqliteConnection cn in _connections.Values) | ||||||
|  |             { | ||||||
|  |                 cn?.Close(); | ||||||
|  |                 cn?.Dispose(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             _connections.Clear(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static async Task<SqliteConnection> GetAndOpenConnectionAsync(string connectionString, int numberOfRetries = 2) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 SqliteConnection connection = new(connectionString); | ||||||
|  |                 connection.Open(); | ||||||
|  | 
 | ||||||
|  |                 return connection; | ||||||
|  |             } | ||||||
|  |             catch (Exception) | ||||||
|  |             { | ||||||
|  |                 if (--numberOfRetries <= 0) | ||||||
|  |                     throw; | ||||||
|  | 
 | ||||||
|  |                 await Task.Delay(300); | ||||||
|  |                 return await GetAndOpenConnectionAsync(connectionString, numberOfRetries); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										209
									
								
								OF DL/Program.cs
									
									
									
									
									
								
							
							
						
						
									
										209
									
								
								OF DL/Program.cs
									
									
									
									
									
								
							| @ -22,6 +22,8 @@ using static OF_DL.Entities.Messages.Messages; | |||||||
| using Akka.Configuration; | using Akka.Configuration; | ||||||
| using System.Text; | using System.Text; | ||||||
| using static Akka.Actor.ProviderSelection; | using static Akka.Actor.ProviderSelection; | ||||||
|  | using System.Diagnostics; | ||||||
|  | using OF_DL.Entities.Chats; | ||||||
| 
 | 
 | ||||||
| namespace OF_DL; | namespace OF_DL; | ||||||
| 
 | 
 | ||||||
| @ -118,6 +120,8 @@ public class Program | |||||||
| 
 | 
 | ||||||
| 			AnsiConsole.Write(new FigletText("Welcome to OF-DL").Color(Color.Red)); | 			AnsiConsole.Write(new FigletText("Welcome to OF-DL").Color(Color.Red)); | ||||||
| 
 | 
 | ||||||
|  |             ExitIfOtherProcess(); | ||||||
|  | 
 | ||||||
|             //Remove config.json and convert to config.conf |             //Remove config.json and convert to config.conf | ||||||
|             if (File.Exists("config.json")) |             if (File.Exists("config.json")) | ||||||
|             { |             { | ||||||
| @ -474,15 +478,52 @@ public class Program | |||||||
| 
 | 
 | ||||||
| 			if (args is not null && args.Length > 0) | 			if (args is not null && args.Length > 0) | ||||||
| 			{ | 			{ | ||||||
| 				const string NON_INTERACTIVE_ARG = "--non-interactive"; |                 const string NON_INTERACTIVE_ARG = "--non-interactive"; | ||||||
|  |                 const string SPECIFIC_LISTS_ARG = "--specific-lists"; | ||||||
|  |                 const string SPECIFIC_USERS_ARG = "--specific-users"; | ||||||
| 
 | 
 | ||||||
| 				if (args.Any(a => NON_INTERACTIVE_ARG.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase))) |                 if (args.Any(a => NON_INTERACTIVE_ARG.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase))) | ||||||
| 				{ |                 { | ||||||
| 					cliNonInteractive = true; |                     AnsiConsole.Markup($"[grey]Non-Interactive Mode enabled through command-line argument![/]\n"); | ||||||
| 					Log.Debug("NonInteractiveMode set via command line"); |  | ||||||
| 				} |  | ||||||
| 
 | 
 | ||||||
| 				Log.Debug("Additional arguments:"); |                     config.NonInteractiveMode = true; | ||||||
|  | 
 | ||||||
|  |                     int indexOfSpecificListsArg = Array.FindIndex(args, a => a.Contains(SPECIFIC_LISTS_ARG, StringComparison.OrdinalIgnoreCase)); | ||||||
|  |                     int indexOfSpecificUsersArg = Array.FindIndex(args, a => a.Contains(SPECIFIC_USERS_ARG, StringComparison.OrdinalIgnoreCase)); | ||||||
|  |                     char[] separator = [',']; | ||||||
|  | 
 | ||||||
|  |                     if (indexOfSpecificListsArg >= 0) | ||||||
|  |                     { | ||||||
|  |                         int indexOfListValues = indexOfSpecificListsArg + 1; | ||||||
|  | 
 | ||||||
|  |                         string[] strListValues = args.ElementAtOrDefault(indexOfListValues)?.Split(separator, StringSplitOptions.RemoveEmptyEntries) ?? []; | ||||||
|  |                         if (strListValues.Length > 0) | ||||||
|  |                         { | ||||||
|  |                             config.NonInteractiveSpecificLists = strListValues; | ||||||
|  |                             config.NonInteractiveModeListName = string.Empty; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     if (indexOfSpecificUsersArg >= 0) | ||||||
|  |                     { | ||||||
|  |                         int indexOfUserValues = indexOfSpecificUsersArg + 1; | ||||||
|  |                         string[] strUserValues = args.ElementAtOrDefault(indexOfUserValues)?.Split(separator, StringSplitOptions.RemoveEmptyEntries) ?? []; | ||||||
|  |                         if (strUserValues.Length > 0) | ||||||
|  |                         { | ||||||
|  |                             config.NonInteractiveSpecificUsers = strUserValues; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const string OUTPUT_BLOCKED_USERS_ARG = "--output-blocked"; | ||||||
|  | 
 | ||||||
|  |                 if (args.Any(a => OUTPUT_BLOCKED_USERS_ARG.Equals(a, StringComparison.OrdinalIgnoreCase))) | ||||||
|  |                 { | ||||||
|  |                     config.NonInteractiveMode = true; | ||||||
|  |                     config.OutputBlockedUsers = true; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 Log.Debug("Additional arguments:"); | ||||||
| 				foreach (string argument in args) | 				foreach (string argument in args) | ||||||
| 				{ | 				{ | ||||||
| 					Log.Debug(argument); | 					Log.Debug(argument); | ||||||
| @ -774,7 +815,21 @@ public class Program | |||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			AnsiConsole.Markup($"[green]Logged In successfully as {validate.name} {validate.username}\n[/]"); | 			AnsiConsole.Markup($"[green]Logged In successfully as {validate.name} {validate.username}\n[/]"); | ||||||
| 			await DownloadAllData(apiHelper, auth, config); | 
 | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 if (config.OutputBlockedUsers) | ||||||
|  |                 { | ||||||
|  |                     await DownloadBlockedUsers(apiHelper, config); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 await DownloadAllData(apiHelper, auth, config); | ||||||
|  |             } | ||||||
|  |             finally | ||||||
|  |             { | ||||||
|  |                 DBHelper.CloseAllConnections(); | ||||||
|  |             } | ||||||
| 		} | 		} | ||||||
| 		catch (Exception ex) | 		catch (Exception ex) | ||||||
| 		{ | 		{ | ||||||
| @ -795,8 +850,29 @@ public class Program | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |     private static async Task DownloadBlockedUsers(APIHelper m_ApiHelper, Entities.Config Config) | ||||||
|  |     { | ||||||
|  |         const string OUTPUT_FILE = "blocked-users.json"; | ||||||
| 
 | 
 | ||||||
| 	private static async Task DownloadAllData(APIHelper m_ApiHelper, Auth Auth, Entities.Config Config) |         Log.Debug($"Calling GetBlockedUsers"); | ||||||
|  | 
 | ||||||
|  |         AnsiConsole.Markup($"[red]Getting Blocked Users\n[/]"); | ||||||
|  | 
 | ||||||
|  |         Dictionary<string, int>? blockedUsers = await m_ApiHelper.GetBlockedUsers("/users/blocked", Config); | ||||||
|  | 
 | ||||||
|  |         if (blockedUsers is null || blockedUsers.Count == 0) | ||||||
|  |         { | ||||||
|  |             AnsiConsole.Markup($"[red]No Blocked Users found.\n[/]"); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             AnsiConsole.Markup($"[red]Found {blockedUsers.Count} Blocked Users, saving to '{OUTPUT_FILE}'\n[/]"); | ||||||
|  |             string json = JsonConvert.SerializeObject(blockedUsers, Formatting.Indented); | ||||||
|  |             await File.WriteAllTextAsync(OUTPUT_FILE, json); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static async Task DownloadAllData(APIHelper m_ApiHelper, Auth Auth, Entities.Config Config) | ||||||
| 	{ | 	{ | ||||||
| 		DBHelper dBHelper = new DBHelper(Config); | 		DBHelper dBHelper = new DBHelper(Config); | ||||||
| 
 | 
 | ||||||
| @ -805,13 +881,21 @@ public class Program | |||||||
| 		do | 		do | ||||||
| 		{ | 		{ | ||||||
| 			DateTime startTime = DateTime.Now; | 			DateTime startTime = DateTime.Now; | ||||||
| 			Dictionary<string, int> users = new(); |             Dictionary<string, int> users = new(); | ||||||
| 			Dictionary<string, int> activeSubs = await m_ApiHelper.GetActiveSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config); |  | ||||||
| 
 | 
 | ||||||
| 			Log.Debug("Subscriptions: "); |             Task<Dictionary<string, int>?> taskActive = m_ApiHelper.GetActiveSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config); | ||||||
|  |             Task<Dictionary<string, int>?> taskExpired = Config!.IncludeExpiredSubscriptions | ||||||
|  |                 ? m_ApiHelper.GetExpiredSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config) | ||||||
|  |                 : Task.FromResult<Dictionary<string, int>?>([]); | ||||||
| 
 | 
 | ||||||
| 			foreach (KeyValuePair<string, int> activeSub in activeSubs) |             await Task.WhenAll(taskActive, taskExpired); | ||||||
| 			{ | 
 | ||||||
|  |             Dictionary<string, int> subsActive = await taskActive ?? []; | ||||||
|  |             Dictionary<string, int> subsExpired = await taskExpired ?? []; | ||||||
|  | 
 | ||||||
|  |             Log.Debug("Subscriptions: "); | ||||||
|  |             foreach (KeyValuePair<string, int> activeSub in subsActive) | ||||||
|  |             { | ||||||
| 				if (!users.ContainsKey(activeSub.Key)) | 				if (!users.ContainsKey(activeSub.Key)) | ||||||
| 				{ | 				{ | ||||||
| 					users.Add(activeSub.Key, activeSub.Value); | 					users.Add(activeSub.Key, activeSub.Value); | ||||||
| @ -821,11 +905,9 @@ public class Program | |||||||
| 			if (Config!.IncludeExpiredSubscriptions) | 			if (Config!.IncludeExpiredSubscriptions) | ||||||
| 			{ | 			{ | ||||||
| 				Log.Debug("Inactive Subscriptions: "); | 				Log.Debug("Inactive Subscriptions: "); | ||||||
| 
 |                 foreach (KeyValuePair<string, int> expiredSub in subsExpired) | ||||||
| 				Dictionary<string, int> expiredSubs = await m_ApiHelper.GetExpiredSubscriptions("/subscriptions/subscribes", Config.IncludeRestrictedSubscriptions, Config); |                 { | ||||||
| 				foreach (KeyValuePair<string, int> expiredSub in expiredSubs) |                     if (!users.ContainsKey(expiredSub.Key)) | ||||||
| 				{ |  | ||||||
| 					if (!users.ContainsKey(expiredSub.Key)) |  | ||||||
| 					{ | 					{ | ||||||
| 						users.Add(expiredSub.Key, expiredSub.Value); | 						users.Add(expiredSub.Key, expiredSub.Value); | ||||||
| 						Log.Debug($"Name: {expiredSub.Key} ID: {expiredSub.Value}"); | 						Log.Debug($"Name: {expiredSub.Key} ID: {expiredSub.Value}"); | ||||||
| @ -848,15 +930,39 @@ public class Program | |||||||
| 					var ignoredUsernames = await m_ApiHelper.GetListUsers($"/lists/{ignoredUsersListId}/users", Config) ?? []; | 					var ignoredUsernames = await m_ApiHelper.GetListUsers($"/lists/{ignoredUsersListId}/users", Config) ?? []; | ||||||
| 					users = users.Where(x => !ignoredUsernames.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value); | 					users = users.Where(x => !ignoredUsernames.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value); | ||||||
| 				} | 				} | ||||||
| 			} |             } | ||||||
| 
 | 
 | ||||||
| 			await dBHelper.CreateUsersDB(users); |             if (users.Count <= 0) | ||||||
|  |                 throw new InvalidOperationException("No users found!"); | ||||||
|  | 
 | ||||||
|  |             await dBHelper.CreateUsersDB(users); | ||||||
| 			KeyValuePair<bool, Dictionary<string, int>> hasSelectedUsersKVP; | 			KeyValuePair<bool, Dictionary<string, int>> hasSelectedUsersKVP; | ||||||
| 			if(Config.NonInteractiveMode && Config.NonInteractiveModePurchasedTab) | 			if(Config.NonInteractiveMode && Config.NonInteractiveModePurchasedTab) | ||||||
| 			{ | 			{ | ||||||
| 				hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, new Dictionary<string, int> { { "PurchasedTab", 0 } }); | 				hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, new Dictionary<string, int> { { "PurchasedTab", 0 } }); | ||||||
| 			} | 			} | ||||||
| 			else if (Config.NonInteractiveMode && string.IsNullOrEmpty(Config.NonInteractiveModeListName)) |             else if (Config.NonInteractiveMode && Config.NonInteractiveSpecificLists is not null && Config.NonInteractiveSpecificLists.Length > 0) | ||||||
|  |             { | ||||||
|  |                 HashSet<string> listUsernames = []; | ||||||
|  |                 foreach (string listName in Config.NonInteractiveSpecificLists) | ||||||
|  |                 { | ||||||
|  |                     if (!lists.TryGetValue(listName, out int listId)) | ||||||
|  |                         continue; | ||||||
|  | 
 | ||||||
|  |                     List<string> usernames = await m_ApiHelper.GetListUsers($"/lists/{listId}/users", Config); | ||||||
|  |                     foreach (string user in usernames) | ||||||
|  |                         listUsernames.Add(user); | ||||||
|  |                 } | ||||||
|  |                 users = users.Where(x => listUsernames.Contains(x.Key)).Distinct().ToDictionary(x => x.Key, x => x.Value); | ||||||
|  |                 hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, users); | ||||||
|  |             } | ||||||
|  |             else if (Config.NonInteractiveMode && Config.NonInteractiveSpecificUsers is not null && Config.NonInteractiveSpecificUsers.Length > 0) | ||||||
|  |             { | ||||||
|  |                 HashSet<string> usernames = [.. Config.NonInteractiveSpecificUsers]; | ||||||
|  |                 users = users.Where(u => usernames.Contains(u.Key)).ToDictionary(u => u.Key, u => u.Value); | ||||||
|  |                 hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, users); | ||||||
|  |             } | ||||||
|  |             else if (Config.NonInteractiveMode && string.IsNullOrEmpty(Config.NonInteractiveModeListName)) | ||||||
| 			{ | 			{ | ||||||
| 				hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, users); | 				hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, int>>(true, users); | ||||||
| 			} | 			} | ||||||
| @ -986,9 +1092,11 @@ public class Program | |||||||
| 				Log.Debug($"Download path: {p}"); | 				Log.Debug($"Download path: {p}"); | ||||||
| 
 | 
 | ||||||
| 				List<PurchasedTabCollection> purchasedTabCollections = await m_ApiHelper.GetPurchasedTab("/posts/paid", p, Config, users); | 				List<PurchasedTabCollection> purchasedTabCollections = await m_ApiHelper.GetPurchasedTab("/posts/paid", p, Config, users); | ||||||
| 				foreach(PurchasedTabCollection purchasedTabCollection in purchasedTabCollections) |                 int userNum = 1; | ||||||
|  |                 int userCount = purchasedTabCollections.Count; | ||||||
|  |                 foreach (PurchasedTabCollection purchasedTabCollection in purchasedTabCollections) | ||||||
| 				{ | 				{ | ||||||
| 					AnsiConsole.Markup($"[red]\nScraping Data for {purchasedTabCollection.Username}\n[/]"); | 					AnsiConsole.Markup($"[red]\nScraping Data for {purchasedTabCollection.Username} ({userNum++} of {userCount})\n[/]"); | ||||||
| 					string path = ""; | 					string path = ""; | ||||||
| 					if (!string.IsNullOrEmpty(Config.DownloadPath)) | 					if (!string.IsNullOrEmpty(Config.DownloadPath)) | ||||||
| 					{ | 					{ | ||||||
| @ -1097,8 +1205,10 @@ public class Program | |||||||
| 			} | 			} | ||||||
| 			else if (hasSelectedUsersKVP.Key && !hasSelectedUsersKVP.Value.ContainsKey("ConfigChanged")) | 			else if (hasSelectedUsersKVP.Key && !hasSelectedUsersKVP.Value.ContainsKey("ConfigChanged")) | ||||||
| 			{ | 			{ | ||||||
| 				//Iterate over each user in the list of users |                 //Iterate over each user in the list of users | ||||||
| 				foreach (KeyValuePair<string, int> user in hasSelectedUsersKVP.Value) |                 int userNum = 1; | ||||||
|  |                 int userCount = hasSelectedUsersKVP.Value.Count; | ||||||
|  |                 foreach (KeyValuePair<string, int> user in hasSelectedUsersKVP.Value) | ||||||
| 				{ | 				{ | ||||||
| 					int paidPostCount = 0; | 					int paidPostCount = 0; | ||||||
| 					int postCount = 0; | 					int postCount = 0; | ||||||
| @ -1108,7 +1218,7 @@ public class Program | |||||||
| 					int highlightsCount = 0; | 					int highlightsCount = 0; | ||||||
| 					int messagesCount = 0; | 					int messagesCount = 0; | ||||||
| 					int paidMessagesCount = 0; | 					int paidMessagesCount = 0; | ||||||
| 					AnsiConsole.Markup($"[red]\nScraping Data for {user.Key}\n[/]"); | 					AnsiConsole.Markup($"[red]\nScraping Data for {user.Key} ({userNum++} of {userCount})\n[/]"); | ||||||
| 
 | 
 | ||||||
| 					Log.Debug($"Scraping Data for {user.Key}"); | 					Log.Debug($"Scraping Data for {user.Key}"); | ||||||
| 
 | 
 | ||||||
| @ -1393,6 +1503,9 @@ public class Program | |||||||
| 	{ | 	{ | ||||||
| 		Log.Debug($"Calling DownloadMessages - {user.Key}"); | 		Log.Debug($"Calling DownloadMessages - {user.Key}"); | ||||||
| 
 | 
 | ||||||
|  |         AnsiConsole.Markup($"[grey]Getting Unread Chats\n[/]"); | ||||||
|  |         HashSet<int> unreadChats = await GetUsersWithUnreadChats(downloadContext.ApiHelper, downloadContext.DownloadConfig); | ||||||
|  | 
 | ||||||
|         MessageCollection messages = new MessageCollection(); |         MessageCollection messages = new MessageCollection(); | ||||||
| 
 | 
 | ||||||
|         await AnsiConsole.Status() |         await AnsiConsole.Status() | ||||||
| @ -1400,7 +1513,14 @@ public class Program | |||||||
|         { |         { | ||||||
|             messages = await downloadContext.ApiHelper.GetMessages($"/chats/{user.Value}/messages", path, downloadContext.DownloadConfig!, ctx); |             messages = await downloadContext.ApiHelper.GetMessages($"/chats/{user.Value}/messages", path, downloadContext.DownloadConfig!, ctx); | ||||||
|         }); |         }); | ||||||
| 		int oldMessagesCount = 0; | 
 | ||||||
|  |         if (unreadChats.Contains(user.Value)) | ||||||
|  |         { | ||||||
|  |             AnsiConsole.Markup($"[grey]Restoring unread state\n[/]"); | ||||||
|  |             await downloadContext.ApiHelper.MarkAsUnread($"/chats/{user.Value}/mark-as-read", downloadContext.DownloadConfig); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         int oldMessagesCount = 0; | ||||||
| 		int newMessagesCount = 0; | 		int newMessagesCount = 0; | ||||||
| 		if (messages != null && messages.Messages.Count > 0) | 		if (messages != null && messages.Messages.Count > 0) | ||||||
| 		{ | 		{ | ||||||
| @ -3149,7 +3269,18 @@ public class Program | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	static bool ValidateFilePath(string path) |     private static async Task<HashSet<int>> GetUsersWithUnreadChats(APIHelper apiHelper, IDownloadConfig currentConfig) | ||||||
|  |     { | ||||||
|  |         ChatCollection chats = await apiHelper.GetChats($"/chats", currentConfig, onlyUnread: true); | ||||||
|  | 
 | ||||||
|  |         var unreadChats = chats.Chats | ||||||
|  |             .Where(c => c.Value.unreadMessagesCount > 0) | ||||||
|  |             .ToList(); | ||||||
|  | 
 | ||||||
|  |         return [.. unreadChats.Select(c => c.Key)]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static bool ValidateFilePath(string path) | ||||||
| 	{ | 	{ | ||||||
| 		char[] invalidChars = System.IO.Path.GetInvalidPathChars(); | 		char[] invalidChars = System.IO.Path.GetInvalidPathChars(); | ||||||
| 		char[] foundInvalidChars = path.Where(c => invalidChars.Contains(c)).ToArray(); | 		char[] foundInvalidChars = path.Where(c => invalidChars.Contains(c)).ToArray(); | ||||||
| @ -3252,4 +3383,24 @@ public class Program | |||||||
| 
 | 
 | ||||||
|         return Enum.Parse<VideoResolution>("_" + value, ignoreCase: true); |         return Enum.Parse<VideoResolution>("_" + value, ignoreCase: true); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     static void ExitIfOtherProcess() | ||||||
|  |     { | ||||||
|  |         Assembly entryAssembly = Assembly.GetEntryAssembly(); | ||||||
|  |         AssemblyName entryAssemblyName = entryAssembly?.GetName(); | ||||||
|  | 
 | ||||||
|  |         if (entryAssemblyName?.Name is null) | ||||||
|  |             return; | ||||||
|  | 
 | ||||||
|  |         Process thisProcess = Process.GetCurrentProcess(); | ||||||
|  |         Process[] otherProcesses = [.. Process.GetProcessesByName(entryAssemblyName.Name).Where(p => p.Id != thisProcess.Id)]; | ||||||
|  | 
 | ||||||
|  |         if (otherProcesses.Length <= 0) | ||||||
|  |             return; | ||||||
|  | 
 | ||||||
|  |         AnsiConsole.Markup($"[green]Other OF DL process detected, exiting..\n[/]"); | ||||||
|  |         Log.Warning("Other OF DL process detected, exiting.."); | ||||||
|  | 
 | ||||||
|  |         Environment.Exit(0); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,33 @@ | |||||||
| @ECHO OFF | @ECHO OFF | ||||||
| 
 | 
 | ||||||
| ECHO. | 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" | dotnet publish ".\OF DL\OF DL.csproj" -o ".\Publish" | ||||||
| 
 | 
 | ||||||
|  | ECHO. | ||||||
|  | 
 | ||||||
|  | ECHO ============================== | ||||||
|  | ECHO == Copy to network drive? ==== | ||||||
|  | ECHO ============================== | ||||||
|  | CHOICE /C yn /m "Copy published files to network drive? " | ||||||
|  | 
 | ||||||
|  | IF %ERRORLEVEL%==1 (GOTO Copy) ELSE (GOTO Exit) | ||||||
|  | 
 | ||||||
|  | :Copy | ||||||
|  | xcopy .\Publish\* p:\_Utils\OF_DL /I /Y /Q /EXCLUDE:.\excludes.txt | ||||||
|  | 
 | ||||||
|  | :Exit | ||||||
|  | ECHO. | ||||||
| ECHO. | ECHO. | ||||||
| PAUSE | PAUSE | ||||||
							
								
								
									
										2
									
								
								excludes.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								excludes.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | excludes.txt | ||||||
|  | rules.json | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user