forked from sim0n00ps/OF-DL
Autoformat the entire solution
This commit is contained in:
parent
7af7bd8cfa
commit
6784ba0a18
@ -4,21 +4,15 @@ using Spectre.Console;
|
|||||||
namespace OF_DL.CLI;
|
namespace OF_DL.CLI;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implementation of IProgressReporter that uses Spectre.Console's ProgressTask for CLI output.
|
/// Implementation of IProgressReporter that uses Spectre.Console's ProgressTask for CLI output.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SpectreProgressReporter : IProgressReporter
|
public class SpectreProgressReporter : IProgressReporter
|
||||||
{
|
{
|
||||||
private readonly ProgressTask _task;
|
private readonly ProgressTask _task;
|
||||||
|
|
||||||
public SpectreProgressReporter(ProgressTask task)
|
public SpectreProgressReporter(ProgressTask task) => _task = task ?? throw new ArgumentNullException(nameof(task));
|
||||||
{
|
|
||||||
_task = task ?? throw new ArgumentNullException(nameof(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReportProgress(long increment)
|
public void ReportProgress(long increment) => _task.Increment(increment);
|
||||||
{
|
|
||||||
_task.Increment(increment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReportStatus(string message)
|
public void ReportStatus(string message)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,30 +4,26 @@ using Org.BouncyCastle.Crypto.Engines;
|
|||||||
using Org.BouncyCastle.Crypto.Macs;
|
using Org.BouncyCastle.Crypto.Macs;
|
||||||
using Org.BouncyCastle.Crypto.Parameters;
|
using Org.BouncyCastle.Crypto.Parameters;
|
||||||
|
|
||||||
namespace OF_DL.Crypto
|
namespace OF_DL.Crypto;
|
||||||
|
|
||||||
|
public class CryptoUtils
|
||||||
{
|
{
|
||||||
public class CryptoUtils
|
public static byte[] GetHMACSHA256Digest(byte[] data, byte[] key) => new HMACSHA256(key).ComputeHash(data);
|
||||||
|
|
||||||
|
public static byte[] GetCMACDigest(byte[] data, byte[] key)
|
||||||
{
|
{
|
||||||
public static byte[] GetHMACSHA256Digest(byte[] data, byte[] key)
|
IBlockCipher cipher = new AesEngine();
|
||||||
{
|
IMac mac = new CMac(cipher, 128);
|
||||||
return new HMACSHA256(key).ComputeHash(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] GetCMACDigest(byte[] data, byte[] key)
|
KeyParameter keyParam = new(key);
|
||||||
{
|
|
||||||
IBlockCipher cipher = new AesEngine();
|
|
||||||
IMac mac = new CMac(cipher, 128);
|
|
||||||
|
|
||||||
KeyParameter keyParam = new KeyParameter(key);
|
mac.Init(keyParam);
|
||||||
|
|
||||||
mac.Init(keyParam);
|
mac.BlockUpdate(data, 0, data.Length);
|
||||||
|
|
||||||
mac.BlockUpdate(data, 0, data.Length);
|
byte[] outBytes = new byte[16];
|
||||||
|
|
||||||
byte[] outBytes = new byte[16];
|
mac.DoFinal(outBytes, 0);
|
||||||
|
return outBytes;
|
||||||
mac.DoFinal(outBytes, 0);
|
|
||||||
return outBytes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,118 +1,126 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace OF_DL.Crypto
|
namespace OF_DL.Crypto;
|
||||||
|
|
||||||
|
public class Padding
|
||||||
{
|
{
|
||||||
public class Padding
|
public static byte[] AddPKCS7Padding(byte[] data, int k)
|
||||||
{
|
{
|
||||||
public static byte[] AddPKCS7Padding(byte[] data, int k)
|
int m = k - data.Length % k;
|
||||||
|
|
||||||
|
byte[] padding = new byte[m];
|
||||||
|
Array.Fill(padding, (byte)m);
|
||||||
|
|
||||||
|
byte[] paddedBytes = new byte[data.Length + padding.Length];
|
||||||
|
Buffer.BlockCopy(data, 0, paddedBytes, 0, data.Length);
|
||||||
|
Buffer.BlockCopy(padding, 0, paddedBytes, data.Length, padding.Length);
|
||||||
|
|
||||||
|
return paddedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] RemovePKCS7Padding(byte[] paddedByteArray)
|
||||||
|
{
|
||||||
|
byte last = paddedByteArray[^1];
|
||||||
|
if (paddedByteArray.Length <= last)
|
||||||
{
|
{
|
||||||
int m = k - (data.Length % k);
|
return paddedByteArray;
|
||||||
|
|
||||||
byte[] padding = new byte[m];
|
|
||||||
Array.Fill(padding, (byte)m);
|
|
||||||
|
|
||||||
byte[] paddedBytes = new byte[data.Length + padding.Length];
|
|
||||||
Buffer.BlockCopy(data, 0, paddedBytes, 0, data.Length);
|
|
||||||
Buffer.BlockCopy(padding, 0, paddedBytes, data.Length, padding.Length);
|
|
||||||
|
|
||||||
return paddedBytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] RemovePKCS7Padding(byte[] paddedByteArray)
|
return SubArray(paddedByteArray, 0, paddedByteArray.Length - last);
|
||||||
{
|
}
|
||||||
var last = paddedByteArray[^1];
|
|
||||||
if (paddedByteArray.Length <= last)
|
|
||||||
{
|
|
||||||
return paddedByteArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SubArray(paddedByteArray, 0, (paddedByteArray.Length - last));
|
public static T[] SubArray<T>(T[] arr, int start, int length)
|
||||||
|
{
|
||||||
|
T[] result = new T[length];
|
||||||
|
Buffer.BlockCopy(arr, start, result, 0, length);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] AddPSSPadding(byte[] hash)
|
||||||
|
{
|
||||||
|
int modBits = 2048;
|
||||||
|
int hLen = 20;
|
||||||
|
int emLen = 256;
|
||||||
|
|
||||||
|
int lmask = 0;
|
||||||
|
for (int i = 0; i < 8 * emLen - (modBits - 1); i++)
|
||||||
|
{
|
||||||
|
lmask = (lmask >> 1) | 0x80;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T[] SubArray<T>(T[] arr, int start, int length)
|
if (emLen < hLen + hLen + 2)
|
||||||
{
|
{
|
||||||
var result = new T[length];
|
return null;
|
||||||
Buffer.BlockCopy(arr, start, result, 0, length);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] AddPSSPadding(byte[] hash)
|
byte[] salt = new byte[hLen];
|
||||||
|
new Random().NextBytes(salt);
|
||||||
|
|
||||||
|
byte[] m_prime = Enumerable.Repeat((byte)0, 8).ToArray().Concat(hash).Concat(salt).ToArray();
|
||||||
|
byte[] h = SHA1.Create().ComputeHash(m_prime);
|
||||||
|
|
||||||
|
byte[] ps = Enumerable.Repeat((byte)0, emLen - hLen - hLen - 2).ToArray();
|
||||||
|
byte[] db = ps.Concat(new byte[] { 0x01 }).Concat(salt).ToArray();
|
||||||
|
|
||||||
|
byte[] dbMask = MGF1(h, emLen - hLen - 1);
|
||||||
|
|
||||||
|
byte[] maskedDb = new byte[dbMask.Length];
|
||||||
|
for (int i = 0; i < dbMask.Length; i++)
|
||||||
{
|
{
|
||||||
int modBits = 2048;
|
maskedDb[i] = (byte)(db[i] ^ dbMask[i]);
|
||||||
int hLen = 20;
|
|
||||||
int emLen = 256;
|
|
||||||
|
|
||||||
int lmask = 0;
|
|
||||||
for (int i = 0; i < 8 * emLen - (modBits - 1); i++)
|
|
||||||
lmask = lmask >> 1 | 0x80;
|
|
||||||
|
|
||||||
if (emLen < hLen + hLen + 2)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] salt = new byte[hLen];
|
|
||||||
new Random().NextBytes(salt);
|
|
||||||
|
|
||||||
byte[] m_prime = Enumerable.Repeat((byte)0, 8).ToArray().Concat(hash).Concat(salt).ToArray();
|
|
||||||
byte[] h = SHA1.Create().ComputeHash(m_prime);
|
|
||||||
|
|
||||||
byte[] ps = Enumerable.Repeat((byte)0, emLen - hLen - hLen - 2).ToArray();
|
|
||||||
byte[] db = ps.Concat(new byte[] { 0x01 }).Concat(salt).ToArray();
|
|
||||||
|
|
||||||
byte[] dbMask = MGF1(h, emLen - hLen - 1);
|
|
||||||
|
|
||||||
byte[] maskedDb = new byte[dbMask.Length];
|
|
||||||
for (int i = 0; i < dbMask.Length; i++)
|
|
||||||
maskedDb[i] = (byte)(db[i] ^ dbMask[i]);
|
|
||||||
|
|
||||||
maskedDb[0] = (byte)(maskedDb[0] & ~lmask);
|
|
||||||
|
|
||||||
byte[] padded = maskedDb.Concat(h).Concat(new byte[] { 0xBC }).ToArray();
|
|
||||||
|
|
||||||
return padded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] RemoveOAEPPadding(byte[] data)
|
maskedDb[0] = (byte)(maskedDb[0] & ~lmask);
|
||||||
|
|
||||||
|
byte[] padded = maskedDb.Concat(h).Concat(new byte[] { 0xBC }).ToArray();
|
||||||
|
|
||||||
|
return padded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] RemoveOAEPPadding(byte[] data)
|
||||||
|
{
|
||||||
|
int k = 256;
|
||||||
|
int hLen = 20;
|
||||||
|
|
||||||
|
byte[] maskedSeed = data[1..(hLen + 1)];
|
||||||
|
byte[] maskedDB = data[(hLen + 1)..];
|
||||||
|
|
||||||
|
byte[] seedMask = MGF1(maskedDB, hLen);
|
||||||
|
|
||||||
|
byte[] seed = new byte[maskedSeed.Length];
|
||||||
|
for (int i = 0; i < maskedSeed.Length; i++)
|
||||||
{
|
{
|
||||||
int k = 256;
|
seed[i] = (byte)(maskedSeed[i] ^ seedMask[i]);
|
||||||
int hLen = 20;
|
|
||||||
|
|
||||||
byte[] maskedSeed = data[1..(hLen + 1)];
|
|
||||||
byte[] maskedDB = data[(hLen + 1)..];
|
|
||||||
|
|
||||||
byte[] seedMask = MGF1(maskedDB, hLen);
|
|
||||||
|
|
||||||
byte[] seed = new byte[maskedSeed.Length];
|
|
||||||
for (int i = 0; i < maskedSeed.Length; i++)
|
|
||||||
seed[i] = (byte)(maskedSeed[i] ^ seedMask[i]);
|
|
||||||
|
|
||||||
byte[] dbMask = MGF1(seed, k - hLen - 1);
|
|
||||||
|
|
||||||
byte[] db = new byte[maskedDB.Length];
|
|
||||||
for (int i = 0; i < maskedDB.Length; i++)
|
|
||||||
db[i] = (byte)(maskedDB[i] ^ dbMask[i]);
|
|
||||||
|
|
||||||
int onePos = BitConverter.ToString(db[hLen..]).Replace("-", "").IndexOf("01") / 2;
|
|
||||||
byte[] unpadded = db[(hLen + onePos + 1)..];
|
|
||||||
|
|
||||||
return unpadded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] MGF1(byte[] seed, int maskLen)
|
byte[] dbMask = MGF1(seed, k - hLen - 1);
|
||||||
|
|
||||||
|
byte[] db = new byte[maskedDB.Length];
|
||||||
|
for (int i = 0; i < maskedDB.Length; i++)
|
||||||
{
|
{
|
||||||
SHA1 hobj = SHA1.Create();
|
db[i] = (byte)(maskedDB[i] ^ dbMask[i]);
|
||||||
int hLen = hobj.HashSize / 8;
|
|
||||||
List<byte> T = new List<byte>();
|
|
||||||
for (int i = 0; i < (int)Math.Ceiling(((double)maskLen / (double)hLen)); i++)
|
|
||||||
{
|
|
||||||
byte[] c = BitConverter.GetBytes(i);
|
|
||||||
Array.Reverse(c);
|
|
||||||
byte[] digest = hobj.ComputeHash(seed.Concat(c).ToArray());
|
|
||||||
T.AddRange(digest);
|
|
||||||
}
|
|
||||||
return T.GetRange(0, maskLen).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int onePos = BitConverter.ToString(db[hLen..]).Replace("-", "").IndexOf("01") / 2;
|
||||||
|
byte[] unpadded = db[(hLen + onePos + 1)..];
|
||||||
|
|
||||||
|
return unpadded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] MGF1(byte[] seed, int maskLen)
|
||||||
|
{
|
||||||
|
SHA1 hobj = SHA1.Create();
|
||||||
|
int hLen = hobj.HashSize / 8;
|
||||||
|
List<byte> T = new();
|
||||||
|
for (int i = 0; i < (int)Math.Ceiling(maskLen / (double)hLen); i++)
|
||||||
|
{
|
||||||
|
byte[] c = BitConverter.GetBytes(i);
|
||||||
|
Array.Reverse(c);
|
||||||
|
byte[] digest = hobj.ComputeHash(seed.Concat(c).ToArray());
|
||||||
|
T.AddRange(digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return T.GetRange(0, maskLen).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,270 +1,262 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OF_DL.Utils;
|
using OF_DL.Utils;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Archived
|
namespace OF_DL.Entities.Archived;
|
||||||
|
|
||||||
|
public class Archived
|
||||||
{
|
{
|
||||||
public class Archived
|
public List<List> list { get; set; }
|
||||||
|
public bool hasMore { get; set; }
|
||||||
|
public string headMarker { get; set; }
|
||||||
|
public string tailMarker { get; set; }
|
||||||
|
public Counters counters { get; set; }
|
||||||
|
|
||||||
|
public class Author
|
||||||
{
|
{
|
||||||
public List<List> list { get; set; }
|
public long id { get; set; }
|
||||||
public bool hasMore { get; set; }
|
public string _view { get; set; }
|
||||||
public string headMarker { get; set; }
|
}
|
||||||
public string tailMarker { get; set; }
|
|
||||||
public Counters counters { get; set; }
|
public class Counters
|
||||||
public class Author
|
{
|
||||||
|
public int? audiosCount { get; set; }
|
||||||
|
public int? photosCount { get; set; }
|
||||||
|
public int? videosCount { get; set; }
|
||||||
|
public int? mediasCount { get; set; }
|
||||||
|
public int? postsCount { get; set; }
|
||||||
|
public int? streamsCount { get; set; }
|
||||||
|
public int? archivedPostsCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dash
|
||||||
|
{
|
||||||
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Drm
|
||||||
|
{
|
||||||
|
public Manifest manifest { get; set; }
|
||||||
|
public Signature signature { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Files
|
||||||
|
{
|
||||||
|
public Full full { get; set; }
|
||||||
|
public Thumb thumb { get; set; }
|
||||||
|
public Preview preview { get; set; }
|
||||||
|
public SquarePreview squarePreview { get; set; }
|
||||||
|
public Drm drm { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Full
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
public List<object> sources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SquarePreview
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Thumb
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Hls
|
||||||
|
{
|
||||||
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Info
|
||||||
|
{
|
||||||
|
public Source source { get; set; }
|
||||||
|
public Preview preview { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LinkedPost
|
||||||
|
{
|
||||||
|
private string _rawText;
|
||||||
|
public string responseType { get; set; }
|
||||||
|
public long? id { get; set; }
|
||||||
|
public DateTime? postedAt { get; set; }
|
||||||
|
public string postedAtPrecise { get; set; }
|
||||||
|
public object expiredAt { get; set; }
|
||||||
|
public Author author { get; set; }
|
||||||
|
public string text { get; set; }
|
||||||
|
|
||||||
|
public string rawText
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
get
|
||||||
public string _view { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Counters
|
|
||||||
{
|
|
||||||
public int? audiosCount { get; set; }
|
|
||||||
public int? photosCount { get; set; }
|
|
||||||
public int? videosCount { get; set; }
|
|
||||||
public int? mediasCount { get; set; }
|
|
||||||
public int? postsCount { get; set; }
|
|
||||||
public int? streamsCount { get; set; }
|
|
||||||
public int? archivedPostsCount { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Dash
|
|
||||||
{
|
|
||||||
[JsonProperty("CloudFront-Policy")]
|
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
|
||||||
public string CloudFrontSignature { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Drm
|
|
||||||
{
|
|
||||||
public Manifest manifest { get; set; }
|
|
||||||
public Signature signature { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Files
|
|
||||||
{
|
|
||||||
public Full full { get; set; }
|
|
||||||
public Thumb thumb { get; set; }
|
|
||||||
public Preview preview { get; set; }
|
|
||||||
public SquarePreview squarePreview { get; set; }
|
|
||||||
public Drm drm { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Full
|
|
||||||
{
|
|
||||||
public string url { get; set; }
|
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
public List<object> sources { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SquarePreview
|
|
||||||
{
|
|
||||||
public string url { get; set; }
|
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Thumb
|
|
||||||
{
|
|
||||||
public string url { get; set; }
|
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Hls
|
|
||||||
{
|
|
||||||
[JsonProperty("CloudFront-Policy")]
|
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
|
||||||
public string CloudFrontSignature { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Info
|
|
||||||
{
|
|
||||||
public Source source { get; set; }
|
|
||||||
public Preview preview { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LinkedPost
|
|
||||||
{
|
|
||||||
public string responseType { get; set; }
|
|
||||||
public long? id { get; set; }
|
|
||||||
public DateTime? postedAt { get; set; }
|
|
||||||
public string postedAtPrecise { get; set; }
|
|
||||||
public object expiredAt { get; set; }
|
|
||||||
public Author author { get; set; }
|
|
||||||
public string text { get; set; }
|
|
||||||
private string _rawText;
|
|
||||||
public string rawText
|
|
||||||
{
|
{
|
||||||
get
|
if (string.IsNullOrEmpty(_rawText))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_rawText))
|
_rawText = XmlUtils.EvaluateInnerText(text);
|
||||||
{
|
}
|
||||||
_rawText = XmlUtils.EvaluateInnerText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _rawText;
|
return _rawText;
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_rawText = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public bool? lockedText { get; set; }
|
set => _rawText = value;
|
||||||
public bool? isFavorite { get; set; }
|
|
||||||
public bool? canReport { get; set; }
|
|
||||||
public bool? canDelete { get; set; }
|
|
||||||
public bool? canComment { get; set; }
|
|
||||||
public bool? canEdit { get; set; }
|
|
||||||
public bool? isPinned { get; set; }
|
|
||||||
public int? favoritesCount { get; set; }
|
|
||||||
public int? mediaCount { get; set; }
|
|
||||||
public bool? isMediaReady { get; set; }
|
|
||||||
public object voting { get; set; }
|
|
||||||
public bool? isOpened { get; set; }
|
|
||||||
public bool? canToggleFavorite { get; set; }
|
|
||||||
public object streamId { get; set; }
|
|
||||||
public string? price { get; set; }
|
|
||||||
public bool? hasVoting { get; set; }
|
|
||||||
public bool? isAddedToBookmarks { get; set; }
|
|
||||||
public bool? isArchived { get; set; }
|
|
||||||
public bool? isPrivateArchived { get; set; }
|
|
||||||
public bool? isDeleted { get; set; }
|
|
||||||
public bool? hasUrl { get; set; }
|
|
||||||
public bool? isCouplePeopleMedia { get; set; }
|
|
||||||
public string cantCommentReason { get; set; }
|
|
||||||
public int? commentsCount { get; set; }
|
|
||||||
public List<object> mentionedUsers { get; set; }
|
|
||||||
public List<object> linkedUsers { get; set; }
|
|
||||||
public List<Medium> media { get; set; }
|
|
||||||
public bool? canViewMedia { get; set; }
|
|
||||||
public List<object> preview { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class List
|
public bool? lockedText { get; set; }
|
||||||
|
public bool? isFavorite { get; set; }
|
||||||
|
public bool? canReport { get; set; }
|
||||||
|
public bool? canDelete { get; set; }
|
||||||
|
public bool? canComment { get; set; }
|
||||||
|
public bool? canEdit { get; set; }
|
||||||
|
public bool? isPinned { get; set; }
|
||||||
|
public int? favoritesCount { get; set; }
|
||||||
|
public int? mediaCount { get; set; }
|
||||||
|
public bool? isMediaReady { get; set; }
|
||||||
|
public object voting { get; set; }
|
||||||
|
public bool? isOpened { get; set; }
|
||||||
|
public bool? canToggleFavorite { get; set; }
|
||||||
|
public object streamId { get; set; }
|
||||||
|
public string? price { get; set; }
|
||||||
|
public bool? hasVoting { get; set; }
|
||||||
|
public bool? isAddedToBookmarks { get; set; }
|
||||||
|
public bool? isArchived { get; set; }
|
||||||
|
public bool? isPrivateArchived { get; set; }
|
||||||
|
public bool? isDeleted { get; set; }
|
||||||
|
public bool? hasUrl { get; set; }
|
||||||
|
public bool? isCouplePeopleMedia { get; set; }
|
||||||
|
public string cantCommentReason { get; set; }
|
||||||
|
public int? commentsCount { get; set; }
|
||||||
|
public List<object> mentionedUsers { get; set; }
|
||||||
|
public List<object> linkedUsers { get; set; }
|
||||||
|
public List<Medium> media { get; set; }
|
||||||
|
public bool? canViewMedia { get; set; }
|
||||||
|
public List<object> preview { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class List
|
||||||
|
{
|
||||||
|
private string _rawText;
|
||||||
|
public string responseType { get; set; }
|
||||||
|
public long id { get; set; }
|
||||||
|
public DateTime postedAt { get; set; }
|
||||||
|
public string postedAtPrecise { get; set; }
|
||||||
|
public object expiredAt { get; set; }
|
||||||
|
public Author author { get; set; }
|
||||||
|
public string text { get; set; }
|
||||||
|
|
||||||
|
public string rawText
|
||||||
{
|
{
|
||||||
public string responseType { get; set; }
|
get
|
||||||
public long id { get; set; }
|
|
||||||
public DateTime postedAt { get; set; }
|
|
||||||
public string postedAtPrecise { get; set; }
|
|
||||||
public object expiredAt { get; set; }
|
|
||||||
public Author author { get; set; }
|
|
||||||
public string text { get; set; }
|
|
||||||
private string _rawText;
|
|
||||||
public string rawText
|
|
||||||
{
|
{
|
||||||
get
|
if (string.IsNullOrEmpty(_rawText))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_rawText))
|
_rawText = XmlUtils.EvaluateInnerText(text);
|
||||||
{
|
}
|
||||||
_rawText = XmlUtils.EvaluateInnerText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _rawText;
|
return _rawText;
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_rawText = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public bool? lockedText { get; set; }
|
set => _rawText = value;
|
||||||
public bool? isFavorite { get; set; }
|
|
||||||
public bool? canReport { get; set; }
|
|
||||||
public bool? canDelete { get; set; }
|
|
||||||
public bool? canComment { get; set; }
|
|
||||||
public bool? canEdit { get; set; }
|
|
||||||
public bool? isPinned { get; set; }
|
|
||||||
public int? favoritesCount { get; set; }
|
|
||||||
public int? mediaCount { get; set; }
|
|
||||||
public bool? isMediaReady { get; set; }
|
|
||||||
public object voting { get; set; }
|
|
||||||
public bool isOpened { get; set; }
|
|
||||||
public bool? canToggleFavorite { get; set; }
|
|
||||||
public object streamId { get; set; }
|
|
||||||
public string price { get; set; }
|
|
||||||
public bool? hasVoting { get; set; }
|
|
||||||
public bool? isAddedToBookmarks { get; set; }
|
|
||||||
public bool isArchived { get; set; }
|
|
||||||
public bool? isPrivateArchived { get; set; }
|
|
||||||
public bool? isDeleted { get; set; }
|
|
||||||
public bool? hasUrl { get; set; }
|
|
||||||
public bool? isCouplePeopleMedia { get; set; }
|
|
||||||
public int? commentsCount { get; set; }
|
|
||||||
public List<object> mentionedUsers { get; set; }
|
|
||||||
public List<object> linkedUsers { get; set; }
|
|
||||||
public List<Medium> media { get; set; }
|
|
||||||
public bool? canViewMedia { get; set; }
|
|
||||||
public List<object> preview { get; set; }
|
|
||||||
public string cantCommentReason { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Manifest
|
public bool? lockedText { get; set; }
|
||||||
{
|
public bool? isFavorite { get; set; }
|
||||||
public string hls { get; set; }
|
public bool? canReport { get; set; }
|
||||||
public string dash { get; set; }
|
public bool? canDelete { get; set; }
|
||||||
}
|
public bool? canComment { get; set; }
|
||||||
|
public bool? canEdit { get; set; }
|
||||||
|
public bool? isPinned { get; set; }
|
||||||
|
public int? favoritesCount { get; set; }
|
||||||
|
public int? mediaCount { get; set; }
|
||||||
|
public bool? isMediaReady { get; set; }
|
||||||
|
public object voting { get; set; }
|
||||||
|
public bool isOpened { get; set; }
|
||||||
|
public bool? canToggleFavorite { get; set; }
|
||||||
|
public object streamId { get; set; }
|
||||||
|
public string price { get; set; }
|
||||||
|
public bool? hasVoting { get; set; }
|
||||||
|
public bool? isAddedToBookmarks { get; set; }
|
||||||
|
public bool isArchived { get; set; }
|
||||||
|
public bool? isPrivateArchived { get; set; }
|
||||||
|
public bool? isDeleted { get; set; }
|
||||||
|
public bool? hasUrl { get; set; }
|
||||||
|
public bool? isCouplePeopleMedia { get; set; }
|
||||||
|
public int? commentsCount { get; set; }
|
||||||
|
public List<object> mentionedUsers { get; set; }
|
||||||
|
public List<object> linkedUsers { get; set; }
|
||||||
|
public List<Medium> media { get; set; }
|
||||||
|
public bool? canViewMedia { get; set; }
|
||||||
|
public List<object> preview { get; set; }
|
||||||
|
public string cantCommentReason { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Medium
|
public class Manifest
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
public string hls { get; set; }
|
||||||
public string type { get; set; }
|
public string dash { get; set; }
|
||||||
public bool? convertedToVideo { get; set; }
|
}
|
||||||
public bool canView { get; set; }
|
|
||||||
public bool? hasError { get; set; }
|
|
||||||
public DateTime? createdAt { get; set; }
|
|
||||||
public Info info { get; set; }
|
|
||||||
public Source source { get; set; }
|
|
||||||
public string squarePreview { get; set; }
|
|
||||||
public string full { get; set; }
|
|
||||||
public string preview { get; set; }
|
|
||||||
public string thumb { get; set; }
|
|
||||||
public Files files { get; set; }
|
|
||||||
public VideoSources videoSources { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Preview
|
public class Medium
|
||||||
{
|
{
|
||||||
public int? width { get; set; }
|
public long id { get; set; }
|
||||||
public int? height { get; set; }
|
public string type { get; set; }
|
||||||
public int? size { get; set; }
|
public bool? convertedToVideo { get; set; }
|
||||||
public string url { get; set; }
|
public bool canView { get; set; }
|
||||||
}
|
public bool? hasError { get; set; }
|
||||||
|
public DateTime? createdAt { get; set; }
|
||||||
|
public Info info { get; set; }
|
||||||
|
public Source source { get; set; }
|
||||||
|
public string squarePreview { get; set; }
|
||||||
|
public string full { get; set; }
|
||||||
|
public string preview { get; set; }
|
||||||
|
public string thumb { get; set; }
|
||||||
|
public Files files { get; set; }
|
||||||
|
public VideoSources videoSources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Signature
|
public class Preview
|
||||||
{
|
{
|
||||||
public Hls hls { get; set; }
|
public int? width { get; set; }
|
||||||
public Dash dash { get; set; }
|
public int? height { get; set; }
|
||||||
}
|
public int? size { get; set; }
|
||||||
|
public string url { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Source
|
public class Signature
|
||||||
{
|
{
|
||||||
public string source { get; set; }
|
public Hls hls { get; set; }
|
||||||
public int? width { get; set; }
|
public Dash dash { get; set; }
|
||||||
public int? height { get; set; }
|
}
|
||||||
public int? size { get; set; }
|
|
||||||
public int? duration { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class VideoSources
|
public class Source
|
||||||
{
|
{
|
||||||
[JsonProperty("720")]
|
public string source { get; set; }
|
||||||
public string _720 { get; set; }
|
public int? width { get; set; }
|
||||||
|
public int? height { get; set; }
|
||||||
|
public int? size { get; set; }
|
||||||
|
public int? duration { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[JsonProperty("240")]
|
public class VideoSources
|
||||||
public string _240 { get; set; }
|
{
|
||||||
}
|
[JsonProperty("720")] public string _720 { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("240")] public string _240 { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
namespace OF_DL.Entities.Archived
|
namespace OF_DL.Entities.Archived;
|
||||||
|
|
||||||
|
public class ArchivedCollection
|
||||||
{
|
{
|
||||||
public class ArchivedCollection
|
public List<Archived.Medium> ArchivedPostMedia = new();
|
||||||
{
|
public List<Archived.List> ArchivedPostObjects = new();
|
||||||
public Dictionary<long, string> ArchivedPosts = new Dictionary<long, string>();
|
public Dictionary<long, string> ArchivedPosts = new();
|
||||||
public List<Archived.List> ArchivedPostObjects = new List<Archived.List>();
|
|
||||||
public List<Archived.Medium> ArchivedPostMedia = new List<Archived.Medium>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class Auth
|
||||||
{
|
{
|
||||||
public class Auth
|
public string? USER_ID { get; set; } = string.Empty;
|
||||||
{
|
public string? USER_AGENT { get; set; } = string.Empty;
|
||||||
public string? USER_ID { get; set; } = string.Empty;
|
public string? X_BC { get; set; } = string.Empty;
|
||||||
public string? USER_AGENT { get; set; } = string.Empty;
|
public string? COOKIE { get; set; } = string.Empty;
|
||||||
public string? X_BC { get; set; } = string.Empty;
|
|
||||||
public string? COOKIE { get; set; } = string.Empty;
|
[JsonIgnore] public string? FFMPEG_PATH { get; set; } = string.Empty;
|
||||||
[JsonIgnore]
|
|
||||||
public string? FFMPEG_PATH { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,16 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class CDRMProjectRequest
|
||||||
{
|
{
|
||||||
public class CDRMProjectRequest
|
[JsonProperty("pssh")] public string PSSH { get; set; } = "";
|
||||||
{
|
|
||||||
[JsonProperty("pssh")]
|
|
||||||
public string PSSH { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("licurl")]
|
[JsonProperty("licurl")] public string LicenseURL { get; set; } = "";
|
||||||
public string LicenseURL { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("headers")]
|
[JsonProperty("headers")] public string Headers { get; set; } = "";
|
||||||
public string Headers { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("cookies")]
|
[JsonProperty("cookies")] public string Cookies { get; set; } = "";
|
||||||
public string Cookies { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("data")]
|
[JsonProperty("data")] public string Data { get; set; } = "";
|
||||||
public string Data { get; set; } = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,152 +3,154 @@ using Newtonsoft.Json.Converters;
|
|||||||
using OF_DL.Enumerations;
|
using OF_DL.Enumerations;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class Config : IFileNameFormatConfig
|
||||||
{
|
{
|
||||||
|
[ToggleableConfig] public bool DownloadAvatarHeaderPhoto { get; set; } = true;
|
||||||
|
|
||||||
public class Config : IFileNameFormatConfig
|
[ToggleableConfig] public bool DownloadPaidPosts { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadPosts { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadArchived { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadStreams { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadStories { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadHighlights { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadMessages { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadPaidMessages { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadImages { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadVideos { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadAudios { get; set; } = true;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool IncludeExpiredSubscriptions { get; set; } = false;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool IncludeRestrictedSubscriptions { get; set; } = false;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool SkipAds { get; set; } = false;
|
||||||
|
|
||||||
|
public string? DownloadPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool RenameExistingFilesWhenCustomFormatIsSelected { get; set; } = false;
|
||||||
|
|
||||||
|
public int? Timeout { get; set; } = -1;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool FolderPerPaidPost { get; set; } = false;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool FolderPerPost { get; set; } = false;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool FolderPerPaidMessage { get; set; } = false;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool FolderPerMessage { get; set; } = false;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool LimitDownloadRate { get; set; } = false;
|
||||||
|
|
||||||
|
public int DownloadLimitInMbPerSec { get; set; } = 4;
|
||||||
|
|
||||||
|
// Indicates if you want to download only on specific dates.
|
||||||
|
[ToggleableConfig] public bool DownloadOnlySpecificDates { get; set; } = false;
|
||||||
|
|
||||||
|
// This enum will define if we want data from before or after the CustomDate.
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public DownloadDateSelection DownloadDateSelection { get; set; } = DownloadDateSelection.before;
|
||||||
|
// This is the specific date used in combination with the above enum.
|
||||||
|
|
||||||
|
[JsonConverter(typeof(ShortDateConverter))]
|
||||||
|
public DateTime? CustomDate { get; set; } = null;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool ShowScrapeSize { get; set; } = false;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadPostsIncrementally { get; set; } = false;
|
||||||
|
|
||||||
|
public bool NonInteractiveMode { get; set; } = false;
|
||||||
|
public string NonInteractiveModeListName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool NonInteractiveModePurchasedTab { get; set; } = false;
|
||||||
|
|
||||||
|
public string? FFmpegPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool BypassContentForCreatorsWhoNoLongerExist { get; set; } = false;
|
||||||
|
|
||||||
|
public Dictionary<string, CreatorConfig> CreatorConfigs { get; set; } = new();
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DownloadDuplicatedMedia { get; set; } = false;
|
||||||
|
|
||||||
|
public string IgnoredUsersListName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public LoggingLevel LoggingLevel { get; set; } = LoggingLevel.Error;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool IgnoreOwnMessages { get; set; } = false;
|
||||||
|
|
||||||
|
[ToggleableConfig] public bool DisableBrowserAuth { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public VideoResolution DownloadVideoResolution { get; set; } = VideoResolution.source;
|
||||||
|
|
||||||
|
// When enabled, post/message text is stored as-is without XML stripping.
|
||||||
|
[ToggleableConfig] public bool DisableTextSanitization { get; set; } = false;
|
||||||
|
|
||||||
|
public string? PaidPostFileNameFormat { get; set; } = string.Empty;
|
||||||
|
public string? PostFileNameFormat { get; set; } = string.Empty;
|
||||||
|
public string? PaidMessageFileNameFormat { get; set; } = string.Empty;
|
||||||
|
public string? MessageFileNameFormat { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public IFileNameFormatConfig GetCreatorFileNameFormatConfig(string username)
|
||||||
{
|
{
|
||||||
[ToggleableConfig]
|
FileNameFormatConfig createFileNameFormatConfig = new();
|
||||||
public bool DownloadAvatarHeaderPhoto { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadPaidPosts { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadPosts { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadArchived { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadStreams { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadStories { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadHighlights { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadMessages { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadPaidMessages { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadImages { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadVideos { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadAudios { get; set; } = true;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool IncludeExpiredSubscriptions { get; set; } = false;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool IncludeRestrictedSubscriptions { get; set; } = false;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool SkipAds { get; set; } = false;
|
|
||||||
|
|
||||||
public string? DownloadPath { get; set; } = string.Empty;
|
Func<string?, string?, string?> func = (val1, val2) =>
|
||||||
public string? PaidPostFileNameFormat { get; set; } = string.Empty;
|
|
||||||
public string? PostFileNameFormat { get; set; } = string.Empty;
|
|
||||||
public string? PaidMessageFileNameFormat { get; set; } = string.Empty;
|
|
||||||
public string? MessageFileNameFormat { get; set; } = string.Empty;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool RenameExistingFilesWhenCustomFormatIsSelected { get; set; } = false;
|
|
||||||
public int? Timeout { get; set; } = -1;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool FolderPerPaidPost { get; set; } = false;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool FolderPerPost { get; set; } = false;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool FolderPerPaidMessage { get; set; } = false;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool FolderPerMessage { get; set; } = false;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool LimitDownloadRate { get; set; } = false;
|
|
||||||
public int DownloadLimitInMbPerSec { get; set; } = 4;
|
|
||||||
|
|
||||||
// Indicates if you want to download only on specific dates.
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadOnlySpecificDates { get; set; } = false;
|
|
||||||
|
|
||||||
// This enum will define if we want data from before or after the CustomDate.
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
|
||||||
public DownloadDateSelection DownloadDateSelection { get; set; } = DownloadDateSelection.before;
|
|
||||||
// This is the specific date used in combination with the above enum.
|
|
||||||
|
|
||||||
[JsonConverter(typeof(ShortDateConverter))]
|
|
||||||
public DateTime? CustomDate { get; set; } = null;
|
|
||||||
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool ShowScrapeSize { get; set; } = false;
|
|
||||||
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadPostsIncrementally { get; set; } = false;
|
|
||||||
|
|
||||||
public bool NonInteractiveMode { get; set; } = false;
|
|
||||||
public string NonInteractiveModeListName { get; set; } = string.Empty;
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool NonInteractiveModePurchasedTab { get; set; } = false;
|
|
||||||
public string? FFmpegPath { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool BypassContentForCreatorsWhoNoLongerExist { get; set; } = false;
|
|
||||||
|
|
||||||
public Dictionary<string, CreatorConfig> CreatorConfigs { get; set; } = new Dictionary<string, CreatorConfig>();
|
|
||||||
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DownloadDuplicatedMedia { get; set; } = false;
|
|
||||||
|
|
||||||
public string IgnoredUsersListName { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
|
||||||
public LoggingLevel LoggingLevel { get; set; } = LoggingLevel.Error;
|
|
||||||
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool IgnoreOwnMessages { get; set; } = false;
|
|
||||||
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DisableBrowserAuth { get; set; } = false;
|
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
|
||||||
public VideoResolution DownloadVideoResolution { get; set; } = VideoResolution.source;
|
|
||||||
|
|
||||||
// When enabled, post/message text is stored as-is without XML stripping.
|
|
||||||
[ToggleableConfig]
|
|
||||||
public bool DisableTextSanitization { get; set; } = false;
|
|
||||||
|
|
||||||
public IFileNameFormatConfig GetCreatorFileNameFormatConfig(string username)
|
|
||||||
{
|
{
|
||||||
FileNameFormatConfig createFileNameFormatConfig = new FileNameFormatConfig();
|
if (string.IsNullOrEmpty(val1))
|
||||||
|
|
||||||
Func<string?, string?, string?> func = (val1, val2) =>
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(val1))
|
return val2;
|
||||||
return val2;
|
|
||||||
else
|
|
||||||
return val1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if(CreatorConfigs.TryGetValue(username, out CreatorConfig? creatorConfig))
|
|
||||||
{
|
|
||||||
createFileNameFormatConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat;
|
|
||||||
createFileNameFormatConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat;
|
|
||||||
createFileNameFormatConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat;
|
|
||||||
createFileNameFormatConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createFileNameFormatConfig.PaidMessageFileNameFormat = func(createFileNameFormatConfig.PaidMessageFileNameFormat, PaidMessageFileNameFormat);
|
return val1;
|
||||||
createFileNameFormatConfig.PostFileNameFormat = func(createFileNameFormatConfig.PostFileNameFormat, PostFileNameFormat);
|
};
|
||||||
createFileNameFormatConfig.MessageFileNameFormat = func(createFileNameFormatConfig.MessageFileNameFormat, MessageFileNameFormat);
|
|
||||||
createFileNameFormatConfig.PaidPostFileNameFormat = func(createFileNameFormatConfig.PaidPostFileNameFormat, PaidPostFileNameFormat);
|
|
||||||
|
|
||||||
Log.Debug("PaidMessageFilenameFormat: {CombinedConfigPaidMessageFileNameFormat}", createFileNameFormatConfig.PaidMessageFileNameFormat);
|
if (CreatorConfigs.TryGetValue(username, out CreatorConfig? creatorConfig))
|
||||||
Log.Debug("PostFileNameFormat: {CombinedConfigPostFileNameFormat}", createFileNameFormatConfig.PostFileNameFormat);
|
{
|
||||||
Log.Debug("MessageFileNameFormat: {CombinedConfigMessageFileNameFormat}", createFileNameFormatConfig.MessageFileNameFormat);
|
createFileNameFormatConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat;
|
||||||
Log.Debug("PaidPostFileNameFormat: {CombinedConfigPaidPostFileNameFormat}", createFileNameFormatConfig.PaidPostFileNameFormat);
|
createFileNameFormatConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat;
|
||||||
|
createFileNameFormatConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat;
|
||||||
return createFileNameFormatConfig;
|
createFileNameFormatConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class CreatorConfig : IFileNameFormatConfig
|
createFileNameFormatConfig.PaidMessageFileNameFormat =
|
||||||
{
|
func(createFileNameFormatConfig.PaidMessageFileNameFormat, PaidMessageFileNameFormat);
|
||||||
public string? PaidPostFileNameFormat { get; set; }
|
createFileNameFormatConfig.PostFileNameFormat =
|
||||||
public string? PostFileNameFormat { get; set; }
|
func(createFileNameFormatConfig.PostFileNameFormat, PostFileNameFormat);
|
||||||
public string? PaidMessageFileNameFormat { get; set; }
|
createFileNameFormatConfig.MessageFileNameFormat =
|
||||||
public string? MessageFileNameFormat { get; set; }
|
func(createFileNameFormatConfig.MessageFileNameFormat, MessageFileNameFormat);
|
||||||
}
|
createFileNameFormatConfig.PaidPostFileNameFormat =
|
||||||
|
func(createFileNameFormatConfig.PaidPostFileNameFormat, PaidPostFileNameFormat);
|
||||||
|
|
||||||
|
Log.Debug("PaidMessageFilenameFormat: {CombinedConfigPaidMessageFileNameFormat}",
|
||||||
|
createFileNameFormatConfig.PaidMessageFileNameFormat);
|
||||||
|
Log.Debug("PostFileNameFormat: {CombinedConfigPostFileNameFormat}",
|
||||||
|
createFileNameFormatConfig.PostFileNameFormat);
|
||||||
|
Log.Debug("MessageFileNameFormat: {CombinedConfigMessageFileNameFormat}",
|
||||||
|
createFileNameFormatConfig.MessageFileNameFormat);
|
||||||
|
Log.Debug("PaidPostFileNameFormat: {CombinedConfigPaidPostFileNameFormat}",
|
||||||
|
createFileNameFormatConfig.PaidPostFileNameFormat);
|
||||||
|
|
||||||
|
return createFileNameFormatConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreatorConfig : IFileNameFormatConfig
|
||||||
|
{
|
||||||
|
public string? PaidPostFileNameFormat { get; set; }
|
||||||
|
public string? PostFileNameFormat { get; set; }
|
||||||
|
public string? PaidMessageFileNameFormat { get; set; }
|
||||||
|
public string? MessageFileNameFormat { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +1,37 @@
|
|||||||
namespace OF_DL.Entities;
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the result of a download operation.
|
/// Represents the result of a download operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DownloadResult
|
public class DownloadResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total number of media items processed.
|
/// Total number of media items processed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalCount { get; set; }
|
public int TotalCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Number of newly downloaded media items.
|
/// Number of newly downloaded media items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int NewDownloads { get; set; }
|
public int NewDownloads { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Number of media items that were already downloaded.
|
/// Number of media items that were already downloaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ExistingDownloads { get; set; }
|
public int ExistingDownloads { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of media downloaded (e.g., "Posts", "Messages", "Stories", etc.).
|
/// The type of media downloaded (e.g., "Posts", "Messages", "Stories", etc.).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string MediaType { get; set; } = string.Empty;
|
public string MediaType { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates whether the download operation was successful.
|
/// Indicates whether the download operation was successful.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Success { get; set; } = true;
|
public bool Success { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optional error message if the download failed.
|
/// Optional error message if the download failed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ErrorMessage { get; set; }
|
public string? ErrorMessage { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,30 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class DynamicRules
|
||||||
{
|
{
|
||||||
public class DynamicRules
|
[JsonProperty(PropertyName = "app-token")]
|
||||||
|
public string? AppToken { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "app_token")]
|
||||||
|
private string AppToken2
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName="app-token")]
|
set => AppToken = value;
|
||||||
public string? AppToken { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName="app_token")]
|
|
||||||
private string AppToken2 { set { AppToken = value; } }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName="static_param")]
|
|
||||||
public string? StaticParam { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName="prefix")]
|
|
||||||
public string? Prefix { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName="suffix")]
|
|
||||||
public string? Suffix { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName="checksum_constant")]
|
|
||||||
public int? ChecksumConstant { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "checksum_indexes")]
|
|
||||||
public List<int> ChecksumIndexes { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "static_param")]
|
||||||
|
public string? StaticParam { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "prefix")]
|
||||||
|
public string? Prefix { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "suffix")]
|
||||||
|
public string? Suffix { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "checksum_constant")]
|
||||||
|
public int? ChecksumConstant { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "checksum_indexes")]
|
||||||
|
public List<int> ChecksumIndexes { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
{
|
|
||||||
public class FileNameFormatConfig : IFileNameFormatConfig
|
|
||||||
{
|
|
||||||
public string? PaidPostFileNameFormat { get; set; }
|
|
||||||
public string? PostFileNameFormat { get; set; }
|
|
||||||
public string? PaidMessageFileNameFormat { get; set; }
|
|
||||||
public string? MessageFileNameFormat { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public class FileNameFormatConfig : IFileNameFormatConfig
|
||||||
|
{
|
||||||
|
public string? PaidPostFileNameFormat { get; set; }
|
||||||
|
public string? PostFileNameFormat { get; set; }
|
||||||
|
public string? PaidMessageFileNameFormat { get; set; }
|
||||||
|
public string? MessageFileNameFormat { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,103 +1,102 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Highlights
|
namespace OF_DL.Entities.Highlights;
|
||||||
|
|
||||||
|
public class HighlightMedia
|
||||||
{
|
{
|
||||||
public class HighlightMedia
|
public long id { get; set; }
|
||||||
{
|
public long userId { get; set; }
|
||||||
public long id { get; set; }
|
public string title { get; set; }
|
||||||
public long userId { get; set; }
|
public long coverStoryId { get; set; }
|
||||||
public string title { get; set; }
|
public string cover { get; set; }
|
||||||
public long coverStoryId { get; set; }
|
public int storiesCount { get; set; }
|
||||||
public string cover { get; set; }
|
public DateTime? createdAt { get; set; }
|
||||||
public int storiesCount { get; set; }
|
public List<Story> stories { get; set; }
|
||||||
public DateTime? createdAt { get; set; }
|
|
||||||
public List<Story> stories { get; set; }
|
|
||||||
public class Files
|
|
||||||
{
|
|
||||||
public Full full { get; set; }
|
|
||||||
public Thumb thumb { get; set; }
|
|
||||||
public Preview preview { get; set; }
|
|
||||||
public SquarePreview squarePreview { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Full
|
public class Files
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public Full full { get; set; }
|
||||||
public int width { get; set; }
|
public Thumb thumb { get; set; }
|
||||||
public int height { get; set; }
|
public Preview preview { get; set; }
|
||||||
public long size { get; set; }
|
public SquarePreview squarePreview { get; set; }
|
||||||
public List<object> sources { get; set; }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class Medium
|
public class Full
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
public string url { get; set; }
|
||||||
public string type { get; set; }
|
public int width { get; set; }
|
||||||
public bool convertedToVideo { get; set; }
|
public int height { get; set; }
|
||||||
public bool canView { get; set; }
|
public long size { get; set; }
|
||||||
public bool hasError { get; set; }
|
public List<object> sources { get; set; }
|
||||||
public DateTime? createdAt { get; set; }
|
}
|
||||||
public Files files { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Preview
|
public class Medium
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public long id { get; set; }
|
||||||
public int width { get; set; }
|
public string type { get; set; }
|
||||||
public int height { get; set; }
|
public bool convertedToVideo { get; set; }
|
||||||
public long size { get; set; }
|
public bool canView { get; set; }
|
||||||
public Sources sources { get; set; }
|
public bool hasError { get; set; }
|
||||||
}
|
public DateTime? createdAt { get; set; }
|
||||||
|
public Files files { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Source
|
public class Preview
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public int duration { get; set; }
|
public long size { get; set; }
|
||||||
public long size { get; set; }
|
public Sources sources { get; set; }
|
||||||
public Sources sources { get; set; }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class Sources
|
public class Source
|
||||||
{
|
{
|
||||||
[JsonProperty("720")]
|
public string url { get; set; }
|
||||||
public string _720 { get; set; }
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public int duration { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
public Sources sources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[JsonProperty("240")]
|
public class Sources
|
||||||
public string _240 { get; set; }
|
{
|
||||||
public string w150 { get; set; }
|
[JsonProperty("720")] public string _720 { get; set; }
|
||||||
public string w480 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SquarePreview
|
[JsonProperty("240")] public string _240 { get; set; }
|
||||||
{
|
|
||||||
public string url { get; set; }
|
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
public Sources sources { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Story
|
public string w150 { get; set; }
|
||||||
{
|
public string w480 { 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; }
|
|
||||||
public DateTime? createdAt { get; set; }
|
|
||||||
public object question { get; set; }
|
|
||||||
public bool canLike { get; set; }
|
|
||||||
public bool isLiked { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Thumb
|
public class SquarePreview
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
}
|
public Sources sources { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Story
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
public DateTime? createdAt { get; set; }
|
||||||
|
public object question { get; set; }
|
||||||
|
public bool canLike { get; set; }
|
||||||
|
public bool isLiked { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Thumb
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
namespace OF_DL.Entities.Highlights
|
namespace OF_DL.Entities.Highlights;
|
||||||
|
|
||||||
|
public class Highlights
|
||||||
{
|
{
|
||||||
public class Highlights
|
public List<List> list { get; set; }
|
||||||
{
|
public bool hasMore { get; set; }
|
||||||
public List<List> list { get; set; }
|
|
||||||
public bool hasMore { get; set; }
|
public class List
|
||||||
public class List
|
{
|
||||||
{
|
public long id { get; set; }
|
||||||
public long id { get; set; }
|
public long userId { get; set; }
|
||||||
public long userId { get; set; }
|
public string title { get; set; }
|
||||||
public string title { get; set; }
|
public long coverStoryId { get; set; }
|
||||||
public long coverStoryId { get; set; }
|
public string cover { get; set; }
|
||||||
public string cover { get; set; }
|
public int storiesCount { get; set; }
|
||||||
public int storiesCount { get; set; }
|
public DateTime? createdAt { get; set; }
|
||||||
public DateTime? createdAt { get; set; }
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
{
|
|
||||||
public interface IFileNameFormatConfig
|
|
||||||
{
|
|
||||||
string? PaidPostFileNameFormat { get; set; }
|
|
||||||
string? PostFileNameFormat { get; set; }
|
|
||||||
string? PaidMessageFileNameFormat { get; set; }
|
|
||||||
string? MessageFileNameFormat { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public interface IFileNameFormatConfig
|
||||||
|
{
|
||||||
|
string? PaidPostFileNameFormat { get; set; }
|
||||||
|
string? PostFileNameFormat { get; set; }
|
||||||
|
string? PaidMessageFileNameFormat { get; set; }
|
||||||
|
string? MessageFileNameFormat { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,35 @@
|
|||||||
namespace OF_DL.Entities.Lists
|
namespace OF_DL.Entities.Lists;
|
||||||
{
|
|
||||||
public class UserList
|
|
||||||
{
|
|
||||||
public List<List> list { get; set; }
|
|
||||||
public bool? hasMore { get; set; }
|
|
||||||
public class List
|
|
||||||
{
|
|
||||||
public string id { get; set; }
|
|
||||||
public string type { get; set; }
|
|
||||||
public string name { get; set; }
|
|
||||||
public int? usersCount { get; set; }
|
|
||||||
public int? postsCount { get; set; }
|
|
||||||
public bool? canUpdate { get; set; }
|
|
||||||
public bool? canDelete { get; set; }
|
|
||||||
public bool? canManageUsers { get; set; }
|
|
||||||
public bool? canAddUsers { get; set; }
|
|
||||||
public bool? canPinnedToFeed { get; set; }
|
|
||||||
public bool? isPinnedToFeed { get; set; }
|
|
||||||
public bool? canPinnedToChat { get; set; }
|
|
||||||
public bool? isPinnedToChat { get; set; }
|
|
||||||
public string order { get; set; }
|
|
||||||
public string direction { get; set; }
|
|
||||||
public List<User> users { get; set; }
|
|
||||||
public List<object> customOrderUsersIds { get; set; }
|
|
||||||
public List<object> posts { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class User
|
public class UserList
|
||||||
{
|
{
|
||||||
public long? id { get; set; }
|
public List<List> list { get; set; }
|
||||||
public string _view { get; set; }
|
public bool? hasMore { get; set; }
|
||||||
}
|
|
||||||
}
|
public class List
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public int? usersCount { get; set; }
|
||||||
|
public int? postsCount { get; set; }
|
||||||
|
public bool? canUpdate { get; set; }
|
||||||
|
public bool? canDelete { get; set; }
|
||||||
|
public bool? canManageUsers { get; set; }
|
||||||
|
public bool? canAddUsers { get; set; }
|
||||||
|
public bool? canPinnedToFeed { get; set; }
|
||||||
|
public bool? isPinnedToFeed { get; set; }
|
||||||
|
public bool? canPinnedToChat { get; set; }
|
||||||
|
public bool? isPinnedToChat { get; set; }
|
||||||
|
public string order { get; set; }
|
||||||
|
public string direction { get; set; }
|
||||||
|
public List<User> users { get; set; }
|
||||||
|
public List<object> customOrderUsersIds { get; set; }
|
||||||
|
public List<object> posts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public long? id { get; set; }
|
||||||
|
public string _view { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,165 +1,164 @@
|
|||||||
namespace OF_DL.Entities.Lists
|
namespace OF_DL.Entities.Lists;
|
||||||
|
|
||||||
|
public class UsersList
|
||||||
{
|
{
|
||||||
public class UsersList
|
public string view { get; set; }
|
||||||
{
|
public string avatar { get; set; }
|
||||||
public string view { get; set; }
|
public AvatarThumbs avatarThumbs { get; set; }
|
||||||
public string avatar { get; set; }
|
public string header { get; set; }
|
||||||
public AvatarThumbs avatarThumbs { get; set; }
|
public HeaderSize headerSize { get; set; }
|
||||||
public string header { get; set; }
|
public HeaderThumbs headerThumbs { get; set; }
|
||||||
public HeaderSize headerSize { get; set; }
|
public long? id { get; set; }
|
||||||
public HeaderThumbs headerThumbs { get; set; }
|
public string name { get; set; }
|
||||||
public long? id { get; set; }
|
public string username { get; set; }
|
||||||
public string name { get; set; }
|
public bool? canLookStory { get; set; }
|
||||||
public string username { get; set; }
|
public bool? canCommentStory { get; set; }
|
||||||
public bool? canLookStory { get; set; }
|
public bool? hasNotViewedStory { get; set; }
|
||||||
public bool? canCommentStory { get; set; }
|
public bool? isVerified { get; set; }
|
||||||
public bool? hasNotViewedStory { get; set; }
|
public bool? canPayInternal { get; set; }
|
||||||
public bool? isVerified { get; set; }
|
public bool? hasScheduledStream { get; set; }
|
||||||
public bool? canPayInternal { get; set; }
|
public bool? hasStream { get; set; }
|
||||||
public bool? hasScheduledStream { get; set; }
|
public bool? hasStories { get; set; }
|
||||||
public bool? hasStream { get; set; }
|
public bool? tipsEnabled { get; set; }
|
||||||
public bool? hasStories { get; set; }
|
public bool? tipsTextEnabled { get; set; }
|
||||||
public bool? tipsEnabled { get; set; }
|
public int? tipsMin { get; set; }
|
||||||
public bool? tipsTextEnabled { get; set; }
|
public int? tipsMinInternal { get; set; }
|
||||||
public int? tipsMin { get; set; }
|
public int? tipsMax { get; set; }
|
||||||
public int? tipsMinInternal { get; set; }
|
public bool? canEarn { get; set; }
|
||||||
public int? tipsMax { get; set; }
|
public bool? canAddSubscriber { get; set; }
|
||||||
public bool? canEarn { get; set; }
|
public string? subscribePrice { get; set; }
|
||||||
public bool? canAddSubscriber { get; set; }
|
public List<SubscriptionBundle> subscriptionBundles { get; set; }
|
||||||
public string? subscribePrice { get; set; }
|
public string displayName { get; set; }
|
||||||
public List<SubscriptionBundle> subscriptionBundles { get; set; }
|
public string notice { get; set; }
|
||||||
public string displayName { get; set; }
|
public bool? isPaywallRequired { get; set; }
|
||||||
public string notice { get; set; }
|
public bool? unprofitable { get; set; }
|
||||||
public bool? isPaywallRequired { get; set; }
|
public List<ListsState> listsStates { get; set; }
|
||||||
public bool? unprofitable { get; set; }
|
public bool? isMuted { get; set; }
|
||||||
public List<ListsState> listsStates { get; set; }
|
public bool? isRestricted { get; set; }
|
||||||
public bool? isMuted { get; set; }
|
public bool? canRestrict { get; set; }
|
||||||
public bool? isRestricted { get; set; }
|
public bool? subscribedBy { get; set; }
|
||||||
public bool? canRestrict { get; set; }
|
public bool? subscribedByExpire { get; set; }
|
||||||
public bool? subscribedBy { get; set; }
|
public DateTime? subscribedByExpireDate { get; set; }
|
||||||
public bool? subscribedByExpire { get; set; }
|
public bool? subscribedByAutoprolong { get; set; }
|
||||||
public DateTime? subscribedByExpireDate { get; set; }
|
public bool? subscribedIsExpiredNow { get; set; }
|
||||||
public bool? subscribedByAutoprolong { get; set; }
|
public string? currentSubscribePrice { get; set; }
|
||||||
public bool? subscribedIsExpiredNow { get; set; }
|
public bool? subscribedOn { get; set; }
|
||||||
public string? currentSubscribePrice { get; set; }
|
public bool? subscribedOnExpiredNow { get; set; }
|
||||||
public bool? subscribedOn { get; set; }
|
public string subscribedOnDuration { get; set; }
|
||||||
public bool? subscribedOnExpiredNow { get; set; }
|
public bool? canReport { get; set; }
|
||||||
public string subscribedOnDuration { get; set; }
|
public bool? canReceiveChatMessage { get; set; }
|
||||||
public bool? canReport { get; set; }
|
public bool? hideChat { get; set; }
|
||||||
public bool? canReceiveChatMessage { get; set; }
|
public DateTime? lastSeen { get; set; }
|
||||||
public bool? hideChat { get; set; }
|
public bool? isPerformer { get; set; }
|
||||||
public DateTime? lastSeen { get; set; }
|
public bool? isRealPerformer { get; set; }
|
||||||
public bool? isPerformer { get; set; }
|
public SubscribedByData subscribedByData { get; set; }
|
||||||
public bool? isRealPerformer { get; set; }
|
public SubscribedOnData subscribedOnData { get; set; }
|
||||||
public SubscribedByData subscribedByData { get; set; }
|
public bool? canTrialSend { get; set; }
|
||||||
public SubscribedOnData subscribedOnData { get; set; }
|
public bool? isBlocked { get; set; }
|
||||||
public bool? canTrialSend { get; set; }
|
public List<object> promoOffers { get; set; }
|
||||||
public bool? isBlocked { get; set; }
|
|
||||||
public List<object> promoOffers { get; set; }
|
|
||||||
public class AvatarThumbs
|
|
||||||
{
|
|
||||||
public string c50 { get; set; }
|
|
||||||
public string c144 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HeaderSize
|
public class AvatarThumbs
|
||||||
{
|
{
|
||||||
public int? width { get; set; }
|
public string c50 { get; set; }
|
||||||
public int? height { get; set; }
|
public string c144 { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HeaderThumbs
|
public class HeaderSize
|
||||||
{
|
{
|
||||||
public string w480 { get; set; }
|
public int? width { get; set; }
|
||||||
public string w760 { get; set; }
|
public int? height { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ListsState
|
public class HeaderThumbs
|
||||||
{
|
{
|
||||||
public string id { get; set; }
|
public string w480 { get; set; }
|
||||||
public string type { get; set; }
|
public string w760 { get; set; }
|
||||||
public string name { get; set; }
|
}
|
||||||
public bool hasUser { get; set; }
|
|
||||||
public bool canAddUser { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Subscribe
|
public class ListsState
|
||||||
{
|
{
|
||||||
public object id { get; set; }
|
public string id { get; set; }
|
||||||
public long? userId { get; set; }
|
public string type { get; set; }
|
||||||
public int? subscriberId { get; set; }
|
public string name { get; set; }
|
||||||
public DateTime? date { get; set; }
|
public bool hasUser { get; set; }
|
||||||
public int? duration { get; set; }
|
public bool canAddUser { get; set; }
|
||||||
public DateTime? startDate { get; set; }
|
}
|
||||||
public DateTime? expireDate { get; set; }
|
|
||||||
public object cancelDate { get; set; }
|
|
||||||
public string? price { get; set; }
|
|
||||||
public string? regularPrice { get; set; }
|
|
||||||
public string? discount { get; set; }
|
|
||||||
public string action { get; set; }
|
|
||||||
public string type { get; set; }
|
|
||||||
public object offerStart { get; set; }
|
|
||||||
public object offerEnd { get; set; }
|
|
||||||
public bool? isCurrent { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SubscribedByData
|
public class Subscribe
|
||||||
{
|
{
|
||||||
public string? price { get; set; }
|
public object id { get; set; }
|
||||||
public string? newPrice { get; set; }
|
public long? userId { get; set; }
|
||||||
public string? regularPrice { get; set; }
|
public int? subscriberId { get; set; }
|
||||||
public string? subscribePrice { get; set; }
|
public DateTime? date { get; set; }
|
||||||
public string? discountPercent { get; set; }
|
public int? duration { get; set; }
|
||||||
public string? discountPeriod { get; set; }
|
public DateTime? startDate { get; set; }
|
||||||
public DateTime? subscribeAt { get; set; }
|
public DateTime? expireDate { get; set; }
|
||||||
public DateTime? expiredAt { get; set; }
|
public object cancelDate { get; set; }
|
||||||
public object renewedAt { get; set; }
|
public string? price { get; set; }
|
||||||
public object discountFinishedAt { get; set; }
|
public string? regularPrice { get; set; }
|
||||||
public object discountStartedAt { get; set; }
|
public string? discount { get; set; }
|
||||||
public string status { get; set; }
|
public string action { get; set; }
|
||||||
public bool? isMuted { get; set; }
|
public string type { get; set; }
|
||||||
public string unsubscribeReason { get; set; }
|
public object offerStart { get; set; }
|
||||||
public string duration { get; set; }
|
public object offerEnd { get; set; }
|
||||||
public bool? showPostsInFeed { get; set; }
|
public bool? isCurrent { get; set; }
|
||||||
public List<Subscribe> subscribes { get; set; }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class SubscribedOnData
|
public class SubscribedByData
|
||||||
{
|
{
|
||||||
public string? price { get; set; }
|
public string? price { get; set; }
|
||||||
public string? newPrice { get; set; }
|
public string? newPrice { get; set; }
|
||||||
public string? regularPrice { get; set; }
|
public string? regularPrice { get; set; }
|
||||||
public string? subscribePrice { get; set; }
|
public string? subscribePrice { get; set; }
|
||||||
public string? discountPercent { get; set; }
|
public string? discountPercent { get; set; }
|
||||||
public string? discountPeriod { get; set; }
|
public string? discountPeriod { get; set; }
|
||||||
public DateTime? subscribeAt { get; set; }
|
public DateTime? subscribeAt { get; set; }
|
||||||
public DateTime? expiredAt { get; set; }
|
public DateTime? expiredAt { get; set; }
|
||||||
public object renewedAt { get; set; }
|
public object renewedAt { get; set; }
|
||||||
public object discountFinishedAt { get; set; }
|
public object discountFinishedAt { get; set; }
|
||||||
public object discountStartedAt { get; set; }
|
public object discountStartedAt { get; set; }
|
||||||
public object status { get; set; }
|
public string status { get; set; }
|
||||||
public bool? isMuted { get; set; }
|
public bool? isMuted { get; set; }
|
||||||
public string unsubscribeReason { get; set; }
|
public string unsubscribeReason { get; set; }
|
||||||
public string duration { get; set; }
|
public string duration { get; set; }
|
||||||
public string? tipsSumm { get; set; }
|
public bool? showPostsInFeed { get; set; }
|
||||||
public string? subscribesSumm { get; set; }
|
public List<Subscribe> subscribes { get; set; }
|
||||||
public string? messagesSumm { get; set; }
|
}
|
||||||
public string? postsSumm { get; set; }
|
|
||||||
public string? streamsSumm { get; set; }
|
|
||||||
public string? totalSumm { get; set; }
|
|
||||||
public DateTime? lastActivity { get; set; }
|
|
||||||
public int? recommendations { get; set; }
|
|
||||||
public List<object> subscribes { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SubscriptionBundle
|
public class SubscribedOnData
|
||||||
{
|
{
|
||||||
public long? id { get; set; }
|
public string? price { get; set; }
|
||||||
public string? discount { get; set; }
|
public string? newPrice { get; set; }
|
||||||
public string? duration { get; set; }
|
public string? regularPrice { get; set; }
|
||||||
public string? price { get; set; }
|
public string? subscribePrice { get; set; }
|
||||||
public bool? canBuy { get; set; }
|
public string? discountPercent { get; set; }
|
||||||
}
|
public string? discountPeriod { get; set; }
|
||||||
|
public DateTime? subscribeAt { get; set; }
|
||||||
|
public DateTime? expiredAt { get; set; }
|
||||||
|
public object renewedAt { get; set; }
|
||||||
|
public object discountFinishedAt { get; set; }
|
||||||
|
public object discountStartedAt { get; set; }
|
||||||
|
public object status { get; set; }
|
||||||
|
public bool? isMuted { get; set; }
|
||||||
|
public string unsubscribeReason { get; set; }
|
||||||
|
public string duration { get; set; }
|
||||||
|
public string? tipsSumm { get; set; }
|
||||||
|
public string? subscribesSumm { get; set; }
|
||||||
|
public string? messagesSumm { get; set; }
|
||||||
|
public string? postsSumm { get; set; }
|
||||||
|
public string? streamsSumm { get; set; }
|
||||||
|
public string? totalSumm { get; set; }
|
||||||
|
public DateTime? lastActivity { get; set; }
|
||||||
|
public int? recommendations { get; set; }
|
||||||
|
public List<object> subscribes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
public class SubscriptionBundle
|
||||||
|
{
|
||||||
|
public long? id { get; set; }
|
||||||
|
public string? discount { get; set; }
|
||||||
|
public string? duration { get; set; }
|
||||||
|
public string? price { get; set; }
|
||||||
|
public bool? canBuy { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
namespace OF_DL.Entities.Messages
|
namespace OF_DL.Entities.Messages;
|
||||||
|
|
||||||
|
public class MessageCollection
|
||||||
{
|
{
|
||||||
public class MessageCollection
|
public List<Messages.Medium> MessageMedia = new();
|
||||||
{
|
public List<Messages.List> MessageObjects = new();
|
||||||
public Dictionary<long, string> Messages = new Dictionary<long, string>();
|
public Dictionary<long, string> Messages = new();
|
||||||
public List<Messages.List> MessageObjects = new List<Messages.List>();
|
|
||||||
public List<Messages.Medium> MessageMedia = new List<Messages.Medium>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,179 +1,173 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Messages
|
namespace OF_DL.Entities.Messages;
|
||||||
|
|
||||||
|
public class Messages
|
||||||
{
|
{
|
||||||
public class Messages
|
public List<List> list { get; set; }
|
||||||
{
|
public bool hasMore { get; set; }
|
||||||
public List<List> list { get; set; }
|
|
||||||
public bool hasMore { get; set; }
|
|
||||||
public class Dash
|
|
||||||
{
|
|
||||||
[JsonProperty("CloudFront-Policy")]
|
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
public class Dash
|
||||||
public string CloudFrontSignature { get; set; }
|
{
|
||||||
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Drm
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
{
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
public Manifest manifest { get; set; }
|
}
|
||||||
public Signature signature { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Files
|
public class Drm
|
||||||
{
|
{
|
||||||
public Full full { get; set; }
|
public Manifest manifest { get; set; }
|
||||||
public Thumb thumb { get; set; }
|
public Signature signature { get; set; }
|
||||||
public Preview preview { get; set; }
|
}
|
||||||
public SquarePreview squarePreview { get; set; }
|
|
||||||
public Drm drm { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Full
|
public class Files
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public Full full { get; set; }
|
||||||
public int width { get; set; }
|
public Thumb thumb { get; set; }
|
||||||
public int height { get; set; }
|
public Preview preview { get; set; }
|
||||||
public long size { get; set; }
|
public SquarePreview squarePreview { get; set; }
|
||||||
public List<object> sources { get; set; }
|
public Drm drm { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SquarePreview
|
public class Full
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
}
|
public List<object> sources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Thumb
|
public class SquarePreview
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FromUser
|
public class Thumb
|
||||||
{
|
{
|
||||||
public long? id { get; set; }
|
public string url { get; set; }
|
||||||
public string _view { get; set; }
|
public int width { get; set; }
|
||||||
}
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Hls
|
public class FromUser
|
||||||
{
|
{
|
||||||
[JsonProperty("CloudFront-Policy")]
|
public long? id { get; set; }
|
||||||
public string CloudFrontPolicy { get; set; }
|
public string _view { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
public class Hls
|
||||||
public string CloudFrontSignature { get; set; }
|
{
|
||||||
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Info
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
{
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
public Source source { get; set; }
|
}
|
||||||
public Preview preview { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class List
|
public class Info
|
||||||
{
|
{
|
||||||
public string responseType { get; set; }
|
public Source source { get; set; }
|
||||||
public string text { get; set; }
|
public Preview preview { get; set; }
|
||||||
public object giphyId { get; set; }
|
}
|
||||||
public bool? lockedText { get; set; }
|
|
||||||
public bool? isFree { get; set; }
|
|
||||||
public string? price { get; set; }
|
|
||||||
public bool? isMediaReady { get; set; }
|
|
||||||
public int? mediaCount { get; set; }
|
|
||||||
public List<Medium> media { get; set; }
|
|
||||||
public List<object> previews { get; set; }
|
|
||||||
public bool? isTip { get; set; }
|
|
||||||
public bool? isReportedByMe { get; set; }
|
|
||||||
public bool? isCouplePeopleMedia { get; set; }
|
|
||||||
public object queueId { get; set; }
|
|
||||||
public FromUser fromUser { get; set; }
|
|
||||||
public bool? isFromQueue { get; set; }
|
|
||||||
public bool? canUnsendQueue { get; set; }
|
|
||||||
public int? unsendSecondsQueue { get; set; }
|
|
||||||
public long id { get; set; }
|
|
||||||
public bool? isOpened { get; set; }
|
|
||||||
public bool? isNew { get; set; }
|
|
||||||
public DateTime? createdAt { get; set; }
|
|
||||||
public DateTime? changedAt { get; set; }
|
|
||||||
public int? cancelSeconds { get; set; }
|
|
||||||
public bool? isLiked { get; set; }
|
|
||||||
public bool? canPurchase { get; set; }
|
|
||||||
public string canPurchaseReason { get; set; }
|
|
||||||
public bool? canReport { get; set; }
|
|
||||||
public bool? canBePinned { get; set; }
|
|
||||||
public bool? isPinned { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Manifest
|
public class List
|
||||||
{
|
{
|
||||||
public string hls { get; set; }
|
public string responseType { get; set; }
|
||||||
public string dash { get; set; }
|
public string text { get; set; }
|
||||||
}
|
public object giphyId { get; set; }
|
||||||
|
public bool? lockedText { get; set; }
|
||||||
|
public bool? isFree { get; set; }
|
||||||
|
public string? price { get; set; }
|
||||||
|
public bool? isMediaReady { get; set; }
|
||||||
|
public int? mediaCount { get; set; }
|
||||||
|
public List<Medium> media { get; set; }
|
||||||
|
public List<object> previews { get; set; }
|
||||||
|
public bool? isTip { get; set; }
|
||||||
|
public bool? isReportedByMe { get; set; }
|
||||||
|
public bool? isCouplePeopleMedia { get; set; }
|
||||||
|
public object queueId { get; set; }
|
||||||
|
public FromUser fromUser { get; set; }
|
||||||
|
public bool? isFromQueue { get; set; }
|
||||||
|
public bool? canUnsendQueue { get; set; }
|
||||||
|
public int? unsendSecondsQueue { get; set; }
|
||||||
|
public long id { get; set; }
|
||||||
|
public bool? isOpened { get; set; }
|
||||||
|
public bool? isNew { get; set; }
|
||||||
|
public DateTime? createdAt { get; set; }
|
||||||
|
public DateTime? changedAt { get; set; }
|
||||||
|
public int? cancelSeconds { get; set; }
|
||||||
|
public bool? isLiked { get; set; }
|
||||||
|
public bool? canPurchase { get; set; }
|
||||||
|
public string canPurchaseReason { get; set; }
|
||||||
|
public bool? canReport { get; set; }
|
||||||
|
public bool? canBePinned { get; set; }
|
||||||
|
public bool? isPinned { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Medium
|
public class Manifest
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
public string hls { get; set; }
|
||||||
public bool canView { get; set; }
|
public string dash { get; set; }
|
||||||
public string type { get; set; }
|
}
|
||||||
public string src { get; set; }
|
|
||||||
public string preview { get; set; }
|
|
||||||
public string thumb { get; set; }
|
|
||||||
public object locked { get; set; }
|
|
||||||
public int? duration { get; set; }
|
|
||||||
public bool? hasError { get; set; }
|
|
||||||
public string squarePreview { get; set; }
|
|
||||||
public Video video { get; set; }
|
|
||||||
public VideoSources videoSources { get; set; }
|
|
||||||
public Source source { get; set; }
|
|
||||||
public Info info { get; set; }
|
|
||||||
public Files files { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Preview
|
public class Medium
|
||||||
{
|
{
|
||||||
public int? width { get; set; }
|
public long id { get; set; }
|
||||||
public int? height { get; set; }
|
public bool canView { get; set; }
|
||||||
public int? size { get; set; }
|
public string type { get; set; }
|
||||||
}
|
public string src { get; set; }
|
||||||
|
public string preview { get; set; }
|
||||||
|
public string thumb { get; set; }
|
||||||
|
public object locked { get; set; }
|
||||||
|
public int? duration { get; set; }
|
||||||
|
public bool? hasError { get; set; }
|
||||||
|
public string squarePreview { get; set; }
|
||||||
|
public Video video { get; set; }
|
||||||
|
public VideoSources videoSources { get; set; }
|
||||||
|
public Source source { get; set; }
|
||||||
|
public Info info { get; set; }
|
||||||
|
public Files files { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Signature
|
public class Preview
|
||||||
{
|
{
|
||||||
public Hls hls { get; set; }
|
public int? width { get; set; }
|
||||||
public Dash dash { get; set; }
|
public int? height { get; set; }
|
||||||
}
|
public int? size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Source
|
public class Signature
|
||||||
{
|
{
|
||||||
public string source { get; set; }
|
public Hls hls { get; set; }
|
||||||
public int? width { get; set; }
|
public Dash dash { get; set; }
|
||||||
public int? height { get; set; }
|
}
|
||||||
public int? size { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Video
|
public class Source
|
||||||
{
|
{
|
||||||
public string mp4 { get; set; }
|
public string source { get; set; }
|
||||||
}
|
public int? width { get; set; }
|
||||||
|
public int? height { get; set; }
|
||||||
|
public int? size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class VideoSources
|
public class Video
|
||||||
{
|
{
|
||||||
[JsonProperty("720")]
|
public string mp4 { get; set; }
|
||||||
public string _720 { get; set; }
|
}
|
||||||
|
|
||||||
[JsonProperty("240")]
|
public class VideoSources
|
||||||
public string _240 { get; set; }
|
{
|
||||||
}
|
[JsonProperty("720")] public string _720 { get; set; }
|
||||||
}
|
|
||||||
|
[JsonProperty("240")] public string _240 { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,118 +1,115 @@
|
|||||||
using static OF_DL.Entities.Messages.Messages;
|
using static OF_DL.Entities.Messages.Messages;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Messages
|
namespace OF_DL.Entities.Messages;
|
||||||
|
|
||||||
|
public class AvatarThumbs
|
||||||
{
|
{
|
||||||
public class AvatarThumbs
|
public string c50 { get; set; }
|
||||||
{
|
public string c144 { get; set; }
|
||||||
public string c50 { get; set; }
|
|
||||||
public string c144 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FromUser
|
|
||||||
{
|
|
||||||
public string view { get; set; }
|
|
||||||
public string avatar { get; set; }
|
|
||||||
public AvatarThumbs avatarThumbs { get; set; }
|
|
||||||
public string header { get; set; }
|
|
||||||
public HeaderSize headerSize { get; set; }
|
|
||||||
public HeaderThumbs headerThumbs { get; set; }
|
|
||||||
public long? id { get; set; }
|
|
||||||
public string name { get; set; }
|
|
||||||
public string username { get; set; }
|
|
||||||
public bool canLookStory { get; set; }
|
|
||||||
public bool canCommentStory { get; set; }
|
|
||||||
public bool hasNotViewedStory { get; set; }
|
|
||||||
public bool isVerified { get; set; }
|
|
||||||
public bool canPayInternal { get; set; }
|
|
||||||
public bool hasScheduledStream { get; set; }
|
|
||||||
public bool hasStream { get; set; }
|
|
||||||
public bool hasStories { get; set; }
|
|
||||||
public bool tipsEnabled { get; set; }
|
|
||||||
public bool tipsTextEnabled { get; set; }
|
|
||||||
public int tipsMin { get; set; }
|
|
||||||
public int tipsMinInternal { get; set; }
|
|
||||||
public int tipsMax { get; set; }
|
|
||||||
public bool canEarn { get; set; }
|
|
||||||
public bool canAddSubscriber { get; set; }
|
|
||||||
public string? subscribePrice { get; set; }
|
|
||||||
public List<object> subscriptionBundles { get; set; }
|
|
||||||
public bool isPaywallRequired { get; set; }
|
|
||||||
public List<ListsState> listsStates { get; set; }
|
|
||||||
public bool isRestricted { get; set; }
|
|
||||||
public bool canRestrict { get; set; }
|
|
||||||
public object subscribedBy { get; set; }
|
|
||||||
public object subscribedByExpire { get; set; }
|
|
||||||
public DateTime subscribedByExpireDate { get; set; }
|
|
||||||
public object subscribedByAutoprolong { get; set; }
|
|
||||||
public bool subscribedIsExpiredNow { get; set; }
|
|
||||||
public object currentSubscribePrice { get; set; }
|
|
||||||
public object subscribedOn { get; set; }
|
|
||||||
public object subscribedOnExpiredNow { get; set; }
|
|
||||||
public object subscribedOnDuration { get; set; }
|
|
||||||
public int callPrice { get; set; }
|
|
||||||
public DateTime? lastSeen { get; set; }
|
|
||||||
public bool canReport { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HeaderSize
|
|
||||||
{
|
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HeaderThumbs
|
|
||||||
{
|
|
||||||
public string w480 { get; set; }
|
|
||||||
public string w760 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ListsState
|
|
||||||
{
|
|
||||||
public string id { get; set; }
|
|
||||||
public string type { get; set; }
|
|
||||||
public string name { get; set; }
|
|
||||||
public bool hasUser { get; set; }
|
|
||||||
public bool canAddUser { get; set; }
|
|
||||||
public string cannotAddUserReason { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Preview
|
|
||||||
{
|
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SingleMessage
|
|
||||||
{
|
|
||||||
public string responseType { get; set; }
|
|
||||||
public string text { get; set; }
|
|
||||||
public object giphyId { get; set; }
|
|
||||||
public bool lockedText { get; set; }
|
|
||||||
public bool isFree { get; set; }
|
|
||||||
public double price { get; set; }
|
|
||||||
public bool isMediaReady { get; set; }
|
|
||||||
public int mediaCount { get; set; }
|
|
||||||
public List<Medium> media { get; set; }
|
|
||||||
public List<object> previews { get; set; }
|
|
||||||
public bool isTip { get; set; }
|
|
||||||
public bool isReportedByMe { get; set; }
|
|
||||||
public bool isCouplePeopleMedia { get; set; }
|
|
||||||
public long queueId { get; set; }
|
|
||||||
public FromUser fromUser { get; set; }
|
|
||||||
public bool isFromQueue { get; set; }
|
|
||||||
public bool canUnsendQueue { get; set; }
|
|
||||||
public int unsendSecondsQueue { get; set; }
|
|
||||||
public long id { get; set; }
|
|
||||||
public bool isOpened { get; set; }
|
|
||||||
public bool isNew { get; set; }
|
|
||||||
public DateTime? createdAt { get; set; }
|
|
||||||
public DateTime? changedAt { get; set; }
|
|
||||||
public int cancelSeconds { get; set; }
|
|
||||||
public bool isLiked { get; set; }
|
|
||||||
public bool canPurchase { get; set; }
|
|
||||||
public bool canReport { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class FromUser
|
||||||
|
{
|
||||||
|
public string view { get; set; }
|
||||||
|
public string avatar { get; set; }
|
||||||
|
public AvatarThumbs avatarThumbs { get; set; }
|
||||||
|
public string header { get; set; }
|
||||||
|
public HeaderSize headerSize { get; set; }
|
||||||
|
public HeaderThumbs headerThumbs { get; set; }
|
||||||
|
public long? id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public string username { get; set; }
|
||||||
|
public bool canLookStory { get; set; }
|
||||||
|
public bool canCommentStory { get; set; }
|
||||||
|
public bool hasNotViewedStory { get; set; }
|
||||||
|
public bool isVerified { get; set; }
|
||||||
|
public bool canPayInternal { get; set; }
|
||||||
|
public bool hasScheduledStream { get; set; }
|
||||||
|
public bool hasStream { get; set; }
|
||||||
|
public bool hasStories { get; set; }
|
||||||
|
public bool tipsEnabled { get; set; }
|
||||||
|
public bool tipsTextEnabled { get; set; }
|
||||||
|
public int tipsMin { get; set; }
|
||||||
|
public int tipsMinInternal { get; set; }
|
||||||
|
public int tipsMax { get; set; }
|
||||||
|
public bool canEarn { get; set; }
|
||||||
|
public bool canAddSubscriber { get; set; }
|
||||||
|
public string? subscribePrice { get; set; }
|
||||||
|
public List<object> subscriptionBundles { get; set; }
|
||||||
|
public bool isPaywallRequired { get; set; }
|
||||||
|
public List<ListsState> listsStates { get; set; }
|
||||||
|
public bool isRestricted { get; set; }
|
||||||
|
public bool canRestrict { get; set; }
|
||||||
|
public object subscribedBy { get; set; }
|
||||||
|
public object subscribedByExpire { get; set; }
|
||||||
|
public DateTime subscribedByExpireDate { get; set; }
|
||||||
|
public object subscribedByAutoprolong { get; set; }
|
||||||
|
public bool subscribedIsExpiredNow { get; set; }
|
||||||
|
public object currentSubscribePrice { get; set; }
|
||||||
|
public object subscribedOn { get; set; }
|
||||||
|
public object subscribedOnExpiredNow { get; set; }
|
||||||
|
public object subscribedOnDuration { get; set; }
|
||||||
|
public int callPrice { get; set; }
|
||||||
|
public DateTime? lastSeen { get; set; }
|
||||||
|
public bool canReport { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HeaderSize
|
||||||
|
{
|
||||||
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HeaderThumbs
|
||||||
|
{
|
||||||
|
public string w480 { get; set; }
|
||||||
|
public string w760 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ListsState
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public bool hasUser { get; set; }
|
||||||
|
public bool canAddUser { get; set; }
|
||||||
|
public string cannotAddUserReason { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Preview
|
||||||
|
{
|
||||||
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SingleMessage
|
||||||
|
{
|
||||||
|
public string responseType { get; set; }
|
||||||
|
public string text { get; set; }
|
||||||
|
public object giphyId { get; set; }
|
||||||
|
public bool lockedText { get; set; }
|
||||||
|
public bool isFree { get; set; }
|
||||||
|
public double price { get; set; }
|
||||||
|
public bool isMediaReady { get; set; }
|
||||||
|
public int mediaCount { get; set; }
|
||||||
|
public List<Medium> media { get; set; }
|
||||||
|
public List<object> previews { get; set; }
|
||||||
|
public bool isTip { get; set; }
|
||||||
|
public bool isReportedByMe { get; set; }
|
||||||
|
public bool isCouplePeopleMedia { get; set; }
|
||||||
|
public long queueId { get; set; }
|
||||||
|
public FromUser fromUser { get; set; }
|
||||||
|
public bool isFromQueue { get; set; }
|
||||||
|
public bool canUnsendQueue { get; set; }
|
||||||
|
public int unsendSecondsQueue { get; set; }
|
||||||
|
public long id { get; set; }
|
||||||
|
public bool isOpened { get; set; }
|
||||||
|
public bool isNew { get; set; }
|
||||||
|
public DateTime? createdAt { get; set; }
|
||||||
|
public DateTime? changedAt { get; set; }
|
||||||
|
public int cancelSeconds { get; set; }
|
||||||
|
public bool isLiked { get; set; }
|
||||||
|
public bool canPurchase { get; set; }
|
||||||
|
public bool canReport { get; set; }
|
||||||
|
}
|
||||||
|
|||||||
@ -1,16 +1,12 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class OFDLRequest
|
||||||
{
|
{
|
||||||
public class OFDLRequest
|
[JsonProperty("pssh")] public string PSSH { get; set; } = "";
|
||||||
{
|
|
||||||
[JsonProperty("pssh")]
|
|
||||||
public string PSSH { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("licenceURL")]
|
[JsonProperty("licenceURL")] public string LicenseURL { get; set; } = "";
|
||||||
public string LicenseURL { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("headers")]
|
[JsonProperty("headers")] public string Headers { get; set; } = "";
|
||||||
public string Headers { get; set; } = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ public class Post
|
|||||||
public string headMarker { get; set; }
|
public string headMarker { get; set; }
|
||||||
|
|
||||||
public string tailMarker { get; set; }
|
public string tailMarker { get; set; }
|
||||||
|
|
||||||
public class Author
|
public class Author
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
public long id { get; set; }
|
||||||
@ -20,11 +21,9 @@ public class Post
|
|||||||
|
|
||||||
public class Dash
|
public class Dash
|
||||||
{
|
{
|
||||||
[JsonProperty("CloudFront-Policy")]
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
public string CloudFrontSignature { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
@ -72,11 +71,9 @@ public class Post
|
|||||||
|
|
||||||
public class Hls
|
public class Hls
|
||||||
{
|
{
|
||||||
[JsonProperty("CloudFront-Policy")]
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
public string CloudFrontSignature { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
@ -90,6 +87,7 @@ public class Post
|
|||||||
|
|
||||||
public class List
|
public class List
|
||||||
{
|
{
|
||||||
|
private string _rawText;
|
||||||
public string responseType { get; set; }
|
public string responseType { get; set; }
|
||||||
public long id { get; set; }
|
public long id { get; set; }
|
||||||
public DateTime postedAt { get; set; }
|
public DateTime postedAt { get; set; }
|
||||||
@ -98,23 +96,20 @@ public class Post
|
|||||||
public Author author { get; set; }
|
public Author author { get; set; }
|
||||||
public string text { get; set; }
|
public string text { get; set; }
|
||||||
|
|
||||||
private string _rawText;
|
|
||||||
public string rawText
|
public string rawText
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if(string.IsNullOrEmpty(_rawText))
|
if (string.IsNullOrEmpty(_rawText))
|
||||||
{
|
{
|
||||||
_rawText = XmlUtils.EvaluateInnerText(text);
|
_rawText = XmlUtils.EvaluateInnerText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _rawText;
|
return _rawText;
|
||||||
}
|
}
|
||||||
set
|
set => _rawText = value;
|
||||||
{
|
|
||||||
_rawText = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool? lockedText { get; set; }
|
public bool? lockedText { get; set; }
|
||||||
public bool? isFavorite { get; set; }
|
public bool? isFavorite { get; set; }
|
||||||
public bool? canReport { get; set; }
|
public bool? canReport { get; set; }
|
||||||
@ -197,11 +192,9 @@ public class Post
|
|||||||
|
|
||||||
public class VideoSources
|
public class VideoSources
|
||||||
{
|
{
|
||||||
[JsonProperty("720")]
|
[JsonProperty("720")] public object _720 { get; set; }
|
||||||
public object _720 { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("240")]
|
[JsonProperty("240")] public object _240 { get; set; }
|
||||||
public object _240 { get; set; }
|
|
||||||
}
|
}
|
||||||
#pragma warning restore IDE1006 // Naming Styles
|
#pragma warning restore IDE1006 // Naming Styles
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
namespace OF_DL.Entities.Post
|
namespace OF_DL.Entities.Post;
|
||||||
|
|
||||||
|
public class PostCollection
|
||||||
{
|
{
|
||||||
public class PostCollection
|
public List<Post.Medium> PostMedia = new();
|
||||||
{
|
public List<Post.List> PostObjects = new();
|
||||||
public Dictionary<long, string> Posts = new Dictionary<long, string>();
|
public Dictionary<long, string> Posts = new();
|
||||||
public List<Post.List> PostObjects = new List<Post.List>();
|
|
||||||
public List<Post.Medium> PostMedia = new List<Post.Medium>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,191 +1,188 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OF_DL.Utils;
|
using OF_DL.Utils;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Post
|
namespace OF_DL.Entities.Post;
|
||||||
|
|
||||||
|
public class SinglePost
|
||||||
{
|
{
|
||||||
public class SinglePost
|
private string _rawText;
|
||||||
|
public string responseType { get; set; }
|
||||||
|
public long id { get; set; }
|
||||||
|
public DateTime postedAt { get; set; }
|
||||||
|
public string postedAtPrecise { get; set; }
|
||||||
|
public object expiredAt { get; set; }
|
||||||
|
public Author author { get; set; }
|
||||||
|
public string text { get; set; }
|
||||||
|
|
||||||
|
public string rawText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_rawText))
|
||||||
|
{
|
||||||
|
_rawText = XmlUtils.EvaluateInnerText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _rawText;
|
||||||
|
}
|
||||||
|
set => _rawText = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool lockedText { get; set; }
|
||||||
|
public bool isFavorite { get; set; }
|
||||||
|
public bool canReport { get; set; }
|
||||||
|
public bool canDelete { get; set; }
|
||||||
|
public bool canComment { get; set; }
|
||||||
|
public bool canEdit { get; set; }
|
||||||
|
public bool isPinned { get; set; }
|
||||||
|
public int favoritesCount { get; set; }
|
||||||
|
public int mediaCount { get; set; }
|
||||||
|
public bool isMediaReady { get; set; }
|
||||||
|
public object voting { get; set; }
|
||||||
|
public bool isOpened { get; set; }
|
||||||
|
public bool canToggleFavorite { get; set; }
|
||||||
|
public string streamId { get; set; }
|
||||||
|
public string price { get; set; }
|
||||||
|
public bool hasVoting { get; set; }
|
||||||
|
public bool isAddedToBookmarks { get; set; }
|
||||||
|
public bool isArchived { get; set; }
|
||||||
|
public bool isPrivateArchived { get; set; }
|
||||||
|
public bool isDeleted { get; set; }
|
||||||
|
public bool hasUrl { get; set; }
|
||||||
|
public bool isCouplePeopleMedia { get; set; }
|
||||||
|
public int commentsCount { get; set; }
|
||||||
|
public List<object> mentionedUsers { get; set; }
|
||||||
|
public List<object> linkedUsers { get; set; }
|
||||||
|
public string tipsAmount { get; set; }
|
||||||
|
public string tipsAmountRaw { get; set; }
|
||||||
|
public List<Medium> media { get; set; }
|
||||||
|
public bool canViewMedia { get; set; }
|
||||||
|
public List<object> preview { get; set; }
|
||||||
|
|
||||||
|
public class Author
|
||||||
{
|
{
|
||||||
public string responseType { get; set; }
|
|
||||||
public long id { get; set; }
|
public long id { get; set; }
|
||||||
public DateTime postedAt { get; set; }
|
public string _view { get; set; }
|
||||||
public string postedAtPrecise { get; set; }
|
}
|
||||||
public object expiredAt { get; set; }
|
|
||||||
public Author author { get; set; }
|
|
||||||
public string text { get; set; }
|
|
||||||
private string _rawText;
|
|
||||||
public string rawText
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(_rawText))
|
|
||||||
{
|
|
||||||
_rawText = XmlUtils.EvaluateInnerText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _rawText;
|
public class Files
|
||||||
}
|
{
|
||||||
set
|
public Full full { get; set; }
|
||||||
{
|
public Thumb thumb { get; set; }
|
||||||
_rawText = value;
|
public Preview preview { get; set; }
|
||||||
}
|
public SquarePreview squarePreview { get; set; }
|
||||||
}
|
public Drm drm { get; set; }
|
||||||
public bool lockedText { get; set; }
|
}
|
||||||
public bool isFavorite { get; set; }
|
|
||||||
public bool canReport { get; set; }
|
|
||||||
public bool canDelete { get; set; }
|
|
||||||
public bool canComment { get; set; }
|
|
||||||
public bool canEdit { get; set; }
|
|
||||||
public bool isPinned { get; set; }
|
|
||||||
public int favoritesCount { get; set; }
|
|
||||||
public int mediaCount { get; set; }
|
|
||||||
public bool isMediaReady { get; set; }
|
|
||||||
public object voting { get; set; }
|
|
||||||
public bool isOpened { get; set; }
|
|
||||||
public bool canToggleFavorite { get; set; }
|
|
||||||
public string streamId { get; set; }
|
|
||||||
public string price { get; set; }
|
|
||||||
public bool hasVoting { get; set; }
|
|
||||||
public bool isAddedToBookmarks { get; set; }
|
|
||||||
public bool isArchived { get; set; }
|
|
||||||
public bool isPrivateArchived { get; set; }
|
|
||||||
public bool isDeleted { get; set; }
|
|
||||||
public bool hasUrl { get; set; }
|
|
||||||
public bool isCouplePeopleMedia { get; set; }
|
|
||||||
public int commentsCount { get; set; }
|
|
||||||
public List<object> mentionedUsers { get; set; }
|
|
||||||
public List<object> linkedUsers { get; set; }
|
|
||||||
public string tipsAmount { get; set; }
|
|
||||||
public string tipsAmountRaw { get; set; }
|
|
||||||
public List<Medium> media { get; set; }
|
|
||||||
public bool canViewMedia { get; set; }
|
|
||||||
public List<object> preview { get; set; }
|
|
||||||
public class Author
|
|
||||||
{
|
|
||||||
public long id { get; set; }
|
|
||||||
public string _view { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Files
|
public class Full
|
||||||
{
|
{
|
||||||
public Full full { get; set; }
|
public string url { get; set; }
|
||||||
public Thumb thumb { get; set; }
|
public int width { get; set; }
|
||||||
public Preview preview { get; set; }
|
public int height { get; set; }
|
||||||
public SquarePreview squarePreview { get; set; }
|
public long size { get; set; }
|
||||||
public Drm drm { get; set; }
|
public List<object> sources { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Full
|
public class SquarePreview
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
public List<object> sources { get; set; }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class SquarePreview
|
public class Thumb
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Thumb
|
public class Info
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public Source source { get; set; }
|
||||||
public int width { get; set; }
|
public Preview preview { get; set; }
|
||||||
public int height { get; set; }
|
}
|
||||||
public long size { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Info
|
public class Medium
|
||||||
{
|
{
|
||||||
public Source source { get; set; }
|
public long id { get; set; }
|
||||||
public Preview preview { get; set; }
|
public string type { get; set; }
|
||||||
}
|
public bool convertedToVideo { get; set; }
|
||||||
|
public bool canView { get; set; }
|
||||||
|
public bool hasError { get; set; }
|
||||||
|
public DateTime? createdAt { get; set; }
|
||||||
|
public Info info { get; set; }
|
||||||
|
public Source source { get; set; }
|
||||||
|
public string squarePreview { get; set; }
|
||||||
|
public string full { get; set; }
|
||||||
|
public string preview { get; set; }
|
||||||
|
public string thumb { get; set; }
|
||||||
|
public bool hasCustomPreview { get; set; }
|
||||||
|
public Files files { get; set; }
|
||||||
|
public VideoSources videoSources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Medium
|
public class Preview
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
public int width { get; set; }
|
||||||
public string type { get; set; }
|
public int height { get; set; }
|
||||||
public bool convertedToVideo { get; set; }
|
public long size { get; set; }
|
||||||
public bool canView { get; set; }
|
public string url { get; set; }
|
||||||
public bool hasError { get; set; }
|
}
|
||||||
public DateTime? createdAt { get; set; }
|
|
||||||
public Info info { get; set; }
|
|
||||||
public Source source { get; set; }
|
|
||||||
public string squarePreview { get; set; }
|
|
||||||
public string full { get; set; }
|
|
||||||
public string preview { get; set; }
|
|
||||||
public string thumb { get; set; }
|
|
||||||
public bool hasCustomPreview { get; set; }
|
|
||||||
public Files files { get; set; }
|
|
||||||
public VideoSources videoSources { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Preview
|
public class Source
|
||||||
{
|
{
|
||||||
public int width { get; set; }
|
public string source { get; set; }
|
||||||
public int height { get; set; }
|
public int width { get; set; }
|
||||||
public long size { get; set; }
|
public int height { get; set; }
|
||||||
public string url { get; set; }
|
public long size { get; set; }
|
||||||
}
|
public int duration { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Source
|
public class VideoSources
|
||||||
{
|
{
|
||||||
public string source { get; set; }
|
[JsonProperty("720")] public string _720 { get; set; }
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
public int duration { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class VideoSources
|
[JsonProperty("240")] public string _240 { get; set; }
|
||||||
{
|
}
|
||||||
[JsonProperty("720")]
|
|
||||||
public string _720 { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("240")]
|
public class Dash
|
||||||
public string _240 { get; set; }
|
{
|
||||||
}
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
public class Dash
|
|
||||||
{
|
|
||||||
[JsonProperty("CloudFront-Policy")]
|
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
public string CloudFrontSignature { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Drm
|
public class Drm
|
||||||
{
|
{
|
||||||
public Manifest manifest { get; set; }
|
public Manifest manifest { get; set; }
|
||||||
public Signature signature { get; set; }
|
public Signature signature { get; set; }
|
||||||
}
|
}
|
||||||
public class Hls
|
|
||||||
{
|
|
||||||
[JsonProperty("CloudFront-Policy")]
|
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
public class Hls
|
||||||
public string CloudFrontSignature { get; set; }
|
{
|
||||||
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
|
||||||
}
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
public class Manifest
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
{
|
}
|
||||||
public string? hls { get; set; }
|
|
||||||
public string? dash { get; set; }
|
public class Manifest
|
||||||
}
|
{
|
||||||
public class Signature
|
public string? hls { get; set; }
|
||||||
{
|
public string? dash { get; set; }
|
||||||
public Hls hls { get; set; }
|
}
|
||||||
public Dash dash { get; set; }
|
|
||||||
}
|
public class Signature
|
||||||
|
{
|
||||||
|
public Hls hls { get; set; }
|
||||||
|
public Dash dash { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
namespace OF_DL.Entities.Post
|
namespace OF_DL.Entities.Post;
|
||||||
|
|
||||||
|
public class SinglePostCollection
|
||||||
{
|
{
|
||||||
public class SinglePostCollection
|
public List<SinglePost.Medium> SinglePostMedia = new();
|
||||||
{
|
public List<SinglePost> SinglePostObjects = new();
|
||||||
public Dictionary<long, string> SinglePosts = new Dictionary<long, string>();
|
public Dictionary<long, string> SinglePosts = new();
|
||||||
public List<SinglePost> SinglePostObjects = new List<SinglePost>();
|
|
||||||
public List<SinglePost.Medium> SinglePostMedia = new List<SinglePost.Medium>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
using static OF_DL.Entities.Messages.Messages;
|
using static OF_DL.Entities.Messages.Messages;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Purchased
|
namespace OF_DL.Entities.Purchased;
|
||||||
|
|
||||||
|
public class PaidMessageCollection
|
||||||
{
|
{
|
||||||
public class PaidMessageCollection
|
public List<Medium> PaidMessageMedia = new();
|
||||||
{
|
public List<Purchased.List> PaidMessageObjects = new();
|
||||||
public Dictionary<long, string> PaidMessages = new Dictionary<long, string>();
|
public Dictionary<long, string> PaidMessages = new();
|
||||||
public List<Purchased.List> PaidMessageObjects = new List<Purchased.List>();
|
|
||||||
public List<Medium> PaidMessageMedia = new List<Medium>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
using static OF_DL.Entities.Messages.Messages;
|
using static OF_DL.Entities.Messages.Messages;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Purchased
|
namespace OF_DL.Entities.Purchased;
|
||||||
|
|
||||||
|
public class PaidPostCollection
|
||||||
{
|
{
|
||||||
public class PaidPostCollection
|
public List<Medium> PaidPostMedia = new();
|
||||||
{
|
public List<Purchased.List> PaidPostObjects = new();
|
||||||
public Dictionary<long, string> PaidPosts = new Dictionary<long, string>();
|
public Dictionary<long, string> PaidPosts = new();
|
||||||
public List<Purchased.List> PaidPostObjects = new List<Purchased.List>();
|
|
||||||
public List<Medium> PaidPostMedia = new List<Medium>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,77 +1,75 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using static OF_DL.Entities.Messages.Messages;
|
using static OF_DL.Entities.Messages.Messages;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Purchased
|
namespace OF_DL.Entities.Purchased;
|
||||||
|
|
||||||
|
public class Purchased
|
||||||
{
|
{
|
||||||
public class Purchased
|
public List<List> list { get; set; }
|
||||||
|
public bool hasMore { get; set; }
|
||||||
|
|
||||||
|
public class FromUser
|
||||||
{
|
{
|
||||||
public List<List> list { get; set; }
|
public long id { get; set; }
|
||||||
public bool hasMore { get; set; }
|
public string _view { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class FromUser
|
public class Author
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
public long id { get; set; }
|
||||||
public string _view { get; set; }
|
public string _view { get; set; }
|
||||||
}
|
}
|
||||||
public class Author
|
|
||||||
{
|
|
||||||
public long id { get; set; }
|
|
||||||
public string _view { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Hls
|
public class Hls
|
||||||
{
|
{
|
||||||
[JsonProperty("CloudFront-Policy")]
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
public string CloudFrontSignature { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class List
|
public class List
|
||||||
{
|
{
|
||||||
public string responseType { get; set; }
|
public string responseType { get; set; }
|
||||||
public string text { get; set; }
|
public string text { get; set; }
|
||||||
public object giphyId { get; set; }
|
public object giphyId { get; set; }
|
||||||
public bool? lockedText { get; set; }
|
public bool? lockedText { get; set; }
|
||||||
public bool? isFree { get; set; }
|
public bool? isFree { get; set; }
|
||||||
public string? price { get; set; }
|
public string? price { get; set; }
|
||||||
public bool? isMediaReady { get; set; }
|
public bool? isMediaReady { get; set; }
|
||||||
public int? mediaCount { get; set; }
|
public int? mediaCount { get; set; }
|
||||||
public List<Medium> media { get; set; }
|
public List<Medium> media { get; set; }
|
||||||
public List<object> previews { get; set; }
|
public List<object> previews { get; set; }
|
||||||
public List<object> preview { get; set; }
|
public List<object> preview { get; set; }
|
||||||
public bool? isTip { get; set; }
|
public bool? isTip { get; set; }
|
||||||
public bool? isReportedByMe { get; set; }
|
public bool? isReportedByMe { get; set; }
|
||||||
public bool? isCouplePeopleMedia { get; set; }
|
public bool? isCouplePeopleMedia { get; set; }
|
||||||
public object queueId { get; set; }
|
public object queueId { get; set; }
|
||||||
public FromUser fromUser { get; set; }
|
public FromUser fromUser { get; set; }
|
||||||
public Author author { get; set; }
|
public Author author { get; set; }
|
||||||
public bool? isFromQueue { get; set; }
|
public bool? isFromQueue { get; set; }
|
||||||
public bool? canUnsendQueue { get; set; }
|
public bool? canUnsendQueue { get; set; }
|
||||||
public int? unsendSecondsQueue { get; set; }
|
public int? unsendSecondsQueue { get; set; }
|
||||||
public long id { get; set; }
|
public long id { get; set; }
|
||||||
public bool isOpened { get; set; }
|
public bool isOpened { get; set; }
|
||||||
public bool? isNew { get; set; }
|
public bool? isNew { get; set; }
|
||||||
public DateTime? createdAt { get; set; }
|
public DateTime? createdAt { get; set; }
|
||||||
public DateTime? postedAt { get; set; }
|
public DateTime? postedAt { get; set; }
|
||||||
public DateTime? changedAt { get; set; }
|
public DateTime? changedAt { get; set; }
|
||||||
public int? cancelSeconds { get; set; }
|
public int? cancelSeconds { get; set; }
|
||||||
public bool? isLiked { get; set; }
|
public bool? isLiked { get; set; }
|
||||||
public bool? canPurchase { get; set; }
|
public bool? canPurchase { get; set; }
|
||||||
public bool? canReport { get; set; }
|
public bool? canReport { get; set; }
|
||||||
public bool? isCanceled { get; set; }
|
public bool? isCanceled { get; set; }
|
||||||
public bool? isArchived { get; set; }
|
public bool? isArchived { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Manifest
|
public class Manifest
|
||||||
{
|
{
|
||||||
public string hls { get; set; }
|
public string hls { get; set; }
|
||||||
public string dash { get; set; }
|
public string dash { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
namespace OF_DL.Entities.Purchased
|
namespace OF_DL.Entities.Purchased;
|
||||||
|
|
||||||
|
public class PurchasedTabCollection
|
||||||
{
|
{
|
||||||
public class PurchasedTabCollection
|
public long UserId { get; set; }
|
||||||
{
|
public string Username { get; set; } = string.Empty;
|
||||||
public long UserId { get; set; }
|
public PaidPostCollection PaidPosts { get; set; } = new();
|
||||||
public string Username { get; set; } = string.Empty;
|
public PaidMessageCollection PaidMessages { get; set; } = new();
|
||||||
public PaidPostCollection PaidPosts { get; set; } = new PaidPostCollection();
|
|
||||||
public PaidMessageCollection PaidMessages { get; set; } = new PaidMessageCollection();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
using OF_DL.Entities.Messages;
|
using OF_DL.Entities.Messages;
|
||||||
using static OF_DL.Entities.Messages.Messages;
|
using static OF_DL.Entities.Messages.Messages;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Purchased
|
namespace OF_DL.Entities.Purchased;
|
||||||
{
|
|
||||||
public class SinglePaidMessageCollection
|
|
||||||
{
|
|
||||||
public Dictionary<long, string> SingleMessages = new Dictionary<long, string>();
|
|
||||||
public List<SingleMessage> SingleMessageObjects = new List<SingleMessage>();
|
|
||||||
public List<Medium> SingleMessageMedia = new List<Medium>();
|
|
||||||
|
|
||||||
public Dictionary<long, string> PreviewSingleMessages = new Dictionary<long, string>();
|
public class SinglePaidMessageCollection
|
||||||
public List<Medium> PreviewSingleMessageMedia = new List<Medium>();
|
{
|
||||||
}
|
public List<Medium> PreviewSingleMessageMedia = new();
|
||||||
|
|
||||||
|
public Dictionary<long, string> PreviewSingleMessages = new();
|
||||||
|
public List<Medium> SingleMessageMedia = new();
|
||||||
|
public List<SingleMessage> SingleMessageObjects = new();
|
||||||
|
public Dictionary<long, string> SingleMessages = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class ShortDateConverter : IsoDateTimeConverter
|
||||||
{
|
{
|
||||||
public class ShortDateConverter : IsoDateTimeConverter
|
public ShortDateConverter() => DateTimeFormat = "yyyy-MM-dd";
|
||||||
{
|
|
||||||
public ShortDateConverter()
|
|
||||||
{
|
|
||||||
DateTimeFormat = "yyyy-MM-dd";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,91 +1,90 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Stories
|
namespace OF_DL.Entities.Stories;
|
||||||
|
|
||||||
|
public class Stories
|
||||||
{
|
{
|
||||||
public class Stories
|
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; }
|
||||||
|
public DateTime? createdAt { get; set; }
|
||||||
|
public object question { get; set; }
|
||||||
|
public bool canLike { get; set; }
|
||||||
|
public bool isLiked { get; set; }
|
||||||
|
|
||||||
|
public class Files
|
||||||
|
{
|
||||||
|
public Full full { get; set; }
|
||||||
|
public Thumb thumb { get; set; }
|
||||||
|
public Preview preview { get; set; }
|
||||||
|
public SquarePreview squarePreview { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Full
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
public List<object> sources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Medium
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
public long id { get; set; }
|
||||||
public long userId { get; set; }
|
public string type { get; set; }
|
||||||
public bool isWatched { get; set; }
|
public bool convertedToVideo { get; set; }
|
||||||
public bool isReady { get; set; }
|
public bool canView { get; set; }
|
||||||
public List<Medium> media { get; set; }
|
public bool hasError { get; set; }
|
||||||
public DateTime? createdAt { get; set; }
|
public DateTime? createdAt { get; set; }
|
||||||
public object question { get; set; }
|
public Files files { get; set; }
|
||||||
public bool canLike { get; set; }
|
}
|
||||||
public bool isLiked { get; set; }
|
|
||||||
public class Files
|
|
||||||
{
|
|
||||||
public Full full { get; set; }
|
|
||||||
public Thumb thumb { get; set; }
|
|
||||||
public Preview preview { get; set; }
|
|
||||||
public SquarePreview squarePreview { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Full
|
public class Preview
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
public List<object> sources { get; set; }
|
public Sources sources { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Medium
|
public class Source
|
||||||
{
|
{
|
||||||
public long id { get; set; }
|
public string url { get; set; }
|
||||||
public string type { get; set; }
|
public int width { get; set; }
|
||||||
public bool convertedToVideo { get; set; }
|
public int height { get; set; }
|
||||||
public bool canView { get; set; }
|
public int duration { get; set; }
|
||||||
public bool hasError { get; set; }
|
public long size { get; set; }
|
||||||
public DateTime? createdAt { get; set; }
|
public Sources sources { get; set; }
|
||||||
public Files files { get; set; }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class Preview
|
public class Sources
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
[JsonProperty("720")] public object _720 { get; set; }
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
public Sources sources { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Source
|
[JsonProperty("240")] public object _240 { get; set; }
|
||||||
{
|
|
||||||
public string url { get; set; }
|
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public int duration { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
public Sources sources { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Sources
|
public string w150 { get; set; }
|
||||||
{
|
public string w480 { get; set; }
|
||||||
[JsonProperty("720")]
|
}
|
||||||
public object _720 { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("240")]
|
public class SquarePreview
|
||||||
public object _240 { get; set; }
|
{
|
||||||
public string w150 { get; set; }
|
public string url { get; set; }
|
||||||
public string w480 { get; set; }
|
public int width { get; set; }
|
||||||
}
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
public Sources sources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class SquarePreview
|
public class Thumb
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
public Sources sources { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Thumb
|
|
||||||
{
|
|
||||||
public string url { get; set; }
|
|
||||||
public int width { get; set; }
|
|
||||||
public int height { get; set; }
|
|
||||||
public long size { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,211 +1,209 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OF_DL.Utils;
|
using OF_DL.Utils;
|
||||||
|
|
||||||
namespace OF_DL.Entities.Streams
|
namespace OF_DL.Entities.Streams;
|
||||||
|
|
||||||
|
public class Streams
|
||||||
{
|
{
|
||||||
public class Streams
|
public List<List> list { get; set; }
|
||||||
|
public bool hasMore { get; set; }
|
||||||
|
public string headMarker { get; set; }
|
||||||
|
public string tailMarker { get; set; }
|
||||||
|
public Counters counters { get; set; }
|
||||||
|
|
||||||
|
public class Author
|
||||||
{
|
{
|
||||||
public List<List> list { get; set; }
|
public long id { get; set; }
|
||||||
public bool hasMore { get; set; }
|
public string _view { get; set; }
|
||||||
public string headMarker { get; set; }
|
}
|
||||||
public string tailMarker { get; set; }
|
|
||||||
public Counters counters { get; set; }
|
|
||||||
public class Author
|
|
||||||
{
|
|
||||||
public long id { get; set; }
|
|
||||||
public string _view { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Counters
|
public class Counters
|
||||||
{
|
{
|
||||||
public int audiosCount { get; set; }
|
public int audiosCount { get; set; }
|
||||||
public int photosCount { get; set; }
|
public int photosCount { get; set; }
|
||||||
public int videosCount { get; set; }
|
public int videosCount { get; set; }
|
||||||
public int mediasCount { get; set; }
|
public int mediasCount { get; set; }
|
||||||
public int postsCount { get; set; }
|
public int postsCount { get; set; }
|
||||||
public int streamsCount { get; set; }
|
public int streamsCount { get; set; }
|
||||||
public int archivedPostsCount { get; set; }
|
public int archivedPostsCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Files
|
public class Files
|
||||||
{
|
{
|
||||||
public Full full { get; set; }
|
public Full full { get; set; }
|
||||||
public Thumb thumb { get; set; }
|
public Thumb thumb { get; set; }
|
||||||
public Preview preview { get; set; }
|
public Preview preview { get; set; }
|
||||||
public SquarePreview squarePreview { get; set; }
|
public SquarePreview squarePreview { get; set; }
|
||||||
public Drm drm { get; set; }
|
public Drm drm { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Full
|
public class Full
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
public List<object> sources { get; set; }
|
public List<object> sources { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SquarePreview
|
public class SquarePreview
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Thumb
|
public class Thumb
|
||||||
{
|
{
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public int width { get; set; }
|
public int width { get; set; }
|
||||||
public int height { get; set; }
|
public int height { get; set; }
|
||||||
public long size { get; set; }
|
public long size { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Info
|
public class Info
|
||||||
{
|
{
|
||||||
public Source source { get; set; }
|
public Source source { get; set; }
|
||||||
public Preview preview { get; set; }
|
public Preview preview { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class List
|
public class List
|
||||||
|
{
|
||||||
|
private string _rawText;
|
||||||
|
public string responseType { get; set; }
|
||||||
|
public long id { get; set; }
|
||||||
|
public DateTime postedAt { get; set; }
|
||||||
|
public string postedAtPrecise { get; set; }
|
||||||
|
public object expiredAt { get; set; }
|
||||||
|
public Author author { get; set; }
|
||||||
|
public string text { get; set; }
|
||||||
|
|
||||||
|
public string rawText
|
||||||
{
|
{
|
||||||
public string responseType { get; set; }
|
get
|
||||||
public long id { get; set; }
|
|
||||||
public DateTime postedAt { get; set; }
|
|
||||||
public string postedAtPrecise { get; set; }
|
|
||||||
public object expiredAt { get; set; }
|
|
||||||
public Author author { get; set; }
|
|
||||||
public string text { get; set; }
|
|
||||||
private string _rawText;
|
|
||||||
public string rawText
|
|
||||||
{
|
{
|
||||||
get
|
if (string.IsNullOrEmpty(_rawText))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_rawText))
|
_rawText = XmlUtils.EvaluateInnerText(text);
|
||||||
{
|
}
|
||||||
_rawText = XmlUtils.EvaluateInnerText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _rawText;
|
return _rawText;
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_rawText = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public bool lockedText { get; set; }
|
set => _rawText = value;
|
||||||
public bool isFavorite { get; set; }
|
|
||||||
public bool canReport { get; set; }
|
|
||||||
public bool canDelete { get; set; }
|
|
||||||
public bool canComment { get; set; }
|
|
||||||
public bool canEdit { get; set; }
|
|
||||||
public bool isPinned { get; set; }
|
|
||||||
public int favoritesCount { get; set; }
|
|
||||||
public int mediaCount { get; set; }
|
|
||||||
public bool isMediaReady { get; set; }
|
|
||||||
public object voting { get; set; }
|
|
||||||
public bool isOpened { get; set; }
|
|
||||||
public bool canToggleFavorite { get; set; }
|
|
||||||
public int streamId { get; set; }
|
|
||||||
public string price { get; set; }
|
|
||||||
public bool hasVoting { get; set; }
|
|
||||||
public bool isAddedToBookmarks { get; set; }
|
|
||||||
public bool isArchived { get; set; }
|
|
||||||
public bool isPrivateArchived { get; set; }
|
|
||||||
public bool isDeleted { get; set; }
|
|
||||||
public bool hasUrl { get; set; }
|
|
||||||
public bool isCouplePeopleMedia { get; set; }
|
|
||||||
public string cantCommentReason { get; set; }
|
|
||||||
public int commentsCount { get; set; }
|
|
||||||
public List<object> mentionedUsers { get; set; }
|
|
||||||
public List<object> linkedUsers { get; set; }
|
|
||||||
public string tipsAmount { get; set; }
|
|
||||||
public string tipsAmountRaw { get; set; }
|
|
||||||
public List<Medium> media { get; set; }
|
|
||||||
public bool canViewMedia { get; set; }
|
|
||||||
public List<object> preview { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Medium
|
public bool lockedText { get; set; }
|
||||||
{
|
public bool isFavorite { get; set; }
|
||||||
public long id { get; set; }
|
public bool canReport { get; set; }
|
||||||
public string type { get; set; }
|
public bool canDelete { get; set; }
|
||||||
public bool convertedToVideo { get; set; }
|
public bool canComment { get; set; }
|
||||||
public bool canView { get; set; }
|
public bool canEdit { get; set; }
|
||||||
public bool hasError { get; set; }
|
public bool isPinned { get; set; }
|
||||||
public DateTime? createdAt { get; set; }
|
public int favoritesCount { get; set; }
|
||||||
public Info info { get; set; }
|
public int mediaCount { get; set; }
|
||||||
public Source source { get; set; }
|
public bool isMediaReady { get; set; }
|
||||||
public string squarePreview { get; set; }
|
public object voting { get; set; }
|
||||||
public string full { get; set; }
|
public bool isOpened { get; set; }
|
||||||
public string preview { get; set; }
|
public bool canToggleFavorite { get; set; }
|
||||||
public string thumb { get; set; }
|
public int streamId { get; set; }
|
||||||
public bool hasCustomPreview { get; set; }
|
public string price { get; set; }
|
||||||
public Files files { get; set; }
|
public bool hasVoting { get; set; }
|
||||||
public VideoSources videoSources { get; set; }
|
public bool isAddedToBookmarks { get; set; }
|
||||||
}
|
public bool isArchived { get; set; }
|
||||||
|
public bool isPrivateArchived { get; set; }
|
||||||
|
public bool isDeleted { get; set; }
|
||||||
|
public bool hasUrl { get; set; }
|
||||||
|
public bool isCouplePeopleMedia { get; set; }
|
||||||
|
public string cantCommentReason { get; set; }
|
||||||
|
public int commentsCount { get; set; }
|
||||||
|
public List<object> mentionedUsers { get; set; }
|
||||||
|
public List<object> linkedUsers { get; set; }
|
||||||
|
public string tipsAmount { get; set; }
|
||||||
|
public string tipsAmountRaw { get; set; }
|
||||||
|
public List<Medium> media { get; set; }
|
||||||
|
public bool canViewMedia { get; set; }
|
||||||
|
public List<object> preview { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Preview
|
public class Medium
|
||||||
{
|
{
|
||||||
public int width { get; set; }
|
public long id { get; set; }
|
||||||
public int height { get; set; }
|
public string type { get; set; }
|
||||||
public long size { get; set; }
|
public bool convertedToVideo { get; set; }
|
||||||
public string url { get; set; }
|
public bool canView { get; set; }
|
||||||
}
|
public bool hasError { get; set; }
|
||||||
|
public DateTime? createdAt { get; set; }
|
||||||
|
public Info info { get; set; }
|
||||||
|
public Source source { get; set; }
|
||||||
|
public string squarePreview { get; set; }
|
||||||
|
public string full { get; set; }
|
||||||
|
public string preview { get; set; }
|
||||||
|
public string thumb { get; set; }
|
||||||
|
public bool hasCustomPreview { get; set; }
|
||||||
|
public Files files { get; set; }
|
||||||
|
public VideoSources videoSources { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Source
|
public class Preview
|
||||||
{
|
{
|
||||||
public string source { get; set; }
|
public int width { get; set; }
|
||||||
public int width { get; set; }
|
public int height { get; set; }
|
||||||
public int height { get; set; }
|
public long size { get; set; }
|
||||||
public long size { get; set; }
|
public string url { get; set; }
|
||||||
public int duration { get; set; }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class VideoSources
|
public class Source
|
||||||
{
|
{
|
||||||
[JsonProperty("720")]
|
public string source { get; set; }
|
||||||
public object _720 { get; set; }
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public long size { get; set; }
|
||||||
|
public int duration { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[JsonProperty("240")]
|
public class VideoSources
|
||||||
public object _240 { get; set; }
|
{
|
||||||
}
|
[JsonProperty("720")] public object _720 { get; set; }
|
||||||
public class Drm
|
|
||||||
{
|
|
||||||
public Manifest manifest { get; set; }
|
|
||||||
public Signature signature { get; set; }
|
|
||||||
}
|
|
||||||
public class Manifest
|
|
||||||
{
|
|
||||||
public string? hls { get; set; }
|
|
||||||
public string? dash { get; set; }
|
|
||||||
}
|
|
||||||
public class Signature
|
|
||||||
{
|
|
||||||
public Hls hls { get; set; }
|
|
||||||
public Dash dash { get; set; }
|
|
||||||
}
|
|
||||||
public class Hls
|
|
||||||
{
|
|
||||||
[JsonProperty("CloudFront-Policy")]
|
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
[JsonProperty("240")] public object _240 { get; set; }
|
||||||
public string CloudFrontSignature { get; set; }
|
}
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
public class Drm
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
{
|
||||||
}
|
public Manifest manifest { get; set; }
|
||||||
public class Dash
|
public Signature signature { get; set; }
|
||||||
{
|
}
|
||||||
[JsonProperty("CloudFront-Policy")]
|
|
||||||
public string CloudFrontPolicy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Signature")]
|
public class Manifest
|
||||||
public string CloudFrontSignature { get; set; }
|
{
|
||||||
|
public string? hls { get; set; }
|
||||||
|
public string? dash { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[JsonProperty("CloudFront-Key-Pair-Id")]
|
public class Signature
|
||||||
public string CloudFrontKeyPairId { get; set; }
|
{
|
||||||
}
|
public Hls hls { get; set; }
|
||||||
|
public Dash dash { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Hls
|
||||||
|
{
|
||||||
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dash
|
||||||
|
{
|
||||||
|
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("CloudFront-Key-Pair-Id")]
|
||||||
|
public string CloudFrontKeyPairId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
namespace OF_DL.Entities.Streams
|
namespace OF_DL.Entities.Streams;
|
||||||
|
|
||||||
|
public class StreamsCollection
|
||||||
{
|
{
|
||||||
public class StreamsCollection
|
public List<Streams.Medium> StreamMedia = new();
|
||||||
{
|
public List<Streams.List> StreamObjects = new();
|
||||||
public Dictionary<long, string> Streams = new Dictionary<long, string>();
|
public Dictionary<long, string> Streams = new();
|
||||||
public List<Streams.List> StreamObjects = new List<Streams.List>();
|
|
||||||
public List<Streams.Medium> StreamMedia = new List<Streams.Medium>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,159 +1,159 @@
|
|||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class Subscriptions
|
||||||
{
|
{
|
||||||
public class Subscriptions
|
public List<List> list { get; set; }
|
||||||
|
public bool hasMore { get; set; }
|
||||||
|
|
||||||
|
public class AvatarThumbs
|
||||||
{
|
{
|
||||||
public List<List> list { get; set; }
|
public string c50 { get; set; }
|
||||||
public bool hasMore { get; set; }
|
public string c144 { get; set; }
|
||||||
public class AvatarThumbs
|
}
|
||||||
{
|
|
||||||
public string c50 { get; set; }
|
|
||||||
public string c144 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HeaderSize
|
public class HeaderSize
|
||||||
{
|
{
|
||||||
public int? width { get; set; }
|
public int? width { get; set; }
|
||||||
public int? height { get; set; }
|
public int? height { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HeaderThumbs
|
public class HeaderThumbs
|
||||||
{
|
{
|
||||||
public string w480 { get; set; }
|
public string w480 { get; set; }
|
||||||
public string w760 { get; set; }
|
public string w760 { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class List
|
public class List
|
||||||
{
|
{
|
||||||
public string view { get; set; }
|
public string view { get; set; }
|
||||||
public string avatar { get; set; }
|
public string avatar { get; set; }
|
||||||
public AvatarThumbs avatarThumbs { get; set; }
|
public AvatarThumbs avatarThumbs { get; set; }
|
||||||
public string header { get; set; }
|
public string header { get; set; }
|
||||||
public HeaderSize headerSize { get; set; }
|
public HeaderSize headerSize { get; set; }
|
||||||
public HeaderThumbs headerThumbs { get; set; }
|
public HeaderThumbs headerThumbs { get; set; }
|
||||||
public long id { get; set; }
|
public long id { get; set; }
|
||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
public string username { get; set; }
|
public string username { get; set; }
|
||||||
public bool? canLookStory { get; set; }
|
public bool? canLookStory { get; set; }
|
||||||
public bool? canCommentStory { get; set; }
|
public bool? canCommentStory { get; set; }
|
||||||
public bool? hasNotViewedStory { get; set; }
|
public bool? hasNotViewedStory { get; set; }
|
||||||
public bool? isVerified { get; set; }
|
public bool? isVerified { get; set; }
|
||||||
public bool? canPayInternal { get; set; }
|
public bool? canPayInternal { get; set; }
|
||||||
public bool? hasScheduledStream { get; set; }
|
public bool? hasScheduledStream { get; set; }
|
||||||
public bool? hasStream { get; set; }
|
public bool? hasStream { get; set; }
|
||||||
public bool? hasStories { get; set; }
|
public bool? hasStories { get; set; }
|
||||||
public bool? tipsEnabled { get; set; }
|
public bool? tipsEnabled { get; set; }
|
||||||
public bool? tipsTextEnabled { get; set; }
|
public bool? tipsTextEnabled { get; set; }
|
||||||
public int? tipsMin { get; set; }
|
public int? tipsMin { get; set; }
|
||||||
public int? tipsMinInternal { get; set; }
|
public int? tipsMinInternal { get; set; }
|
||||||
public int? tipsMax { get; set; }
|
public int? tipsMax { get; set; }
|
||||||
public bool? canEarn { get; set; }
|
public bool? canEarn { get; set; }
|
||||||
public bool? canAddSubscriber { get; set; }
|
public bool? canAddSubscriber { get; set; }
|
||||||
public string? subscribePrice { get; set; }
|
public string? subscribePrice { get; set; }
|
||||||
public bool? isPaywallRequired { get; set; }
|
public bool? isPaywallRequired { get; set; }
|
||||||
public bool? unprofitable { get; set; }
|
public bool? unprofitable { get; set; }
|
||||||
public List<ListsState> listsStates { get; set; }
|
public List<ListsState> listsStates { get; set; }
|
||||||
public bool? isMuted { get; set; }
|
public bool? isMuted { get; set; }
|
||||||
public bool? isRestricted { get; set; }
|
public bool? isRestricted { get; set; }
|
||||||
public bool? canRestrict { get; set; }
|
public bool? canRestrict { get; set; }
|
||||||
public bool? subscribedBy { get; set; }
|
public bool? subscribedBy { get; set; }
|
||||||
public bool? subscribedByExpire { get; set; }
|
public bool? subscribedByExpire { get; set; }
|
||||||
public DateTime? subscribedByExpireDate { get; set; }
|
public DateTime? subscribedByExpireDate { get; set; }
|
||||||
public bool? subscribedByAutoprolong { get; set; }
|
public bool? subscribedByAutoprolong { get; set; }
|
||||||
public bool? subscribedIsExpiredNow { get; set; }
|
public bool? subscribedIsExpiredNow { get; set; }
|
||||||
public string? currentSubscribePrice { get; set; }
|
public string? currentSubscribePrice { get; set; }
|
||||||
public bool? subscribedOn { get; set; }
|
public bool? subscribedOn { get; set; }
|
||||||
public bool? subscribedOnExpiredNow { get; set; }
|
public bool? subscribedOnExpiredNow { get; set; }
|
||||||
public string subscribedOnDuration { get; set; }
|
public string subscribedOnDuration { get; set; }
|
||||||
public bool? canReport { get; set; }
|
public bool? canReport { get; set; }
|
||||||
public bool? canReceiveChatMessage { get; set; }
|
public bool? canReceiveChatMessage { get; set; }
|
||||||
public bool? hideChat { get; set; }
|
public bool? hideChat { get; set; }
|
||||||
public DateTime? lastSeen { get; set; }
|
public DateTime? lastSeen { get; set; }
|
||||||
public bool? isPerformer { get; set; }
|
public bool? isPerformer { get; set; }
|
||||||
public bool? isRealPerformer { get; set; }
|
public bool? isRealPerformer { get; set; }
|
||||||
public SubscribedByData subscribedByData { get; set; }
|
public SubscribedByData subscribedByData { get; set; }
|
||||||
public SubscribedOnData subscribedOnData { get; set; }
|
public SubscribedOnData subscribedOnData { get; set; }
|
||||||
public bool? canTrialSend { get; set; }
|
public bool? canTrialSend { get; set; }
|
||||||
public bool? isBlocked { get; set; }
|
public bool? isBlocked { get; set; }
|
||||||
public string displayName { get; set; }
|
public string displayName { get; set; }
|
||||||
public string notice { get; set; }
|
public string notice { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ListsState
|
public class ListsState
|
||||||
{
|
{
|
||||||
public object id { get; set; }
|
public object id { get; set; }
|
||||||
public string type { get; set; }
|
public string type { get; set; }
|
||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
public bool? hasUser { get; set; }
|
public bool? hasUser { get; set; }
|
||||||
public bool? canAddUser { get; set; }
|
public bool? canAddUser { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Subscribe
|
public class Subscribe
|
||||||
{
|
{
|
||||||
public object id { get; set; }
|
public object id { get; set; }
|
||||||
public long? userId { get; set; }
|
public long? userId { get; set; }
|
||||||
public int? subscriberId { get; set; }
|
public int? subscriberId { 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; }
|
||||||
public DateTime? expireDate { get; set; }
|
public DateTime? expireDate { get; set; }
|
||||||
public object cancelDate { get; set; }
|
public object cancelDate { get; set; }
|
||||||
public string? price { get; set; }
|
public string? price { get; set; }
|
||||||
public string? regularPrice { get; set; }
|
public string? regularPrice { get; set; }
|
||||||
public string? discount { get; set; }
|
public string? discount { get; set; }
|
||||||
public string action { get; set; }
|
public string action { get; set; }
|
||||||
public string type { get; set; }
|
public string type { get; set; }
|
||||||
public object offerStart { get; set; }
|
public object offerStart { get; set; }
|
||||||
public object offerEnd { get; set; }
|
public object offerEnd { get; set; }
|
||||||
public bool? isCurrent { get; set; }
|
public bool? isCurrent { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SubscribedByData
|
public class SubscribedByData
|
||||||
{
|
{
|
||||||
public string? price { get; set; }
|
public string? price { get; set; }
|
||||||
public string? newPrice { get; set; }
|
public string? newPrice { get; set; }
|
||||||
public string? regularPrice { get; set; }
|
public string? regularPrice { get; set; }
|
||||||
public string? subscribePrice { get; set; }
|
public string? subscribePrice { get; set; }
|
||||||
public int? discountPercent { get; set; }
|
public int? discountPercent { get; set; }
|
||||||
public int? discountPeriod { get; set; }
|
public int? discountPeriod { get; set; }
|
||||||
public DateTime? subscribeAt { get; set; }
|
public DateTime? subscribeAt { get; set; }
|
||||||
public DateTime? expiredAt { get; set; }
|
public DateTime? expiredAt { get; set; }
|
||||||
public DateTime? renewedAt { get; set; }
|
public DateTime? renewedAt { get; set; }
|
||||||
public object discountFinishedAt { get; set; }
|
public object discountFinishedAt { get; set; }
|
||||||
public object discountStartedAt { get; set; }
|
public object discountStartedAt { get; set; }
|
||||||
public string status { get; set; }
|
public string status { get; set; }
|
||||||
public bool? isMuted { get; set; }
|
public bool? isMuted { get; set; }
|
||||||
public string unsubscribeReason { get; set; }
|
public string unsubscribeReason { get; set; }
|
||||||
public string duration { get; set; }
|
public string duration { get; set; }
|
||||||
public bool? showPostsInFeed { get; set; }
|
public bool? showPostsInFeed { get; set; }
|
||||||
public List<Subscribe> subscribes { get; set; }
|
public List<Subscribe> subscribes { get; set; }
|
||||||
public bool? hasActivePaidSubscriptions { get; set; }
|
public bool? hasActivePaidSubscriptions { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SubscribedOnData
|
public class SubscribedOnData
|
||||||
{
|
{
|
||||||
public string? price { get; set; }
|
public string? price { get; set; }
|
||||||
public string? newPrice { get; set; }
|
public string? newPrice { get; set; }
|
||||||
public string? regularPrice { get; set; }
|
public string? regularPrice { get; set; }
|
||||||
public string? subscribePrice { get; set; }
|
public string? subscribePrice { get; set; }
|
||||||
public int? discountPercent { get; set; }
|
public int? discountPercent { get; set; }
|
||||||
public int? discountPeriod { get; set; }
|
public int? discountPeriod { get; set; }
|
||||||
public DateTime? subscribeAt { get; set; }
|
public DateTime? subscribeAt { get; set; }
|
||||||
public DateTime? expiredAt { get; set; }
|
public DateTime? expiredAt { get; set; }
|
||||||
public DateTime? renewedAt { get; set; }
|
public DateTime? renewedAt { get; set; }
|
||||||
public object discountFinishedAt { get; set; }
|
public object discountFinishedAt { get; set; }
|
||||||
public object discountStartedAt { get; set; }
|
public object discountStartedAt { get; set; }
|
||||||
public object status { get; set; }
|
public object status { get; set; }
|
||||||
public bool? isMuted { get; set; }
|
public bool? isMuted { get; set; }
|
||||||
public string unsubscribeReason { get; set; }
|
public string unsubscribeReason { get; set; }
|
||||||
public string duration { get; set; }
|
public string duration { get; set; }
|
||||||
public string? tipsSumm { get; set; }
|
public string? tipsSumm { get; set; }
|
||||||
public string? subscribesSumm { get; set; }
|
public string? subscribesSumm { get; set; }
|
||||||
public string? messagesSumm { get; set; }
|
public string? messagesSumm { get; set; }
|
||||||
public string? postsSumm { get; set; }
|
public string? postsSumm { get; set; }
|
||||||
public string? streamsSumm { get; set; }
|
public string? streamsSumm { get; set; }
|
||||||
public string? totalSumm { get; set; }
|
public string? totalSumm { get; set; }
|
||||||
public List<Subscribe> subscribes { get; set; }
|
public List<Subscribe> subscribes { get; set; }
|
||||||
public bool? hasActivePaidSubscriptions { get; set; }
|
public bool? hasActivePaidSubscriptions { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
|
||||||
internal class ToggleableConfigAttribute : Attribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
internal class ToggleableConfigAttribute : Attribute
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,185 +1,185 @@
|
|||||||
namespace OF_DL.Entities
|
namespace OF_DL.Entities;
|
||||||
|
|
||||||
|
public class User
|
||||||
{
|
{
|
||||||
public class User
|
public string view { get; set; }
|
||||||
|
public string? avatar { get; set; }
|
||||||
|
public AvatarThumbs avatarThumbs { get; set; }
|
||||||
|
public string? header { get; set; }
|
||||||
|
public HeaderSize headerSize { get; set; }
|
||||||
|
public HeaderThumbs headerThumbs { get; set; }
|
||||||
|
public long? id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public string username { get; set; }
|
||||||
|
public bool? canLookStory { get; set; }
|
||||||
|
public bool? canCommentStory { get; set; }
|
||||||
|
public bool? hasNotViewedStory { get; set; }
|
||||||
|
public bool? isVerified { get; set; }
|
||||||
|
public bool? canPayInternal { get; set; }
|
||||||
|
public bool? hasScheduledStream { get; set; }
|
||||||
|
public bool? hasStream { get; set; }
|
||||||
|
public bool? hasStories { get; set; }
|
||||||
|
public bool? tipsEnabled { get; set; }
|
||||||
|
public bool? tipsTextEnabled { get; set; }
|
||||||
|
public int? tipsMin { get; set; }
|
||||||
|
public int? tipsMinInternal { get; set; }
|
||||||
|
public int? tipsMax { get; set; }
|
||||||
|
public bool? canEarn { get; set; }
|
||||||
|
public bool? canAddSubscriber { get; set; }
|
||||||
|
public string? subscribePrice { get; set; }
|
||||||
|
public string displayName { get; set; }
|
||||||
|
public string notice { get; set; }
|
||||||
|
public bool? isPaywallRequired { get; set; }
|
||||||
|
public bool? unprofitable { get; set; }
|
||||||
|
public List<ListsState> listsStates { get; set; }
|
||||||
|
public bool? isMuted { get; set; }
|
||||||
|
public bool? isRestricted { get; set; }
|
||||||
|
public bool? canRestrict { get; set; }
|
||||||
|
public bool? subscribedBy { get; set; }
|
||||||
|
public bool? subscribedByExpire { get; set; }
|
||||||
|
public DateTime? subscribedByExpireDate { get; set; }
|
||||||
|
public bool? subscribedByAutoprolong { get; set; }
|
||||||
|
public bool? subscribedIsExpiredNow { get; set; }
|
||||||
|
public string? currentSubscribePrice { get; set; }
|
||||||
|
public bool? subscribedOn { get; set; }
|
||||||
|
public bool? subscribedOnExpiredNow { get; set; }
|
||||||
|
public string subscribedOnDuration { get; set; }
|
||||||
|
public DateTime? joinDate { get; set; }
|
||||||
|
public bool? isReferrerAllowed { get; set; }
|
||||||
|
public string about { get; set; }
|
||||||
|
public string rawAbout { get; set; }
|
||||||
|
public object website { get; set; }
|
||||||
|
public object wishlist { get; set; }
|
||||||
|
public object location { get; set; }
|
||||||
|
public int? postsCount { get; set; }
|
||||||
|
public int? archivedPostsCount { get; set; }
|
||||||
|
public int? privateArchivedPostsCount { get; set; }
|
||||||
|
public int? photosCount { get; set; }
|
||||||
|
public int? videosCount { get; set; }
|
||||||
|
public int? audiosCount { get; set; }
|
||||||
|
public int? mediasCount { get; set; }
|
||||||
|
public DateTime? lastSeen { get; set; }
|
||||||
|
public int? favoritesCount { get; set; }
|
||||||
|
public int? favoritedCount { get; set; }
|
||||||
|
public bool? showPostsInFeed { get; set; }
|
||||||
|
public bool? canReceiveChatMessage { get; set; }
|
||||||
|
public bool? isPerformer { get; set; }
|
||||||
|
public bool? isRealPerformer { get; set; }
|
||||||
|
public bool? isSpotifyConnected { get; set; }
|
||||||
|
public int? subscribersCount { get; set; }
|
||||||
|
public bool? hasPinnedPosts { get; set; }
|
||||||
|
public bool? hasLabels { get; set; }
|
||||||
|
public bool? canChat { get; set; }
|
||||||
|
public string? callPrice { get; set; }
|
||||||
|
public bool? isPrivateRestriction { get; set; }
|
||||||
|
public bool? showSubscribersCount { get; set; }
|
||||||
|
public bool? showMediaCount { get; set; }
|
||||||
|
public SubscribedByData subscribedByData { get; set; }
|
||||||
|
public SubscribedOnData subscribedOnData { get; set; }
|
||||||
|
public bool? canPromotion { get; set; }
|
||||||
|
public bool? canCreatePromotion { get; set; }
|
||||||
|
public bool? canCreateTrial { get; set; }
|
||||||
|
public bool? isAdultContent { get; set; }
|
||||||
|
public bool? canTrialSend { get; set; }
|
||||||
|
public bool? hadEnoughLastPhotos { get; set; }
|
||||||
|
public bool? hasLinks { get; set; }
|
||||||
|
public DateTime? firstPublishedPostDate { get; set; }
|
||||||
|
public bool? isSpringConnected { get; set; }
|
||||||
|
public bool? isFriend { get; set; }
|
||||||
|
public bool? isBlocked { get; set; }
|
||||||
|
public bool? canReport { get; set; }
|
||||||
|
|
||||||
|
public class AvatarThumbs
|
||||||
{
|
{
|
||||||
public string view { get; set; }
|
public string c50 { get; set; }
|
||||||
public string? avatar { get; set; }
|
public string c144 { get; set; }
|
||||||
public AvatarThumbs avatarThumbs { get; set; }
|
}
|
||||||
public string? header { get; set; }
|
|
||||||
public HeaderSize headerSize { get; set; }
|
public class HeaderSize
|
||||||
public HeaderThumbs headerThumbs { get; set; }
|
{
|
||||||
public long? id { get; set; }
|
public int? width { get; set; }
|
||||||
|
public int? height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HeaderThumbs
|
||||||
|
{
|
||||||
|
public string w480 { get; set; }
|
||||||
|
public string w760 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ListsState
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
public string username { get; set; }
|
public bool hasUser { get; set; }
|
||||||
public bool? canLookStory { get; set; }
|
public bool canAddUser { get; set; }
|
||||||
public bool? canCommentStory { get; set; }
|
}
|
||||||
public bool? hasNotViewedStory { get; set; }
|
|
||||||
public bool? isVerified { get; set; }
|
public class Subscribe
|
||||||
public bool? canPayInternal { get; set; }
|
{
|
||||||
public bool? hasScheduledStream { get; set; }
|
public long? id { get; set; }
|
||||||
public bool? hasStream { get; set; }
|
public long? userId { get; set; }
|
||||||
public bool? hasStories { get; set; }
|
public int? subscriberId { get; set; }
|
||||||
public bool? tipsEnabled { get; set; }
|
public DateTime? date { get; set; }
|
||||||
public bool? tipsTextEnabled { get; set; }
|
public int? duration { get; set; }
|
||||||
public int? tipsMin { get; set; }
|
public DateTime? startDate { get; set; }
|
||||||
public int? tipsMinInternal { get; set; }
|
public DateTime? expireDate { get; set; }
|
||||||
public int? tipsMax { get; set; }
|
public object cancelDate { get; set; }
|
||||||
public bool? canEarn { get; set; }
|
public string? price { get; set; }
|
||||||
public bool? canAddSubscriber { get; set; }
|
public string? regularPrice { get; set; }
|
||||||
|
public int? discount { get; set; }
|
||||||
|
public string action { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public object offerStart { get; set; }
|
||||||
|
public object offerEnd { get; set; }
|
||||||
|
public bool? isCurrent { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubscribedByData
|
||||||
|
{
|
||||||
|
public string? price { get; set; }
|
||||||
|
public string? newPrice { get; set; }
|
||||||
|
public string? regularPrice { get; set; }
|
||||||
public string? subscribePrice { get; set; }
|
public string? subscribePrice { get; set; }
|
||||||
public string displayName { get; set; }
|
public int? discountPercent { get; set; }
|
||||||
public string notice { get; set; }
|
public int? discountPeriod { get; set; }
|
||||||
public bool? isPaywallRequired { get; set; }
|
public DateTime? subscribeAt { get; set; }
|
||||||
public bool? unprofitable { get; set; }
|
public DateTime? expiredAt { get; set; }
|
||||||
public List<ListsState> listsStates { get; set; }
|
public object? renewedAt { get; set; }
|
||||||
|
public object? discountFinishedAt { get; set; }
|
||||||
|
public object? discountStartedAt { get; set; }
|
||||||
|
public string? status { get; set; }
|
||||||
public bool? isMuted { get; set; }
|
public bool? isMuted { get; set; }
|
||||||
public bool? isRestricted { get; set; }
|
public string? unsubscribeReason { get; set; }
|
||||||
public bool? canRestrict { get; set; }
|
public string? duration { get; set; }
|
||||||
public bool? subscribedBy { get; set; }
|
|
||||||
public bool? subscribedByExpire { get; set; }
|
|
||||||
public DateTime? subscribedByExpireDate { get; set; }
|
|
||||||
public bool? subscribedByAutoprolong { get; set; }
|
|
||||||
public bool? subscribedIsExpiredNow { get; set; }
|
|
||||||
public string? currentSubscribePrice { get; set; }
|
|
||||||
public bool? subscribedOn { get; set; }
|
|
||||||
public bool? subscribedOnExpiredNow { get; set; }
|
|
||||||
public string subscribedOnDuration { get; set; }
|
|
||||||
public DateTime? joinDate { get; set; }
|
|
||||||
public bool? isReferrerAllowed { get; set; }
|
|
||||||
public string about { get; set; }
|
|
||||||
public string rawAbout { get; set; }
|
|
||||||
public object website { get; set; }
|
|
||||||
public object wishlist { get; set; }
|
|
||||||
public object location { get; set; }
|
|
||||||
public int? postsCount { get; set; }
|
|
||||||
public int? archivedPostsCount { get; set; }
|
|
||||||
public int? privateArchivedPostsCount { get; set; }
|
|
||||||
public int? photosCount { get; set; }
|
|
||||||
public int? videosCount { get; set; }
|
|
||||||
public int? audiosCount { get; set; }
|
|
||||||
public int? mediasCount { get; set; }
|
|
||||||
public DateTime? lastSeen { get; set; }
|
|
||||||
public int? favoritesCount { get; set; }
|
|
||||||
public int? favoritedCount { get; set; }
|
|
||||||
public bool? showPostsInFeed { get; set; }
|
public bool? showPostsInFeed { get; set; }
|
||||||
public bool? canReceiveChatMessage { get; set; }
|
public List<Subscribe>? subscribes { get; set; }
|
||||||
public bool? isPerformer { get; set; }
|
}
|
||||||
public bool? isRealPerformer { get; set; }
|
|
||||||
public bool? isSpotifyConnected { get; set; }
|
|
||||||
public int? subscribersCount { get; set; }
|
|
||||||
public bool? hasPinnedPosts { get; set; }
|
|
||||||
public bool? hasLabels { get; set; }
|
|
||||||
public bool? canChat { get; set; }
|
|
||||||
public string? callPrice { get; set; }
|
|
||||||
public bool? isPrivateRestriction { get; set; }
|
|
||||||
public bool? showSubscribersCount { get; set; }
|
|
||||||
public bool? showMediaCount { get; set; }
|
|
||||||
public SubscribedByData subscribedByData { get; set; }
|
|
||||||
public SubscribedOnData subscribedOnData { get; set; }
|
|
||||||
public bool? canPromotion { get; set; }
|
|
||||||
public bool? canCreatePromotion { get; set; }
|
|
||||||
public bool? canCreateTrial { get; set; }
|
|
||||||
public bool? isAdultContent { get; set; }
|
|
||||||
public bool? canTrialSend { get; set; }
|
|
||||||
public bool? hadEnoughLastPhotos { get; set; }
|
|
||||||
public bool? hasLinks { get; set; }
|
|
||||||
public DateTime? firstPublishedPostDate { get; set; }
|
|
||||||
public bool? isSpringConnected { get; set; }
|
|
||||||
public bool? isFriend { get; set; }
|
|
||||||
public bool? isBlocked { get; set; }
|
|
||||||
public bool? canReport { get; set; }
|
|
||||||
public class AvatarThumbs
|
|
||||||
{
|
|
||||||
public string c50 { get; set; }
|
|
||||||
public string c144 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HeaderSize
|
public class SubscribedOnData
|
||||||
{
|
{
|
||||||
public int? width { get; set; }
|
public string? price { get; set; }
|
||||||
public int? height { get; set; }
|
public string? newPrice { get; set; }
|
||||||
}
|
public string? regularPrice { get; set; }
|
||||||
|
public string? subscribePrice { get; set; }
|
||||||
public class HeaderThumbs
|
public int? discountPercent { get; set; }
|
||||||
{
|
public int? discountPeriod { get; set; }
|
||||||
public string w480 { get; set; }
|
public DateTime? subscribeAt { get; set; }
|
||||||
public string w760 { get; set; }
|
public DateTime? expiredAt { get; set; }
|
||||||
}
|
public DateTime? renewedAt { get; set; }
|
||||||
|
public object? discountFinishedAt { get; set; }
|
||||||
public class ListsState
|
public object? discountStartedAt { get; set; }
|
||||||
{
|
public object? status { get; set; }
|
||||||
public string id { get; set; }
|
public bool? isMuted { get; set; }
|
||||||
public string type { get; set; }
|
public string? unsubscribeReason { get; set; }
|
||||||
public string name { get; set; }
|
public string? duration { get; set; }
|
||||||
public bool hasUser { get; set; }
|
public string? tipsSumm { get; set; }
|
||||||
public bool canAddUser { get; set; }
|
public string? subscribesSumm { get; set; }
|
||||||
}
|
public string? messagesSumm { get; set; }
|
||||||
|
public string? postsSumm { get; set; }
|
||||||
public class Subscribe
|
public string? streamsSumm { get; set; }
|
||||||
{
|
public string? totalSumm { get; set; }
|
||||||
public long? id { get; set; }
|
public List<Subscribe>? subscribes { get; set; }
|
||||||
public long? userId { get; set; }
|
|
||||||
public int? subscriberId { get; set; }
|
|
||||||
public DateTime? date { get; set; }
|
|
||||||
public int? duration { get; set; }
|
|
||||||
public DateTime? startDate { get; set; }
|
|
||||||
public DateTime? expireDate { get; set; }
|
|
||||||
public object cancelDate { get; set; }
|
|
||||||
public string? price { get; set; }
|
|
||||||
public string? regularPrice { get; set; }
|
|
||||||
public int? discount { get; set; }
|
|
||||||
public string action { get; set; }
|
|
||||||
public string type { get; set; }
|
|
||||||
public object offerStart { get; set; }
|
|
||||||
public object offerEnd { get; set; }
|
|
||||||
public bool? isCurrent { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SubscribedByData
|
|
||||||
{
|
|
||||||
public string? price { get; set; }
|
|
||||||
public string? newPrice { get; set; }
|
|
||||||
public string? regularPrice { get; set; }
|
|
||||||
public string? subscribePrice { get; set; }
|
|
||||||
public int? discountPercent { get; set; }
|
|
||||||
public int? discountPeriod { get; set; }
|
|
||||||
public DateTime? subscribeAt { get; set; }
|
|
||||||
public DateTime? expiredAt { get; set; }
|
|
||||||
public object? renewedAt { get; set; }
|
|
||||||
public object? discountFinishedAt { get; set; }
|
|
||||||
public object? discountStartedAt { get; set; }
|
|
||||||
public string? status { get; set; }
|
|
||||||
public bool? isMuted { get; set; }
|
|
||||||
public string? unsubscribeReason { get; set; }
|
|
||||||
public string? duration { get; set; }
|
|
||||||
public bool? showPostsInFeed { get; set; }
|
|
||||||
public List<Subscribe>? subscribes { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SubscribedOnData
|
|
||||||
{
|
|
||||||
public string? price { get; set; }
|
|
||||||
public string? newPrice { get; set; }
|
|
||||||
public string? regularPrice { get; set; }
|
|
||||||
public string? subscribePrice { get; set; }
|
|
||||||
public int? discountPercent { get; set; }
|
|
||||||
public int? discountPeriod { get; set; }
|
|
||||||
public DateTime? subscribeAt { get; set; }
|
|
||||||
public DateTime? expiredAt { get; set; }
|
|
||||||
public DateTime? renewedAt { get; set; }
|
|
||||||
public object? discountFinishedAt { get; set; }
|
|
||||||
public object? discountStartedAt { get; set; }
|
|
||||||
public object? status { get; set; }
|
|
||||||
public bool? isMuted { get; set; }
|
|
||||||
public string? unsubscribeReason { get; set; }
|
|
||||||
public string? duration { get; set; }
|
|
||||||
public string? tipsSumm { get; set; }
|
|
||||||
public string? subscribesSumm { get; set; }
|
|
||||||
public string? messagesSumm { get; set; }
|
|
||||||
public string? postsSumm { get; set; }
|
|
||||||
public string? streamsSumm { get; set; }
|
|
||||||
public string? totalSumm { get; set; }
|
|
||||||
public List<Subscribe>? subscribes { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,5 +3,5 @@ namespace OF_DL.Enumerations;
|
|||||||
public enum CustomFileNameOption
|
public enum CustomFileNameOption
|
||||||
{
|
{
|
||||||
ReturnOriginal,
|
ReturnOriginal,
|
||||||
ReturnEmpty,
|
ReturnEmpty
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
namespace OF_DL.Enumerations
|
namespace OF_DL.Enumerations;
|
||||||
|
|
||||||
|
public enum DownloadDateSelection
|
||||||
{
|
{
|
||||||
public enum DownloadDateSelection
|
before,
|
||||||
{
|
after
|
||||||
before,
|
|
||||||
after,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,34 @@
|
|||||||
namespace OF_DL.Enumerations
|
namespace OF_DL.Enumerations;
|
||||||
|
|
||||||
|
public enum LoggingLevel
|
||||||
{
|
{
|
||||||
public enum LoggingLevel
|
//
|
||||||
{
|
// Summary:
|
||||||
//
|
// Anything and everything you might want to know about a running block of code.
|
||||||
// Summary:
|
Verbose,
|
||||||
// Anything and everything you might want to know about a running block of code.
|
|
||||||
Verbose,
|
//
|
||||||
//
|
// Summary:
|
||||||
// Summary:
|
// Internal system events that aren't necessarily observable from the outside.
|
||||||
// Internal system events that aren't necessarily observable from the outside.
|
Debug,
|
||||||
Debug,
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// The lifeblood of operational intelligence - things happen.
|
// The lifeblood of operational intelligence - things happen.
|
||||||
Information,
|
Information,
|
||||||
//
|
|
||||||
// Summary:
|
//
|
||||||
// Service is degraded or endangered.
|
// Summary:
|
||||||
Warning,
|
// Service is degraded or endangered.
|
||||||
//
|
Warning,
|
||||||
// Summary:
|
|
||||||
// Functionality is unavailable, invariants are broken or data is lost.
|
//
|
||||||
Error,
|
// Summary:
|
||||||
//
|
// Functionality is unavailable, invariants are broken or data is lost.
|
||||||
// Summary:
|
Error,
|
||||||
// If you have a pager, it goes off when one of these occurs.
|
|
||||||
Fatal
|
//
|
||||||
}
|
// Summary:
|
||||||
|
// If you have a pager, it goes off when one of these occurs.
|
||||||
|
Fatal
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
namespace OF_DL.Enumerations
|
namespace OF_DL.Enumerations;
|
||||||
|
|
||||||
|
public enum MediaType
|
||||||
{
|
{
|
||||||
public enum MediaType
|
PaidPosts = 10,
|
||||||
{
|
Posts = 20,
|
||||||
PaidPosts = 10,
|
Archived = 30,
|
||||||
Posts = 20,
|
Stories = 40,
|
||||||
Archived = 30,
|
Highlights = 50,
|
||||||
Stories = 40,
|
Messages = 60,
|
||||||
Highlights = 50,
|
PaidMessages = 70
|
||||||
Messages = 60,
|
|
||||||
PaidMessages = 70
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
namespace OF_DL.Enumerations
|
namespace OF_DL.Enumerations;
|
||||||
|
|
||||||
|
public enum VideoResolution
|
||||||
{
|
{
|
||||||
public enum VideoResolution
|
_240,
|
||||||
{
|
_720,
|
||||||
_240,
|
source
|
||||||
_720,
|
|
||||||
source
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,25 @@
|
|||||||
using OF_DL.Entities;
|
using OF_DL.Entities;
|
||||||
using OF_DL.Services;
|
using OF_DL.Services;
|
||||||
|
|
||||||
namespace OF_DL.Helpers
|
namespace OF_DL.Helpers;
|
||||||
{
|
|
||||||
internal interface IDownloadContext
|
|
||||||
{
|
|
||||||
public IFileNameFormatConfig FileNameFormatConfig { get; }
|
|
||||||
public IAPIService ApiService { get; }
|
|
||||||
public IDBService DBService { get; }
|
|
||||||
public IDownloadService DownloadService { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class DownloadContext(
|
internal interface IDownloadContext
|
||||||
IFileNameFormatConfig fileNameFormatConfig,
|
{
|
||||||
IAPIService apiService,
|
public IFileNameFormatConfig FileNameFormatConfig { get; }
|
||||||
IDBService dBService,
|
public IAPIService ApiService { get; }
|
||||||
IDownloadService downloadService)
|
public IDBService DBService { get; }
|
||||||
: IDownloadContext
|
public IDownloadService DownloadService { get; }
|
||||||
{
|
}
|
||||||
public IAPIService ApiService { get; } = apiService;
|
|
||||||
public IDBService DBService { get; } = dBService;
|
internal class DownloadContext(
|
||||||
public IDownloadService DownloadService { get; } = downloadService;
|
IFileNameFormatConfig fileNameFormatConfig,
|
||||||
public IFileNameFormatConfig FileNameFormatConfig { get; } = fileNameFormatConfig;
|
IAPIService apiService,
|
||||||
}
|
IDBService dBService,
|
||||||
|
IDownloadService downloadService)
|
||||||
|
: IDownloadContext
|
||||||
|
{
|
||||||
|
public IAPIService ApiService { get; } = apiService;
|
||||||
|
public IDBService DBService { get; } = dBService;
|
||||||
|
public IDownloadService DownloadService { get; } = downloadService;
|
||||||
|
public IFileNameFormatConfig FileNameFormatConfig { get; } = fileNameFormatConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
namespace OF_DL.Helpers
|
namespace OF_DL.Helpers;
|
||||||
|
|
||||||
|
public interface IFileNameHelper
|
||||||
{
|
{
|
||||||
public interface IFileNameHelper
|
Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values);
|
||||||
{
|
|
||||||
Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values);
|
Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties,
|
||||||
Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, long> users = null);
|
string username, Dictionary<string, long> users = null);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,15 +6,15 @@ namespace OF_DL.Helpers;
|
|||||||
|
|
||||||
public static class VersionHelper
|
public static class VersionHelper
|
||||||
{
|
{
|
||||||
private static readonly HttpClient httpClient = new HttpClient();
|
|
||||||
private const string url = "https://git.ofdl.tools/api/v1/repos/sim0n00ps/OF-DL/releases/latest";
|
private const string url = "https://git.ofdl.tools/api/v1/repos/sim0n00ps/OF-DL/releases/latest";
|
||||||
|
private static readonly HttpClient httpClient = new();
|
||||||
|
|
||||||
public static async Task<string?> GetLatestReleaseTag(CancellationToken cancellationToken = default)
|
public static async Task<string?> GetLatestReleaseTag(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Log.Debug("Calling GetLatestReleaseTag");
|
Log.Debug("Calling GetLatestReleaseTag");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await httpClient.GetAsync(url, cancellationToken);
|
HttpResponseMessage response = await httpClient.GetAsync(url, cancellationToken);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
@ -22,12 +22,13 @@ public static class VersionHelper
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var body = await response.Content.ReadAsStringAsync();
|
string body = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
Log.Debug("GetLatestReleaseTag API Response: ");
|
Log.Debug("GetLatestReleaseTag API Response: ");
|
||||||
Log.Debug(body);
|
Log.Debug(body);
|
||||||
|
|
||||||
var versionCheckResponse = JsonConvert.DeserializeObject<LatestReleaseAPIResponse>(body);
|
LatestReleaseAPIResponse? versionCheckResponse =
|
||||||
|
JsonConvert.DeserializeObject<LatestReleaseAPIResponse>(body);
|
||||||
|
|
||||||
if (versionCheckResponse == null || versionCheckResponse.TagName == "")
|
if (versionCheckResponse == null || versionCheckResponse.TagName == "")
|
||||||
{
|
{
|
||||||
@ -48,10 +49,13 @@ public static class VersionHelper
|
|||||||
if (ex.InnerException != null)
|
if (ex.InnerException != null)
|
||||||
{
|
{
|
||||||
Console.WriteLine("\nInner Exception:");
|
Console.WriteLine("\nInner Exception:");
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
|
||||||
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
ex.InnerException.StackTrace);
|
||||||
|
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
|
||||||
|
ex.InnerException.StackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,23 +10,23 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Icon\download.ico" />
|
<Content Include="Icon\download.ico"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Akka" Version="1.5.39" />
|
<PackageReference Include="Akka" Version="1.5.39"/>
|
||||||
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />
|
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1"/>
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.12.0"/>
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1"/>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.46" />
|
<PackageReference Include="protobuf-net" Version="3.2.46"/>
|
||||||
<PackageReference Include="PuppeteerSharp" Version="20.2.5" />
|
<PackageReference Include="PuppeteerSharp" Version="20.2.5"/>
|
||||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
<PackageReference Include="Serilog" Version="4.2.0"/>
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0"/>
|
||||||
<PackageReference Include="System.Reactive" Version="6.0.1" />
|
<PackageReference Include="System.Reactive" Version="6.0.1"/>
|
||||||
<PackageReference Include="xFFmpeg.NET" Version="7.2.0" />
|
<PackageReference Include="xFFmpeg.NET" Version="7.2.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
4569
OF DL/Program.cs
4569
OF DL/Program.cs
File diff suppressed because it is too large
Load Diff
@ -1,112 +1,112 @@
|
|||||||
{
|
{
|
||||||
"runtimeTarget": {
|
"runtimeTarget": {
|
||||||
"name": ".NETCoreApp,Version=v7.0",
|
"name": ".NETCoreApp,Version=v7.0",
|
||||||
"signature": ""
|
"signature": ""
|
||||||
},
|
},
|
||||||
"compilationOptions": {},
|
"compilationOptions": {},
|
||||||
"targets": {
|
"targets": {
|
||||||
".NETCoreApp,Version=v7.0": {
|
".NETCoreApp,Version=v7.0": {
|
||||||
"Spectre.Console/0.0.0-preview.0": {
|
"Spectre.Console/0.0.0-preview.0": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.SourceLink.GitHub": "1.1.1",
|
"Microsoft.SourceLink.GitHub": "1.1.1",
|
||||||
"MinVer": "4.2.0",
|
"MinVer": "4.2.0",
|
||||||
"Roslynator.Analyzers": "4.1.2",
|
"Roslynator.Analyzers": "4.1.2",
|
||||||
"StyleCop.Analyzers": "1.2.0-beta.435",
|
"StyleCop.Analyzers": "1.2.0-beta.435",
|
||||||
"System.Memory": "4.5.5",
|
"System.Memory": "4.5.5",
|
||||||
"Wcwidth.Sources": "1.0.0"
|
"Wcwidth.Sources": "1.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"Spectre.Console.dll": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.Build.Tasks.Git/1.1.1": {},
|
||||||
|
"Microsoft.SourceLink.Common/1.1.1": {},
|
||||||
|
"Microsoft.SourceLink.GitHub/1.1.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Build.Tasks.Git": "1.1.1",
|
||||||
|
"Microsoft.SourceLink.Common": "1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MinVer/4.2.0": {},
|
||||||
|
"Roslynator.Analyzers/4.1.2": {},
|
||||||
|
"StyleCop.Analyzers/1.2.0-beta.435": {
|
||||||
|
"dependencies": {
|
||||||
|
"StyleCop.Analyzers.Unstable": "1.2.0.435"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"StyleCop.Analyzers.Unstable/1.2.0.435": {},
|
||||||
|
"System.Memory/4.5.5": {},
|
||||||
|
"Wcwidth.Sources/1.0.0": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"Spectre.Console/0.0.0-preview.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"runtime": {
|
"Microsoft.Build.Tasks.Git/1.1.1": {
|
||||||
"Spectre.Console.dll": {}
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-AT3HlgTjsqHnWpBHSNeR0KxbLZD7bztlZVj7I8vgeYG9SYqbeFGh0TM/KVtC6fg53nrWHl3VfZFvb5BiQFcY6Q==",
|
||||||
|
"path": "microsoft.build.tasks.git/1.1.1",
|
||||||
|
"hashPath": "microsoft.build.tasks.git.1.1.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.SourceLink.Common/1.1.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg==",
|
||||||
|
"path": "microsoft.sourcelink.common/1.1.1",
|
||||||
|
"hashPath": "microsoft.sourcelink.common.1.1.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.SourceLink.GitHub/1.1.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==",
|
||||||
|
"path": "microsoft.sourcelink.github/1.1.1",
|
||||||
|
"hashPath": "microsoft.sourcelink.github.1.1.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"MinVer/4.2.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-Po4tv+sri1jsaebQ8F6+yD5ru9Gas5mR111F6HR2ULqwflvjjZXMstpeOc1GHMJeQa3g4E3b8MX8K2cShkuUAg==",
|
||||||
|
"path": "minver/4.2.0",
|
||||||
|
"hashPath": "minver.4.2.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Roslynator.Analyzers/4.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-bNl3GRSBFjJymYnwq/IRDD9MOSZz9VKdGk9RsN0MWIXoSRnVKQv84f6s9nLE13y20lZgMZKlDqGw2uInBH4JgA==",
|
||||||
|
"path": "roslynator.analyzers/4.1.2",
|
||||||
|
"hashPath": "roslynator.analyzers.4.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"StyleCop.Analyzers/1.2.0-beta.435": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==",
|
||||||
|
"path": "stylecop.analyzers/1.2.0-beta.435",
|
||||||
|
"hashPath": "stylecop.analyzers.1.2.0-beta.435.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"StyleCop.Analyzers.Unstable/1.2.0.435": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==",
|
||||||
|
"path": "stylecop.analyzers.unstable/1.2.0.435",
|
||||||
|
"hashPath": "stylecop.analyzers.unstable.1.2.0.435.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Memory/4.5.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
|
||||||
|
"path": "system.memory/4.5.5",
|
||||||
|
"hashPath": "system.memory.4.5.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Wcwidth.Sources/1.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-86tmwfGXRz7GJQXBnoTFoMvqSqd6irfkEkRzQFR54W/nweaR8cUvzY8x++z+B/+eUPSuqD2Ah1iPJHgthy4pzg==",
|
||||||
|
"path": "wcwidth.sources/1.0.0",
|
||||||
|
"hashPath": "wcwidth.sources.1.0.0.nupkg.sha512"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Microsoft.Build.Tasks.Git/1.1.1": {},
|
|
||||||
"Microsoft.SourceLink.Common/1.1.1": {},
|
|
||||||
"Microsoft.SourceLink.GitHub/1.1.1": {
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Build.Tasks.Git": "1.1.1",
|
|
||||||
"Microsoft.SourceLink.Common": "1.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"MinVer/4.2.0": {},
|
|
||||||
"Roslynator.Analyzers/4.1.2": {},
|
|
||||||
"StyleCop.Analyzers/1.2.0-beta.435": {
|
|
||||||
"dependencies": {
|
|
||||||
"StyleCop.Analyzers.Unstable": "1.2.0.435"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"StyleCop.Analyzers.Unstable/1.2.0.435": {},
|
|
||||||
"System.Memory/4.5.5": {},
|
|
||||||
"Wcwidth.Sources/1.0.0": {}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"libraries": {
|
|
||||||
"Spectre.Console/0.0.0-preview.0": {
|
|
||||||
"type": "project",
|
|
||||||
"serviceable": false,
|
|
||||||
"sha512": ""
|
|
||||||
},
|
|
||||||
"Microsoft.Build.Tasks.Git/1.1.1": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-AT3HlgTjsqHnWpBHSNeR0KxbLZD7bztlZVj7I8vgeYG9SYqbeFGh0TM/KVtC6fg53nrWHl3VfZFvb5BiQFcY6Q==",
|
|
||||||
"path": "microsoft.build.tasks.git/1.1.1",
|
|
||||||
"hashPath": "microsoft.build.tasks.git.1.1.1.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"Microsoft.SourceLink.Common/1.1.1": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg==",
|
|
||||||
"path": "microsoft.sourcelink.common/1.1.1",
|
|
||||||
"hashPath": "microsoft.sourcelink.common.1.1.1.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"Microsoft.SourceLink.GitHub/1.1.1": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==",
|
|
||||||
"path": "microsoft.sourcelink.github/1.1.1",
|
|
||||||
"hashPath": "microsoft.sourcelink.github.1.1.1.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"MinVer/4.2.0": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-Po4tv+sri1jsaebQ8F6+yD5ru9Gas5mR111F6HR2ULqwflvjjZXMstpeOc1GHMJeQa3g4E3b8MX8K2cShkuUAg==",
|
|
||||||
"path": "minver/4.2.0",
|
|
||||||
"hashPath": "minver.4.2.0.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"Roslynator.Analyzers/4.1.2": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-bNl3GRSBFjJymYnwq/IRDD9MOSZz9VKdGk9RsN0MWIXoSRnVKQv84f6s9nLE13y20lZgMZKlDqGw2uInBH4JgA==",
|
|
||||||
"path": "roslynator.analyzers/4.1.2",
|
|
||||||
"hashPath": "roslynator.analyzers.4.1.2.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"StyleCop.Analyzers/1.2.0-beta.435": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==",
|
|
||||||
"path": "stylecop.analyzers/1.2.0-beta.435",
|
|
||||||
"hashPath": "stylecop.analyzers.1.2.0-beta.435.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"StyleCop.Analyzers.Unstable/1.2.0.435": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==",
|
|
||||||
"path": "stylecop.analyzers.unstable/1.2.0.435",
|
|
||||||
"hashPath": "stylecop.analyzers.unstable.1.2.0.435.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"System.Memory/4.5.5": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
|
|
||||||
"path": "system.memory/4.5.5",
|
|
||||||
"hashPath": "system.memory.4.5.5.nupkg.sha512"
|
|
||||||
},
|
|
||||||
"Wcwidth.Sources/1.0.0": {
|
|
||||||
"type": "package",
|
|
||||||
"serviceable": true,
|
|
||||||
"sha512": "sha512-86tmwfGXRz7GJQXBnoTFoMvqSqd6irfkEkRzQFR54W/nweaR8cUvzY8x++z+B/+eUPSuqD2Ah1iPJHgthy4pzg==",
|
|
||||||
"path": "wcwidth.sources/1.0.0",
|
|
||||||
"hashPath": "wcwidth.sources.1.0.0.nupkg.sha512"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -4,227 +4,220 @@ using PuppeteerSharp;
|
|||||||
using PuppeteerSharp.BrowserData;
|
using PuppeteerSharp.BrowserData;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public class AuthService : IAuthService
|
||||||
{
|
{
|
||||||
public class AuthService : IAuthService
|
private const int LoginTimeout = 600000; // 10 minutes
|
||||||
|
private const int FeedLoadTimeout = 60000; // 1 minute
|
||||||
|
|
||||||
|
private readonly string[] _desiredCookies =
|
||||||
|
[
|
||||||
|
"auth_id",
|
||||||
|
"sess"
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly LaunchOptions _options = new()
|
||||||
{
|
{
|
||||||
private readonly LaunchOptions _options = new()
|
Headless = false,
|
||||||
|
Channel = ChromeReleaseChannel.Stable,
|
||||||
|
DefaultViewport = null,
|
||||||
|
Args = ["--no-sandbox", "--disable-setuid-sandbox"],
|
||||||
|
UserDataDir = Path.GetFullPath("chrome-data")
|
||||||
|
};
|
||||||
|
|
||||||
|
public Auth? CurrentAuth { get; set; }
|
||||||
|
|
||||||
|
public async Task<bool> LoadFromFileAsync(string filePath = "auth.json")
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Headless = false,
|
if (!File.Exists(filePath))
|
||||||
Channel = ChromeReleaseChannel.Stable,
|
|
||||||
DefaultViewport = null,
|
|
||||||
Args = ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
||||||
UserDataDir = Path.GetFullPath("chrome-data")
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly string[] _desiredCookies =
|
|
||||||
[
|
|
||||||
"auth_id",
|
|
||||||
"sess"
|
|
||||||
];
|
|
||||||
|
|
||||||
private const int LoginTimeout = 600000; // 10 minutes
|
|
||||||
private const int FeedLoadTimeout = 60000; // 1 minute
|
|
||||||
|
|
||||||
public Auth? CurrentAuth { get; set; }
|
|
||||||
|
|
||||||
public async Task<bool> LoadFromFileAsync(string filePath = "auth.json")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
Log.Debug("Auth file not found: {FilePath}", filePath);
|
||||||
{
|
|
||||||
Log.Debug("Auth file not found: {FilePath}", filePath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var json = await File.ReadAllTextAsync(filePath);
|
|
||||||
CurrentAuth = JsonConvert.DeserializeObject<Auth>(json);
|
|
||||||
Log.Debug("Auth file loaded and deserialized successfully");
|
|
||||||
return CurrentAuth != null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Failed to load auth from file");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string json = await File.ReadAllTextAsync(filePath);
|
||||||
|
CurrentAuth = JsonConvert.DeserializeObject<Auth>(json);
|
||||||
|
Log.Debug("Auth file loaded and deserialized successfully");
|
||||||
|
return CurrentAuth != null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to load auth from file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> LoadFromBrowserAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool runningInDocker = Environment.GetEnvironmentVariable("OFDL_DOCKER") != null;
|
||||||
|
|
||||||
|
await SetupBrowser(runningInDocker);
|
||||||
|
CurrentAuth = await GetAuthFromBrowser();
|
||||||
|
|
||||||
|
return CurrentAuth != null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to load auth from browser");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveToFileAsync(string filePath = "auth.json")
|
||||||
|
{
|
||||||
|
if (CurrentAuth == null)
|
||||||
|
{
|
||||||
|
Log.Warning("Attempted to save null auth to file");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> LoadFromBrowserAsync()
|
try
|
||||||
{
|
{
|
||||||
try
|
string json = JsonConvert.SerializeObject(CurrentAuth, Formatting.Indented);
|
||||||
{
|
await File.WriteAllTextAsync(filePath, json);
|
||||||
bool runningInDocker = Environment.GetEnvironmentVariable("OFDL_DOCKER") != null;
|
Log.Debug($"Auth saved to file: {filePath}");
|
||||||
|
|
||||||
await SetupBrowser(runningInDocker);
|
|
||||||
CurrentAuth = await GetAuthFromBrowser();
|
|
||||||
|
|
||||||
return CurrentAuth != null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Failed to load auth from browser");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public async Task SaveToFileAsync(string filePath = "auth.json")
|
|
||||||
{
|
{
|
||||||
if (CurrentAuth == null)
|
Log.Error(ex, "Failed to save auth to file");
|
||||||
{
|
|
||||||
Log.Warning("Attempted to save null auth to file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(CurrentAuth, Formatting.Indented);
|
|
||||||
await File.WriteAllTextAsync(filePath, json);
|
|
||||||
Log.Debug($"Auth saved to file: {filePath}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Failed to save auth to file");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SetupBrowser(bool runningInDocker)
|
private async Task SetupBrowser(bool runningInDocker)
|
||||||
|
{
|
||||||
|
string? executablePath = Environment.GetEnvironmentVariable("OFDL_PUPPETEER_EXECUTABLE_PATH");
|
||||||
|
if (executablePath != null)
|
||||||
{
|
{
|
||||||
string? executablePath = Environment.GetEnvironmentVariable("OFDL_PUPPETEER_EXECUTABLE_PATH");
|
Log.Information(
|
||||||
if (executablePath != null)
|
"OFDL_PUPPETEER_EXECUTABLE_PATH environment variable found. Using browser executable path: {executablePath}",
|
||||||
|
executablePath);
|
||||||
|
_options.ExecutablePath = executablePath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BrowserFetcher browserFetcher = new();
|
||||||
|
List<InstalledBrowser> installedBrowsers = browserFetcher.GetInstalledBrowsers().ToList();
|
||||||
|
if (installedBrowsers.Count == 0)
|
||||||
{
|
{
|
||||||
Log.Information("OFDL_PUPPETEER_EXECUTABLE_PATH environment variable found. Using browser executable path: {executablePath}", executablePath);
|
Log.Information("Downloading browser.");
|
||||||
_options.ExecutablePath = executablePath;
|
InstalledBrowser? downloadedBrowser = await browserFetcher.DownloadAsync();
|
||||||
|
Log.Information("Browser downloaded. Path: {executablePath}",
|
||||||
|
downloadedBrowser.GetExecutablePath());
|
||||||
|
_options.ExecutablePath = downloadedBrowser.GetExecutablePath();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var browserFetcher = new BrowserFetcher();
|
_options.ExecutablePath = installedBrowsers.First().GetExecutablePath();
|
||||||
var installedBrowsers = browserFetcher.GetInstalledBrowsers().ToList();
|
}
|
||||||
if (installedBrowsers.Count == 0)
|
}
|
||||||
|
|
||||||
|
if (runningInDocker)
|
||||||
|
{
|
||||||
|
Log.Information("Running in Docker. Disabling sandbox and GPU.");
|
||||||
|
_options.Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetBcToken(IPage page) =>
|
||||||
|
await page.EvaluateExpressionAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
|
||||||
|
|
||||||
|
private async Task<Auth?> GetAuthFromBrowser(bool isDocker = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IBrowser? browser;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
browser = await Puppeteer.LaunchAsync(_options);
|
||||||
|
}
|
||||||
|
catch (ProcessException e)
|
||||||
|
{
|
||||||
|
if (e.Message.Contains("Failed to launch browser") && Directory.Exists(_options.UserDataDir))
|
||||||
{
|
{
|
||||||
Log.Information("Downloading browser.");
|
Log.Error("Failed to launch browser. Deleting chrome-data directory and trying again.");
|
||||||
var downloadedBrowser = await browserFetcher.DownloadAsync();
|
Directory.Delete(_options.UserDataDir, true);
|
||||||
Log.Information("Browser downloaded. Path: {executablePath}",
|
browser = await Puppeteer.LaunchAsync(_options);
|
||||||
downloadedBrowser.GetExecutablePath());
|
|
||||||
_options.ExecutablePath = downloadedBrowser.GetExecutablePath();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_options.ExecutablePath = installedBrowsers.First().GetExecutablePath();
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runningInDocker)
|
if (browser == null)
|
||||||
{
|
{
|
||||||
Log.Information("Running in Docker. Disabling sandbox and GPU.");
|
throw new Exception("Could not get browser");
|
||||||
_options.Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"];
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> GetBcToken(IPage page)
|
IPage[]? pages = await browser.PagesAsync();
|
||||||
{
|
IPage? page = pages.First();
|
||||||
return await page.EvaluateExpressionAsync<string>("window.localStorage.getItem('bcTokenSha') || ''");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Auth?> GetAuthFromBrowser(bool isDocker = false)
|
if (page == null)
|
||||||
{
|
{
|
||||||
|
throw new Exception("Could not get page");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug("Navigating to OnlyFans.");
|
||||||
|
await page.GoToAsync("https://onlyfans.com");
|
||||||
|
|
||||||
|
Log.Debug("Waiting for user to login");
|
||||||
|
await page.WaitForSelectorAsync(".b-feed", new WaitForSelectorOptions { Timeout = LoginTimeout });
|
||||||
|
Log.Debug("Feed element detected (user logged in)");
|
||||||
|
|
||||||
|
await page.ReloadAsync();
|
||||||
|
|
||||||
|
await page.WaitForNavigationAsync(new NavigationOptions
|
||||||
|
{
|
||||||
|
WaitUntil = [WaitUntilNavigation.Networkidle2], Timeout = FeedLoadTimeout
|
||||||
|
});
|
||||||
|
Log.Debug("DOM loaded. Getting BC token and cookies ...");
|
||||||
|
|
||||||
|
string xBc;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IBrowser? browser;
|
xBc = await GetBcToken(page);
|
||||||
try
|
|
||||||
{
|
|
||||||
browser = await Puppeteer.LaunchAsync(_options);
|
|
||||||
}
|
|
||||||
catch (ProcessException e)
|
|
||||||
{
|
|
||||||
if (e.Message.Contains("Failed to launch browser") && Directory.Exists(_options.UserDataDir))
|
|
||||||
{
|
|
||||||
Log.Error("Failed to launch browser. Deleting chrome-data directory and trying again.");
|
|
||||||
Directory.Delete(_options.UserDataDir, true);
|
|
||||||
browser = await Puppeteer.LaunchAsync(_options);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Could not get browser");
|
|
||||||
}
|
|
||||||
|
|
||||||
IPage[]? pages = await browser.PagesAsync();
|
|
||||||
IPage? page = pages.First();
|
|
||||||
|
|
||||||
if (page == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Could not get page");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Debug("Navigating to OnlyFans.");
|
|
||||||
await page.GoToAsync("https://onlyfans.com");
|
|
||||||
|
|
||||||
Log.Debug("Waiting for user to login");
|
|
||||||
await page.WaitForSelectorAsync(".b-feed", new WaitForSelectorOptions { Timeout = LoginTimeout });
|
|
||||||
Log.Debug("Feed element detected (user logged in)");
|
|
||||||
|
|
||||||
await page.ReloadAsync();
|
|
||||||
|
|
||||||
await page.WaitForNavigationAsync(new NavigationOptions {
|
|
||||||
WaitUntil = [WaitUntilNavigation.Networkidle2],
|
|
||||||
Timeout = FeedLoadTimeout
|
|
||||||
});
|
|
||||||
Log.Debug("DOM loaded. Getting BC token and cookies ...");
|
|
||||||
|
|
||||||
string xBc;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
xBc = await GetBcToken(page);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception("Error getting bcToken");
|
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary<string, string> mappedCookies = (await page.GetCookiesAsync())
|
|
||||||
.Where(cookie => cookie.Domain.Contains("onlyfans.com"))
|
|
||||||
.ToDictionary(cookie => cookie.Name, cookie => cookie.Value);
|
|
||||||
|
|
||||||
mappedCookies.TryGetValue("auth_id", out string? userId);
|
|
||||||
if (userId == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Could not find 'auth_id' cookie");
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedCookies.TryGetValue("sess", out string? sess);
|
|
||||||
if (sess == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Could not find 'sess' cookie");
|
|
||||||
}
|
|
||||||
|
|
||||||
string? userAgent = await browser.GetUserAgentAsync();
|
|
||||||
if (userAgent == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Could not get user agent");
|
|
||||||
}
|
|
||||||
|
|
||||||
string cookies = string.Join(" ", mappedCookies.Keys.Where(key => _desiredCookies.Contains(key))
|
|
||||||
.Select(key => $"${key}={mappedCookies[key]};"));
|
|
||||||
|
|
||||||
return new Auth()
|
|
||||||
{
|
|
||||||
COOKIE = cookies,
|
|
||||||
USER_AGENT = userAgent,
|
|
||||||
USER_ID = userId,
|
|
||||||
X_BC = xBc
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error(e, "Error getting auth from browser");
|
throw new Exception("Error getting bcToken");
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dictionary<string, string> mappedCookies = (await page.GetCookiesAsync())
|
||||||
|
.Where(cookie => cookie.Domain.Contains("onlyfans.com"))
|
||||||
|
.ToDictionary(cookie => cookie.Name, cookie => cookie.Value);
|
||||||
|
|
||||||
|
mappedCookies.TryGetValue("auth_id", out string? userId);
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Could not find 'auth_id' cookie");
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedCookies.TryGetValue("sess", out string? sess);
|
||||||
|
if (sess == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Could not find 'sess' cookie");
|
||||||
|
}
|
||||||
|
|
||||||
|
string? userAgent = await browser.GetUserAgentAsync();
|
||||||
|
if (userAgent == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Could not get user agent");
|
||||||
|
}
|
||||||
|
|
||||||
|
string cookies = string.Join(" ", mappedCookies.Keys.Where(key => _desiredCookies.Contains(key))
|
||||||
|
.Select(key => $"${key}={mappedCookies[key]};"));
|
||||||
|
|
||||||
|
return new Auth { COOKIE = cookies, USER_AGENT = userAgent, USER_ID = userId, X_BC = xBc };
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Error getting auth from browser");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,223 +1,241 @@
|
|||||||
|
using System.Text;
|
||||||
using Akka.Configuration;
|
using Akka.Configuration;
|
||||||
|
using Akka.Configuration.Hocon;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OF_DL.Entities;
|
using OF_DL.Entities;
|
||||||
using Serilog;
|
|
||||||
using System.Text;
|
|
||||||
using OF_DL.Enumerations;
|
using OF_DL.Enumerations;
|
||||||
|
using OF_DL.Utils;
|
||||||
|
using Serilog;
|
||||||
using Config = OF_DL.Entities.Config;
|
using Config = OF_DL.Entities.Config;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public class ConfigService(ILoggingService loggingService) : IConfigService
|
||||||
{
|
{
|
||||||
public class ConfigService(ILoggingService loggingService) : IConfigService
|
public Config CurrentConfig { get; private set; } = new();
|
||||||
|
public bool IsCliNonInteractive { get; private set; }
|
||||||
|
|
||||||
|
public async Task<bool> LoadConfigurationAsync(string[] args)
|
||||||
{
|
{
|
||||||
public Config CurrentConfig { get; private set; } = new();
|
try
|
||||||
public bool IsCliNonInteractive { get; private set; }
|
|
||||||
|
|
||||||
public async Task<bool> LoadConfigurationAsync(string[] args)
|
|
||||||
{
|
{
|
||||||
try
|
IsCliNonInteractive = false;
|
||||||
|
|
||||||
|
// Migrate from config.json to config.conf if needed
|
||||||
|
await MigrateFromJsonToConfAsync();
|
||||||
|
|
||||||
|
// Load config.conf or create default
|
||||||
|
if (File.Exists("config.conf"))
|
||||||
{
|
{
|
||||||
IsCliNonInteractive = false;
|
Log.Debug("config.conf located successfully");
|
||||||
|
if (!await LoadConfigFromFileAsync("config.conf"))
|
||||||
// Migrate from config.json to config.conf if needed
|
|
||||||
await MigrateFromJsonToConfAsync();
|
|
||||||
|
|
||||||
// Load config.conf or create default
|
|
||||||
if (File.Exists("config.conf"))
|
|
||||||
{
|
{
|
||||||
Log.Debug("config.conf located successfully");
|
return false;
|
||||||
if (!await LoadConfigFromFileAsync("config.conf"))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Debug("config.conf not found, creating default");
|
|
||||||
await CreateDefaultConfigFileAsync();
|
|
||||||
if (!await LoadConfigFromFileAsync("config.conf"))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for command-line arguments
|
|
||||||
if (args != null && args.Length > 0)
|
|
||||||
{
|
|
||||||
const string NON_INTERACTIVE_ARG = "--non-interactive";
|
|
||||||
if (args.Any(a => a.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
|
||||||
IsCliNonInteractive = true;
|
|
||||||
Log.Debug("NonInteractiveMode set via command line");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Debug("Additional arguments:");
|
|
||||||
foreach (string argument in args)
|
|
||||||
{
|
|
||||||
Log.Debug(argument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Configuration loading failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SaveConfigurationAsync(string filePath = "config.conf")
|
|
||||||
{
|
|
||||||
if (CurrentConfig == null)
|
|
||||||
{
|
|
||||||
Log.Warning("Attempted to save null config to file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var hoconConfig = BuildHoconFromConfig(CurrentConfig);
|
|
||||||
await File.WriteAllTextAsync(filePath, hoconConfig);
|
|
||||||
Log.Debug($"Config saved to file: {filePath}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Failed to save config to file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateConfig(Config newConfig)
|
|
||||||
{
|
|
||||||
CurrentConfig = newConfig;
|
|
||||||
|
|
||||||
// Update logging level
|
|
||||||
loggingService.UpdateLoggingLevel(newConfig.LoggingLevel);
|
|
||||||
|
|
||||||
// Apply text sanitization preference globally
|
|
||||||
OF_DL.Utils.XmlUtils.Passthrough = newConfig.DisableTextSanitization;
|
|
||||||
|
|
||||||
Log.Debug("Configuration updated");
|
|
||||||
string configString = JsonConvert.SerializeObject(newConfig, Formatting.Indented);
|
|
||||||
Log.Debug(configString);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task MigrateFromJsonToConfAsync()
|
|
||||||
{
|
|
||||||
if (!File.Exists("config.json"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Log.Debug("config.json found, migrating to config.conf");
|
|
||||||
string jsonText = await File.ReadAllTextAsync("config.json");
|
|
||||||
var jsonConfig = JsonConvert.DeserializeObject<Config>(jsonText);
|
|
||||||
|
|
||||||
if (jsonConfig != null)
|
|
||||||
{
|
|
||||||
var hoconConfig = BuildHoconFromConfig(jsonConfig);
|
|
||||||
await File.WriteAllTextAsync("config.conf", hoconConfig);
|
|
||||||
File.Delete("config.json");
|
|
||||||
Log.Information("config.conf created successfully from config.json");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Failed to migrate config.json to config.conf");
|
Log.Debug("config.conf not found, creating default");
|
||||||
throw;
|
await CreateDefaultConfigFileAsync();
|
||||||
|
if (!await LoadConfigFromFileAsync("config.conf"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for command-line arguments
|
||||||
|
if (args != null && args.Length > 0)
|
||||||
|
{
|
||||||
|
const string NON_INTERACTIVE_ARG = "--non-interactive";
|
||||||
|
if (args.Any(a => a.Equals(NON_INTERACTIVE_ARG, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
IsCliNonInteractive = true;
|
||||||
|
Log.Debug("NonInteractiveMode set via command line");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug("Additional arguments:");
|
||||||
|
foreach (string argument in args)
|
||||||
|
{
|
||||||
|
Log.Debug(argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Configuration loading failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveConfigurationAsync(string filePath = "config.conf")
|
||||||
|
{
|
||||||
|
if (CurrentConfig == null)
|
||||||
|
{
|
||||||
|
Log.Warning("Attempted to save null config to file");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> LoadConfigFromFileAsync(string filePath)
|
try
|
||||||
{
|
{
|
||||||
try
|
string hoconConfig = BuildHoconFromConfig(CurrentConfig);
|
||||||
|
await File.WriteAllTextAsync(filePath, hoconConfig);
|
||||||
|
Log.Debug($"Config saved to file: {filePath}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to save config to file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateConfig(Config newConfig)
|
||||||
|
{
|
||||||
|
CurrentConfig = newConfig;
|
||||||
|
|
||||||
|
// Update logging level
|
||||||
|
loggingService.UpdateLoggingLevel(newConfig.LoggingLevel);
|
||||||
|
|
||||||
|
// Apply text sanitization preference globally
|
||||||
|
XmlUtils.Passthrough = newConfig.DisableTextSanitization;
|
||||||
|
|
||||||
|
Log.Debug("Configuration updated");
|
||||||
|
string configString = JsonConvert.SerializeObject(newConfig, Formatting.Indented);
|
||||||
|
Log.Debug(configString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MigrateFromJsonToConfAsync()
|
||||||
|
{
|
||||||
|
if (!File.Exists("config.json"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.Debug("config.json found, migrating to config.conf");
|
||||||
|
string jsonText = await File.ReadAllTextAsync("config.json");
|
||||||
|
Config? jsonConfig = JsonConvert.DeserializeObject<Config>(jsonText);
|
||||||
|
|
||||||
|
if (jsonConfig != null)
|
||||||
{
|
{
|
||||||
string hoconText = await File.ReadAllTextAsync(filePath);
|
string hoconConfig = BuildHoconFromConfig(jsonConfig);
|
||||||
var hoconConfig = ConfigurationFactory.ParseString(hoconText);
|
await File.WriteAllTextAsync("config.conf", hoconConfig);
|
||||||
|
File.Delete("config.json");
|
||||||
|
Log.Information("config.conf created successfully from config.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to migrate config.json to config.conf");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CurrentConfig = new Config
|
private async Task<bool> LoadConfigFromFileAsync(string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string hoconText = await File.ReadAllTextAsync(filePath);
|
||||||
|
Akka.Configuration.Config? hoconConfig = ConfigurationFactory.ParseString(hoconText);
|
||||||
|
|
||||||
|
CurrentConfig = new Config
|
||||||
|
{
|
||||||
|
// Auth
|
||||||
|
DisableBrowserAuth = hoconConfig.GetBoolean("Auth.DisableBrowserAuth"),
|
||||||
|
|
||||||
|
// FFmpeg Settings
|
||||||
|
FFmpegPath = hoconConfig.GetString("External.FFmpegPath"),
|
||||||
|
|
||||||
|
// Download Settings
|
||||||
|
DownloadAvatarHeaderPhoto = hoconConfig.GetBoolean("Download.Media.DownloadAvatarHeaderPhoto"),
|
||||||
|
DownloadPaidPosts = hoconConfig.GetBoolean("Download.Media.DownloadPaidPosts"),
|
||||||
|
DownloadPosts = hoconConfig.GetBoolean("Download.Media.DownloadPosts"),
|
||||||
|
DownloadArchived = hoconConfig.GetBoolean("Download.Media.DownloadArchived"),
|
||||||
|
DownloadStreams = hoconConfig.GetBoolean("Download.Media.DownloadStreams"),
|
||||||
|
DownloadStories = hoconConfig.GetBoolean("Download.Media.DownloadStories"),
|
||||||
|
DownloadHighlights = hoconConfig.GetBoolean("Download.Media.DownloadHighlights"),
|
||||||
|
DownloadMessages = hoconConfig.GetBoolean("Download.Media.DownloadMessages"),
|
||||||
|
DownloadPaidMessages = hoconConfig.GetBoolean("Download.Media.DownloadPaidMessages"),
|
||||||
|
DownloadImages = hoconConfig.GetBoolean("Download.Media.DownloadImages"),
|
||||||
|
DownloadVideos = hoconConfig.GetBoolean("Download.Media.DownloadVideos"),
|
||||||
|
DownloadAudios = hoconConfig.GetBoolean("Download.Media.DownloadAudios"),
|
||||||
|
IgnoreOwnMessages = hoconConfig.GetBoolean("Download.IgnoreOwnMessages"),
|
||||||
|
DownloadPostsIncrementally = hoconConfig.GetBoolean("Download.DownloadPostsIncrementally"),
|
||||||
|
BypassContentForCreatorsWhoNoLongerExist =
|
||||||
|
hoconConfig.GetBoolean("Download.BypassContentForCreatorsWhoNoLongerExist"),
|
||||||
|
DownloadDuplicatedMedia = hoconConfig.GetBoolean("Download.DownloadDuplicatedMedia"),
|
||||||
|
SkipAds = hoconConfig.GetBoolean("Download.SkipAds"),
|
||||||
|
DownloadPath = hoconConfig.GetString("Download.DownloadPath"),
|
||||||
|
DownloadOnlySpecificDates = hoconConfig.GetBoolean("Download.DownloadOnlySpecificDates"),
|
||||||
|
DownloadDateSelection =
|
||||||
|
Enum.Parse<DownloadDateSelection>(hoconConfig.GetString("Download.DownloadDateSelection"), true),
|
||||||
|
CustomDate =
|
||||||
|
!string.IsNullOrWhiteSpace(hoconConfig.GetString("Download.CustomDate"))
|
||||||
|
? DateTime.Parse(hoconConfig.GetString("Download.CustomDate"))
|
||||||
|
: null,
|
||||||
|
ShowScrapeSize = hoconConfig.GetBoolean("Download.ShowScrapeSize"),
|
||||||
|
DisableTextSanitization =
|
||||||
|
bool.TryParse(hoconConfig.GetString("Download.DisableTextSanitization", "false"), out bool dts)
|
||||||
|
? dts
|
||||||
|
: false,
|
||||||
|
DownloadVideoResolution =
|
||||||
|
ParseVideoResolution(hoconConfig.GetString("Download.DownloadVideoResolution", "source")),
|
||||||
|
|
||||||
|
// File Settings
|
||||||
|
PaidPostFileNameFormat = hoconConfig.GetString("File.PaidPostFileNameFormat"),
|
||||||
|
PostFileNameFormat = hoconConfig.GetString("File.PostFileNameFormat"),
|
||||||
|
PaidMessageFileNameFormat = hoconConfig.GetString("File.PaidMessageFileNameFormat"),
|
||||||
|
MessageFileNameFormat = hoconConfig.GetString("File.MessageFileNameFormat"),
|
||||||
|
RenameExistingFilesWhenCustomFormatIsSelected =
|
||||||
|
hoconConfig.GetBoolean("File.RenameExistingFilesWhenCustomFormatIsSelected"),
|
||||||
|
|
||||||
|
// Folder Settings
|
||||||
|
FolderPerPaidPost = hoconConfig.GetBoolean("Folder.FolderPerPaidPost"),
|
||||||
|
FolderPerPost = hoconConfig.GetBoolean("Folder.FolderPerPost"),
|
||||||
|
FolderPerPaidMessage = hoconConfig.GetBoolean("Folder.FolderPerPaidMessage"),
|
||||||
|
FolderPerMessage = hoconConfig.GetBoolean("Folder.FolderPerMessage"),
|
||||||
|
|
||||||
|
// Subscription Settings
|
||||||
|
IncludeExpiredSubscriptions = hoconConfig.GetBoolean("Subscriptions.IncludeExpiredSubscriptions"),
|
||||||
|
IncludeRestrictedSubscriptions = hoconConfig.GetBoolean("Subscriptions.IncludeRestrictedSubscriptions"),
|
||||||
|
IgnoredUsersListName = hoconConfig.GetString("Subscriptions.IgnoredUsersListName"),
|
||||||
|
|
||||||
|
// Interaction Settings
|
||||||
|
NonInteractiveMode = hoconConfig.GetBoolean("Interaction.NonInteractiveMode"),
|
||||||
|
NonInteractiveModeListName = hoconConfig.GetString("Interaction.NonInteractiveModeListName"),
|
||||||
|
NonInteractiveModePurchasedTab = hoconConfig.GetBoolean("Interaction.NonInteractiveModePurchasedTab"),
|
||||||
|
|
||||||
|
// Performance Settings
|
||||||
|
Timeout =
|
||||||
|
string.IsNullOrWhiteSpace(hoconConfig.GetString("Performance.Timeout"))
|
||||||
|
? -1
|
||||||
|
: hoconConfig.GetInt("Performance.Timeout"),
|
||||||
|
LimitDownloadRate = hoconConfig.GetBoolean("Performance.LimitDownloadRate"),
|
||||||
|
DownloadLimitInMbPerSec = hoconConfig.GetInt("Performance.DownloadLimitInMbPerSec"),
|
||||||
|
|
||||||
|
// Logging/Debug Settings
|
||||||
|
LoggingLevel = Enum.Parse<LoggingLevel>(hoconConfig.GetString("Logging.LoggingLevel"), true)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate file name formats
|
||||||
|
ValidateFileNameFormat(CurrentConfig.PaidPostFileNameFormat, "PaidPostFileNameFormat");
|
||||||
|
ValidateFileNameFormat(CurrentConfig.PostFileNameFormat, "PostFileNameFormat");
|
||||||
|
ValidateFileNameFormat(CurrentConfig.PaidMessageFileNameFormat, "PaidMessageFileNameFormat");
|
||||||
|
ValidateFileNameFormat(CurrentConfig.MessageFileNameFormat, "MessageFileNameFormat");
|
||||||
|
|
||||||
|
// Load creator-specific configs
|
||||||
|
Akka.Configuration.Config? creatorConfigsSection = hoconConfig.GetConfig("CreatorConfigs");
|
||||||
|
if (creatorConfigsSection != null)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<string, HoconValue> key in creatorConfigsSection.AsEnumerable())
|
||||||
{
|
{
|
||||||
// Auth
|
string creatorKey = key.Key;
|
||||||
DisableBrowserAuth = hoconConfig.GetBoolean("Auth.DisableBrowserAuth"),
|
Akka.Configuration.Config? creatorHocon = creatorConfigsSection.GetConfig(creatorKey);
|
||||||
|
if (!CurrentConfig.CreatorConfigs.ContainsKey(creatorKey) && creatorHocon != null)
|
||||||
// FFmpeg Settings
|
|
||||||
FFmpegPath = hoconConfig.GetString("External.FFmpegPath"),
|
|
||||||
|
|
||||||
// Download Settings
|
|
||||||
DownloadAvatarHeaderPhoto = hoconConfig.GetBoolean("Download.Media.DownloadAvatarHeaderPhoto"),
|
|
||||||
DownloadPaidPosts = hoconConfig.GetBoolean("Download.Media.DownloadPaidPosts"),
|
|
||||||
DownloadPosts = hoconConfig.GetBoolean("Download.Media.DownloadPosts"),
|
|
||||||
DownloadArchived = hoconConfig.GetBoolean("Download.Media.DownloadArchived"),
|
|
||||||
DownloadStreams = hoconConfig.GetBoolean("Download.Media.DownloadStreams"),
|
|
||||||
DownloadStories = hoconConfig.GetBoolean("Download.Media.DownloadStories"),
|
|
||||||
DownloadHighlights = hoconConfig.GetBoolean("Download.Media.DownloadHighlights"),
|
|
||||||
DownloadMessages = hoconConfig.GetBoolean("Download.Media.DownloadMessages"),
|
|
||||||
DownloadPaidMessages = hoconConfig.GetBoolean("Download.Media.DownloadPaidMessages"),
|
|
||||||
DownloadImages = hoconConfig.GetBoolean("Download.Media.DownloadImages"),
|
|
||||||
DownloadVideos = hoconConfig.GetBoolean("Download.Media.DownloadVideos"),
|
|
||||||
DownloadAudios = hoconConfig.GetBoolean("Download.Media.DownloadAudios"),
|
|
||||||
IgnoreOwnMessages = hoconConfig.GetBoolean("Download.IgnoreOwnMessages"),
|
|
||||||
DownloadPostsIncrementally = hoconConfig.GetBoolean("Download.DownloadPostsIncrementally"),
|
|
||||||
BypassContentForCreatorsWhoNoLongerExist = hoconConfig.GetBoolean("Download.BypassContentForCreatorsWhoNoLongerExist"),
|
|
||||||
DownloadDuplicatedMedia = hoconConfig.GetBoolean("Download.DownloadDuplicatedMedia"),
|
|
||||||
SkipAds = hoconConfig.GetBoolean("Download.SkipAds"),
|
|
||||||
DownloadPath = hoconConfig.GetString("Download.DownloadPath"),
|
|
||||||
DownloadOnlySpecificDates = hoconConfig.GetBoolean("Download.DownloadOnlySpecificDates"),
|
|
||||||
DownloadDateSelection = Enum.Parse<DownloadDateSelection>(hoconConfig.GetString("Download.DownloadDateSelection"), true),
|
|
||||||
CustomDate = !string.IsNullOrWhiteSpace(hoconConfig.GetString("Download.CustomDate")) ? DateTime.Parse(hoconConfig.GetString("Download.CustomDate")) : null,
|
|
||||||
ShowScrapeSize = hoconConfig.GetBoolean("Download.ShowScrapeSize"),
|
|
||||||
DisableTextSanitization = bool.TryParse(hoconConfig.GetString("Download.DisableTextSanitization", "false"), out var dts) ? dts : false,
|
|
||||||
DownloadVideoResolution = ParseVideoResolution(hoconConfig.GetString("Download.DownloadVideoResolution", "source")),
|
|
||||||
|
|
||||||
// File Settings
|
|
||||||
PaidPostFileNameFormat = hoconConfig.GetString("File.PaidPostFileNameFormat"),
|
|
||||||
PostFileNameFormat = hoconConfig.GetString("File.PostFileNameFormat"),
|
|
||||||
PaidMessageFileNameFormat = hoconConfig.GetString("File.PaidMessageFileNameFormat"),
|
|
||||||
MessageFileNameFormat = hoconConfig.GetString("File.MessageFileNameFormat"),
|
|
||||||
RenameExistingFilesWhenCustomFormatIsSelected = hoconConfig.GetBoolean("File.RenameExistingFilesWhenCustomFormatIsSelected"),
|
|
||||||
|
|
||||||
// Folder Settings
|
|
||||||
FolderPerPaidPost = hoconConfig.GetBoolean("Folder.FolderPerPaidPost"),
|
|
||||||
FolderPerPost = hoconConfig.GetBoolean("Folder.FolderPerPost"),
|
|
||||||
FolderPerPaidMessage = hoconConfig.GetBoolean("Folder.FolderPerPaidMessage"),
|
|
||||||
FolderPerMessage = hoconConfig.GetBoolean("Folder.FolderPerMessage"),
|
|
||||||
|
|
||||||
// Subscription Settings
|
|
||||||
IncludeExpiredSubscriptions = hoconConfig.GetBoolean("Subscriptions.IncludeExpiredSubscriptions"),
|
|
||||||
IncludeRestrictedSubscriptions = hoconConfig.GetBoolean("Subscriptions.IncludeRestrictedSubscriptions"),
|
|
||||||
IgnoredUsersListName = hoconConfig.GetString("Subscriptions.IgnoredUsersListName"),
|
|
||||||
|
|
||||||
// Interaction Settings
|
|
||||||
NonInteractiveMode = hoconConfig.GetBoolean("Interaction.NonInteractiveMode"),
|
|
||||||
NonInteractiveModeListName = hoconConfig.GetString("Interaction.NonInteractiveModeListName"),
|
|
||||||
NonInteractiveModePurchasedTab = hoconConfig.GetBoolean("Interaction.NonInteractiveModePurchasedTab"),
|
|
||||||
|
|
||||||
// Performance Settings
|
|
||||||
Timeout = string.IsNullOrWhiteSpace(hoconConfig.GetString("Performance.Timeout")) ? -1 : hoconConfig.GetInt("Performance.Timeout"),
|
|
||||||
LimitDownloadRate = hoconConfig.GetBoolean("Performance.LimitDownloadRate"),
|
|
||||||
DownloadLimitInMbPerSec = hoconConfig.GetInt("Performance.DownloadLimitInMbPerSec"),
|
|
||||||
|
|
||||||
// Logging/Debug Settings
|
|
||||||
LoggingLevel = Enum.Parse<LoggingLevel>(hoconConfig.GetString("Logging.LoggingLevel"), true)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate file name formats
|
|
||||||
ValidateFileNameFormat(CurrentConfig.PaidPostFileNameFormat, "PaidPostFileNameFormat");
|
|
||||||
ValidateFileNameFormat(CurrentConfig.PostFileNameFormat, "PostFileNameFormat");
|
|
||||||
ValidateFileNameFormat(CurrentConfig.PaidMessageFileNameFormat, "PaidMessageFileNameFormat");
|
|
||||||
ValidateFileNameFormat(CurrentConfig.MessageFileNameFormat, "MessageFileNameFormat");
|
|
||||||
|
|
||||||
// Load creator-specific configs
|
|
||||||
var creatorConfigsSection = hoconConfig.GetConfig("CreatorConfigs");
|
|
||||||
if (creatorConfigsSection != null)
|
|
||||||
{
|
|
||||||
foreach (var key in creatorConfigsSection.AsEnumerable())
|
|
||||||
{
|
{
|
||||||
var creatorKey = key.Key;
|
CurrentConfig.CreatorConfigs.Add(key.Key,
|
||||||
var creatorHocon = creatorConfigsSection.GetConfig(creatorKey);
|
new CreatorConfig
|
||||||
if (!CurrentConfig.CreatorConfigs.ContainsKey(creatorKey) && creatorHocon != null)
|
|
||||||
{
|
|
||||||
CurrentConfig.CreatorConfigs.Add(key.Key, new CreatorConfig
|
|
||||||
{
|
{
|
||||||
PaidPostFileNameFormat = creatorHocon.GetString("PaidPostFileNameFormat"),
|
PaidPostFileNameFormat = creatorHocon.GetString("PaidPostFileNameFormat"),
|
||||||
PostFileNameFormat = creatorHocon.GetString("PostFileNameFormat"),
|
PostFileNameFormat = creatorHocon.GetString("PostFileNameFormat"),
|
||||||
@ -225,161 +243,172 @@ namespace OF_DL.Services
|
|||||||
MessageFileNameFormat = creatorHocon.GetString("MessageFileNameFormat")
|
MessageFileNameFormat = creatorHocon.GetString("MessageFileNameFormat")
|
||||||
});
|
});
|
||||||
|
|
||||||
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[key.Key].PaidPostFileNameFormat, $"{key.Key}.PaidPostFileNameFormat");
|
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[key.Key].PaidPostFileNameFormat,
|
||||||
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[key.Key].PostFileNameFormat, $"{key.Key}.PostFileNameFormat");
|
$"{key.Key}.PaidPostFileNameFormat");
|
||||||
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[key.Key].PaidMessageFileNameFormat, $"{key.Key}.PaidMessageFileNameFormat");
|
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[key.Key].PostFileNameFormat,
|
||||||
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[key.Key].MessageFileNameFormat, $"{key.Key}.MessageFileNameFormat");
|
$"{key.Key}.PostFileNameFormat");
|
||||||
}
|
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[key.Key].PaidMessageFileNameFormat,
|
||||||
|
$"{key.Key}.PaidMessageFileNameFormat");
|
||||||
|
ValidateFileNameFormat(CurrentConfig.CreatorConfigs[key.Key].MessageFileNameFormat,
|
||||||
|
$"{key.Key}.MessageFileNameFormat");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update logging level
|
|
||||||
loggingService.UpdateLoggingLevel(CurrentConfig.LoggingLevel);
|
|
||||||
|
|
||||||
// Apply text sanitization preference globally
|
|
||||||
OF_DL.Utils.XmlUtils.Passthrough = CurrentConfig.DisableTextSanitization;
|
|
||||||
|
|
||||||
Log.Debug("Configuration loaded successfully");
|
|
||||||
string configString = JsonConvert.SerializeObject(CurrentConfig, Formatting.Indented);
|
|
||||||
Log.Debug(configString);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Failed to parse config file");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update logging level
|
||||||
|
loggingService.UpdateLoggingLevel(CurrentConfig.LoggingLevel);
|
||||||
|
|
||||||
|
// Apply text sanitization preference globally
|
||||||
|
XmlUtils.Passthrough = CurrentConfig.DisableTextSanitization;
|
||||||
|
|
||||||
|
Log.Debug("Configuration loaded successfully");
|
||||||
|
string configString = JsonConvert.SerializeObject(CurrentConfig, Formatting.Indented);
|
||||||
|
Log.Debug(configString);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
private async Task CreateDefaultConfigFileAsync()
|
|
||||||
{
|
{
|
||||||
Config defaultConfig = new Config();
|
Log.Error(ex, "Failed to parse config file");
|
||||||
var hoconConfig = BuildHoconFromConfig(defaultConfig);
|
return false;
|
||||||
await File.WriteAllTextAsync("config.conf", hoconConfig);
|
|
||||||
Log.Information("Created default config.conf file");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string BuildHoconFromConfig(Config config)
|
|
||||||
{
|
|
||||||
var hocon = new StringBuilder();
|
|
||||||
|
|
||||||
hocon.AppendLine("# Auth");
|
|
||||||
hocon.AppendLine("Auth {");
|
|
||||||
hocon.AppendLine($" DisableBrowserAuth = {config.DisableBrowserAuth.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# External Tools");
|
|
||||||
hocon.AppendLine("External {");
|
|
||||||
hocon.AppendLine($" FFmpegPath = \"{config.FFmpegPath}\"");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# Download Settings");
|
|
||||||
hocon.AppendLine("Download {");
|
|
||||||
hocon.AppendLine(" Media {");
|
|
||||||
hocon.AppendLine($" DownloadAvatarHeaderPhoto = {config.DownloadAvatarHeaderPhoto.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadPaidPosts = {config.DownloadPaidPosts.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadPosts = {config.DownloadPosts.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadArchived = {config.DownloadArchived.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadStreams = {config.DownloadStreams.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadStories = {config.DownloadStories.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadHighlights = {config.DownloadHighlights.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadMessages = {config.DownloadMessages.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadPaidMessages = {config.DownloadPaidMessages.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadImages = {config.DownloadImages.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadVideos = {config.DownloadVideos.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadAudios = {config.DownloadAudios.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine(" }");
|
|
||||||
hocon.AppendLine($" IgnoreOwnMessages = {config.IgnoreOwnMessages.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadPostsIncrementally = {config.DownloadPostsIncrementally.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" BypassContentForCreatorsWhoNoLongerExist = {config.BypassContentForCreatorsWhoNoLongerExist.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadDuplicatedMedia = {config.DownloadDuplicatedMedia.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" SkipAds = {config.SkipAds.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadPath = \"{config.DownloadPath}\"");
|
|
||||||
hocon.AppendLine($" DownloadOnlySpecificDates = {config.DownloadOnlySpecificDates.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadDateSelection = \"{config.DownloadDateSelection.ToString().ToLower()}\"");
|
|
||||||
hocon.AppendLine($" CustomDate = \"{config.CustomDate?.ToString("yyyy-MM-dd")}\"");
|
|
||||||
hocon.AppendLine($" ShowScrapeSize = {config.ShowScrapeSize.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DisableTextSanitization = {config.DisableTextSanitization.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadVideoResolution = \"{(config.DownloadVideoResolution == VideoResolution.source ? "source" : config.DownloadVideoResolution.ToString().TrimStart('_'))}\"");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# File Settings");
|
|
||||||
hocon.AppendLine("File {");
|
|
||||||
hocon.AppendLine($" PaidPostFileNameFormat = \"{config.PaidPostFileNameFormat}\"");
|
|
||||||
hocon.AppendLine($" PostFileNameFormat = \"{config.PostFileNameFormat}\"");
|
|
||||||
hocon.AppendLine($" PaidMessageFileNameFormat = \"{config.PaidMessageFileNameFormat}\"");
|
|
||||||
hocon.AppendLine($" MessageFileNameFormat = \"{config.MessageFileNameFormat}\"");
|
|
||||||
hocon.AppendLine($" RenameExistingFilesWhenCustomFormatIsSelected = {config.RenameExistingFilesWhenCustomFormatIsSelected.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# Creator-Specific Configurations");
|
|
||||||
hocon.AppendLine("CreatorConfigs {");
|
|
||||||
foreach (var creatorConfig in config.CreatorConfigs)
|
|
||||||
{
|
|
||||||
hocon.AppendLine($" \"{creatorConfig.Key}\" {{");
|
|
||||||
hocon.AppendLine($" PaidPostFileNameFormat = \"{creatorConfig.Value.PaidPostFileNameFormat}\"");
|
|
||||||
hocon.AppendLine($" PostFileNameFormat = \"{creatorConfig.Value.PostFileNameFormat}\"");
|
|
||||||
hocon.AppendLine($" PaidMessageFileNameFormat = \"{creatorConfig.Value.PaidMessageFileNameFormat}\"");
|
|
||||||
hocon.AppendLine($" MessageFileNameFormat = \"{creatorConfig.Value.MessageFileNameFormat}\"");
|
|
||||||
hocon.AppendLine(" }");
|
|
||||||
}
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# Folder Settings");
|
|
||||||
hocon.AppendLine("Folder {");
|
|
||||||
hocon.AppendLine($" FolderPerPaidPost = {config.FolderPerPaidPost.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" FolderPerPost = {config.FolderPerPost.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" FolderPerPaidMessage = {config.FolderPerPaidMessage.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" FolderPerMessage = {config.FolderPerMessage.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# Subscription Settings");
|
|
||||||
hocon.AppendLine("Subscriptions {");
|
|
||||||
hocon.AppendLine($" IncludeExpiredSubscriptions = {config.IncludeExpiredSubscriptions.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" IncludeRestrictedSubscriptions = {config.IncludeRestrictedSubscriptions.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" IgnoredUsersListName = \"{config.IgnoredUsersListName}\"");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# Interaction Settings");
|
|
||||||
hocon.AppendLine("Interaction {");
|
|
||||||
hocon.AppendLine($" NonInteractiveMode = {config.NonInteractiveMode.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" NonInteractiveModeListName = \"{config.NonInteractiveModeListName}\"");
|
|
||||||
hocon.AppendLine($" NonInteractiveModePurchasedTab = {config.NonInteractiveModePurchasedTab.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# Performance Settings");
|
|
||||||
hocon.AppendLine("Performance {");
|
|
||||||
hocon.AppendLine($" Timeout = {(config.Timeout.HasValue ? config.Timeout.Value : -1)}");
|
|
||||||
hocon.AppendLine($" LimitDownloadRate = {config.LimitDownloadRate.ToString().ToLower()}");
|
|
||||||
hocon.AppendLine($" DownloadLimitInMbPerSec = {config.DownloadLimitInMbPerSec}");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
hocon.AppendLine("# Logging/Debug Settings");
|
|
||||||
hocon.AppendLine("Logging {");
|
|
||||||
hocon.AppendLine($" LoggingLevel = \"{config.LoggingLevel.ToString().ToLower()}\"");
|
|
||||||
hocon.AppendLine("}");
|
|
||||||
|
|
||||||
return hocon.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ValidateFileNameFormat(string? format, string settingName)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(format) &&
|
|
||||||
!format.Contains("{mediaId}", StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!format.Contains("{filename}", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"{settingName} is not unique enough. Please include either '{{mediaId}}' or '{{filename}}' to ensure files are not overwritten.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private VideoResolution ParseVideoResolution(string value)
|
|
||||||
{
|
|
||||||
if (value.Equals("source", StringComparison.OrdinalIgnoreCase))
|
|
||||||
return VideoResolution.source;
|
|
||||||
|
|
||||||
return Enum.Parse<VideoResolution>("_" + value, ignoreCase: true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CreateDefaultConfigFileAsync()
|
||||||
|
{
|
||||||
|
Config defaultConfig = new();
|
||||||
|
string hoconConfig = BuildHoconFromConfig(defaultConfig);
|
||||||
|
await File.WriteAllTextAsync("config.conf", hoconConfig);
|
||||||
|
Log.Information("Created default config.conf file");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildHoconFromConfig(Config config)
|
||||||
|
{
|
||||||
|
StringBuilder hocon = new();
|
||||||
|
|
||||||
|
hocon.AppendLine("# Auth");
|
||||||
|
hocon.AppendLine("Auth {");
|
||||||
|
hocon.AppendLine($" DisableBrowserAuth = {config.DisableBrowserAuth.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# External Tools");
|
||||||
|
hocon.AppendLine("External {");
|
||||||
|
hocon.AppendLine($" FFmpegPath = \"{config.FFmpegPath}\"");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# Download Settings");
|
||||||
|
hocon.AppendLine("Download {");
|
||||||
|
hocon.AppendLine(" Media {");
|
||||||
|
hocon.AppendLine($" DownloadAvatarHeaderPhoto = {config.DownloadAvatarHeaderPhoto.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadPaidPosts = {config.DownloadPaidPosts.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadPosts = {config.DownloadPosts.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadArchived = {config.DownloadArchived.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadStreams = {config.DownloadStreams.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadStories = {config.DownloadStories.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadHighlights = {config.DownloadHighlights.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadMessages = {config.DownloadMessages.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadPaidMessages = {config.DownloadPaidMessages.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadImages = {config.DownloadImages.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadVideos = {config.DownloadVideos.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadAudios = {config.DownloadAudios.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine(" }");
|
||||||
|
hocon.AppendLine($" IgnoreOwnMessages = {config.IgnoreOwnMessages.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadPostsIncrementally = {config.DownloadPostsIncrementally.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine(
|
||||||
|
$" BypassContentForCreatorsWhoNoLongerExist = {config.BypassContentForCreatorsWhoNoLongerExist.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadDuplicatedMedia = {config.DownloadDuplicatedMedia.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" SkipAds = {config.SkipAds.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadPath = \"{config.DownloadPath}\"");
|
||||||
|
hocon.AppendLine($" DownloadOnlySpecificDates = {config.DownloadOnlySpecificDates.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadDateSelection = \"{config.DownloadDateSelection.ToString().ToLower()}\"");
|
||||||
|
hocon.AppendLine($" CustomDate = \"{config.CustomDate?.ToString("yyyy-MM-dd")}\"");
|
||||||
|
hocon.AppendLine($" ShowScrapeSize = {config.ShowScrapeSize.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DisableTextSanitization = {config.DisableTextSanitization.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine(
|
||||||
|
$" DownloadVideoResolution = \"{(config.DownloadVideoResolution == VideoResolution.source ? "source" : config.DownloadVideoResolution.ToString().TrimStart('_'))}\"");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# File Settings");
|
||||||
|
hocon.AppendLine("File {");
|
||||||
|
hocon.AppendLine($" PaidPostFileNameFormat = \"{config.PaidPostFileNameFormat}\"");
|
||||||
|
hocon.AppendLine($" PostFileNameFormat = \"{config.PostFileNameFormat}\"");
|
||||||
|
hocon.AppendLine($" PaidMessageFileNameFormat = \"{config.PaidMessageFileNameFormat}\"");
|
||||||
|
hocon.AppendLine($" MessageFileNameFormat = \"{config.MessageFileNameFormat}\"");
|
||||||
|
hocon.AppendLine(
|
||||||
|
$" RenameExistingFilesWhenCustomFormatIsSelected = {config.RenameExistingFilesWhenCustomFormatIsSelected.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# Creator-Specific Configurations");
|
||||||
|
hocon.AppendLine("CreatorConfigs {");
|
||||||
|
foreach (KeyValuePair<string, CreatorConfig> creatorConfig in config.CreatorConfigs)
|
||||||
|
{
|
||||||
|
hocon.AppendLine($" \"{creatorConfig.Key}\" {{");
|
||||||
|
hocon.AppendLine($" PaidPostFileNameFormat = \"{creatorConfig.Value.PaidPostFileNameFormat}\"");
|
||||||
|
hocon.AppendLine($" PostFileNameFormat = \"{creatorConfig.Value.PostFileNameFormat}\"");
|
||||||
|
hocon.AppendLine($" PaidMessageFileNameFormat = \"{creatorConfig.Value.PaidMessageFileNameFormat}\"");
|
||||||
|
hocon.AppendLine($" MessageFileNameFormat = \"{creatorConfig.Value.MessageFileNameFormat}\"");
|
||||||
|
hocon.AppendLine(" }");
|
||||||
|
}
|
||||||
|
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# Folder Settings");
|
||||||
|
hocon.AppendLine("Folder {");
|
||||||
|
hocon.AppendLine($" FolderPerPaidPost = {config.FolderPerPaidPost.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" FolderPerPost = {config.FolderPerPost.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" FolderPerPaidMessage = {config.FolderPerPaidMessage.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" FolderPerMessage = {config.FolderPerMessage.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# Subscription Settings");
|
||||||
|
hocon.AppendLine("Subscriptions {");
|
||||||
|
hocon.AppendLine($" IncludeExpiredSubscriptions = {config.IncludeExpiredSubscriptions.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine(
|
||||||
|
$" IncludeRestrictedSubscriptions = {config.IncludeRestrictedSubscriptions.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" IgnoredUsersListName = \"{config.IgnoredUsersListName}\"");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# Interaction Settings");
|
||||||
|
hocon.AppendLine("Interaction {");
|
||||||
|
hocon.AppendLine($" NonInteractiveMode = {config.NonInteractiveMode.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" NonInteractiveModeListName = \"{config.NonInteractiveModeListName}\"");
|
||||||
|
hocon.AppendLine(
|
||||||
|
$" NonInteractiveModePurchasedTab = {config.NonInteractiveModePurchasedTab.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# Performance Settings");
|
||||||
|
hocon.AppendLine("Performance {");
|
||||||
|
hocon.AppendLine($" Timeout = {(config.Timeout.HasValue ? config.Timeout.Value : -1)}");
|
||||||
|
hocon.AppendLine($" LimitDownloadRate = {config.LimitDownloadRate.ToString().ToLower()}");
|
||||||
|
hocon.AppendLine($" DownloadLimitInMbPerSec = {config.DownloadLimitInMbPerSec}");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
hocon.AppendLine("# Logging/Debug Settings");
|
||||||
|
hocon.AppendLine("Logging {");
|
||||||
|
hocon.AppendLine($" LoggingLevel = \"{config.LoggingLevel.ToString().ToLower()}\"");
|
||||||
|
hocon.AppendLine("}");
|
||||||
|
|
||||||
|
return hocon.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateFileNameFormat(string? format, string settingName)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(format) &&
|
||||||
|
!format.Contains("{mediaId}", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!format.Contains("{filename}", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"{settingName} is not unique enough. Please include either '{{mediaId}}' or '{{filename}}' to ensure files are not overwritten.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private VideoResolution ParseVideoResolution(string value)
|
||||||
|
{
|
||||||
|
if (value.Equals("source", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return VideoResolution.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Enum.Parse<VideoResolution>("_" + value, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,38 +2,41 @@ using System.Text;
|
|||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public class DBService(IConfigService configService) : IDBService
|
||||||
{
|
{
|
||||||
public class DBService(IConfigService configService) : IDBService
|
public async Task CreateDB(string folder)
|
||||||
{
|
{
|
||||||
public async Task CreateDB(string folder)
|
try
|
||||||
{
|
{
|
||||||
try
|
if (!Directory.Exists(folder + "/Metadata"))
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(folder + "/Metadata"))
|
Directory.CreateDirectory(folder + "/Metadata");
|
||||||
{
|
}
|
||||||
Directory.CreateDirectory(folder + "/Metadata");
|
|
||||||
}
|
|
||||||
|
|
||||||
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}");
|
using SqliteConnection connection = new($"Data Source={dbFilePath}");
|
||||||
// open the connection
|
// open the connection
|
||||||
connection.Open();
|
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(
|
||||||
await cmd.ExecuteNonQueryAsync();
|
"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))
|
||||||
|
{
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
await EnsureCreatedAtColumnExists(connection, "medias");
|
await EnsureCreatedAtColumnExists(connection, "medias");
|
||||||
|
|
||||||
//
|
//
|
||||||
// Alter existing databases to create unique constraint on `medias`
|
// Alter existing databases to create unique constraint on `medias`
|
||||||
//
|
//
|
||||||
using (SqliteCommand cmd = new(@"
|
using (SqliteCommand cmd = new(@"
|
||||||
PRAGMA foreign_keys=off;
|
PRAGMA foreign_keys=off;
|
||||||
|
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
@ -66,408 +69,473 @@ namespace OF_DL.Services
|
|||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
PRAGMA foreign_keys=on;", connection))
|
PRAGMA foreign_keys=on;", connection))
|
||||||
{
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the 'messages' table
|
|
||||||
using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS messages (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, user_id INTEGER, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", connection))
|
|
||||||
{
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the 'posts' table
|
|
||||||
using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS posts (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", connection))
|
|
||||||
{
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the 'stories' table
|
|
||||||
using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS stories (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", connection))
|
|
||||||
{
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the 'others' table
|
|
||||||
using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS others (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", connection))
|
|
||||||
{
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the 'products' table
|
|
||||||
using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS products (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, title VARCHAR, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));", connection))
|
|
||||||
{
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the 'profiles' table
|
|
||||||
using (SqliteCommand cmd = new("CREATE TABLE IF NOT EXISTS profiles (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(username));", connection))
|
|
||||||
{
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.Close();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
await cmd.ExecuteNonQueryAsync();
|
||||||
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
}
|
||||||
if (ex.InnerException != null)
|
|
||||||
{
|
// create the 'messages' table
|
||||||
Console.WriteLine("\nInner Exception:");
|
using (SqliteCommand cmd =
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
new(
|
||||||
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
"CREATE TABLE IF NOT EXISTS messages (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, user_id INTEGER, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
|
||||||
}
|
connection))
|
||||||
|
{
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the 'posts' table
|
||||||
|
using (SqliteCommand cmd =
|
||||||
|
new(
|
||||||
|
"CREATE TABLE IF NOT EXISTS posts (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
|
||||||
|
connection))
|
||||||
|
{
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the 'stories' table
|
||||||
|
using (SqliteCommand cmd =
|
||||||
|
new(
|
||||||
|
"CREATE TABLE IF NOT EXISTS stories (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
|
||||||
|
connection))
|
||||||
|
{
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the 'others' table
|
||||||
|
using (SqliteCommand cmd =
|
||||||
|
new(
|
||||||
|
"CREATE TABLE IF NOT EXISTS others (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
|
||||||
|
connection))
|
||||||
|
{
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the 'products' table
|
||||||
|
using (SqliteCommand cmd =
|
||||||
|
new(
|
||||||
|
"CREATE TABLE IF NOT EXISTS products (id INTEGER NOT NULL, post_id INTEGER NOT NULL, text VARCHAR, price INTEGER, paid INTEGER, archived BOOLEAN, created_at TIMESTAMP, title VARCHAR, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(post_id));",
|
||||||
|
connection))
|
||||||
|
{
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the 'profiles' table
|
||||||
|
using (SqliteCommand cmd =
|
||||||
|
new(
|
||||||
|
"CREATE TABLE IF NOT EXISTS profiles (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, record_created_at TIMESTAMP, PRIMARY KEY(id), UNIQUE(username));",
|
||||||
|
connection))
|
||||||
|
{
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.Close();
|
||||||
|
}
|
||||||
|
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 CreateUsersDB(Dictionary<string, long> users)
|
public async Task CreateUsersDB(Dictionary<string, long> users)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
using SqliteConnection connection = new($"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 SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
await cmd.ExecuteNonQueryAsync();
|
||||||
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))
|
|
||||||
{
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Debug("Adding missing creators");
|
|
||||||
foreach (KeyValuePair<string, long> user in users)
|
|
||||||
{
|
|
||||||
using (SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection))
|
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.Close();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
Log.Debug("Adding missing creators");
|
||||||
|
foreach (KeyValuePair<string, long> user in users)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
using (SqliteCommand checkCmd = new("SELECT user_id, username FROM users WHERE user_id = @userId;",
|
||||||
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
connection))
|
||||||
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 CheckUsername(KeyValuePair<string, long> user, string path)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
|
||||||
|
|
||||||
connection.Open();
|
|
||||||
|
|
||||||
using (SqliteCommand checkCmd = new($"SELECT user_id, username FROM users WHERE user_id = @userId;", connection))
|
|
||||||
{
|
{
|
||||||
checkCmd.Parameters.AddWithValue("@userId", user.Value);
|
checkCmd.Parameters.AddWithValue("@userId", user.Value);
|
||||||
using (var reader = await checkCmd.ExecuteReaderAsync())
|
using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync())
|
||||||
{
|
{
|
||||||
if (reader.Read())
|
if (!reader.Read())
|
||||||
{
|
{
|
||||||
long storedUserId = reader.GetInt64(0);
|
using (SqliteCommand insertCmd =
|
||||||
string storedUsername = reader.GetString(1);
|
new("INSERT INTO users (user_id, username) VALUES (@userId, @username);",
|
||||||
|
connection))
|
||||||
if (storedUsername != user.Key)
|
|
||||||
{
|
{
|
||||||
using (SqliteCommand updateCmd = new($"UPDATE users SET username = @newUsername WHERE user_id = @userId;", connection))
|
insertCmd.Parameters.AddWithValue("@userId", user.Value);
|
||||||
{
|
insertCmd.Parameters.AddWithValue("@username", user.Key);
|
||||||
updateCmd.Parameters.AddWithValue("@newUsername", user.Key);
|
await insertCmd.ExecuteNonQueryAsync();
|
||||||
updateCmd.Parameters.AddWithValue("@userId", user.Value);
|
Log.Debug("Inserted new creator: " + user.Key);
|
||||||
await updateCmd.ExecuteNonQueryAsync();
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Debug("Creator " + user.Key + " already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string oldPath = path.Replace(path.Split("/")[^1], storedUsername);
|
connection.Close();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Directory.Exists(oldPath))
|
public async Task CheckUsername(KeyValuePair<string, long> user, string path)
|
||||||
{
|
{
|
||||||
Directory.Move(path.Replace(path.Split("/")[^1], storedUsername), path);
|
try
|
||||||
}
|
{
|
||||||
|
using SqliteConnection connection = new($"Data Source={Directory.GetCurrentDirectory()}/users.db");
|
||||||
|
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
using (SqliteCommand checkCmd = new("SELECT user_id, username FROM users WHERE user_id = @userId;",
|
||||||
|
connection))
|
||||||
|
{
|
||||||
|
checkCmd.Parameters.AddWithValue("@userId", user.Value);
|
||||||
|
using (SqliteDataReader reader = await checkCmd.ExecuteReaderAsync())
|
||||||
|
{
|
||||||
|
if (reader.Read())
|
||||||
|
{
|
||||||
|
long storedUserId = reader.GetInt64(0);
|
||||||
|
string storedUsername = reader.GetString(1);
|
||||||
|
|
||||||
|
if (storedUsername != user.Key)
|
||||||
|
{
|
||||||
|
using (SqliteCommand updateCmd =
|
||||||
|
new("UPDATE users SET username = @newUsername WHERE user_id = @userId;", connection))
|
||||||
|
{
|
||||||
|
updateCmd.Parameters.AddWithValue("@newUsername", user.Key);
|
||||||
|
updateCmd.Parameters.AddWithValue("@userId", user.Value);
|
||||||
|
await updateCmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
string oldPath = path.Replace(path.Split("/")[^1], storedUsername);
|
||||||
|
|
||||||
|
if (Directory.Exists(oldPath))
|
||||||
|
{
|
||||||
|
Directory.Move(path.Replace(path.Split("/")[^1], storedUsername), path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connection.Close();
|
connection.Close();
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
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
|
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)
|
||||||
{
|
{
|
||||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
Console.WriteLine("\nInner Exception:");
|
||||||
connection.Open();
|
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
|
||||||
await EnsureCreatedAtColumnExists(connection, "messages");
|
ex.InnerException.StackTrace);
|
||||||
using SqliteCommand cmd = new($"SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection);
|
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
|
||||||
cmd.Parameters.AddWithValue("@post_id", post_id);
|
ex.InnerException.StackTrace);
|
||||||
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
// If the record doesn't exist, insert a new one
|
|
||||||
using SqliteCommand insertCmd = new("INSERT INTO messages(post_id, text, price, paid, archived, created_at, user_id, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @user_id, @record_created_at)", connection);
|
|
||||||
insertCmd.Parameters.AddWithValue("@post_id", post_id);
|
|
||||||
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value);
|
|
||||||
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value);
|
|
||||||
insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
|
|
||||||
insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
|
|
||||||
insertCmd.Parameters.AddWithValue("@created_at", created_at);
|
|
||||||
insertCmd.Parameters.AddWithValue("@user_id", user_id);
|
|
||||||
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
|
||||||
await insertCmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 AddMessage(string folder, long post_id, string message_text, string price, bool is_paid,
|
||||||
public async Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at)
|
bool is_archived, DateTime created_at, long user_id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
|
||||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
|
||||||
connection.Open();
|
|
||||||
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);
|
|
||||||
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
// If the record doesn't exist, insert a new one
|
|
||||||
using SqliteCommand insertCmd = new("INSERT INTO posts(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)", connection);
|
|
||||||
insertCmd.Parameters.AddWithValue("@post_id", post_id);
|
|
||||||
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value);
|
|
||||||
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value);
|
|
||||||
insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
|
|
||||||
insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
|
|
||||||
insertCmd.Parameters.AddWithValue("@created_at", created_at);
|
|
||||||
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
|
||||||
await insertCmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 AddStory(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
|
||||||
connection.Open();
|
|
||||||
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);
|
|
||||||
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
// If the record doesn't exist, insert a new one
|
|
||||||
using SqliteCommand insertCmd = new("INSERT INTO stories(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)", connection);
|
|
||||||
insertCmd.Parameters.AddWithValue("@post_id", post_id);
|
|
||||||
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value);
|
|
||||||
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value);
|
|
||||||
insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
|
|
||||||
insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
|
|
||||||
insertCmd.Parameters.AddWithValue("@created_at", created_at);
|
|
||||||
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
|
||||||
await insertCmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 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)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
|
||||||
connection.Open();
|
|
||||||
await EnsureCreatedAtColumnExists(connection, "medias");
|
|
||||||
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
|
|
||||||
if (configService.CurrentConfig.DownloadDuplicatedMedia)
|
|
||||||
{
|
|
||||||
sql.Append(" and api_type=@api_type");
|
|
||||||
}
|
|
||||||
|
|
||||||
using SqliteCommand cmd = new(sql.ToString(), connection);
|
|
||||||
cmd.Parameters.AddWithValue("@media_id", media_id);
|
|
||||||
cmd.Parameters.AddWithValue("@api_type", api_type);
|
|
||||||
int count = Convert.ToInt32(cmd.ExecuteScalar());
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
// If the record doesn't exist, insert a new one
|
|
||||||
using SqliteCommand insertCmd = new($"INSERT INTO medias(media_id, post_id, link, directory, filename, size, api_type, media_type, preview, downloaded, created_at, record_created_at) VALUES({media_id}, {post_id}, '{link}', '{directory?.ToString() ?? "NULL"}', '{filename?.ToString() ?? "NULL"}', {size?.ToString() ?? "NULL"}, '{api_type}', '{media_type}', {Convert.ToInt32(preview)}, {Convert.ToInt32(downloaded)}, '{created_at?.ToString("yyyy-MM-dd HH:mm:ss")}', '{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}')", connection);
|
|
||||||
await insertCmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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<bool> CheckDownloaded(string folder, long media_id, string api_type)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool downloaded = false;
|
|
||||||
|
|
||||||
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(configService.CurrentConfig.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());
|
|
||||||
}
|
|
||||||
return downloaded;
|
|
||||||
}
|
|
||||||
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 false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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");
|
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
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);
|
||||||
|
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
// If the record doesn't exist, insert a new one
|
||||||
|
using SqliteCommand insertCmd =
|
||||||
|
new(
|
||||||
|
"INSERT INTO messages(post_id, text, price, paid, archived, created_at, user_id, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @user_id, @record_created_at)",
|
||||||
|
connection);
|
||||||
|
insertCmd.Parameters.AddWithValue("@post_id", post_id);
|
||||||
|
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value);
|
||||||
|
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value);
|
||||||
|
insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
|
||||||
|
insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
|
||||||
|
insertCmd.Parameters.AddWithValue("@created_at", created_at);
|
||||||
|
insertCmd.Parameters.AddWithValue("@user_id", user_id);
|
||||||
|
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
await insertCmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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");
|
public async Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid,
|
||||||
|
bool is_archived, DateTime created_at)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||||
|
connection.Open();
|
||||||
|
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);
|
||||||
|
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
// If the record doesn't exist, insert a new one
|
||||||
|
using SqliteCommand insertCmd =
|
||||||
|
new(
|
||||||
|
"INSERT INTO posts(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)",
|
||||||
|
connection);
|
||||||
|
insertCmd.Parameters.AddWithValue("@post_id", post_id);
|
||||||
|
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value);
|
||||||
|
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value);
|
||||||
|
insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
|
||||||
|
insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
|
||||||
|
insertCmd.Parameters.AddWithValue("@created_at", created_at);
|
||||||
|
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
await insertCmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 AddStory(string folder, long post_id, string message_text, string price, bool is_paid,
|
||||||
|
bool is_archived, DateTime created_at)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||||
|
connection.Open();
|
||||||
|
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);
|
||||||
|
int count = Convert.ToInt32(await cmd.ExecuteScalarAsync());
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
// If the record doesn't exist, insert a new one
|
||||||
|
using SqliteCommand insertCmd =
|
||||||
|
new(
|
||||||
|
"INSERT INTO stories(post_id, text, price, paid, archived, created_at, record_created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @record_created_at)",
|
||||||
|
connection);
|
||||||
|
insertCmd.Parameters.AddWithValue("@post_id", post_id);
|
||||||
|
insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value);
|
||||||
|
insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value);
|
||||||
|
insertCmd.Parameters.AddWithValue("@is_paid", is_paid);
|
||||||
|
insertCmd.Parameters.AddWithValue("@is_archived", is_archived);
|
||||||
|
insertCmd.Parameters.AddWithValue("@created_at", created_at);
|
||||||
|
insertCmd.Parameters.AddWithValue("@record_created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
await insertCmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 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)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db");
|
||||||
|
connection.Open();
|
||||||
|
await EnsureCreatedAtColumnExists(connection, "medias");
|
||||||
|
StringBuilder sql = new("SELECT COUNT(*) FROM medias WHERE media_id=@media_id");
|
||||||
if (configService.CurrentConfig.DownloadDuplicatedMedia)
|
if (configService.CurrentConfig.DownloadDuplicatedMedia)
|
||||||
{
|
{
|
||||||
sql.Append(" and api_type=@api_type");
|
sql.Append(" and api_type=@api_type");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new command object
|
using SqliteCommand cmd = new(sql.ToString(), connection);
|
||||||
using SqliteCommand command = new(sql.ToString(), connection);
|
cmd.Parameters.AddWithValue("@media_id", media_id);
|
||||||
// Add parameters to the command object
|
cmd.Parameters.AddWithValue("@api_type", api_type);
|
||||||
command.Parameters.AddWithValue("@directory", directory);
|
int count = Convert.ToInt32(cmd.ExecuteScalar());
|
||||||
command.Parameters.AddWithValue("@filename", filename);
|
if (count == 0)
|
||||||
command.Parameters.AddWithValue("@size", size);
|
{
|
||||||
command.Parameters.AddWithValue("@downloaded", downloaded ? 1 : 0);
|
// If the record doesn't exist, insert a new one
|
||||||
command.Parameters.AddWithValue("@created_at", created_at);
|
using SqliteCommand insertCmd = new(
|
||||||
command.Parameters.AddWithValue("@media_id", media_id);
|
$"INSERT INTO medias(media_id, post_id, link, directory, filename, size, api_type, media_type, preview, downloaded, created_at, record_created_at) VALUES({media_id}, {post_id}, '{link}', '{directory ?? "NULL"}', '{filename ?? "NULL"}', {size?.ToString() ?? "NULL"}, '{api_type}', '{media_type}', {Convert.ToInt32(preview)}, {Convert.ToInt32(downloaded)}, '{created_at?.ToString("yyyy-MM-dd HH:mm:ss")}', '{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}')",
|
||||||
command.Parameters.AddWithValue("@api_type", api_type);
|
connection);
|
||||||
|
await insertCmd.ExecuteNonQueryAsync();
|
||||||
// Execute the command
|
}
|
||||||
await command.ExecuteNonQueryAsync();
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
|
||||||
public async Task<long> GetStoredFileSize(string folder, long media_id, string api_type)
|
|
||||||
{
|
{
|
||||||
long size;
|
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<bool> CheckDownloaded(string folder, long media_id, string api_type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool downloaded = false;
|
||||||
|
|
||||||
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
||||||
{
|
{
|
||||||
|
StringBuilder sql = new("SELECT downloaded FROM medias WHERE media_id=@media_id");
|
||||||
|
if (configService.CurrentConfig.DownloadDuplicatedMedia)
|
||||||
|
{
|
||||||
|
sql.Append(" and api_type=@api_type");
|
||||||
|
}
|
||||||
|
|
||||||
connection.Open();
|
connection.Open();
|
||||||
using SqliteCommand cmd = new($"SELECT size FROM medias WHERE media_id=@media_id and api_type=@api_type", connection);
|
using SqliteCommand cmd = new(sql.ToString(), connection);
|
||||||
cmd.Parameters.AddWithValue("@media_id", media_id);
|
cmd.Parameters.AddWithValue("@media_id", media_id);
|
||||||
cmd.Parameters.AddWithValue("@api_type", api_type);
|
cmd.Parameters.AddWithValue("@api_type", api_type);
|
||||||
size = Convert.ToInt64(await cmd.ExecuteScalarAsync());
|
downloaded = Convert.ToBoolean(await cmd.ExecuteScalarAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
return downloaded;
|
||||||
|
}
|
||||||
|
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 size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DateTime?> GetMostRecentPostDate(string folder)
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Construct the update command
|
||||||
|
StringBuilder sql =
|
||||||
|
new(
|
||||||
|
"UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id");
|
||||||
|
if (configService.CurrentConfig.DownloadDuplicatedMedia)
|
||||||
{
|
{
|
||||||
DateTime? mostRecentDate = null;
|
sql.Append(" and api_type=@api_type");
|
||||||
using (SqliteConnection connection = new($"Data Source={folder}/Metadata/user_data.db"))
|
}
|
||||||
{
|
|
||||||
connection.Open();
|
// Create a new command object
|
||||||
using SqliteCommand cmd = new(@"
|
using SqliteCommand command = new(sql.ToString(), connection);
|
||||||
|
// Add parameters to the command object
|
||||||
|
command.Parameters.AddWithValue("@directory", directory);
|
||||||
|
command.Parameters.AddWithValue("@filename", filename);
|
||||||
|
command.Parameters.AddWithValue("@size", size);
|
||||||
|
command.Parameters.AddWithValue("@downloaded", downloaded ? 1 : 0);
|
||||||
|
command.Parameters.AddWithValue("@created_at", created_at);
|
||||||
|
command.Parameters.AddWithValue("@media_id", media_id);
|
||||||
|
command.Parameters.AddWithValue("@api_type", api_type);
|
||||||
|
|
||||||
|
// Execute the command
|
||||||
|
await command.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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(@"
|
||||||
SELECT
|
SELECT
|
||||||
MIN(created_at) AS created_at
|
MIN(created_at) AS created_at
|
||||||
FROM (
|
FROM (
|
||||||
@ -483,35 +551,36 @@ namespace OF_DL.Services
|
|||||||
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();
|
object? scalarValue = await cmd.ExecuteScalarAsync();
|
||||||
if(scalarValue != null && scalarValue != DBNull.Value)
|
if (scalarValue != null && scalarValue != DBNull.Value)
|
||||||
{
|
{
|
||||||
mostRecentDate = Convert.ToDateTime(scalarValue);
|
mostRecentDate = Convert.ToDateTime(scalarValue);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return mostRecentDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName)
|
return mostRecentDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnsureCreatedAtColumnExists(SqliteConnection connection, string tableName)
|
||||||
|
{
|
||||||
|
using SqliteCommand cmd = new($"PRAGMA table_info({tableName});", connection);
|
||||||
|
using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||||
|
bool columnExists = false;
|
||||||
|
|
||||||
|
while (await reader.ReadAsync())
|
||||||
{
|
{
|
||||||
using SqliteCommand cmd = new($"PRAGMA table_info({tableName});", connection);
|
if (reader["name"].ToString() == "record_created_at")
|
||||||
using var reader = await cmd.ExecuteReaderAsync();
|
|
||||||
bool columnExists = false;
|
|
||||||
|
|
||||||
while (await reader.ReadAsync())
|
|
||||||
{
|
{
|
||||||
if (reader["name"].ToString() == "record_created_at")
|
columnExists = true;
|
||||||
{
|
break;
|
||||||
columnExists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!columnExists)
|
if (!columnExists)
|
||||||
{
|
{
|
||||||
using SqliteCommand alterCmd = new($"ALTER TABLE {tableName} ADD COLUMN record_created_at TIMESTAMP;", connection);
|
using SqliteCommand alterCmd = new($"ALTER TABLE {tableName} ADD COLUMN record_created_at TIMESTAMP;",
|
||||||
await alterCmd.ExecuteNonQueryAsync();
|
connection);
|
||||||
}
|
await alterCmd.ExecuteNonQueryAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,181 +1,190 @@
|
|||||||
using HtmlAgilityPack;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HtmlAgilityPack;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public class FileNameService(IAuthService authService) : IFileNameService
|
||||||
{
|
{
|
||||||
public class FileNameService(IAuthService authService) : IFileNameService
|
public async Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3,
|
||||||
|
List<string> selectedProperties, string username, Dictionary<string, long> 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();
|
||||||
|
Type type2 = obj2.GetType();
|
||||||
|
PropertyInfo[] properties1 = type1.GetProperties();
|
||||||
|
PropertyInfo[] properties2 = type2.GetProperties();
|
||||||
|
|
||||||
|
foreach (string propertyName in selectedProperties)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> values = new();
|
if (propertyName.Contains("media"))
|
||||||
Type type1 = obj1.GetType();
|
|
||||||
Type type2 = obj2.GetType();
|
|
||||||
PropertyInfo[] properties1 = type1.GetProperties();
|
|
||||||
PropertyInfo[] properties2 = type2.GetProperties();
|
|
||||||
|
|
||||||
foreach (string propertyName in selectedProperties)
|
|
||||||
{
|
{
|
||||||
if (propertyName.Contains("media"))
|
object drmProperty = null;
|
||||||
|
object fileProperty = GetNestedPropertyValue(obj2, "files");
|
||||||
|
if (fileProperty != null)
|
||||||
{
|
{
|
||||||
object drmProperty = null;
|
drmProperty = GetNestedPropertyValue(obj2, "files.drm");
|
||||||
object fileProperty = GetNestedPropertyValue(obj2, "files");
|
}
|
||||||
if(fileProperty != null)
|
|
||||||
{
|
|
||||||
drmProperty = GetNestedPropertyValue(obj2, "files.drm");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fileProperty != null && drmProperty != null && propertyName == "mediaCreatedAt")
|
if (fileProperty != null && drmProperty != null && propertyName == "mediaCreatedAt")
|
||||||
|
{
|
||||||
|
object mpdurl = GetNestedPropertyValue(obj2, "files.drm.manifest.dash");
|
||||||
|
object policy = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontPolicy");
|
||||||
|
object signature = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontSignature");
|
||||||
|
object kvp = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontKeyPairId");
|
||||||
|
DateTime lastModified =
|
||||||
|
await DownloadService.GetDRMVideoLastModified(string.Join(",", mpdurl, policy, signature, kvp),
|
||||||
|
authService.CurrentAuth);
|
||||||
|
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fileProperty == null || drmProperty == null) && propertyName == "mediaCreatedAt")
|
||||||
|
{
|
||||||
|
object source = GetNestedPropertyValue(obj2, "files.full.url");
|
||||||
|
if (source != null)
|
||||||
{
|
{
|
||||||
object mpdurl = GetNestedPropertyValue(obj2, "files.drm.manifest.dash");
|
DateTime lastModified = await DownloadService.GetMediaLastModified(source.ToString());
|
||||||
object policy = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontPolicy");
|
|
||||||
object signature = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontSignature");
|
|
||||||
object kvp = GetNestedPropertyValue(obj2, "files.drm.signature.dash.CloudFrontKeyPairId");
|
|
||||||
DateTime lastModified = await DownloadService.GetDRMVideoLastModified(string.Join(",", mpdurl, policy, signature, kvp), authService.CurrentAuth);
|
|
||||||
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if((fileProperty == null || drmProperty == null) && propertyName == "mediaCreatedAt")
|
|
||||||
|
object preview = GetNestedPropertyValue(obj2, "preview");
|
||||||
|
if (preview != null)
|
||||||
{
|
{
|
||||||
object source = GetNestedPropertyValue(obj2, "files.full.url");
|
DateTime lastModified = await DownloadService.GetMediaLastModified(preview.ToString());
|
||||||
if(source != null)
|
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyInfo? property = Array.Find(properties2,
|
||||||
|
p => p.Name.Equals(propertyName.Replace("media", ""), StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
object? propertyValue = property.GetValue(obj2);
|
||||||
|
if (propertyValue != null)
|
||||||
|
{
|
||||||
|
if (propertyValue is DateTime dateTimeValue)
|
||||||
{
|
{
|
||||||
DateTime lastModified = await DownloadService.GetMediaLastModified(source.ToString());
|
values.Add(propertyName, dateTimeValue.ToString("yyyy-MM-dd"));
|
||||||
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
object preview = GetNestedPropertyValue(obj2, "preview");
|
values.Add(propertyName, propertyValue.ToString());
|
||||||
if(preview != null)
|
|
||||||
{
|
|
||||||
DateTime lastModified = await DownloadService.GetMediaLastModified(preview.ToString());
|
|
||||||
values.Add(propertyName, lastModified.ToString("yyyy-MM-dd"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
PropertyInfo? property = Array.Find(properties2, p => p.Name.Equals(propertyName.Replace("media", ""), StringComparison.OrdinalIgnoreCase));
|
|
||||||
if (property != null)
|
|
||||||
{
|
|
||||||
object? propertyValue = property.GetValue(obj2);
|
|
||||||
if (propertyValue != null)
|
|
||||||
{
|
|
||||||
if (propertyValue is DateTime dateTimeValue)
|
|
||||||
{
|
|
||||||
values.Add(propertyName, dateTimeValue.ToString("yyyy-MM-dd"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
values.Add(propertyName, propertyValue.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (propertyName.Contains("filename"))
|
}
|
||||||
|
else if (propertyName.Contains("filename"))
|
||||||
|
{
|
||||||
|
string sourcePropertyPath = "files.full.url";
|
||||||
|
object sourcePropertyValue = GetNestedPropertyValue(obj2, sourcePropertyPath);
|
||||||
|
if (sourcePropertyValue != null)
|
||||||
{
|
{
|
||||||
string sourcePropertyPath = "files.full.url";
|
Uri uri = new(sourcePropertyValue.ToString());
|
||||||
object sourcePropertyValue = GetNestedPropertyValue(obj2, sourcePropertyPath);
|
string filename = Path.GetFileName(uri.LocalPath);
|
||||||
if (sourcePropertyValue != null)
|
values.Add(propertyName, filename.Split(".")[0]);
|
||||||
{
|
|
||||||
Uri uri = new(sourcePropertyValue.ToString());
|
|
||||||
string filename = System.IO.Path.GetFileName(uri.LocalPath);
|
|
||||||
values.Add(propertyName, filename.Split(".")[0]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string propertyPath = "files.drm.manifest.dash";
|
|
||||||
object nestedPropertyValue = GetNestedPropertyValue(obj2, propertyPath);
|
|
||||||
if (nestedPropertyValue != null)
|
|
||||||
{
|
|
||||||
Uri uri = new(nestedPropertyValue.ToString());
|
|
||||||
string filename = System.IO.Path.GetFileName(uri.LocalPath);
|
|
||||||
values.Add(propertyName, filename.Split(".")[0] + "_source");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propertyName.Contains("username"))
|
|
||||||
{
|
|
||||||
if(!string.IsNullOrEmpty(username))
|
|
||||||
{
|
|
||||||
values.Add(propertyName, username);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string propertyPath = "id";
|
|
||||||
object nestedPropertyValue = GetNestedPropertyValue(obj3, propertyPath);
|
|
||||||
if (nestedPropertyValue != null)
|
|
||||||
{
|
|
||||||
values.Add(propertyName, users.FirstOrDefault(u => u.Value == Convert.ToInt32(nestedPropertyValue.ToString())).Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propertyName.Contains("text", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
PropertyInfo property = Array.Find(properties1, p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
|
|
||||||
if (property != null)
|
|
||||||
{
|
|
||||||
object propertyValue = property.GetValue(obj1);
|
|
||||||
if (propertyValue != null)
|
|
||||||
{
|
|
||||||
var pageDoc = new HtmlDocument();
|
|
||||||
pageDoc.LoadHtml(propertyValue.ToString());
|
|
||||||
var str = pageDoc.DocumentNode.InnerText;
|
|
||||||
if (str.Length > 100) // todo: add length limit to config
|
|
||||||
str = str.Substring(0, 100);
|
|
||||||
values.Add(propertyName, str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PropertyInfo property = Array.Find(properties1, p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
|
string propertyPath = "files.drm.manifest.dash";
|
||||||
if (property != null)
|
object nestedPropertyValue = GetNestedPropertyValue(obj2, propertyPath);
|
||||||
|
if (nestedPropertyValue != null)
|
||||||
{
|
{
|
||||||
object propertyValue = property.GetValue(obj1);
|
Uri uri = new(nestedPropertyValue.ToString());
|
||||||
if (propertyValue != null)
|
string filename = Path.GetFileName(uri.LocalPath);
|
||||||
|
values.Add(propertyName, filename.Split(".")[0] + "_source");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (propertyName.Contains("username"))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(username))
|
||||||
|
{
|
||||||
|
values.Add(propertyName, username);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string propertyPath = "id";
|
||||||
|
object nestedPropertyValue = GetNestedPropertyValue(obj3, propertyPath);
|
||||||
|
if (nestedPropertyValue != null)
|
||||||
|
{
|
||||||
|
values.Add(propertyName,
|
||||||
|
users.FirstOrDefault(u => u.Value == Convert.ToInt32(nestedPropertyValue.ToString())).Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (propertyName.Contains("text", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
PropertyInfo property = Array.Find(properties1,
|
||||||
|
p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
object propertyValue = property.GetValue(obj1);
|
||||||
|
if (propertyValue != null)
|
||||||
|
{
|
||||||
|
HtmlDocument pageDoc = new();
|
||||||
|
pageDoc.LoadHtml(propertyValue.ToString());
|
||||||
|
string str = pageDoc.DocumentNode.InnerText;
|
||||||
|
if (str.Length > 100) // todo: add length limit to config
|
||||||
{
|
{
|
||||||
if (propertyValue is DateTime dateTimeValue)
|
str = str.Substring(0, 100);
|
||||||
{
|
}
|
||||||
values.Add(propertyName, dateTimeValue.ToString("yyyy-MM-dd"));
|
|
||||||
}
|
values.Add(propertyName, str);
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
values.Add(propertyName, propertyValue.ToString());
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
PropertyInfo property = Array.Find(properties1,
|
||||||
|
p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
object propertyValue = property.GetValue(obj1);
|
||||||
|
if (propertyValue != null)
|
||||||
|
{
|
||||||
|
if (propertyValue is DateTime dateTimeValue)
|
||||||
|
{
|
||||||
|
values.Add(propertyName, dateTimeValue.ToString("yyyy-MM-dd"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
values.Add(propertyName, propertyValue.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
static object GetNestedPropertyValue(object source, string propertyPath)
|
|
||||||
{
|
|
||||||
object value = source;
|
|
||||||
foreach (var propertyName in propertyPath.Split('.'))
|
|
||||||
{
|
|
||||||
PropertyInfo property = value.GetType().GetProperty(propertyName) ?? throw new ArgumentException($"Property '{propertyName}' not found.");
|
|
||||||
value = property.GetValue(value);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values)
|
|
||||||
{
|
|
||||||
foreach (var kvp in values)
|
|
||||||
{
|
|
||||||
string placeholder = "{" + kvp.Key + "}";
|
|
||||||
fileFormat = fileFormat.Replace(placeholder, kvp.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return RemoveInvalidFileNameChars($"{fileFormat}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string RemoveInvalidFileNameChars(string fileName)
|
|
||||||
{
|
|
||||||
return string.IsNullOrEmpty(fileName) ? fileName : string.Concat(fileName.Split(Path.GetInvalidFileNameChars()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<string, string> kvp in values)
|
||||||
|
{
|
||||||
|
string placeholder = "{" + kvp.Key + "}";
|
||||||
|
fileFormat = fileFormat.Replace(placeholder, kvp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RemoveInvalidFileNameChars($"{fileFormat}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object GetNestedPropertyValue(object source, string propertyPath)
|
||||||
|
{
|
||||||
|
object value = source;
|
||||||
|
foreach (string propertyName in propertyPath.Split('.'))
|
||||||
|
{
|
||||||
|
PropertyInfo property = value.GetType().GetProperty(propertyName) ??
|
||||||
|
throw new ArgumentException($"Property '{propertyName}' not found.");
|
||||||
|
value = property.GetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RemoveInvalidFileNameChars(string fileName) => string.IsNullOrEmpty(fileName)
|
||||||
|
? fileName
|
||||||
|
: string.Concat(fileName.Split(Path.GetInvalidFileNameChars()));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,32 +8,36 @@ using OF_DL.Entities.Streams;
|
|||||||
using OF_DL.Enumerations;
|
using OF_DL.Enumerations;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public interface IAPIService
|
||||||
{
|
{
|
||||||
public interface IAPIService
|
Task<string> GetDecryptionKeyCDRMProject(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
||||||
{
|
Task<string> GetDecryptionKeyCDM(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
||||||
Task<string> GetDecryptionKeyCDRMProject(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
Task<DateTime> GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp);
|
||||||
Task<string> GetDecryptionKeyCDM(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp);
|
||||||
Task<DateTime> GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp);
|
Task<Dictionary<string, long>> GetLists(string endpoint);
|
||||||
Task<string> GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp);
|
Task<List<string>> GetListUsers(string endpoint);
|
||||||
Task<Dictionary<string, long>> GetLists(string endpoint);
|
|
||||||
Task<List<string>> GetListUsers(string endpoint);
|
Task<Dictionary<long, string>> GetMedia(MediaType mediatype, string endpoint, string? username, string folder,
|
||||||
Task<Dictionary<long, string>> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, List<long> paid_post_ids);
|
List<long> paid_post_ids);
|
||||||
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, List<long> paid_post_ids, StatusContext ctx);
|
|
||||||
Task<PostCollection> GetPosts(string endpoint, string folder, List<long> paid_post_ids, StatusContext ctx);
|
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, List<long> paid_post_ids,
|
||||||
Task<SinglePostCollection> GetPost(string endpoint, string folder);
|
StatusContext ctx);
|
||||||
Task<StreamsCollection> GetStreams(string endpoint, string folder, List<long> paid_post_ids, StatusContext ctx);
|
|
||||||
Task<ArchivedCollection> GetArchived(string endpoint, string folder, StatusContext ctx);
|
Task<PostCollection> GetPosts(string endpoint, string folder, List<long> paid_post_ids, StatusContext ctx);
|
||||||
Task<MessageCollection> GetMessages(string endpoint, string folder, StatusContext ctx);
|
Task<SinglePostCollection> GetPost(string endpoint, string folder);
|
||||||
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, StatusContext ctx);
|
Task<StreamsCollection> GetStreams(string endpoint, string folder, List<long> paid_post_ids, StatusContext ctx);
|
||||||
Task<SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder);
|
Task<ArchivedCollection> GetArchived(string endpoint, string folder, StatusContext ctx);
|
||||||
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users);
|
Task<MessageCollection> GetMessages(string endpoint, string folder, StatusContext ctx);
|
||||||
Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, Dictionary<string, long> users);
|
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, StatusContext ctx);
|
||||||
Task<User> GetUserInfo(string endpoint);
|
Task<SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder);
|
||||||
Task<JObject> GetUserInfoById(string endpoint);
|
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users);
|
||||||
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);
|
Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, Dictionary<string, long> users);
|
||||||
Task<Dictionary<string, long>> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
Task<User> GetUserInfo(string endpoint);
|
||||||
Task<Dictionary<string, long>> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
Task<JObject> GetUserInfoById(string endpoint);
|
||||||
Task<string> GetDecryptionKeyOFDL(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);
|
||||||
}
|
Task<Dictionary<string, long>> GetActiveSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
||||||
|
Task<Dictionary<string, long>> GetExpiredSubscriptions(string endpoint, bool includeRestrictedSubscriptions);
|
||||||
|
Task<string> GetDecryptionKeyOFDL(Dictionary<string, string> drmHeaders, string licenceURL, string pssh);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
using OF_DL.Entities;
|
using OF_DL.Entities;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public interface IAuthService
|
||||||
{
|
{
|
||||||
public interface IAuthService
|
Auth? CurrentAuth { get; set; }
|
||||||
{
|
Task<bool> LoadFromFileAsync(string filePath = "auth.json");
|
||||||
Auth? CurrentAuth { get; set; }
|
Task<bool> LoadFromBrowserAsync();
|
||||||
Task<bool> LoadFromFileAsync(string filePath = "auth.json");
|
Task SaveToFileAsync(string filePath = "auth.json");
|
||||||
Task<bool> LoadFromBrowserAsync();
|
|
||||||
Task SaveToFileAsync(string filePath = "auth.json");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
using OF_DL.Entities;
|
using OF_DL.Entities;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public interface IConfigService
|
||||||
{
|
{
|
||||||
public interface IConfigService
|
Config CurrentConfig { get; }
|
||||||
{
|
bool IsCliNonInteractive { get; }
|
||||||
Config CurrentConfig { get; }
|
Task<bool> LoadConfigurationAsync(string[] args);
|
||||||
bool IsCliNonInteractive { get; }
|
Task SaveConfigurationAsync(string filePath = "config.conf");
|
||||||
Task<bool> LoadConfigurationAsync(string[] args);
|
void UpdateConfig(Config newConfig);
|
||||||
Task SaveConfigurationAsync(string filePath = "config.conf");
|
|
||||||
void UpdateConfig(Config newConfig);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,27 @@
|
|||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public interface IDBService
|
||||||
{
|
{
|
||||||
public interface IDBService
|
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 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 AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived,
|
||||||
Task AddStory(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at);
|
DateTime created_at);
|
||||||
Task CreateDB(string folder);
|
|
||||||
Task CreateUsersDB(Dictionary<string, long> users);
|
Task AddStory(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived,
|
||||||
Task CheckUsername(KeyValuePair<string, long> user, string path);
|
DateTime created_at);
|
||||||
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 CreateDB(string folder);
|
||||||
Task<long> GetStoredFileSize(string folder, long media_id, string api_type);
|
Task CreateUsersDB(Dictionary<string, long> users);
|
||||||
Task<bool> CheckDownloaded(string folder, long media_id, string api_type);
|
Task CheckUsername(KeyValuePair<string, long> user, string path);
|
||||||
Task<DateTime?> GetMostRecentPostDate(string folder);
|
|
||||||
}
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,44 +7,128 @@ using OF_DL.Entities.Streams;
|
|||||||
using static OF_DL.Entities.Messages.Messages;
|
using static OF_DL.Entities.Messages.Messages;
|
||||||
using FromUser = OF_DL.Entities.Messages.FromUser;
|
using FromUser = OF_DL.Entities.Messages.FromUser;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public interface IDownloadService
|
||||||
{
|
{
|
||||||
public interface IDownloadService
|
Task<long> CalculateTotalFileSize(List<string> urls);
|
||||||
{
|
|
||||||
Task<long> CalculateTotalFileSize(List<string> urls);
|
Task<bool> ProcessMediaDownload(string folder, long media_id, string api_type, string url, string path,
|
||||||
Task<bool> ProcessMediaDownload(string folder, long media_id, string api_type, string url, string path, string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter);
|
string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter);
|
||||||
Task<bool> DownloadArchivedMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, 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, IProgressReporter progressReporter, string? filenameFormat, Archived.List? postInfo, Archived.Medium? postMedia, Archived.Author? author, Dictionary<string, long> users);
|
Task<bool> DownloadArchivedMedia(string url, string folder, long media_id, string api_type,
|
||||||
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author, Dictionary<string, long> users);
|
IProgressReporter progressReporter, string? filenameFormat, Archived.List? messageInfo,
|
||||||
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, long> users);
|
Archived.Medium? messageMedia, Archived.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, IProgressReporter progressReporter, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary<string, long> users);
|
Task<bool> DownloadArchivedPostDRMVideo(string policy, string signature, string kvp, string url,
|
||||||
Task<bool> DownloadMessageMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Messages.List? messageInfo, Messages.Medium? messageMedia, Messages.FromUser? fromUser, Dictionary<string, long> users);
|
string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type,
|
||||||
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author, Dictionary<string, long> users);
|
IProgressReporter progressReporter, string? filenameFormat, Archived.List? postInfo, Archived.Medium? postMedia,
|
||||||
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, SinglePost? postInfo, SinglePost.Medium? postMedia, SinglePost.Author? author, Dictionary<string, long> users);
|
Archived.Author? author, Dictionary<string, long> users);
|
||||||
Task<bool> DownloadPurchasedMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, 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, IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users);
|
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey,
|
||||||
Task<bool> DownloadPurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? messageInfo, Medium? messageMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users);
|
string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter,
|
||||||
Task<bool> DownloadSinglePurchasedMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia, Entities.Messages.FromUser? fromUser, Dictionary<string, long> users);
|
string filenameFormat, SinglePost postInfo, SinglePost.Medium postMedia, SinglePost.Author author,
|
||||||
Task<bool> DownloadPurchasedPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Purchased.List? postInfo, Medium? postMedia, Purchased.FromUser? fromUser, Dictionary<string, long> users);
|
Dictionary<string, long> users);
|
||||||
Task<bool> DownloadPurchasedPostMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, 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, IProgressReporter progressReporter);
|
Task<bool> DownloadPostDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey,
|
||||||
Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users);
|
string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter,
|
||||||
Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users);
|
string? filenameFormat, Post.List? postInfo, Post.Medium? postMedia, Post.Author? author,
|
||||||
Task<bool> DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url,
|
Dictionary<string, long> users);
|
||||||
string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type,
|
|
||||||
IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username);
|
||||||
FromUser? fromUser, Dictionary<string, long> users);
|
|
||||||
Task<bool> DownloadMessagePreviewMedia(string url, string folder, long media_id, string api_type,
|
Task<bool> DownloadMessageDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey,
|
||||||
IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter,
|
||||||
FromUser? fromUser, Dictionary<string, long> users);
|
string? filenameFormat, List? messageInfo, Medium? messageMedia, Messages.FromUser? fromUser,
|
||||||
Task<DownloadResult> DownloadHighlights(string username, long userId, string path, HashSet<long> paidPostIds, IProgressReporter progressReporter);
|
Dictionary<string, long> users);
|
||||||
Task<DownloadResult> DownloadStories(string username, long userId, string path, HashSet<long> paidPostIds, IProgressReporter progressReporter);
|
|
||||||
Task<DownloadResult> DownloadArchived(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, ArchivedCollection archived, IProgressReporter progressReporter);
|
Task<bool> DownloadMessageMedia(string url, string folder, long media_id, string api_type,
|
||||||
Task<DownloadResult> DownloadMessages(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, MessageCollection messages, IProgressReporter progressReporter);
|
IProgressReporter progressReporter, string? filenameFormat, List? messageInfo, Medium? messageMedia,
|
||||||
Task<DownloadResult> DownloadPaidMessages(string username, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter);
|
Messages.FromUser? fromUser, Dictionary<string, long> users);
|
||||||
Task<DownloadResult> DownloadStreams(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamsCollection streams, IProgressReporter progressReporter);
|
|
||||||
Task<DownloadResult> DownloadFreePosts(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostCollection posts, IProgressReporter progressReporter);
|
Task<bool> DownloadPostMedia(string url, string folder, long media_id, string api_type,
|
||||||
Task<DownloadResult> DownloadPaidPosts(string username, long userId, string path, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidPostCollection purchasedPosts, IProgressReporter progressReporter);
|
IProgressReporter progressReporter, 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,
|
||||||
|
IProgressReporter progressReporter, 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,
|
||||||
|
IProgressReporter progressReporter, 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,
|
||||||
|
IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
||||||
|
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,
|
||||||
|
IProgressReporter progressReporter, 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,
|
||||||
|
IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
||||||
|
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,
|
||||||
|
IProgressReporter progressReporter, 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,
|
||||||
|
IProgressReporter progressReporter, 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,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type,
|
||||||
|
IProgressReporter progressReporter, 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, IProgressReporter progressReporter,
|
||||||
|
string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author,
|
||||||
|
Dictionary<string, long> users);
|
||||||
|
|
||||||
|
Task<bool> DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url,
|
||||||
|
string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type,
|
||||||
|
IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
||||||
|
FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
|
||||||
|
Task<bool> DownloadMessagePreviewMedia(string url, string folder, long media_id, string api_type,
|
||||||
|
IProgressReporter progressReporter, string? filenameFormat, SingleMessage? messageInfo, Medium? messageMedia,
|
||||||
|
FromUser? fromUser, Dictionary<string, long> users);
|
||||||
|
|
||||||
|
Task<DownloadResult> DownloadHighlights(string username, long userId, string path, HashSet<long> paidPostIds,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
Task<DownloadResult> DownloadStories(string username, long userId, string path, HashSet<long> paidPostIds,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
Task<DownloadResult> DownloadArchived(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, ArchivedCollection archived,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
Task<DownloadResult> DownloadMessages(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, MessageCollection messages,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
Task<DownloadResult> DownloadPaidMessages(string username, string path, Dictionary<string, long> users,
|
||||||
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidMessageCollection paidMessageCollection,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
Task<DownloadResult> DownloadStreams(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamsCollection streams,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
Task<DownloadResult> DownloadFreePosts(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostCollection posts,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
|
|
||||||
|
Task<DownloadResult> DownloadPaidPosts(string username, long userId, string path, Dictionary<string, long> users,
|
||||||
|
bool clientIdBlobMissing, bool devicePrivateKeyMissing, PaidPostCollection purchasedPosts,
|
||||||
|
IProgressReporter progressReporter);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public interface IFileNameService
|
||||||
{
|
{
|
||||||
public interface IFileNameService
|
Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values);
|
||||||
{
|
|
||||||
Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values);
|
Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties,
|
||||||
Task<Dictionary<string, string>> GetFilename(object obj1, object obj2, object obj3, List<string> selectedProperties, string username, Dictionary<string, long> users = null);
|
string username, Dictionary<string, long> users = null);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
using OF_DL.Enumerations;
|
using OF_DL.Enumerations;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public interface ILoggingService
|
||||||
{
|
{
|
||||||
public interface ILoggingService
|
LoggingLevelSwitch LevelSwitch { get; }
|
||||||
{
|
void UpdateLoggingLevel(LoggingLevel newLevel);
|
||||||
LoggingLevelSwitch LevelSwitch { get; }
|
LoggingLevel GetCurrentLoggingLevel();
|
||||||
void UpdateLoggingLevel(LoggingLevel newLevel);
|
|
||||||
LoggingLevel GetCurrentLoggingLevel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
namespace OF_DL.Services;
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for reporting download progress in a UI-agnostic way.
|
/// Interface for reporting download progress in a UI-agnostic way.
|
||||||
/// This allows the download service to report progress without being coupled to any specific UI framework.
|
/// This allows the download service to report progress without being coupled to any specific UI framework.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IProgressReporter
|
public interface IProgressReporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports progress increment. The value represents either bytes downloaded or file count depending on configuration.
|
/// Reports progress increment. The value represents either bytes downloaded or file count depending on configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="increment">The amount to increment progress by</param>
|
/// <param name="increment">The amount to increment progress by</param>
|
||||||
void ReportProgress(long increment);
|
void ReportProgress(long increment);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports a status message (optional for implementations).
|
/// Reports a status message (optional for implementations).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The status message to report</param>
|
/// <param name="message">The status message to report</param>
|
||||||
void ReportStatus(string message);
|
void ReportStatus(string message);
|
||||||
|
|||||||
@ -3,40 +3,36 @@ using Serilog;
|
|||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace OF_DL.Services
|
namespace OF_DL.Services;
|
||||||
|
|
||||||
|
public class LoggingService : ILoggingService
|
||||||
{
|
{
|
||||||
public class LoggingService : ILoggingService
|
public LoggingService()
|
||||||
{
|
{
|
||||||
public LoggingLevelSwitch LevelSwitch { get; }
|
LevelSwitch = new LoggingLevelSwitch();
|
||||||
|
InitializeLogger();
|
||||||
|
}
|
||||||
|
|
||||||
public LoggingService()
|
public LoggingLevelSwitch LevelSwitch { get; }
|
||||||
{
|
|
||||||
LevelSwitch = new LoggingLevelSwitch();
|
|
||||||
InitializeLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeLogger()
|
public void UpdateLoggingLevel(LoggingLevel newLevel)
|
||||||
{
|
{
|
||||||
// Set the initial level to Error (until we've read from config)
|
LevelSwitch.MinimumLevel = (LogEventLevel)newLevel;
|
||||||
LevelSwitch.MinimumLevel = LogEventLevel.Error;
|
Log.Debug("Logging level updated to: {LoggingLevel}", newLevel);
|
||||||
|
}
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
public LoggingLevel GetCurrentLoggingLevel() => (LoggingLevel)LevelSwitch.MinimumLevel;
|
||||||
.MinimumLevel.ControlledBy(LevelSwitch)
|
|
||||||
.WriteTo.File("logs/OFDL.txt", rollingInterval: RollingInterval.Day)
|
|
||||||
.CreateLogger();
|
|
||||||
|
|
||||||
Log.Debug("Logging service initialized");
|
private void InitializeLogger()
|
||||||
}
|
{
|
||||||
|
// Set the initial level to Error (until we've read from config)
|
||||||
|
LevelSwitch.MinimumLevel = LogEventLevel.Error;
|
||||||
|
|
||||||
public void UpdateLoggingLevel(LoggingLevel newLevel)
|
Log.Logger = new LoggerConfiguration()
|
||||||
{
|
.MinimumLevel.ControlledBy(LevelSwitch)
|
||||||
LevelSwitch.MinimumLevel = (LogEventLevel)newLevel;
|
.WriteTo.File("logs/OFDL.txt", rollingInterval: RollingInterval.Day)
|
||||||
Log.Debug("Logging level updated to: {LoggingLevel}", newLevel);
|
.CreateLogger();
|
||||||
}
|
|
||||||
|
|
||||||
public LoggingLevel GetCurrentLoggingLevel()
|
Log.Debug("Logging service initialized");
|
||||||
{
|
|
||||||
return (LoggingLevel)LevelSwitch.MinimumLevel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,146 +1,141 @@
|
|||||||
using OF_DL.Helpers;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using OF_DL.Helpers;
|
||||||
|
|
||||||
namespace OF_DL.Utils
|
namespace OF_DL.Utils;
|
||||||
|
|
||||||
|
internal class HttpUtil
|
||||||
{
|
{
|
||||||
class HttpUtil
|
public static HttpClient Client { get; set; } = new(new HttpClientHandler
|
||||||
{
|
{
|
||||||
public static HttpClient Client { get; set; } = new HttpClient(new HttpClientHandler
|
AllowAutoRedirect = true
|
||||||
|
//Proxy = null
|
||||||
|
});
|
||||||
|
|
||||||
|
public static async Task<byte[]> PostData(string URL, Dictionary<string, string> headers, string postData)
|
||||||
|
{
|
||||||
|
string mediaType = postData.StartsWith("{") ? "application/json" : "application/x-www-form-urlencoded";
|
||||||
|
HttpResponseMessage response = await PerformOperation(async () =>
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = true,
|
StringContent content = new(postData, Encoding.UTF8, mediaType);
|
||||||
//Proxy = null
|
//ByteArrayContent content = new ByteArrayContent(postData);
|
||||||
|
|
||||||
|
return await Post(URL, headers, content);
|
||||||
});
|
});
|
||||||
|
|
||||||
public static async Task<byte[]> PostData(string URL, Dictionary<string, string> headers, string postData)
|
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<byte[]> PostData(string URL, Dictionary<string, string> headers, byte[] postData)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await PerformOperation(async () =>
|
||||||
{
|
{
|
||||||
var mediaType = postData.StartsWith("{") ? "application/json" : "application/x-www-form-urlencoded";
|
ByteArrayContent content = new(postData);
|
||||||
var response = await PerformOperation(async () =>
|
|
||||||
|
return await Post(URL, headers, content);
|
||||||
|
});
|
||||||
|
|
||||||
|
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<byte[]> PostData(string URL, Dictionary<string, string> headers,
|
||||||
|
Dictionary<string, string> postData)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await PerformOperation(async () =>
|
||||||
|
{
|
||||||
|
FormUrlEncodedContent content = new(postData);
|
||||||
|
|
||||||
|
return await Post(URL, headers, content);
|
||||||
|
});
|
||||||
|
|
||||||
|
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> GetWebSource(string URL, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await PerformOperation(async () => { return await Get(URL, headers); });
|
||||||
|
|
||||||
|
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
return Encoding.UTF8.GetString(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<byte[]> GetBinary(string URL, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await PerformOperation(async () => { return await Get(URL, headers); });
|
||||||
|
|
||||||
|
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetString(byte[] bytes) => Encoding.UTF8.GetString(bytes);
|
||||||
|
|
||||||
|
private static async Task<HttpResponseMessage> Get(string URL, Dictionary<string, string> headers = null)
|
||||||
|
{
|
||||||
|
HttpRequestMessage request = new() { RequestUri = new Uri(URL), Method = HttpMethod.Get };
|
||||||
|
|
||||||
|
if (headers != null)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<string, string> header in headers)
|
||||||
{
|
{
|
||||||
StringContent content = new StringContent(postData, Encoding.UTF8, mediaType);
|
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||||
//ByteArrayContent content = new ByteArrayContent(postData);
|
}
|
||||||
|
|
||||||
return await Post(URL, headers, content);
|
|
||||||
});
|
|
||||||
|
|
||||||
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
|
||||||
return bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<byte[]> PostData(string URL, Dictionary<string, string> headers, byte[] postData)
|
return await Send(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<HttpResponseMessage> Post(string URL, Dictionary<string, string> headers,
|
||||||
|
HttpContent content)
|
||||||
|
{
|
||||||
|
HttpRequestMessage request = new() { RequestUri = new Uri(URL), Method = HttpMethod.Post, Content = content };
|
||||||
|
|
||||||
|
if (headers != null)
|
||||||
{
|
{
|
||||||
var response = await PerformOperation(async () =>
|
foreach (KeyValuePair<string, string> header in headers)
|
||||||
{
|
{
|
||||||
ByteArrayContent content = new ByteArrayContent(postData);
|
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||||
|
}
|
||||||
return await Post(URL, headers, content);
|
|
||||||
});
|
|
||||||
|
|
||||||
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
|
||||||
return bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<byte[]> PostData(string URL, Dictionary<string, string> headers, Dictionary<string, string> postData)
|
return await Send(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<HttpResponseMessage> Send(HttpRequestMessage request) => await Client.SendAsync(request);
|
||||||
|
|
||||||
|
private static async Task<HttpResponseMessage> PerformOperation(Func<Task<HttpResponseMessage>> operation)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await operation();
|
||||||
|
|
||||||
|
int retryCount = 0;
|
||||||
|
|
||||||
|
while (retryCount < Constants.WIDEVINE_MAX_RETRIES && response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||||
{
|
{
|
||||||
var response = await PerformOperation(async () =>
|
//
|
||||||
|
// We've hit a rate limit, so we should wait before retrying.
|
||||||
|
//
|
||||||
|
int retryAfterSeconds =
|
||||||
|
Constants.WIDEVINE_RETRY_DELAY * (retryCount + 1); // Default retry time. Increases with each retry.
|
||||||
|
if (response.Headers.RetryAfter != null && response.Headers.RetryAfter.Delta.HasValue)
|
||||||
{
|
{
|
||||||
FormUrlEncodedContent content = new FormUrlEncodedContent(postData);
|
if (response.Headers.RetryAfter.Delta.Value.TotalSeconds > 0)
|
||||||
|
|
||||||
return await Post(URL, headers, content);
|
|
||||||
});
|
|
||||||
|
|
||||||
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<string> GetWebSource(string URL, Dictionary<string, string> headers = null)
|
|
||||||
{
|
|
||||||
var response = await PerformOperation(async () =>
|
|
||||||
{
|
|
||||||
return await Get(URL, headers);
|
|
||||||
});
|
|
||||||
|
|
||||||
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
|
||||||
return Encoding.UTF8.GetString(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<byte[]> GetBinary(string URL, Dictionary<string, string> headers = null)
|
|
||||||
{
|
|
||||||
var response = await PerformOperation(async () =>
|
|
||||||
{
|
|
||||||
return await Get(URL, headers);
|
|
||||||
});
|
|
||||||
|
|
||||||
byte[] bytes = await response.Content.ReadAsByteArrayAsync();
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
public static string GetString(byte[] bytes)
|
|
||||||
{
|
|
||||||
return Encoding.UTF8.GetString(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<HttpResponseMessage> Get(string URL, Dictionary<string, string> headers = null)
|
|
||||||
{
|
|
||||||
HttpRequestMessage request = new HttpRequestMessage()
|
|
||||||
{
|
|
||||||
RequestUri = new Uri(URL),
|
|
||||||
Method = HttpMethod.Get
|
|
||||||
};
|
|
||||||
|
|
||||||
if (headers != null)
|
|
||||||
foreach (KeyValuePair<string, string> header in headers)
|
|
||||||
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
|
||||||
|
|
||||||
return await Send(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<HttpResponseMessage> Post(string URL, Dictionary<string, string> headers, HttpContent content)
|
|
||||||
{
|
|
||||||
HttpRequestMessage request = new HttpRequestMessage()
|
|
||||||
{
|
|
||||||
RequestUri = new Uri(URL),
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
Content = content
|
|
||||||
};
|
|
||||||
|
|
||||||
if (headers != null)
|
|
||||||
foreach (KeyValuePair<string, string> header in headers)
|
|
||||||
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
|
||||||
|
|
||||||
return await Send(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<HttpResponseMessage> Send(HttpRequestMessage request)
|
|
||||||
{
|
|
||||||
return await Client.SendAsync(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<HttpResponseMessage> PerformOperation(Func<Task<HttpResponseMessage>> operation)
|
|
||||||
{
|
|
||||||
var response = await operation();
|
|
||||||
|
|
||||||
var retryCount = 0;
|
|
||||||
|
|
||||||
while (retryCount < Constants.WIDEVINE_MAX_RETRIES && response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// We've hit a rate limit, so we should wait before retrying.
|
|
||||||
//
|
|
||||||
var retryAfterSeconds = Constants.WIDEVINE_RETRY_DELAY * (retryCount + 1); // Default retry time. Increases with each retry.
|
|
||||||
if (response.Headers.RetryAfter != null && response.Headers.RetryAfter.Delta.HasValue)
|
|
||||||
{
|
{
|
||||||
if (response.Headers.RetryAfter.Delta.Value.TotalSeconds > 0)
|
retryAfterSeconds =
|
||||||
retryAfterSeconds = (int)response.Headers.RetryAfter.Delta.Value.TotalSeconds + 1; // Add 1 second to ensure we wait a bit longer than the suggested time
|
(int)response.Headers.RetryAfter.Delta.Value.TotalSeconds +
|
||||||
|
1; // Add 1 second to ensure we wait a bit longer than the suggested time
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(retryAfterSeconds * 1000); // Peform the delay
|
|
||||||
|
|
||||||
response = await operation();
|
|
||||||
retryCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode(); // Throw an exception if the response is not successful
|
await Task.Delay(retryAfterSeconds * 1000); // Peform the delay
|
||||||
|
|
||||||
return response;
|
response = await operation();
|
||||||
|
retryCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode(); // Throw an exception if the response is not successful
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,15 +2,14 @@ using System.Reactive.Concurrency;
|
|||||||
|
|
||||||
namespace OF_DL.Utils;
|
namespace OF_DL.Utils;
|
||||||
|
|
||||||
|
|
||||||
public class ThrottledStream : Stream
|
public class ThrottledStream : Stream
|
||||||
|
|
||||||
{
|
{
|
||||||
private readonly Stream parent;
|
|
||||||
private readonly int maxBytesPerSecond;
|
private readonly int maxBytesPerSecond;
|
||||||
|
private readonly Stream parent;
|
||||||
private readonly IScheduler scheduler;
|
private readonly IScheduler scheduler;
|
||||||
private readonly IStopwatch stopwatch;
|
|
||||||
private readonly bool shouldThrottle;
|
private readonly bool shouldThrottle;
|
||||||
|
private readonly IStopwatch stopwatch;
|
||||||
|
|
||||||
private long processed;
|
private long processed;
|
||||||
|
|
||||||
@ -30,16 +29,39 @@ public class ThrottledStream : Stream
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override bool CanRead => parent.CanRead;
|
||||||
|
|
||||||
|
|
||||||
|
public override bool CanSeek => parent.CanSeek;
|
||||||
|
|
||||||
|
|
||||||
|
public override bool CanWrite => parent.CanWrite;
|
||||||
|
|
||||||
|
|
||||||
|
public override long Length => parent.Length;
|
||||||
|
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => parent.Position;
|
||||||
|
set => parent.Position = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void Throttle(int bytes)
|
protected void Throttle(int bytes)
|
||||||
{
|
{
|
||||||
if (!shouldThrottle) return;
|
if (!shouldThrottle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
processed += bytes;
|
processed += bytes;
|
||||||
var targetTime = TimeSpan.FromSeconds((double)processed / maxBytesPerSecond);
|
TimeSpan targetTime = TimeSpan.FromSeconds((double)processed / maxBytesPerSecond);
|
||||||
var actualTime = stopwatch.Elapsed;
|
TimeSpan actualTime = stopwatch.Elapsed;
|
||||||
var sleep = targetTime - actualTime;
|
TimeSpan sleep = targetTime - actualTime;
|
||||||
if (sleep > TimeSpan.Zero)
|
if (sleep > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
using var waitHandle = new AutoResetEvent(initialState: false);
|
using AutoResetEvent waitHandle = new(false);
|
||||||
scheduler.Sleep(sleep).GetAwaiter().OnCompleted(() => waitHandle.Set());
|
scheduler.Sleep(sleep).GetAwaiter().OnCompleted(() => waitHandle.Set());
|
||||||
waitHandle.WaitOne();
|
waitHandle.WaitOne();
|
||||||
}
|
}
|
||||||
@ -47,11 +69,15 @@ public class ThrottledStream : Stream
|
|||||||
|
|
||||||
protected async Task ThrottleAsync(int bytes)
|
protected async Task ThrottleAsync(int bytes)
|
||||||
{
|
{
|
||||||
if (!shouldThrottle) return;
|
if (!shouldThrottle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
processed += bytes;
|
processed += bytes;
|
||||||
var targetTime = TimeSpan.FromSeconds((double)processed / maxBytesPerSecond);
|
TimeSpan targetTime = TimeSpan.FromSeconds((double)processed / maxBytesPerSecond);
|
||||||
var actualTime = stopwatch.Elapsed;
|
TimeSpan actualTime = stopwatch.Elapsed;
|
||||||
var sleep = targetTime - actualTime;
|
TimeSpan sleep = targetTime - actualTime;
|
||||||
|
|
||||||
if (sleep > TimeSpan.Zero)
|
if (sleep > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
@ -61,7 +87,7 @@ public class ThrottledStream : Stream
|
|||||||
|
|
||||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var read = await parent.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
|
int read = await parent.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
|
||||||
await ThrottleAsync(read).ConfigureAwait(false);
|
await ThrottleAsync(read).ConfigureAwait(false);
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
@ -79,71 +105,26 @@ public class ThrottledStream : Stream
|
|||||||
await parent.WriteAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
|
await parent.WriteAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await ThrottleAsync(buffer.Length).ConfigureAwait(false);
|
await ThrottleAsync(buffer.Length).ConfigureAwait(false);
|
||||||
await parent.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
|
await parent.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override bool CanRead
|
public override void Flush() => parent.Flush();
|
||||||
{
|
|
||||||
get { return parent.CanRead; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override bool CanSeek
|
|
||||||
{
|
|
||||||
get { return parent.CanSeek; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override bool CanWrite
|
|
||||||
{
|
|
||||||
get { return parent.CanWrite; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
parent.Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override long Length
|
|
||||||
{
|
|
||||||
get { return parent.Length; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override long Position
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return parent.Position;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
parent.Position = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
var read = parent.Read(buffer, offset, count);
|
int read = parent.Read(buffer, offset, count);
|
||||||
Throttle(read);
|
Throttle(read);
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
public override long Seek(long offset, SeekOrigin origin) => parent.Seek(offset, origin);
|
||||||
{
|
|
||||||
return parent.Seek(offset, origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetLength(long value)
|
public override void SetLength(long value) => parent.SetLength(value);
|
||||||
{
|
|
||||||
parent.SetLength(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,28 +1,28 @@
|
|||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace OF_DL.Utils
|
namespace OF_DL.Utils;
|
||||||
|
|
||||||
|
internal static class XmlUtils
|
||||||
{
|
{
|
||||||
internal static class XmlUtils
|
// When true, return original text without parsing/stripping.
|
||||||
|
public static bool Passthrough { get; set; } = false;
|
||||||
|
|
||||||
|
public static string EvaluateInnerText(string xmlValue)
|
||||||
{
|
{
|
||||||
// When true, return original text without parsing/stripping.
|
if (Passthrough)
|
||||||
public static bool Passthrough { get; set; } = false;
|
|
||||||
|
|
||||||
public static string EvaluateInnerText(string xmlValue)
|
|
||||||
{
|
{
|
||||||
if (Passthrough)
|
return xmlValue ?? string.Empty;
|
||||||
{
|
|
||||||
return xmlValue ?? string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var parsedText = XElement.Parse($"<root>{xmlValue}</root>");
|
|
||||||
return parsedText.Value;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{ }
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XElement parsedText = XElement.Parse($"<root>{xmlValue}</root>");
|
||||||
|
return parsedText.Value;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,25 +1,21 @@
|
|||||||
namespace OF_DL.Widevine
|
namespace OF_DL.Widevine;
|
||||||
|
|
||||||
|
public class CDMApi
|
||||||
{
|
{
|
||||||
public class CDMApi
|
private string SessionId { get; set; }
|
||||||
|
|
||||||
|
public byte[] GetChallenge(string initDataB64, string certDataB64, bool offline = false, bool raw = false)
|
||||||
{
|
{
|
||||||
string SessionId { get; set; }
|
SessionId = CDM.OpenSession(initDataB64, Constants.DEVICE_NAME, offline, raw);
|
||||||
|
CDM.SetServiceCertificate(SessionId, Convert.FromBase64String(certDataB64));
|
||||||
public byte[] GetChallenge(string initDataB64, string certDataB64, bool offline = false, bool raw = false)
|
return CDM.GetLicenseRequest(SessionId);
|
||||||
{
|
|
||||||
SessionId = CDM.OpenSession(initDataB64, Constants.DEVICE_NAME, offline, raw);
|
|
||||||
CDM.SetServiceCertificate(SessionId, Convert.FromBase64String(certDataB64));
|
|
||||||
return CDM.GetLicenseRequest(SessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ProvideLicense(string licenseB64)
|
|
||||||
{
|
|
||||||
CDM.ProvideLicense(SessionId, Convert.FromBase64String(licenseB64));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ContentKey> GetKeys()
|
|
||||||
{
|
|
||||||
return CDM.GetKeys(SessionId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ProvideLicense(string licenseB64)
|
||||||
|
{
|
||||||
|
CDM.ProvideLicense(SessionId, Convert.FromBase64String(licenseB64));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ContentKey> GetKeys() => CDM.GetKeys(SessionId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,84 +7,86 @@ using Org.BouncyCastle.Crypto.Signers;
|
|||||||
using Org.BouncyCastle.OpenSsl;
|
using Org.BouncyCastle.OpenSsl;
|
||||||
using ProtoBuf;
|
using ProtoBuf;
|
||||||
|
|
||||||
namespace OF_DL.Widevine
|
namespace OF_DL.Widevine;
|
||||||
|
|
||||||
|
public class CDMDevice
|
||||||
{
|
{
|
||||||
public class CDMDevice
|
public CDMDevice(string deviceName, byte[] clientIdBlobBytes = null, byte[] privateKeyBytes = null,
|
||||||
|
byte[] vmpBytes = null)
|
||||||
{
|
{
|
||||||
public string DeviceName { get; set; }
|
DeviceName = deviceName;
|
||||||
public ClientIdentification ClientID { get; set; }
|
|
||||||
AsymmetricCipherKeyPair DeviceKeys { get; set; }
|
|
||||||
|
|
||||||
public virtual bool IsAndroid { get; set; } = true;
|
string privateKeyPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_private_key");
|
||||||
|
string vmpPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_vmp_blob");
|
||||||
|
|
||||||
public CDMDevice(string deviceName, byte[] clientIdBlobBytes = null, byte[] privateKeyBytes = null, byte[] vmpBytes = null)
|
if (clientIdBlobBytes == null)
|
||||||
{
|
{
|
||||||
DeviceName = deviceName;
|
string clientIDPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_client_id_blob");
|
||||||
|
|
||||||
string privateKeyPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_private_key");
|
if (!File.Exists(clientIDPath))
|
||||||
string vmpPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_vmp_blob");
|
|
||||||
|
|
||||||
if (clientIdBlobBytes == null)
|
|
||||||
{
|
{
|
||||||
string clientIDPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_client_id_blob");
|
throw new Exception("No client id blob found");
|
||||||
|
|
||||||
if (!File.Exists(clientIDPath))
|
|
||||||
throw new Exception("No client id blob found");
|
|
||||||
|
|
||||||
clientIdBlobBytes = File.ReadAllBytes(clientIDPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientID = Serializer.Deserialize<ClientIdentification>(new MemoryStream(clientIdBlobBytes));
|
clientIdBlobBytes = File.ReadAllBytes(clientIDPath);
|
||||||
|
|
||||||
if (privateKeyBytes != null)
|
|
||||||
{
|
|
||||||
using var reader = new StringReader(Encoding.UTF8.GetString(privateKeyBytes));
|
|
||||||
DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
|
|
||||||
}
|
|
||||||
else if (File.Exists(privateKeyPath))
|
|
||||||
{
|
|
||||||
using var reader = File.OpenText(privateKeyPath);
|
|
||||||
DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vmpBytes != null)
|
|
||||||
{
|
|
||||||
var vmp = Serializer.Deserialize<FileHashes>(new MemoryStream(vmpBytes));
|
|
||||||
ClientID.FileHashes = vmp;
|
|
||||||
}
|
|
||||||
else if (File.Exists(vmpPath))
|
|
||||||
{
|
|
||||||
var vmp = Serializer.Deserialize<FileHashes>(new MemoryStream(File.ReadAllBytes(vmpPath)));
|
|
||||||
ClientID.FileHashes = vmp;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual byte[] Decrypt(byte[] data)
|
ClientID = Serializer.Deserialize<ClientIdentification>(new MemoryStream(clientIdBlobBytes));
|
||||||
|
|
||||||
|
if (privateKeyBytes != null)
|
||||||
{
|
{
|
||||||
OaepEncoding eng = new OaepEncoding(new RsaEngine());
|
using StringReader reader = new(Encoding.UTF8.GetString(privateKeyBytes));
|
||||||
eng.Init(false, DeviceKeys.Private);
|
DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
|
||||||
|
}
|
||||||
int length = data.Length;
|
else if (File.Exists(privateKeyPath))
|
||||||
int blockSize = eng.GetInputBlockSize();
|
{
|
||||||
|
using StreamReader reader = File.OpenText(privateKeyPath);
|
||||||
List<byte> plainText = new List<byte>();
|
DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
|
||||||
|
|
||||||
for (int chunkPosition = 0; chunkPosition < length; chunkPosition += blockSize)
|
|
||||||
{
|
|
||||||
int chunkSize = Math.Min(blockSize, length - chunkPosition);
|
|
||||||
plainText.AddRange(eng.ProcessBlock(data, chunkPosition, chunkSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
return plainText.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual byte[] Sign(byte[] data)
|
if (vmpBytes != null)
|
||||||
{
|
{
|
||||||
PssSigner eng = new PssSigner(new RsaEngine(), new Sha1Digest());
|
FileHashes? vmp = Serializer.Deserialize<FileHashes>(new MemoryStream(vmpBytes));
|
||||||
|
ClientID.FileHashes = vmp;
|
||||||
eng.Init(true, DeviceKeys.Private);
|
}
|
||||||
eng.BlockUpdate(data, 0, data.Length);
|
else if (File.Exists(vmpPath))
|
||||||
return eng.GenerateSignature();
|
{
|
||||||
|
FileHashes? vmp = Serializer.Deserialize<FileHashes>(new MemoryStream(File.ReadAllBytes(vmpPath)));
|
||||||
|
ClientID.FileHashes = vmp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string DeviceName { get; set; }
|
||||||
|
public ClientIdentification ClientID { get; set; }
|
||||||
|
private AsymmetricCipherKeyPair DeviceKeys { get; }
|
||||||
|
|
||||||
|
public virtual bool IsAndroid { get; set; } = true;
|
||||||
|
|
||||||
|
public virtual byte[] Decrypt(byte[] data)
|
||||||
|
{
|
||||||
|
OaepEncoding eng = new(new RsaEngine());
|
||||||
|
eng.Init(false, DeviceKeys.Private);
|
||||||
|
|
||||||
|
int length = data.Length;
|
||||||
|
int blockSize = eng.GetInputBlockSize();
|
||||||
|
|
||||||
|
List<byte> plainText = new();
|
||||||
|
|
||||||
|
for (int chunkPosition = 0; chunkPosition < length; chunkPosition += blockSize)
|
||||||
|
{
|
||||||
|
int chunkSize = Math.Min(blockSize, length - chunkPosition);
|
||||||
|
plainText.AddRange(eng.ProcessBlock(data, chunkPosition, chunkSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
return plainText.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual byte[] Sign(byte[] data)
|
||||||
|
{
|
||||||
|
PssSigner eng = new(new RsaEngine(), new Sha1Digest());
|
||||||
|
|
||||||
|
eng.Init(true, DeviceKeys.Private);
|
||||||
|
eng.BlockUpdate(data, 0, data.Length);
|
||||||
|
return eng.GenerateSignature();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
namespace OF_DL.Widevine
|
namespace OF_DL.Widevine;
|
||||||
|
|
||||||
|
public class Constants
|
||||||
{
|
{
|
||||||
public class Constants
|
public static string WORKING_FOLDER { get; set; } =
|
||||||
{
|
Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "cdm"));
|
||||||
public static string WORKING_FOLDER { get; set; } = System.IO.Path.GetFullPath(System.IO.Path.Join(System.IO.Directory.GetCurrentDirectory(), "cdm"));
|
|
||||||
public static string DEVICES_FOLDER { get; set; } = System.IO.Path.GetFullPath(System.IO.Path.Join(WORKING_FOLDER, "devices"));
|
public static string DEVICES_FOLDER { get; set; } = Path.GetFullPath(Path.Join(WORKING_FOLDER, "devices"));
|
||||||
public static string DEVICE_NAME { get; set; } = "chrome_1610";
|
public static string DEVICE_NAME { get; set; } = "chrome_1610";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,39 +1,27 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace OF_DL.Widevine
|
namespace OF_DL.Widevine;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class ContentKey
|
||||||
{
|
{
|
||||||
[Serializable]
|
[JsonPropertyName("key_id")] public byte[] KeyID { get; set; }
|
||||||
public class ContentKey
|
|
||||||
|
[JsonPropertyName("type")] public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("bytes")] public byte[] Bytes { get; set; }
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
|
[JsonPropertyName("permissions")]
|
||||||
|
public List<string> Permissions
|
||||||
{
|
{
|
||||||
[JsonPropertyName("key_id")]
|
get => PermissionsString.Split(",").ToList();
|
||||||
public byte[] KeyID { get; set; }
|
set => PermissionsString = string.Join(",", value);
|
||||||
|
|
||||||
[JsonPropertyName("type")]
|
|
||||||
public string Type { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("bytes")]
|
|
||||||
public byte[] Bytes { get; set; }
|
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
[JsonPropertyName("permissions")]
|
|
||||||
public List<string> Permissions {
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return PermissionsString.Split(",").ToList();
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
PermissionsString = string.Join(",", value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string PermissionsString { get; set; }
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{BitConverter.ToString(KeyID).Replace("-", "").ToLower()}:{BitConverter.ToString(Bytes).Replace("-", "").ToLower()}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore] public string PermissionsString { get; set; }
|
||||||
|
|
||||||
|
public override string ToString() =>
|
||||||
|
$"{BitConverter.ToString(KeyID).Replace("-", "").ToLower()}:{BitConverter.ToString(Bytes).Replace("-", "").ToLower()}";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
namespace OF_DL.Widevine
|
namespace OF_DL.Widevine;
|
||||||
|
|
||||||
|
public class DerivedKeys
|
||||||
{
|
{
|
||||||
public class DerivedKeys
|
public byte[] Auth1 { get; set; }
|
||||||
{
|
public byte[] Auth2 { get; set; }
|
||||||
public byte[] Auth1 { get; set; }
|
public byte[] Enc { get; set; }
|
||||||
public byte[] Auth2 { get; set; }
|
|
||||||
public byte[] Enc { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,59 +1,68 @@
|
|||||||
namespace OF_DL.Widevine
|
namespace OF_DL.Widevine;
|
||||||
|
|
||||||
|
internal class PSSHBox
|
||||||
{
|
{
|
||||||
class PSSHBox
|
private static readonly byte[] PSSH_HEADER = new byte[] { 0x70, 0x73, 0x73, 0x68 };
|
||||||
|
|
||||||
|
private PSSHBox(List<byte[]> kids, byte[] data)
|
||||||
{
|
{
|
||||||
static readonly byte[] PSSH_HEADER = new byte[] { 0x70, 0x73, 0x73, 0x68 };
|
KIDs = kids;
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
public List<byte[]> KIDs { get; set; } = new List<byte[]>();
|
public List<byte[]> KIDs { get; set; } = new();
|
||||||
public byte[] Data { get; set; }
|
public byte[] Data { get; set; }
|
||||||
|
|
||||||
PSSHBox(List<byte[]> kids, byte[] data)
|
public static PSSHBox FromByteArray(byte[] psshbox)
|
||||||
|
{
|
||||||
|
using MemoryStream stream = new(psshbox);
|
||||||
|
|
||||||
|
stream.Seek(4, SeekOrigin.Current);
|
||||||
|
byte[] header = new byte[4];
|
||||||
|
stream.Read(header, 0, 4);
|
||||||
|
|
||||||
|
if (!header.SequenceEqual(PSSH_HEADER))
|
||||||
{
|
{
|
||||||
KIDs = kids;
|
throw new Exception("Not a pssh box");
|
||||||
Data = data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PSSHBox FromByteArray(byte[] psshbox)
|
stream.Seek(20, SeekOrigin.Current);
|
||||||
|
byte[] kidCountBytes = new byte[4];
|
||||||
|
stream.Read(kidCountBytes, 0, 4);
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
{
|
{
|
||||||
using var stream = new System.IO.MemoryStream(psshbox);
|
Array.Reverse(kidCountBytes);
|
||||||
|
|
||||||
stream.Seek(4, System.IO.SeekOrigin.Current);
|
|
||||||
byte[] header = new byte[4];
|
|
||||||
stream.Read(header, 0, 4);
|
|
||||||
|
|
||||||
if (!header.SequenceEqual(PSSH_HEADER))
|
|
||||||
throw new Exception("Not a pssh box");
|
|
||||||
|
|
||||||
stream.Seek(20, System.IO.SeekOrigin.Current);
|
|
||||||
byte[] kidCountBytes = new byte[4];
|
|
||||||
stream.Read(kidCountBytes, 0, 4);
|
|
||||||
|
|
||||||
if (BitConverter.IsLittleEndian)
|
|
||||||
Array.Reverse(kidCountBytes);
|
|
||||||
uint kidCount = BitConverter.ToUInt32(kidCountBytes);
|
|
||||||
|
|
||||||
List<byte[]> kids = new List<byte[]>();
|
|
||||||
for (int i = 0; i < kidCount; i++)
|
|
||||||
{
|
|
||||||
byte[] kid = new byte[16];
|
|
||||||
stream.Read(kid);
|
|
||||||
kids.Add(kid);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] dataLengthBytes = new byte[4];
|
|
||||||
stream.Read(dataLengthBytes);
|
|
||||||
|
|
||||||
if (BitConverter.IsLittleEndian)
|
|
||||||
Array.Reverse(dataLengthBytes);
|
|
||||||
uint dataLength = BitConverter.ToUInt32(dataLengthBytes);
|
|
||||||
|
|
||||||
if (dataLength == 0)
|
|
||||||
return new PSSHBox(kids, null);
|
|
||||||
|
|
||||||
byte[] data = new byte[dataLength];
|
|
||||||
stream.Read(data);
|
|
||||||
|
|
||||||
return new PSSHBox(kids, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint kidCount = BitConverter.ToUInt32(kidCountBytes);
|
||||||
|
|
||||||
|
List<byte[]> kids = new();
|
||||||
|
for (int i = 0; i < kidCount; i++)
|
||||||
|
{
|
||||||
|
byte[] kid = new byte[16];
|
||||||
|
stream.Read(kid);
|
||||||
|
kids.Add(kid);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] dataLengthBytes = new byte[4];
|
||||||
|
stream.Read(dataLengthBytes);
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(dataLengthBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint dataLength = BitConverter.ToUInt32(dataLengthBytes);
|
||||||
|
|
||||||
|
if (dataLength == 0)
|
||||||
|
{
|
||||||
|
return new PSSHBox(kids, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data = new byte[dataLength];
|
||||||
|
stream.Read(data);
|
||||||
|
|
||||||
|
return new PSSHBox(kids, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,24 @@
|
|||||||
namespace OF_DL.Widevine
|
namespace OF_DL.Widevine;
|
||||||
{
|
|
||||||
class Session
|
|
||||||
{
|
|
||||||
public byte[] SessionId { get; set; }
|
|
||||||
public dynamic InitData { get; set; }
|
|
||||||
public bool Offline { get; set; }
|
|
||||||
public CDMDevice Device { get; set; }
|
|
||||||
public byte[] SessionKey { get; set; }
|
|
||||||
public DerivedKeys DerivedKeys { get; set; }
|
|
||||||
public byte[] LicenseRequest { get; set; }
|
|
||||||
public SignedLicense License { get; set; }
|
|
||||||
public SignedDeviceCertificate ServiceCertificate { get; set; }
|
|
||||||
public bool PrivacyMode { get; set; }
|
|
||||||
public List<ContentKey> ContentKeys { get; set; } = new List<ContentKey>();
|
|
||||||
|
|
||||||
public Session(byte[] sessionId, dynamic initData, CDMDevice device, bool offline)
|
internal class Session
|
||||||
{
|
{
|
||||||
SessionId = sessionId;
|
public Session(byte[] sessionId, dynamic initData, CDMDevice device, bool offline)
|
||||||
InitData = initData;
|
{
|
||||||
Offline = offline;
|
SessionId = sessionId;
|
||||||
Device = device;
|
InitData = initData;
|
||||||
}
|
Offline = offline;
|
||||||
|
Device = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] SessionId { get; set; }
|
||||||
|
public dynamic InitData { get; set; }
|
||||||
|
public bool Offline { get; set; }
|
||||||
|
public CDMDevice Device { get; set; }
|
||||||
|
public byte[] SessionKey { get; set; }
|
||||||
|
public DerivedKeys DerivedKeys { get; set; }
|
||||||
|
public byte[] LicenseRequest { get; set; }
|
||||||
|
public SignedLicense License { get; set; }
|
||||||
|
public SignedDeviceCertificate ServiceCertificate { get; set; }
|
||||||
|
public bool PrivacyMode { get; set; }
|
||||||
|
public List<ContentKey> ContentKeys { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,5 +4,38 @@
|
|||||||
"prefix": "30586",
|
"prefix": "30586",
|
||||||
"suffix": "67000213",
|
"suffix": "67000213",
|
||||||
"checksum_constant": 521,
|
"checksum_constant": 521,
|
||||||
"checksum_indexes": [ 0, 2, 3, 7, 7, 8, 8, 10, 11, 13, 14, 16, 17, 17, 17, 19, 19, 20, 21, 21, 23, 23, 24, 24, 27, 27, 29, 30, 31, 34, 35, 39 ]
|
"checksum_indexes": [
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
7,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
8,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
13,
|
||||||
|
14,
|
||||||
|
16,
|
||||||
|
17,
|
||||||
|
17,
|
||||||
|
17,
|
||||||
|
19,
|
||||||
|
19,
|
||||||
|
20,
|
||||||
|
21,
|
||||||
|
21,
|
||||||
|
23,
|
||||||
|
23,
|
||||||
|
24,
|
||||||
|
24,
|
||||||
|
27,
|
||||||
|
27,
|
||||||
|
29,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
34,
|
||||||
|
35,
|
||||||
|
39
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user