├── OF DRM Video Downloader ├── Icon │ └── download.ico ├── References │ ├── Spectre.Console.dll │ └── Spectre.Console.deps.json ├── Widevine │ ├── DerivedKeys.cs │ ├── Constants.cs │ ├── Session.cs │ ├── ContentKey.cs │ ├── PSSHBox.cs │ ├── CDMDevice.cs │ └── CDM.cs ├── Entities │ ├── PostCollection.cs │ ├── ArchivedCollection.cs │ ├── MessagesCollection.cs │ ├── PaidPostCollection.cs │ ├── PaidMessagesCollection.cs │ ├── DynamicRules.cs │ ├── Auth.cs │ ├── UserList.cs │ ├── Messages.cs │ ├── PaidMessages.cs │ ├── Purchased.cs │ ├── Post.cs │ ├── UsersList.cs │ ├── Subscriptions.cs │ ├── Archived.cs │ └── User.cs ├── DRM.csproj ├── rules.json ├── CDMApi.cs ├── Helpers │ ├── Interfaces │ │ ├── IDBHelper.cs │ │ ├── IAPIHelper.cs │ │ └── IDownloadHelper.cs │ ├── DBHelper.cs │ └── DownloadHelper.cs ├── Crypto │ ├── CryptoUtils.cs │ └── Padding.cs ├── OF DRM Video Downloader.csproj ├── HttpUtil.cs └── Utils.cs ├── OF DRM Video Downloader.sln ├── .gitattributes ├── README.md └── .gitignore /OF DRM Video Downloader/Icon/download.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sim0n00ps/OF-DRM/HEAD/OF DRM Video Downloader/Icon/download.ico -------------------------------------------------------------------------------- /OF DRM Video Downloader/References/Spectre.Console.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sim0n00ps/OF-DRM/HEAD/OF DRM Video Downloader/References/Spectre.Console.dll -------------------------------------------------------------------------------- /OF DRM Video Downloader/Widevine/DerivedKeys.cs: -------------------------------------------------------------------------------- 1 | namespace WidevineClient.Widevine 2 | { 3 | public class DerivedKeys 4 | { 5 | public byte[] Auth1 { get; set; } 6 | public byte[] Auth2 { get; set; } 7 | public byte[] Enc { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Widevine/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace WidevineClient.Widevine 2 | { 3 | public class Constants 4 | { 5 | public static string WORKING_FOLDER { get; set; } = System.IO.Path.GetFullPath(System.IO.Path.Join(System.IO.Directory.GetCurrentDirectory(), "cdm")); 6 | public static string DEVICES_FOLDER { get; set; } = System.IO.Path.GetFullPath(System.IO.Path.Join(WORKING_FOLDER, "devices")); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/PostCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class PostCollection 10 | { 11 | public Dictionary> Video_URLS = new Dictionary>(); 12 | public Dictionary Posts = new Dictionary(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/ArchivedCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class ArchivedCollection 10 | { 11 | public Dictionary> Video_URLS = new Dictionary>(); 12 | public Dictionary Archived = new Dictionary(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/MessagesCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class MessagesCollection 10 | { 11 | public Dictionary> Video_URLS = new Dictionary>(); 12 | public Dictionary Messages = new Dictionary(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/PaidPostCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class PaidPostCollection 10 | { 11 | public Dictionary> Video_URLS = new Dictionary>(); 12 | public Dictionary PaidPosts = new Dictionary(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/PaidMessagesCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class PaidMessagesCollection 10 | { 11 | public Dictionary> Video_URLS = new Dictionary>(); 12 | public Dictionary PaidMessages = new Dictionary(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/DRM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-token": "33d57ade8c02dbc5a333db99ff9ae26a", 3 | "static_param": "r0COhCenVY6tUCrcnkbwz727f1m0UHsv", 4 | "prefix": "36587", 5 | "suffix": "67a0ec50", 6 | "checksum_constant": 118, 7 | "checksum_indexes": [ 8 | 1, 9 | 1, 10 | 1, 11 | 2, 12 | 2, 13 | 5, 14 | 5, 15 | 6, 16 | 6, 17 | 7, 18 | 7, 19 | 11, 20 | 12, 21 | 12, 22 | 13, 23 | 14, 24 | 14, 25 | 16, 26 | 17, 27 | 20, 28 | 20, 29 | 20, 30 | 21, 31 | 23, 32 | 24, 33 | 25, 34 | 25, 35 | 25, 36 | 29, 37 | 30, 38 | 31, 39 | 39 40 | ] 41 | } -------------------------------------------------------------------------------- /OF DRM Video Downloader/CDMApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WidevineClient.Widevine 5 | { 6 | public class CDMApi 7 | { 8 | string SessionId { get; set; } 9 | 10 | public byte[] GetChallenge(string initDataB64, string certDataB64, bool offline = false, bool raw = false) 11 | { 12 | SessionId = CDM.OpenSession(initDataB64, "chrome_1610", offline, raw); 13 | CDM.SetServiceCertificate(SessionId, Convert.FromBase64String(certDataB64)); 14 | return CDM.GetLicenseRequest(SessionId); 15 | } 16 | 17 | public bool ProvideLicense(string licenseB64) 18 | { 19 | CDM.ProvideLicense(SessionId, Convert.FromBase64String(licenseB64)); 20 | return true; 21 | } 22 | 23 | public List GetKeys() 24 | { 25 | return CDM.GetKeys(SessionId); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Helpers/Interfaces/IDBHelper.cs: -------------------------------------------------------------------------------- 1 | namespace OF_DRM_Video_Downloader.Helpers 2 | { 3 | public interface IDBHelper 4 | { 5 | 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); 6 | Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, int user_id); 7 | Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at); 8 | Task CheckDownloaded(string folder, long media_id); 9 | Task CreateDB(string folder); 10 | Task GetFileSize(string folder, long media_id); 11 | Task UpdateMedia(string folder, long media_id, string directory, string filename, long size, bool downloaded, DateTime created_at); 12 | } 13 | } -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/DynamicRules.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Entities 9 | { 10 | public class DynamicRules 11 | { 12 | [JsonProperty(PropertyName = "app-token")] 13 | public string? AppToken { get; set; } 14 | 15 | [JsonProperty(PropertyName = "app_token")] 16 | private string AppToken2 { set { AppToken = value; } } 17 | 18 | [JsonProperty(PropertyName = "static_param")] 19 | public string? StaticParam { get; set; } 20 | 21 | [JsonProperty(PropertyName = "prefix")] 22 | public string? Prefix { get; set; } 23 | 24 | [JsonProperty(PropertyName = "suffix")] 25 | public string? Suffix { get; set; } 26 | 27 | [JsonProperty(PropertyName = "checksum_constant")] 28 | public int? ChecksumConstant { get; set; } 29 | 30 | [JsonProperty(PropertyName = "checksum_indexes")] 31 | public List ChecksumIndexes { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Crypto/CryptoUtils.cs: -------------------------------------------------------------------------------- 1 | using Org.BouncyCastle.Crypto; 2 | using Org.BouncyCastle.Crypto.Engines; 3 | using Org.BouncyCastle.Crypto.Macs; 4 | using Org.BouncyCastle.Crypto.Parameters; 5 | using System.Security.Cryptography; 6 | 7 | namespace WidevineClient.Crypto 8 | { 9 | public class CryptoUtils 10 | { 11 | public static byte[] GetHMACSHA256Digest(byte[] data, byte[] key) 12 | { 13 | return new HMACSHA256(key).ComputeHash(data); 14 | } 15 | 16 | public static byte[] GetCMACDigest(byte[] data, byte[] key) 17 | { 18 | IBlockCipher cipher = new AesEngine(); 19 | IMac mac = new CMac(cipher, 128); 20 | 21 | KeyParameter keyParam = new KeyParameter(key); 22 | 23 | mac.Init(keyParam); 24 | 25 | mac.BlockUpdate(data, 0, data.Length); 26 | 27 | byte[] outBytes = new byte[16]; 28 | 29 | mac.DoFinal(outBytes, 0); 30 | return outBytes; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Widevine/Session.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WidevineClient.Widevine 4 | { 5 | class Session 6 | { 7 | public byte[] SessionId { get; set; } 8 | public dynamic InitData { get; set; } 9 | public bool Offline { get; set; } 10 | public CDMDevice Device { get; set; } 11 | public byte[] SessionKey { get; set; } 12 | public DerivedKeys DerivedKeys { get; set; } 13 | public byte[] LicenseRequest { get; set; } 14 | public SignedLicense License { get; set; } 15 | public SignedDeviceCertificate ServiceCertificate { get; set; } 16 | public bool PrivacyMode { get; set; } 17 | public List ContentKeys { get; set; } = new List(); 18 | 19 | public Session(byte[] sessionId, dynamic initData, CDMDevice device, bool offline) 20 | { 21 | SessionId = sessionId; 22 | InitData = initData; 23 | Offline = offline; 24 | Device = device; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /OF DRM Video Downloader/OF DRM Video Downloader.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | OF_DRM_Video_Downloader 7 | enable 8 | enable 9 | Icon\download.ico 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | References\Spectre.Console.dll 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/Auth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Entities 8 | { 9 | public class Auth 10 | { 11 | public string? USER_ID { get; set; } = string.Empty; 12 | public string? USER_AGENT { get; set; } = string.Empty; 13 | public string? X_BC { get; set; } = string.Empty; 14 | public string? COOKIE { get; set; } = string.Empty; 15 | public string? YTDLP_PATH { get; set; } = string.Empty; 16 | public string? FFMPEG_PATH { get; set;} = string.Empty; 17 | public string? MP4DECRYPT_PATH { get; set;} = string.Empty; 18 | public bool DownloadPaidPosts { get; set; } 19 | public bool DownloadPosts { get; set; } 20 | public bool DownloadArchived { get; set; } 21 | public bool DownloadMessages { get; set; } 22 | public bool DownloadPaidMessages { get; set; } 23 | public bool IncludeExpiredSubscriptions { get; set; } 24 | public string? DownloadPath { get; set; } = string.Empty; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /OF DRM Video Downloader.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33627.172 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OF DRM Video Downloader", "OF DRM Video Downloader\OF DRM Video Downloader.csproj", "{CABFBEAB-4437-4978-9D3E-196C36F8431D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {CABFBEAB-4437-4978-9D3E-196C36F8431D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {CABFBEAB-4437-4978-9D3E-196C36F8431D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {CABFBEAB-4437-4978-9D3E-196C36F8431D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {CABFBEAB-4437-4978-9D3E-196C36F8431D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {C74FA8FA-4800-459F-B90F-251E08759E3F} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Widevine/ContentKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using System.Linq; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace WidevineClient.Widevine 8 | { 9 | [Serializable] 10 | public class ContentKey 11 | { 12 | [JsonPropertyName("key_id")] 13 | public byte[] KeyID { get; set; } 14 | 15 | [JsonPropertyName("type")] 16 | public string Type { get; set; } 17 | 18 | [JsonPropertyName("bytes")] 19 | public byte[] Bytes { get; set; } 20 | 21 | [NotMapped] 22 | [JsonPropertyName("permissions")] 23 | public List Permissions { 24 | get 25 | { 26 | return PermissionsString.Split(",").ToList(); 27 | } 28 | set 29 | { 30 | PermissionsString = string.Join(",", value); 31 | } 32 | } 33 | 34 | [JsonIgnore] 35 | public string PermissionsString { get; set; } 36 | 37 | public override string ToString() 38 | { 39 | return $"{BitConverter.ToString(KeyID).Replace("-", "").ToLower()}:{BitConverter.ToString(Bytes).Replace("-", "").ToLower()}"; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/UserList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class UserList 10 | { 11 | public List list { get; set; } 12 | public bool? hasMore { get; set; } 13 | public class List 14 | { 15 | public string id { get; set; } 16 | public string type { get; set; } 17 | public string name { get; set; } 18 | public int? usersCount { get; set; } 19 | public int? postsCount { get; set; } 20 | public bool? canUpdate { get; set; } 21 | public bool? canDelete { get; set; } 22 | public bool? canManageUsers { get; set; } 23 | public bool? canAddUsers { get; set; } 24 | public bool? canPinnedToFeed { get; set; } 25 | public bool? isPinnedToFeed { get; set; } 26 | public bool? canPinnedToChat { get; set; } 27 | public bool? isPinnedToChat { get; set; } 28 | public string order { get; set; } 29 | public string direction { get; set; } 30 | public List users { get; set; } 31 | public List customOrderUsersIds { get; set; } 32 | public List posts { get; set; } 33 | } 34 | 35 | public class User 36 | { 37 | public int? id { get; set; } 38 | public string _view { get; set; } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Helpers/Interfaces/IAPIHelper.cs: -------------------------------------------------------------------------------- 1 | using Entities; 2 | using OF_DRM_Video_Downloader.Entities; 3 | 4 | namespace OF_DRM_Video_Downloader.Helpers 5 | { 6 | public interface IAPIHelper 7 | { 8 | Task> GetSubscriptions(string endpoint, bool includeExpiredSubscriptions, Auth auth); 9 | Task> GetLists(string endpoint, Auth auth); 10 | Task> GetListUsers(string endpoint, Auth auth); 11 | Task GetUserInfo(string endpoint, Auth auth); 12 | Task GetPaidPostVideos(string endpoint, string username, string folder, Auth auth); 13 | Task GetPostVideos(string endpoint, string folder, Auth auth); 14 | Task GetArchivedVideos(string endpoint, string folder, Auth auth); 15 | Task GetMessageVideos(string endpoint, string folder, Auth auth); 16 | Task GetPaidMessageVideos(string endpoint, string username, string folder, Auth auth); 17 | Task GetDRMMPDPSSH(string mpdUrl, string policy, string signature, string kvp, Auth auth); 18 | Task GetDRMMPDLastModified(string mpdUrl, string policy, string signature, string kvp, Auth auth); 19 | Task GetDecryptionKey(Dictionary drmHeaders, string licenceURL, string pssh); 20 | Task GetDecryptionKeyNew(Dictionary drmHeaders, string licenceURL, string pssh); 21 | Dictionary Headers(string path, string queryParams, Auth auth); 22 | } 23 | } -------------------------------------------------------------------------------- /OF DRM Video Downloader/Helpers/Interfaces/IDownloadHelper.cs: -------------------------------------------------------------------------------- 1 | using Entities; 2 | using Spectre.Console; 3 | 4 | namespace OF_DRM_Video_Downloader.Helpers 5 | { 6 | public interface IDownloadHelper 7 | { 8 | Task DownloadPostDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task); 9 | Task DownloadPurchasedPostDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task); 10 | Task DownloadArchivedDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task); 11 | Task DownloadMessageDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task); 12 | Task DownloadPaidMessageDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task); 13 | Task CalculateTotalFileSize(List urls, Auth auth); 14 | } 15 | } -------------------------------------------------------------------------------- /OF DRM Video Downloader/Widevine/PSSHBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace WidevineClient.Widevine 6 | { 7 | class PSSHBox 8 | { 9 | static readonly byte[] PSSH_HEADER = new byte[] { 0x70, 0x73, 0x73, 0x68 }; 10 | 11 | public List KIDs { get; set; } = new List(); 12 | public byte[] Data { get; set; } 13 | 14 | PSSHBox(List kids, byte[] data) 15 | { 16 | KIDs = kids; 17 | Data = data; 18 | } 19 | 20 | public static PSSHBox FromByteArray(byte[] psshbox) 21 | { 22 | using var stream = new System.IO.MemoryStream(psshbox); 23 | 24 | stream.Seek(4, System.IO.SeekOrigin.Current); 25 | byte[] header = new byte[4]; 26 | stream.Read(header, 0, 4); 27 | 28 | if (!header.SequenceEqual(PSSH_HEADER)) 29 | throw new Exception("Not a pssh box"); 30 | 31 | stream.Seek(20, System.IO.SeekOrigin.Current); 32 | byte[] kidCountBytes = new byte[4]; 33 | stream.Read(kidCountBytes, 0, 4); 34 | 35 | if (BitConverter.IsLittleEndian) 36 | Array.Reverse(kidCountBytes); 37 | uint kidCount = BitConverter.ToUInt32(kidCountBytes); 38 | 39 | List kids = new List(); 40 | for (int i = 0; i < kidCount; i++) 41 | { 42 | byte[] kid = new byte[16]; 43 | stream.Read(kid); 44 | kids.Add(kid); 45 | } 46 | 47 | byte[] dataLengthBytes = new byte[4]; 48 | stream.Read(dataLengthBytes); 49 | 50 | if (BitConverter.IsLittleEndian) 51 | Array.Reverse(dataLengthBytes); 52 | uint dataLength = BitConverter.ToUInt32(dataLengthBytes); 53 | 54 | if (dataLength == 0) 55 | return new PSSHBox(kids, null); 56 | 57 | byte[] data = new byte[dataLength]; 58 | stream.Read(data); 59 | 60 | return new PSSHBox(kids, data); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Widevine/CDMDevice.cs: -------------------------------------------------------------------------------- 1 | using Org.BouncyCastle.Crypto; 2 | using Org.BouncyCastle.Crypto.Digests; 3 | using Org.BouncyCastle.Crypto.Encodings; 4 | using Org.BouncyCastle.Crypto.Engines; 5 | using Org.BouncyCastle.Crypto.Signers; 6 | using Org.BouncyCastle.OpenSsl; 7 | using ProtoBuf; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.IO; 11 | using System.Text; 12 | 13 | namespace WidevineClient.Widevine 14 | { 15 | public class CDMDevice 16 | { 17 | public string DeviceName { get; set; } 18 | public ClientIdentification ClientID { get; set; } 19 | AsymmetricCipherKeyPair DeviceKeys { get; set; } 20 | 21 | public virtual bool IsAndroid { get; set; } = true; 22 | 23 | public CDMDevice(string deviceName, byte[] clientIdBlobBytes = null, byte[] privateKeyBytes = null, byte[] vmpBytes = null) 24 | { 25 | DeviceName = deviceName; 26 | 27 | string privateKeyPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_private_key"); 28 | string vmpPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_vmp_blob"); 29 | 30 | if (clientIdBlobBytes == null) 31 | { 32 | string clientIDPath = Path.Join(Constants.DEVICES_FOLDER, deviceName, "device_client_id_blob"); 33 | 34 | if (!File.Exists(clientIDPath)) 35 | throw new Exception("No client id blob found"); 36 | 37 | clientIdBlobBytes = File.ReadAllBytes(clientIDPath); 38 | } 39 | 40 | ClientID = Serializer.Deserialize(new MemoryStream(clientIdBlobBytes)); 41 | 42 | if (privateKeyBytes != null) 43 | { 44 | using var reader = new StringReader(Encoding.UTF8.GetString(privateKeyBytes)); 45 | DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject(); 46 | } 47 | else if (File.Exists(privateKeyPath)) 48 | { 49 | using var reader = File.OpenText(privateKeyPath); 50 | DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject(); 51 | } 52 | 53 | if (vmpBytes != null) 54 | { 55 | var vmp = Serializer.Deserialize(new MemoryStream(vmpBytes)); 56 | ClientID.FileHashes = vmp; 57 | } 58 | else if (File.Exists(vmpPath)) 59 | { 60 | var vmp = Serializer.Deserialize(new MemoryStream(File.ReadAllBytes(vmpPath))); 61 | ClientID.FileHashes = vmp; 62 | } 63 | } 64 | 65 | public virtual byte[] Decrypt(byte[] data) 66 | { 67 | OaepEncoding eng = new OaepEncoding(new RsaEngine()); 68 | eng.Init(false, DeviceKeys.Private); 69 | 70 | int length = data.Length; 71 | int blockSize = eng.GetInputBlockSize(); 72 | 73 | List plainText = new List(); 74 | 75 | for (int chunkPosition = 0; chunkPosition < length; chunkPosition += blockSize) 76 | { 77 | int chunkSize = Math.Min(blockSize, length - chunkPosition); 78 | plainText.AddRange(eng.ProcessBlock(data, chunkPosition, chunkSize)); 79 | } 80 | 81 | return plainText.ToArray(); 82 | } 83 | 84 | public virtual byte[] Sign(byte[] data) 85 | { 86 | PssSigner eng = new PssSigner(new RsaEngine(), new Sha1Digest()); 87 | 88 | eng.Init(true, DeviceKeys.Private); 89 | eng.BlockUpdate(data, 0, data.Length); 90 | return eng.GenerateSignature(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/HttpUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Text; 5 | 6 | namespace WidevineClient 7 | { 8 | class HttpUtil 9 | { 10 | public static HttpClient Client { get; set; } = new HttpClient(new HttpClientHandler 11 | { 12 | AllowAutoRedirect = true, 13 | //Proxy = null 14 | }); 15 | 16 | public static byte[] PostData(string URL, Dictionary headers, string postData) 17 | { 18 | var mediaType = postData.StartsWith("{") ? "application/json" : "application/x-www-form-urlencoded"; 19 | StringContent content = new StringContent(postData, Encoding.UTF8, mediaType); 20 | //ByteArrayContent content = new ByteArrayContent(postData); 21 | 22 | HttpResponseMessage response = Post(URL, headers, content); 23 | byte[] bytes = response.Content.ReadAsByteArrayAsync().Result; 24 | return bytes; 25 | } 26 | 27 | public static byte[] PostData(string URL, Dictionary headers, byte[] postData) 28 | { 29 | ByteArrayContent content = new ByteArrayContent(postData); 30 | 31 | HttpResponseMessage response = Post(URL, headers, content); 32 | byte[] bytes = response.Content.ReadAsByteArrayAsync().Result; 33 | return bytes; 34 | } 35 | 36 | public static byte[] PostData(string URL, Dictionary headers, Dictionary postData) 37 | { 38 | FormUrlEncodedContent content = new FormUrlEncodedContent(postData); 39 | 40 | HttpResponseMessage response = Post(URL, headers, content); 41 | byte[] bytes = response.Content.ReadAsByteArrayAsync().Result; 42 | return bytes; 43 | } 44 | 45 | public static string GetWebSource(string URL, Dictionary headers = null) 46 | { 47 | HttpResponseMessage response = Get(URL, headers); 48 | byte[] bytes = response.Content.ReadAsByteArrayAsync().Result; 49 | return Encoding.UTF8.GetString(bytes); 50 | } 51 | 52 | public static byte[] GetBinary(string URL, Dictionary headers = null) 53 | { 54 | HttpResponseMessage response = Get(URL, headers); 55 | byte[] bytes = response.Content.ReadAsByteArrayAsync().Result; 56 | return bytes; 57 | } 58 | public static string GetString(byte[] bytes) 59 | { 60 | return Encoding.UTF8.GetString(bytes); 61 | } 62 | 63 | static HttpResponseMessage Get(string URL, Dictionary headers = null) 64 | { 65 | HttpRequestMessage request = new HttpRequestMessage() 66 | { 67 | RequestUri = new Uri(URL), 68 | Method = HttpMethod.Get 69 | }; 70 | 71 | if (headers != null) 72 | foreach (KeyValuePair header in headers) 73 | request.Headers.TryAddWithoutValidation(header.Key, header.Value); 74 | 75 | return Send(request); 76 | } 77 | 78 | static HttpResponseMessage Post(string URL, Dictionary headers, HttpContent content) 79 | { 80 | HttpRequestMessage request = new HttpRequestMessage() 81 | { 82 | RequestUri = new Uri(URL), 83 | Method = HttpMethod.Post, 84 | Content = content 85 | }; 86 | 87 | if (headers != null) 88 | foreach (KeyValuePair header in headers) 89 | request.Headers.TryAddWithoutValidation(header.Key, header.Value); 90 | 91 | return Send(request); 92 | } 93 | 94 | static HttpResponseMessage Send(HttpRequestMessage request) 95 | { 96 | return Client.SendAsync(request).Result; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Crypto/Padding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | 6 | namespace WidevineClient.Crypto 7 | { 8 | public class Padding 9 | { 10 | public static byte[] AddPKCS7Padding(byte[] data, int k) 11 | { 12 | int m = k - (data.Length % k); 13 | 14 | byte[] padding = new byte[m]; 15 | Array.Fill(padding, (byte)m); 16 | 17 | byte[] paddedBytes = new byte[data.Length + padding.Length]; 18 | Buffer.BlockCopy(data, 0, paddedBytes, 0, data.Length); 19 | Buffer.BlockCopy(padding, 0, paddedBytes, data.Length, padding.Length); 20 | 21 | return paddedBytes; 22 | } 23 | 24 | public static byte[] RemovePKCS7Padding(byte[] paddedByteArray) 25 | { 26 | var last = paddedByteArray[^1]; 27 | if (paddedByteArray.Length <= last) 28 | { 29 | return paddedByteArray; 30 | } 31 | 32 | return SubArray(paddedByteArray, 0, (paddedByteArray.Length - last)); 33 | } 34 | 35 | public static T[] SubArray(T[] arr, int start, int length) 36 | { 37 | var result = new T[length]; 38 | Buffer.BlockCopy(arr, start, result, 0, length); 39 | 40 | return result; 41 | } 42 | 43 | public static byte[] AddPSSPadding(byte[] hash) 44 | { 45 | int modBits = 2048; 46 | int hLen = 20; 47 | int emLen = 256; 48 | 49 | int lmask = 0; 50 | for (int i = 0; i < 8 * emLen - (modBits - 1); i++) 51 | lmask = lmask >> 1 | 0x80; 52 | 53 | if (emLen < hLen + hLen + 2) 54 | { 55 | return null; 56 | } 57 | 58 | byte[] salt = new byte[hLen]; 59 | new Random().NextBytes(salt); 60 | 61 | byte[] m_prime = Enumerable.Repeat((byte)0, 8).ToArray().Concat(hash).Concat(salt).ToArray(); 62 | byte[] h = SHA1.Create().ComputeHash(m_prime); 63 | 64 | byte[] ps = Enumerable.Repeat((byte)0, emLen - hLen - hLen - 2).ToArray(); 65 | byte[] db = ps.Concat(new byte[] { 0x01 }).Concat(salt).ToArray(); 66 | 67 | byte[] dbMask = MGF1(h, emLen - hLen - 1); 68 | 69 | byte[] maskedDb = new byte[dbMask.Length]; 70 | for (int i = 0; i < dbMask.Length; i++) 71 | maskedDb[i] = (byte)(db[i] ^ dbMask[i]); 72 | 73 | maskedDb[0] = (byte)(maskedDb[0] & ~lmask); 74 | 75 | byte[] padded = maskedDb.Concat(h).Concat(new byte[] { 0xBC }).ToArray(); 76 | 77 | return padded; 78 | } 79 | 80 | public static byte[] RemoveOAEPPadding(byte[] data) 81 | { 82 | int k = 256; 83 | int hLen = 20; 84 | 85 | byte[] maskedSeed = data[1..(hLen + 1)]; 86 | byte[] maskedDB = data[(hLen + 1)..]; 87 | 88 | byte[] seedMask = MGF1(maskedDB, hLen); 89 | 90 | byte[] seed = new byte[maskedSeed.Length]; 91 | for (int i = 0; i < maskedSeed.Length; i++) 92 | seed[i] = (byte)(maskedSeed[i] ^ seedMask[i]); 93 | 94 | byte[] dbMask = MGF1(seed, k - hLen - 1); 95 | 96 | byte[] db = new byte[maskedDB.Length]; 97 | for (int i = 0; i < maskedDB.Length; i++) 98 | db[i] = (byte)(maskedDB[i] ^ dbMask[i]); 99 | 100 | int onePos = BitConverter.ToString(db[hLen..]).Replace("-", "").IndexOf("01") / 2; 101 | byte[] unpadded = db[(hLen + onePos + 1)..]; 102 | 103 | return unpadded; 104 | } 105 | 106 | static byte[] MGF1(byte[] seed, int maskLen) 107 | { 108 | SHA1 hobj = SHA1.Create(); 109 | int hLen = hobj.HashSize / 8; 110 | List T = new List(); 111 | for (int i = 0; i < (int)Math.Ceiling(((double)maskLen / (double)hLen)); i++) 112 | { 113 | byte[] c = BitConverter.GetBytes(i); 114 | Array.Reverse(c); 115 | byte[] digest = hobj.ComputeHash(seed.Concat(c).ToArray()); 116 | T.AddRange(digest); 117 | } 118 | return T.GetRange(0, maskLen).ToArray(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/References/Spectre.Console.deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeTarget": { 3 | "name": ".NETCoreApp,Version=v7.0", 4 | "signature": "" 5 | }, 6 | "compilationOptions": {}, 7 | "targets": { 8 | ".NETCoreApp,Version=v7.0": { 9 | "Spectre.Console/0.0.0-preview.0": { 10 | "dependencies": { 11 | "Microsoft.SourceLink.GitHub": "1.1.1", 12 | "MinVer": "4.2.0", 13 | "Roslynator.Analyzers": "4.1.2", 14 | "StyleCop.Analyzers": "1.2.0-beta.435", 15 | "System.Memory": "4.5.5", 16 | "Wcwidth.Sources": "1.0.0" 17 | }, 18 | "runtime": { 19 | "Spectre.Console.dll": {} 20 | } 21 | }, 22 | "Microsoft.Build.Tasks.Git/1.1.1": {}, 23 | "Microsoft.SourceLink.Common/1.1.1": {}, 24 | "Microsoft.SourceLink.GitHub/1.1.1": { 25 | "dependencies": { 26 | "Microsoft.Build.Tasks.Git": "1.1.1", 27 | "Microsoft.SourceLink.Common": "1.1.1" 28 | } 29 | }, 30 | "MinVer/4.2.0": {}, 31 | "Roslynator.Analyzers/4.1.2": {}, 32 | "StyleCop.Analyzers/1.2.0-beta.435": { 33 | "dependencies": { 34 | "StyleCop.Analyzers.Unstable": "1.2.0.435" 35 | } 36 | }, 37 | "StyleCop.Analyzers.Unstable/1.2.0.435": {}, 38 | "System.Memory/4.5.5": {}, 39 | "Wcwidth.Sources/1.0.0": {} 40 | } 41 | }, 42 | "libraries": { 43 | "Spectre.Console/0.0.0-preview.0": { 44 | "type": "project", 45 | "serviceable": false, 46 | "sha512": "" 47 | }, 48 | "Microsoft.Build.Tasks.Git/1.1.1": { 49 | "type": "package", 50 | "serviceable": true, 51 | "sha512": "sha512-AT3HlgTjsqHnWpBHSNeR0KxbLZD7bztlZVj7I8vgeYG9SYqbeFGh0TM/KVtC6fg53nrWHl3VfZFvb5BiQFcY6Q==", 52 | "path": "microsoft.build.tasks.git/1.1.1", 53 | "hashPath": "microsoft.build.tasks.git.1.1.1.nupkg.sha512" 54 | }, 55 | "Microsoft.SourceLink.Common/1.1.1": { 56 | "type": "package", 57 | "serviceable": true, 58 | "sha512": "sha512-WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg==", 59 | "path": "microsoft.sourcelink.common/1.1.1", 60 | "hashPath": "microsoft.sourcelink.common.1.1.1.nupkg.sha512" 61 | }, 62 | "Microsoft.SourceLink.GitHub/1.1.1": { 63 | "type": "package", 64 | "serviceable": true, 65 | "sha512": "sha512-IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==", 66 | "path": "microsoft.sourcelink.github/1.1.1", 67 | "hashPath": "microsoft.sourcelink.github.1.1.1.nupkg.sha512" 68 | }, 69 | "MinVer/4.2.0": { 70 | "type": "package", 71 | "serviceable": true, 72 | "sha512": "sha512-Po4tv+sri1jsaebQ8F6+yD5ru9Gas5mR111F6HR2ULqwflvjjZXMstpeOc1GHMJeQa3g4E3b8MX8K2cShkuUAg==", 73 | "path": "minver/4.2.0", 74 | "hashPath": "minver.4.2.0.nupkg.sha512" 75 | }, 76 | "Roslynator.Analyzers/4.1.2": { 77 | "type": "package", 78 | "serviceable": true, 79 | "sha512": "sha512-bNl3GRSBFjJymYnwq/IRDD9MOSZz9VKdGk9RsN0MWIXoSRnVKQv84f6s9nLE13y20lZgMZKlDqGw2uInBH4JgA==", 80 | "path": "roslynator.analyzers/4.1.2", 81 | "hashPath": "roslynator.analyzers.4.1.2.nupkg.sha512" 82 | }, 83 | "StyleCop.Analyzers/1.2.0-beta.435": { 84 | "type": "package", 85 | "serviceable": true, 86 | "sha512": "sha512-TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==", 87 | "path": "stylecop.analyzers/1.2.0-beta.435", 88 | "hashPath": "stylecop.analyzers.1.2.0-beta.435.nupkg.sha512" 89 | }, 90 | "StyleCop.Analyzers.Unstable/1.2.0.435": { 91 | "type": "package", 92 | "serviceable": true, 93 | "sha512": "sha512-ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==", 94 | "path": "stylecop.analyzers.unstable/1.2.0.435", 95 | "hashPath": "stylecop.analyzers.unstable.1.2.0.435.nupkg.sha512" 96 | }, 97 | "System.Memory/4.5.5": { 98 | "type": "package", 99 | "serviceable": true, 100 | "sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", 101 | "path": "system.memory/4.5.5", 102 | "hashPath": "system.memory.4.5.5.nupkg.sha512" 103 | }, 104 | "Wcwidth.Sources/1.0.0": { 105 | "type": "package", 106 | "serviceable": true, 107 | "sha512": "sha512-86tmwfGXRz7GJQXBnoTFoMvqSqd6irfkEkRzQFR54W/nweaR8cUvzY8x++z+B/+eUPSuqD2Ah1iPJHgthy4pzg==", 108 | "path": "wcwidth.sources/1.0.0", 109 | "hashPath": "wcwidth.sources.1.0.0.nupkg.sha512" 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OF-DRM 2 | C# console app to download DRM protected videos from Onlyfans accounts 3 | 4 | # Installation 5 | I have tried to make this a lot more simple to set up compared to [OF DL](https://github.com/sim0n00ps/OF-DL). 6 | 7 | The only thing you need to do is go to the [releases](https://github.com/sim0n00ps/OF-DRM/releases) page and download the latest release zip file. 8 | 9 | Extract the zip file somewhere safe using 7zip or winrar, you should have 3 files and 2 folders: 10 | - OF DRM.exe 11 | - auth.json 12 | - e_sqlite3.dll 13 | - EXEs - this contains yt-dlp.exe, mp4decrypt.exe and ffmpeg.exe with the paths already set up correctly in the auth.json file. DO NOT TOUCH THEM IF YOU DON'T KNOW WHAT YOU ARE DOING! 14 | - cdm - this is where you need to put your device_client_id_blob and device_private_key files. They need to be placed in `cdm/devices/chrome_1610/`. 15 | 16 | Next you need to fill out the auth.json file. 17 | 1. Go to www.onlyfans.com and login. 18 | 2. Press F12 to open dev tools and select the 'Network' tab. 19 | 3. In the search box type 'api' 20 | 21 | ![image](https://user-images.githubusercontent.com/132307467/235547370-5ef8e273-ebf7-4783-a13a-225f5959c606.png) 22 | 23 | 4. Click on one of the requests (if nothing shows up refresh the page or click on one of the tabs such as messages to make something appear). 24 | 5. After clicking on a request, make sure the headers tab is selected and then scroll down to find the 'Request Headers' section, this is where you should be able to find the information you need. 25 | 6. Copy the values of `cookie`, `user-agent`, `user-id` (this should just be a number, do not include a `u`) and `x-bc` to the `auth.json` file where the paths to yt-dlp, ffmpeg and mp4decrypt should already be. 26 | 7. Save the file. 27 | 28 | You should have something like this: 29 | 30 | `"USER_ID": "123456789"` - Do NOT include the `u` that gets exported using the Onlyfans Cookie Helper 31 | 32 | `"USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"` - Make sure this is set to your user-agent value 33 | 34 | `"X_BC": "2a9b28a68e7c03a9f0d3b98c28d70e8105e1f1df"` - Make sure this is set to your x-bc value 35 | 36 | `"COOKIE": "auth_id=123456789; sess=k3s9tnzdc8vt2h47ljxpmwqy5r;"` - Make sure you set auth_id to the same value as `user-id` and that you set your `sess` to your actual `sess` value, everytime you log out of Onlyfans this value will change so make sure to update it after every login. 37 | 38 | Next you will need to get 2 files, device_client_id_blob and device_private_key. These are used to get the decryption keys needed for downloading DRM videos. You can find a tutorial on how to do this here https://forum.videohelp.com/threads/408031-Dumping-Your-own-L3-CDM-with-Android-Studio 39 | Make sure you remove the file extensions when renaming the 2 files, and move both files into `cdm/devices/chrome_1610/`. 40 | 41 | Once you have filled all of the information out you can close auth.json and double click on OF DRM.exe and you should be ready to start downloading videos. 42 | 43 | # Videos 44 | This scraper is aimed to download DRM videos only and nothing else. 45 | 46 | You will get the choice to: 47 | - Download None of the videos found 48 | - Download All of the videos found 49 | - Select individual videos found, you will be able to see the Post/Message Id along with DateTime of that Post/Message. 50 | 51 | To navigate the menu the can use the ↑ & ↓ arrows, to select/deselect video(s) press the `space` key and after you are happy with your selection(s) press `enter` to start downloading. 52 | 53 | # Config Values 54 | `DownloadPaidPosts`: 55 | 56 | If set to `true` then any posts on the users feed that have been purchased by you and have DRM enabled videos will be scraped. 57 | 58 | If set to `false` no paid posts will be scraped. 59 | 60 | `DownloadPosts`: 61 | 62 | If set to `true` then any posts on the users feed that have DRM enabled videos will be scraped. 63 | 64 | If set to `false` no posts will be scraped. 65 | 66 | `DownloadArchived`: 67 | 68 | If set to `true` then any archived posts on the users feed that have DRM enabled videos will be scraped. 69 | 70 | If set to `false` no archived posts will be scraped. 71 | 72 | `DownloadMessages`: 73 | 74 | If set to `true` then any free messages that have DRM enabled videos will be scraped. 75 | 76 | If set to `false` no free messages will be scraped. 77 | 78 | `DownloadPaidMessages`: 79 | 80 | If set to `true` then any paid messages that have been purchased by you and have DRM enabled videos will be scraped. 81 | 82 | If set to `false` no paid messages will be scraped. 83 | 84 | `IncludeExpiredSubscriptions`: 85 | 86 | If set to `true` then any past subscriptions that aren't currently active are included in the list of accounts you can scrape. 87 | 88 | If set to `false` then any past subscriptions are excluded from the list of accounts you can scrape from. 89 | 90 | `DownloadPath`: 91 | 92 | If left blank then content will be downloaded to `__user_data__/sites/OnlyFans/{username}` 93 | 94 | An example is if you set the download path to `"S:/"` then content will be downloaded to `S:/{username}` 95 | 96 | # Donations 97 | If you would like to donate then here is a link to my ko-fi page https://ko-fi.com/sim0n00ps. Donations are not required but are very much appreciated:) 98 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace WidevineClient 10 | { 11 | class Utils 12 | { 13 | public static double EvaluateEquation(string equation, int decimals = 3) 14 | { 15 | var dataTable = new DataTable(); 16 | return Math.Round((double)dataTable.Compute(equation, ""), decimals); 17 | } 18 | 19 | public static string RunCommand(string command, string args) 20 | { 21 | Process p = new Process(); 22 | p.StartInfo.UseShellExecute = false; 23 | p.StartInfo.RedirectStandardOutput = true; 24 | p.StartInfo.FileName = command; 25 | p.StartInfo.Arguments = args; 26 | p.StartInfo.CreateNoWindow = true; 27 | p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; 28 | p.Start(); 29 | string output = p.StandardOutput.ReadToEnd(); 30 | p.WaitForExit(); 31 | return output; 32 | } 33 | 34 | public static int RunCommandCode(string command, string args) 35 | { 36 | Process p = new Process(); 37 | p.StartInfo.UseShellExecute = false; 38 | p.StartInfo.RedirectStandardOutput = false; 39 | p.StartInfo.FileName = command; 40 | p.StartInfo.Arguments = args; 41 | p.Start(); 42 | p.WaitForExit(); 43 | return p.ExitCode; 44 | } 45 | 46 | public static byte[] Xor(byte[] a, byte[] b) 47 | { 48 | byte[] x = new byte[Math.Min(a.Length, b.Length)]; 49 | 50 | for (int i = 0; i < x.Length; i++) 51 | { 52 | x[i] = (byte)(a[i] ^ b[i]); 53 | } 54 | 55 | return x; 56 | } 57 | 58 | public static string GenerateRandomId() 59 | { 60 | return BytesToHex(RandomBytes(3)).ToLower(); 61 | } 62 | 63 | public static byte[] RandomBytes(int length) 64 | { 65 | var bytes = new byte[length]; 66 | new Random().NextBytes(bytes); 67 | return bytes; 68 | } 69 | 70 | public static string[] GetElementsInnerTextByAttribute(string html, string element, string attribute) 71 | { 72 | List content = new List(); 73 | 74 | foreach (string line in html.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None)) 75 | { 76 | if (line.Contains("<" + element) && line.Contains(attribute)) 77 | { 78 | string contentPart = line.Substring(0, line.LastIndexOf("<")); 79 | if (contentPart.EndsWith(">")) 80 | contentPart = contentPart[..^1]; 81 | 82 | contentPart = contentPart[(contentPart.LastIndexOf(">") + 1)..]; 83 | 84 | if (contentPart.Contains("<")) 85 | contentPart = contentPart[..contentPart.IndexOf("<")]; 86 | 87 | content.Add(contentPart); 88 | } 89 | } 90 | return content.ToArray(); 91 | } 92 | 93 | public static string BytesToHex(byte[] data) 94 | { 95 | return BitConverter.ToString(data).Replace("-", ""); 96 | } 97 | public static byte[] HexToBytes(string hex) 98 | { 99 | hex = hex.Trim(); 100 | byte[] bytes = new byte[hex.Length / 2]; 101 | 102 | for (int i = 0; i < hex.Length; i += 2) 103 | bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); 104 | 105 | return bytes; 106 | } 107 | 108 | public static bool IsBase64Encoded(string str) 109 | { 110 | try 111 | { 112 | byte[] data = Convert.FromBase64String(str); 113 | return true; 114 | } 115 | catch 116 | { 117 | return false; 118 | } 119 | } 120 | 121 | public static string Base64Pad(string base64) 122 | { 123 | if (base64.Length % 4 != 0) 124 | { 125 | base64 = base64.PadRight(base64.Length + (4 - (base64.Length % 4)), '='); 126 | } 127 | return base64; 128 | } 129 | public static string Base64ToString(string base64) 130 | { 131 | return Encoding.UTF8.GetString(Convert.FromBase64String(base64)); 132 | } 133 | public static string StringToBase64(string str) 134 | { 135 | return Convert.ToBase64String(Encoding.UTF8.GetBytes(str)); 136 | } 137 | 138 | public static void TitleProgress(long read, long length) 139 | { 140 | long readMB = read / 1024 / 1024; 141 | long lengthMB = length / 1024 / 1024; 142 | Console.Title = $"{readMB}/{lengthMB}MB"; 143 | } 144 | 145 | public static void TitleProgressNoConversion(long read, long length) 146 | { 147 | Console.Title = $"{read}/{length}MB"; 148 | } 149 | 150 | public static string Version() 151 | { 152 | return System.Reflection.Assembly.GetCallingAssembly().GetName().Version.ToString(); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/Messages.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace OF_DRM_Video_Downloader.Entities 9 | { 10 | public class Messages 11 | { 12 | public List list { get; set; } 13 | public bool hasMore { get; set; } 14 | public long? nextLastId { get; set; } 15 | public class Dash 16 | { 17 | [JsonProperty("CloudFront-Policy")] 18 | public string CloudFrontPolicy { get; set; } 19 | 20 | [JsonProperty("CloudFront-Signature")] 21 | public string CloudFrontSignature { get; set; } 22 | 23 | [JsonProperty("CloudFront-Key-Pair-Id")] 24 | public string CloudFrontKeyPairId { get; set; } 25 | } 26 | 27 | public class Drm 28 | { 29 | public Manifest manifest { get; set; } 30 | public Signature signature { get; set; } 31 | } 32 | 33 | public class Files 34 | { 35 | public Drm drm { get; set; } 36 | } 37 | 38 | public class FromUser 39 | { 40 | public int? id { get; set; } 41 | public string _view { get; set; } 42 | } 43 | 44 | public class Hls 45 | { 46 | [JsonProperty("CloudFront-Policy")] 47 | public string CloudFrontPolicy { get; set; } 48 | 49 | [JsonProperty("CloudFront-Signature")] 50 | public string CloudFrontSignature { get; set; } 51 | 52 | [JsonProperty("CloudFront-Key-Pair-Id")] 53 | public string CloudFrontKeyPairId { get; set; } 54 | } 55 | 56 | public class Info 57 | { 58 | public Source source { get; set; } 59 | public Preview preview { get; set; } 60 | } 61 | 62 | public class List 63 | { 64 | public string responseType { get; set; } 65 | public string text { get; set; } 66 | public object giphyId { get; set; } 67 | public bool? lockedText { get; set; } 68 | public bool? isFree { get; set; } 69 | public string? price { get; set; } 70 | public bool? isMediaReady { get; set; } 71 | public int? mediaCount { get; set; } 72 | public List media { get; set; } 73 | public List previews { get; set; } 74 | public bool? isTip { get; set; } 75 | public bool? isReportedByMe { get; set; } 76 | public bool? isCouplePeopleMedia { get; set; } 77 | public object queueId { get; set; } 78 | public FromUser fromUser { get; set; } 79 | public bool? isFromQueue { get; set; } 80 | public bool? canUnsendQueue { get; set; } 81 | public int? unsendSecondsQueue { get; set; } 82 | public long id { get; set; } 83 | public bool? isOpened { get; set; } 84 | public bool? isNew { get; set; } 85 | public DateTime? createdAt { get; set; } 86 | public DateTime? changedAt { get; set; } 87 | public int? cancelSeconds { get; set; } 88 | public bool? isLiked { get; set; } 89 | public bool? canPurchase { get; set; } 90 | public string canPurchaseReason { get; set; } 91 | public bool? canReport { get; set; } 92 | public bool? canBePinned { get; set; } 93 | public bool? isPinned { get; set; } 94 | } 95 | 96 | public class Manifest 97 | { 98 | public string hls { get; set; } 99 | public string dash { get; set; } 100 | } 101 | 102 | public class Medium 103 | { 104 | public long id { get; set; } 105 | public bool canView { get; set; } 106 | public string type { get; set; } 107 | public object src { get; set; } 108 | public string preview { get; set; } 109 | public string thumb { get; set; } 110 | public object locked { get; set; } 111 | public int? duration { get; set; } 112 | public bool? hasError { get; set; } 113 | public string squarePreview { get; set; } 114 | public Video video { get; set; } 115 | public VideoSources videoSources { get; set; } 116 | public Source source { get; set; } 117 | public Info info { get; set; } 118 | public Files files { get; set; } 119 | } 120 | 121 | public class Preview 122 | { 123 | public int? width { get; set; } 124 | public int? height { get; set; } 125 | public int? size { get; set; } 126 | } 127 | 128 | public class Signature 129 | { 130 | public Hls hls { get; set; } 131 | public Dash dash { get; set; } 132 | } 133 | 134 | public class Source 135 | { 136 | public object source { get; set; } 137 | public int? width { get; set; } 138 | public int? height { get; set; } 139 | public int? size { get; set; } 140 | } 141 | 142 | public class Video 143 | { 144 | public object mp4 { get; set; } 145 | } 146 | 147 | public class VideoSources 148 | { 149 | [JsonProperty("720")] 150 | public object _720 { get; set; } 151 | 152 | [JsonProperty("240")] 153 | public object _240 { get; set; } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/PaidMessages.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace OF_DRM_Video_Downloader.Entities 9 | { 10 | public class PaidMessages 11 | { 12 | public List list { get; set; } 13 | public bool hasMore { get; set; } 14 | public long? nextLastId { get; set; } 15 | public class Dash 16 | { 17 | [JsonProperty("CloudFront-Policy")] 18 | public string CloudFrontPolicy { get; set; } 19 | 20 | [JsonProperty("CloudFront-Signature")] 21 | public string CloudFrontSignature { get; set; } 22 | 23 | [JsonProperty("CloudFront-Key-Pair-Id")] 24 | public string CloudFrontKeyPairId { get; set; } 25 | } 26 | 27 | public class Drm 28 | { 29 | public Manifest manifest { get; set; } 30 | public Signature signature { get; set; } 31 | } 32 | 33 | public class Files 34 | { 35 | public Drm drm { get; set; } 36 | } 37 | 38 | public class FromUser 39 | { 40 | public int id { get; set; } 41 | public string _view { get; set; } 42 | } 43 | 44 | public class Hls 45 | { 46 | [JsonProperty("CloudFront-Policy")] 47 | public string CloudFrontPolicy { get; set; } 48 | 49 | [JsonProperty("CloudFront-Signature")] 50 | public string CloudFrontSignature { get; set; } 51 | 52 | [JsonProperty("CloudFront-Key-Pair-Id")] 53 | public string CloudFrontKeyPairId { get; set; } 54 | } 55 | 56 | public class Info 57 | { 58 | public Source source { get; set; } 59 | public Preview preview { get; set; } 60 | } 61 | 62 | public class List 63 | { 64 | public string responseType { get; set; } 65 | public string text { get; set; } 66 | public object giphyId { get; set; } 67 | public bool? lockedText { get; set; } 68 | public bool? isFree { get; set; } 69 | public string? price { get; set; } 70 | public bool? isMediaReady { get; set; } 71 | public int? mediaCount { get; set; } 72 | public List media { get; set; } 73 | public List previews { get; set; } 74 | public bool? isTip { get; set; } 75 | public bool? isReportedByMe { get; set; } 76 | public bool? isCouplePeopleMedia { get; set; } 77 | public object queueId { get; set; } 78 | public FromUser fromUser { get; set; } 79 | public bool? isFromQueue { get; set; } 80 | public bool? canUnsendQueue { get; set; } 81 | public int? unsendSecondsQueue { get; set; } 82 | public long id { get; set; } 83 | public bool? isOpened { get; set; } 84 | public bool? isNew { get; set; } 85 | public DateTime createdAt { get; set; } 86 | public DateTime? changedAt { get; set; } 87 | public int? cancelSeconds { get; set; } 88 | public bool? isLiked { get; set; } 89 | public bool? canPurchase { get; set; } 90 | public string canPurchaseReason { get; set; } 91 | public bool? canReport { get; set; } 92 | public bool? canBePinned { get; set; } 93 | public bool? isPinned { get; set; } 94 | } 95 | 96 | public class Manifest 97 | { 98 | public string hls { get; set; } 99 | public string dash { get; set; } 100 | } 101 | 102 | public class Medium 103 | { 104 | public long id { get; set; } 105 | public bool canView { get; set; } 106 | public string type { get; set; } 107 | public object src { get; set; } 108 | public string preview { get; set; } 109 | public string thumb { get; set; } 110 | public object locked { get; set; } 111 | public int? duration { get; set; } 112 | public bool? hasError { get; set; } 113 | public string squarePreview { get; set; } 114 | public Video video { get; set; } 115 | public VideoSources videoSources { get; set; } 116 | public Source source { get; set; } 117 | public Info info { get; set; } 118 | public Files files { get; set; } 119 | } 120 | 121 | public class Preview 122 | { 123 | public int? width { get; set; } 124 | public int? height { get; set; } 125 | public int? size { get; set; } 126 | } 127 | 128 | public class Signature 129 | { 130 | public Hls hls { get; set; } 131 | public Dash dash { get; set; } 132 | } 133 | 134 | public class Source 135 | { 136 | public object source { get; set; } 137 | public int? width { get; set; } 138 | public int? height { get; set; } 139 | public int? size { get; set; } 140 | } 141 | 142 | public class Video 143 | { 144 | public object mp4 { get; set; } 145 | } 146 | 147 | public class VideoSources 148 | { 149 | [JsonProperty("720")] 150 | public object _720 { get; set; } 151 | 152 | [JsonProperty("240")] 153 | public object _240 { get; set; } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/Purchased.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace OF_DRM_Video_Downloader.Entities 9 | { 10 | public class Purchased 11 | { 12 | public List list { get; set; } 13 | public bool hasMore { get; set; } 14 | public class Dash 15 | { 16 | [JsonProperty("CloudFront-Policy")] 17 | public string CloudFrontPolicy { get; set; } 18 | 19 | [JsonProperty("CloudFront-Signature")] 20 | public string CloudFrontSignature { get; set; } 21 | 22 | [JsonProperty("CloudFront-Key-Pair-Id")] 23 | public string CloudFrontKeyPairId { get; set; } 24 | } 25 | 26 | public class Drm 27 | { 28 | public Manifest manifest { get; set; } 29 | public Signature signature { get; set; } 30 | } 31 | 32 | public class Files 33 | { 34 | public Drm drm { get; set; } 35 | } 36 | 37 | public class FromUser 38 | { 39 | public int id { get; set; } 40 | public string _view { get; set; } 41 | } 42 | 43 | public class Hls 44 | { 45 | [JsonProperty("CloudFront-Policy")] 46 | public string CloudFrontPolicy { get; set; } 47 | 48 | [JsonProperty("CloudFront-Signature")] 49 | public string CloudFrontSignature { get; set; } 50 | 51 | [JsonProperty("CloudFront-Key-Pair-Id")] 52 | public string CloudFrontKeyPairId { get; set; } 53 | } 54 | 55 | public class Info 56 | { 57 | public Source source { get; set; } 58 | public Preview preview { get; set; } 59 | } 60 | 61 | public class List 62 | { 63 | public string responseType { get; set; } 64 | public string text { get; set; } 65 | public object giphyId { get; set; } 66 | public bool? lockedText { get; set; } 67 | public bool? isFree { get; set; } 68 | public string? price { get; set; } 69 | public bool? isMediaReady { get; set; } 70 | public int? mediaCount { get; set; } 71 | public List media { get; set; } 72 | public List previews { get; set; } 73 | public List preview { get; set; } 74 | public bool? isTip { get; set; } 75 | public bool? isReportedByMe { get; set; } 76 | public bool? isCouplePeopleMedia { get; set; } 77 | public object queueId { get; set; } 78 | public FromUser fromUser { get; set; } 79 | public bool? isFromQueue { get; set; } 80 | public bool? canUnsendQueue { get; set; } 81 | public int? unsendSecondsQueue { get; set; } 82 | public long id { get; set; } 83 | public bool isOpened { get; set; } 84 | public bool? isNew { get; set; } 85 | public DateTime? createdAt { get; set; } 86 | public DateTime? postedAt { get; set; } 87 | public DateTime? changedAt { get; set; } 88 | public int? cancelSeconds { get; set; } 89 | public bool? isLiked { get; set; } 90 | public bool? canPurchase { get; set; } 91 | public bool? canReport { get; set; } 92 | public bool? isCanceled { get; set; } 93 | public bool? isArchived { get; set; } 94 | } 95 | 96 | public class Manifest 97 | { 98 | public string hls { get; set; } 99 | public string dash { get; set; } 100 | } 101 | 102 | public class Medium 103 | { 104 | public long id { get; set; } 105 | public bool canView { get; set; } 106 | public string type { get; set; } 107 | public string src { get; set; } 108 | public string preview { get; set; } 109 | public string thumb { get; set; } 110 | public object locked { get; set; } 111 | public int? duration { get; set; } 112 | public bool? hasError { get; set; } 113 | public string squarePreview { get; set; } 114 | public VideoSources videoSources { get; set; } 115 | public Source source { get; set; } 116 | public Info info { get; set; } 117 | public Video video { get; set; } 118 | public Files files { get; set; } 119 | } 120 | 121 | public class Preview 122 | { 123 | public int? width { get; set; } 124 | public int? height { get; set; } 125 | public int? size { get; set; } 126 | } 127 | 128 | public class Signature 129 | { 130 | public Hls hls { get; set; } 131 | public Dash dash { get; set; } 132 | } 133 | 134 | public class Source 135 | { 136 | public string source { get; set; } 137 | public int? width { get; set; } 138 | public int? height { get; set; } 139 | public int? size { get; set; } 140 | } 141 | 142 | public class Video 143 | { 144 | public string mp4 { get; set; } 145 | } 146 | 147 | public class VideoSources 148 | { 149 | [JsonProperty("720")] 150 | public string _720 { get; set; } 151 | 152 | [JsonProperty("240")] 153 | public string _240 { get; set; } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/Post.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace OF_DRM_Video_Downloader.Entities 9 | { 10 | public class Post 11 | { 12 | public List list { get; set; } 13 | public bool hasMore { get; set; } 14 | public string headMarker { get; set; } 15 | public string tailMarker { get; set; } 16 | public class Author 17 | { 18 | public int? id { get; set; } 19 | public string _view { get; set; } 20 | } 21 | 22 | public class Dash 23 | { 24 | [JsonProperty("CloudFront-Policy")] 25 | public string CloudFrontPolicy { get; set; } 26 | 27 | [JsonProperty("CloudFront-Signature")] 28 | public string CloudFrontSignature { get; set; } 29 | 30 | [JsonProperty("CloudFront-Key-Pair-Id")] 31 | public string CloudFrontKeyPairId { get; set; } 32 | } 33 | 34 | public class Drm 35 | { 36 | public Manifest manifest { get; set; } 37 | public Signature signature { get; set; } 38 | } 39 | 40 | public class Files 41 | { 42 | public Preview preview { get; set; } 43 | public Drm drm { get; set; } 44 | } 45 | 46 | public class Hls 47 | { 48 | [JsonProperty("CloudFront-Policy")] 49 | public string CloudFrontPolicy { get; set; } 50 | 51 | [JsonProperty("CloudFront-Signature")] 52 | public string CloudFrontSignature { get; set; } 53 | 54 | [JsonProperty("CloudFront-Key-Pair-Id")] 55 | public string CloudFrontKeyPairId { get; set; } 56 | } 57 | 58 | public class Info 59 | { 60 | public Source source { get; set; } 61 | public Preview preview { get; set; } 62 | } 63 | 64 | public class List 65 | { 66 | public string responseType { get; set; } 67 | public long id { get; set; } 68 | public DateTime postedAt { get; set; } 69 | public string postedAtPrecise { get; set; } 70 | public object expiredAt { get; set; } 71 | public Author author { get; set; } 72 | public string text { get; set; } 73 | public string rawText { get; set; } 74 | public bool? lockedText { get; set; } 75 | public bool? isFavorite { get; set; } 76 | public bool? canReport { get; set; } 77 | public bool? canDelete { get; set; } 78 | public bool? canComment { get; set; } 79 | public bool? canEdit { get; set; } 80 | public bool? isPinned { get; set; } 81 | public int? favoritesCount { get; set; } 82 | public int? mediaCount { get; set; } 83 | public bool? isMediaReady { get; set; } 84 | public object voting { get; set; } 85 | public bool isOpened { get; set; } 86 | public bool? canToggleFavorite { get; set; } 87 | public object streamId { get; set; } 88 | public string? price { get; set; } 89 | public bool? hasVoting { get; set; } 90 | public bool? isAddedToBookmarks { get; set; } 91 | public bool isArchived { get; set; } 92 | public bool? isPrivateArchived { get; set; } 93 | public bool? isDeleted { get; set; } 94 | public bool? hasUrl { get; set; } 95 | public bool? isCouplePeopleMedia { get; set; } 96 | public string cantCommentReason { get; set; } 97 | public int? votingType { get; set; } 98 | public int? commentsCount { get; set; } 99 | public List mentionedUsers { get; set; } 100 | public List linkedUsers { get; set; } 101 | public List linkedPosts { get; set; } 102 | public bool? canVote { get; set; } 103 | public List media { get; set; } 104 | public bool canViewMedia { get; set; } 105 | public List preview { get; set; } 106 | } 107 | 108 | public class Manifest 109 | { 110 | public string? hls { get; set; } 111 | public string? dash { get; set; } 112 | } 113 | 114 | public class Medium 115 | { 116 | public long id { get; set; } 117 | public string type { get; set; } 118 | public bool? convertedToVideo { get; set; } 119 | public bool canView { get; set; } 120 | public bool? hasError { get; set; } 121 | public DateTime? createdAt { get; set; } 122 | public Info info { get; set; } 123 | public Source source { get; set; } 124 | public string squarePreview { get; set; } 125 | public string full { get; set; } 126 | public string preview { get; set; } 127 | public string thumb { get; set; } 128 | public Files files { get; set; } 129 | public VideoSources videoSources { get; set; } 130 | } 131 | 132 | public class Preview 133 | { 134 | public int? width { get; set; } 135 | public int? height { get; set; } 136 | public int? size { get; set; } 137 | public string url { get; set; } 138 | } 139 | 140 | public class Signature 141 | { 142 | public Hls hls { get; set; } 143 | public Dash dash { get; set; } 144 | } 145 | 146 | public class Source 147 | { 148 | public string? source { get; set; } 149 | public int? width { get; set; } 150 | public int? height { get; set; } 151 | public int? size { get; set; } 152 | public int? duration { get; set; } 153 | } 154 | 155 | public class VideoSources 156 | { 157 | [JsonProperty("720")] 158 | public object _720 { get; set; } 159 | 160 | [JsonProperty("240")] 161 | public object _240 { get; set; } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/UsersList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class UsersList 10 | { 11 | public string view { get; set; } 12 | public string avatar { get; set; } 13 | public AvatarThumbs avatarThumbs { get; set; } 14 | public string header { get; set; } 15 | public HeaderSize headerSize { get; set; } 16 | public HeaderThumbs headerThumbs { get; set; } 17 | public int? id { get; set; } 18 | public string name { get; set; } 19 | public string username { get; set; } 20 | public bool? canLookStory { get; set; } 21 | public bool? canCommentStory { get; set; } 22 | public bool? hasNotViewedStory { get; set; } 23 | public bool? isVerified { get; set; } 24 | public bool? canPayInternal { get; set; } 25 | public bool? hasScheduledStream { get; set; } 26 | public bool? hasStream { get; set; } 27 | public bool? hasStories { get; set; } 28 | public bool? tipsEnabled { get; set; } 29 | public bool? tipsTextEnabled { get; set; } 30 | public int? tipsMin { get; set; } 31 | public int? tipsMinInternal { get; set; } 32 | public int? tipsMax { get; set; } 33 | public bool? canEarn { get; set; } 34 | public bool? canAddSubscriber { get; set; } 35 | public string? subscribePrice { get; set; } 36 | public List subscriptionBundles { get; set; } 37 | public string displayName { get; set; } 38 | public string notice { get; set; } 39 | public bool? isPaywallRequired { get; set; } 40 | public bool? unprofitable { get; set; } 41 | public List listsStates { get; set; } 42 | public bool? isMuted { get; set; } 43 | public bool? isRestricted { get; set; } 44 | public bool? canRestrict { get; set; } 45 | public bool? subscribedBy { get; set; } 46 | public bool? subscribedByExpire { get; set; } 47 | public DateTime? subscribedByExpireDate { get; set; } 48 | public bool? subscribedByAutoprolong { get; set; } 49 | public bool? subscribedIsExpiredNow { get; set; } 50 | public string? currentSubscribePrice { get; set; } 51 | public bool? subscribedOn { get; set; } 52 | public bool? subscribedOnExpiredNow { get; set; } 53 | public string subscribedOnDuration { get; set; } 54 | public bool? canReport { get; set; } 55 | public bool? canReceiveChatMessage { get; set; } 56 | public bool? hideChat { get; set; } 57 | public DateTime? lastSeen { get; set; } 58 | public bool? isPerformer { get; set; } 59 | public bool? isRealPerformer { get; set; } 60 | public SubscribedByData subscribedByData { get; set; } 61 | public SubscribedOnData subscribedOnData { get; set; } 62 | public bool? canTrialSend { get; set; } 63 | public bool? isBlocked { get; set; } 64 | public List promoOffers { get; set; } 65 | public class AvatarThumbs 66 | { 67 | public string c50 { get; set; } 68 | public string c144 { get; set; } 69 | } 70 | 71 | public class HeaderSize 72 | { 73 | public int? width { get; set; } 74 | public int? height { get; set; } 75 | } 76 | 77 | public class HeaderThumbs 78 | { 79 | public string w480 { get; set; } 80 | public string w760 { get; set; } 81 | } 82 | 83 | public class ListsState 84 | { 85 | public string id { get; set; } 86 | public string type { get; set; } 87 | public string name { get; set; } 88 | public bool hasUser { get; set; } 89 | public bool canAddUser { get; set; } 90 | } 91 | 92 | public class Subscribe 93 | { 94 | public object id { get; set; } 95 | public int? userId { get; set; } 96 | public int? subscriberId { get; set; } 97 | public DateTime? date { get; set; } 98 | public int? duration { get; set; } 99 | public DateTime? startDate { get; set; } 100 | public DateTime? expireDate { get; set; } 101 | public object cancelDate { get; set; } 102 | public string? price { get; set; } 103 | public string? regularPrice { get; set; } 104 | public string? discount { get; set; } 105 | public string action { get; set; } 106 | public string type { get; set; } 107 | public object offerStart { get; set; } 108 | public object offerEnd { get; set; } 109 | public bool? isCurrent { get; set; } 110 | } 111 | 112 | public class SubscribedByData 113 | { 114 | public string? price { get; set; } 115 | public string? newPrice { get; set; } 116 | public string? regularPrice { get; set; } 117 | public string? subscribePrice { get; set; } 118 | public int? discountPercent { get; set; } 119 | public int? discountPeriod { get; set; } 120 | public DateTime? subscribeAt { get; set; } 121 | public DateTime? expiredAt { get; set; } 122 | public object renewedAt { get; set; } 123 | public object discountFinishedAt { get; set; } 124 | public object discountStartedAt { get; set; } 125 | public string status { get; set; } 126 | public bool? isMuted { get; set; } 127 | public string unsubscribeReason { get; set; } 128 | public string duration { get; set; } 129 | public bool? showPostsInFeed { get; set; } 130 | public List subscribes { get; set; } 131 | } 132 | 133 | public class SubscribedOnData 134 | { 135 | public string? price { get; set; } 136 | public string? newPrice { get; set; } 137 | public string? regularPrice { get; set; } 138 | public string? subscribePrice { get; set; } 139 | public int? discountPercent { get; set; } 140 | public int? discountPeriod { get; set; } 141 | public DateTime? subscribeAt { get; set; } 142 | public DateTime? expiredAt { get; set; } 143 | public object renewedAt { get; set; } 144 | public object discountFinishedAt { get; set; } 145 | public object discountStartedAt { get; set; } 146 | public object status { get; set; } 147 | public bool? isMuted { get; set; } 148 | public string unsubscribeReason { get; set; } 149 | public string duration { get; set; } 150 | public int? tipsSumm { get; set; } 151 | public int? subscribesSumm { get; set; } 152 | public int? messagesSumm { get; set; } 153 | public int? postsSumm { get; set; } 154 | public int? streamsSumm { get; set; } 155 | public int? totalSumm { get; set; } 156 | public DateTime? lastActivity { get; set; } 157 | public int? recommendations { get; set; } 158 | public List subscribes { get; set; } 159 | } 160 | 161 | public class SubscriptionBundle 162 | { 163 | public int? id { get; set; } 164 | public int? discount { get; set; } 165 | public int? duration { get; set; } 166 | public string? price { get; set; } 167 | public bool? canBuy { get; set; } 168 | } 169 | 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/Subscriptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class Subscriptions 10 | { 11 | public List list { get; set; } 12 | public bool hasMore { get; set; } 13 | public class AvatarThumbs 14 | { 15 | public string c50 { get; set; } 16 | public string c144 { get; set; } 17 | } 18 | 19 | public class HeaderSize 20 | { 21 | public int? width { get; set; } 22 | public int? height { get; set; } 23 | } 24 | 25 | public class HeaderThumbs 26 | { 27 | public string w480 { get; set; } 28 | public string w760 { get; set; } 29 | } 30 | 31 | public class List 32 | { 33 | public string view { get; set; } 34 | public string avatar { get; set; } 35 | public AvatarThumbs avatarThumbs { get; set; } 36 | public string header { get; set; } 37 | public HeaderSize headerSize { get; set; } 38 | public HeaderThumbs headerThumbs { get; set; } 39 | public int id { get; set; } 40 | public string name { get; set; } 41 | public string username { get; set; } 42 | public bool? canLookStory { get; set; } 43 | public bool? canCommentStory { get; set; } 44 | public bool? hasNotViewedStory { get; set; } 45 | public bool? isVerified { get; set; } 46 | public bool? canPayInternal { get; set; } 47 | public bool? hasScheduledStream { get; set; } 48 | public bool? hasStream { get; set; } 49 | public bool? hasStories { get; set; } 50 | public bool? tipsEnabled { get; set; } 51 | public bool? tipsTextEnabled { get; set; } 52 | public int? tipsMin { get; set; } 53 | public int? tipsMinInternal { get; set; } 54 | public int? tipsMax { get; set; } 55 | public bool? canEarn { get; set; } 56 | public bool? canAddSubscriber { get; set; } 57 | public string? subscribePrice { get; set; } 58 | public bool? isPaywallRequired { get; set; } 59 | public bool? unprofitable { get; set; } 60 | public List listsStates { get; set; } 61 | public bool? isMuted { get; set; } 62 | public bool? isRestricted { get; set; } 63 | public bool? canRestrict { get; set; } 64 | public bool? subscribedBy { get; set; } 65 | public bool? subscribedByExpire { get; set; } 66 | public DateTime? subscribedByExpireDate { get; set; } 67 | public bool? subscribedByAutoprolong { get; set; } 68 | public bool? subscribedIsExpiredNow { get; set; } 69 | public string? currentSubscribePrice { get; set; } 70 | public bool? subscribedOn { get; set; } 71 | public bool? subscribedOnExpiredNow { get; set; } 72 | public string subscribedOnDuration { get; set; } 73 | public bool? canReport { get; set; } 74 | public bool? canReceiveChatMessage { get; set; } 75 | public bool? hideChat { get; set; } 76 | public DateTime? lastSeen { get; set; } 77 | public bool? isPerformer { get; set; } 78 | public bool? isRealPerformer { get; set; } 79 | public SubscribedByData subscribedByData { get; set; } 80 | public SubscribedOnData subscribedOnData { get; set; } 81 | public bool? canTrialSend { get; set; } 82 | public bool? isBlocked { get; set; } 83 | public string displayName { get; set; } 84 | public string notice { get; set; } 85 | } 86 | 87 | public class ListsState 88 | { 89 | public object id { get; set; } 90 | public string type { get; set; } 91 | public string name { get; set; } 92 | public bool? hasUser { get; set; } 93 | public bool? canAddUser { get; set; } 94 | } 95 | 96 | public class Subscribe 97 | { 98 | public object id { get; set; } 99 | public int? userId { get; set; } 100 | public int? subscriberId { get; set; } 101 | public DateTime? date { get; set; } 102 | public int? duration { get; set; } 103 | public DateTime? startDate { get; set; } 104 | public DateTime? expireDate { get; set; } 105 | public object cancelDate { get; set; } 106 | public string? price { get; set; } 107 | public string? regularPrice { get; set; } 108 | public string? discount { get; set; } 109 | public string action { get; set; } 110 | public string type { get; set; } 111 | public object offerStart { get; set; } 112 | public object offerEnd { get; set; } 113 | public bool? isCurrent { get; set; } 114 | } 115 | 116 | public class SubscribedByData 117 | { 118 | public string? price { get; set; } 119 | public string? newPrice { get; set; } 120 | public string? regularPrice { get; set; } 121 | public string? subscribePrice { get; set; } 122 | public int? discountPercent { get; set; } 123 | public int? discountPeriod { get; set; } 124 | public DateTime? subscribeAt { get; set; } 125 | public DateTime? expiredAt { get; set; } 126 | public DateTime? renewedAt { get; set; } 127 | public object discountFinishedAt { get; set; } 128 | public object discountStartedAt { get; set; } 129 | public string status { get; set; } 130 | public bool? isMuted { get; set; } 131 | public string unsubscribeReason { get; set; } 132 | public string duration { get; set; } 133 | public bool? showPostsInFeed { get; set; } 134 | public List subscribes { get; set; } 135 | public bool? hasActivePaidSubscriptions { get; set; } 136 | } 137 | 138 | public class SubscribedOnData 139 | { 140 | public string? price { get; set; } 141 | public string? newPrice { get; set; } 142 | public string? regularPrice { get; set; } 143 | public string? subscribePrice { get; set; } 144 | public int? discountPercent { get; set; } 145 | public int? discountPeriod { get; set; } 146 | public DateTime? subscribeAt { get; set; } 147 | public DateTime? expiredAt { get; set; } 148 | public DateTime? renewedAt { get; set; } 149 | public object discountFinishedAt { get; set; } 150 | public object discountStartedAt { get; set; } 151 | public object status { get; set; } 152 | public bool? isMuted { get; set; } 153 | public string unsubscribeReason { get; set; } 154 | public string duration { get; set; } 155 | public int? tipsSumm { get; set; } 156 | public int? subscribesSumm { get; set; } 157 | public int? messagesSumm { get; set; } 158 | public int? postsSumm { get; set; } 159 | public int? streamsSumm { get; set; } 160 | public int? totalSumm { get; set; } 161 | public List subscribes { get; set; } 162 | public bool? hasActivePaidSubscriptions { get; set; } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/Archived.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace OF_DRM_Video_Downloader.Entities 9 | { 10 | public class Archived 11 | { 12 | public List list { get; set; } 13 | public bool hasMore { get; set; } 14 | public string headMarker { get; set; } 15 | public string tailMarker { get; set; } 16 | public Counters counters { get; set; } 17 | public class Author 18 | { 19 | public int? id { get; set; } 20 | public string _view { get; set; } 21 | } 22 | 23 | public class Counters 24 | { 25 | public int? audiosCount { get; set; } 26 | public int? photosCount { get; set; } 27 | public int? videosCount { get; set; } 28 | public int? mediasCount { get; set; } 29 | public int? postsCount { get; set; } 30 | public int? streamsCount { get; set; } 31 | public int? archivedPostsCount { get; set; } 32 | } 33 | 34 | public class Dash 35 | { 36 | [JsonProperty("CloudFront-Policy")] 37 | public string CloudFrontPolicy { get; set; } 38 | 39 | [JsonProperty("CloudFront-Signature")] 40 | public string CloudFrontSignature { get; set; } 41 | 42 | [JsonProperty("CloudFront-Key-Pair-Id")] 43 | public string CloudFrontKeyPairId { get; set; } 44 | } 45 | 46 | public class Drm 47 | { 48 | public Manifest manifest { get; set; } 49 | public Signature signature { get; set; } 50 | } 51 | 52 | public class Files 53 | { 54 | public Preview preview { get; set; } 55 | public Drm drm { get; set; } 56 | } 57 | 58 | public class Hls 59 | { 60 | [JsonProperty("CloudFront-Policy")] 61 | public string CloudFrontPolicy { get; set; } 62 | 63 | [JsonProperty("CloudFront-Signature")] 64 | public string CloudFrontSignature { get; set; } 65 | 66 | [JsonProperty("CloudFront-Key-Pair-Id")] 67 | public string CloudFrontKeyPairId { get; set; } 68 | } 69 | 70 | public class Info 71 | { 72 | public Source source { get; set; } 73 | public Preview preview { get; set; } 74 | } 75 | 76 | public class LinkedPost 77 | { 78 | public string responseType { get; set; } 79 | public int? id { get; set; } 80 | public DateTime? postedAt { get; set; } 81 | public string postedAtPrecise { get; set; } 82 | public object expiredAt { get; set; } 83 | public Author author { get; set; } 84 | public string text { get; set; } 85 | public string rawText { get; set; } 86 | public bool? lockedText { get; set; } 87 | public bool? isFavorite { get; set; } 88 | public bool? canReport { get; set; } 89 | public bool? canDelete { get; set; } 90 | public bool? canComment { get; set; } 91 | public bool? canEdit { get; set; } 92 | public bool? isPinned { get; set; } 93 | public int? favoritesCount { get; set; } 94 | public int? mediaCount { get; set; } 95 | public bool? isMediaReady { get; set; } 96 | public List voting { get; set; } 97 | public bool? isOpened { get; set; } 98 | public bool? canToggleFavorite { get; set; } 99 | public object streamId { get; set; } 100 | public string? price { get; set; } 101 | public bool? hasVoting { get; set; } 102 | public bool? isAddedToBookmarks { get; set; } 103 | public bool? isArchived { get; set; } 104 | public bool? isPrivateArchived { get; set; } 105 | public bool? isDeleted { get; set; } 106 | public bool? hasUrl { get; set; } 107 | public bool? isCouplePeopleMedia { get; set; } 108 | public string cantCommentReason { get; set; } 109 | public int? commentsCount { get; set; } 110 | public List mentionedUsers { get; set; } 111 | public List linkedUsers { get; set; } 112 | public List linkedPosts { get; set; } 113 | public List media { get; set; } 114 | public bool? canViewMedia { get; set; } 115 | public List preview { get; set; } 116 | } 117 | 118 | public class List 119 | { 120 | public string responseType { get; set; } 121 | public long id { get; set; } 122 | public DateTime postedAt { get; set; } 123 | public string postedAtPrecise { get; set; } 124 | public object expiredAt { get; set; } 125 | public Author author { get; set; } 126 | public string text { get; set; } 127 | public string rawText { get; set; } 128 | public bool? lockedText { get; set; } 129 | public bool? isFavorite { get; set; } 130 | public bool? canReport { get; set; } 131 | public bool? canDelete { get; set; } 132 | public bool? canComment { get; set; } 133 | public bool? canEdit { get; set; } 134 | public bool? isPinned { get; set; } 135 | public int? favoritesCount { get; set; } 136 | public int? mediaCount { get; set; } 137 | public bool? isMediaReady { get; set; } 138 | public object voting { get; set; } 139 | public bool isOpened { get; set; } 140 | public bool? canToggleFavorite { get; set; } 141 | public object streamId { get; set; } 142 | public string price { get; set; } 143 | public bool? hasVoting { get; set; } 144 | public bool? isAddedToBookmarks { get; set; } 145 | public bool isArchived { get; set; } 146 | public bool? isPrivateArchived { get; set; } 147 | public bool? isDeleted { get; set; } 148 | public bool? hasUrl { get; set; } 149 | public bool? isCouplePeopleMedia { get; set; } 150 | public int? commentsCount { get; set; } 151 | public List mentionedUsers { get; set; } 152 | public List linkedUsers { get; set; } 153 | public List linkedPosts { get; set; } 154 | public List media { get; set; } 155 | public bool canViewMedia { get; set; } 156 | public List preview { get; set; } 157 | public string cantCommentReason { get; set; } 158 | } 159 | 160 | public class Manifest 161 | { 162 | public string hls { get; set; } 163 | public string dash { get; set; } 164 | } 165 | 166 | public class Medium 167 | { 168 | public long id { get; set; } 169 | public string type { get; set; } 170 | public bool? convertedToVideo { get; set; } 171 | public bool canView { get; set; } 172 | public bool? hasError { get; set; } 173 | public DateTime? createdAt { get; set; } 174 | public Info info { get; set; } 175 | public Source source { get; set; } 176 | public string squarePreview { get; set; } 177 | public string full { get; set; } 178 | public string preview { get; set; } 179 | public string thumb { get; set; } 180 | public Files files { get; set; } 181 | public VideoSources videoSources { get; set; } 182 | } 183 | 184 | public class Preview 185 | { 186 | public int? width { get; set; } 187 | public int? height { get; set; } 188 | public int? size { get; set; } 189 | public string url { get; set; } 190 | } 191 | 192 | public class Signature 193 | { 194 | public Hls hls { get; set; } 195 | public Dash dash { get; set; } 196 | } 197 | 198 | public class Source 199 | { 200 | public string source { get; set; } 201 | public int? width { get; set; } 202 | public int? height { get; set; } 203 | public int? size { get; set; } 204 | public int? duration { get; set; } 205 | } 206 | 207 | public class VideoSources 208 | { 209 | [JsonProperty("720")] 210 | public string _720 { get; set; } 211 | 212 | [JsonProperty("240")] 213 | public string _240 { get; set; } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Entities/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OF_DRM_Video_Downloader.Entities 8 | { 9 | public class User 10 | { 11 | public string view { get; set; } 12 | public string? avatar { get; set; } 13 | public AvatarThumbs avatarThumbs { get; set; } 14 | public string? header { get; set; } 15 | public HeaderSize headerSize { get; set; } 16 | public HeaderThumbs headerThumbs { get; set; } 17 | public int? id { get; set; } 18 | public string name { get; set; } 19 | public string username { get; set; } 20 | public bool? canLookStory { get; set; } 21 | public bool? canCommentStory { get; set; } 22 | public bool? hasNotViewedStory { get; set; } 23 | public bool? isVerified { get; set; } 24 | public bool? canPayInternal { get; set; } 25 | public bool? hasScheduledStream { get; set; } 26 | public bool? hasStream { get; set; } 27 | public bool? hasStories { get; set; } 28 | public bool? tipsEnabled { get; set; } 29 | public bool? tipsTextEnabled { get; set; } 30 | public int? tipsMin { get; set; } 31 | public int? tipsMinInternal { get; set; } 32 | public int? tipsMax { get; set; } 33 | public bool? canEarn { get; set; } 34 | public bool? canAddSubscriber { get; set; } 35 | public string? subscribePrice { get; set; } 36 | public string displayName { get; set; } 37 | public string notice { get; set; } 38 | public bool? isPaywallRequired { get; set; } 39 | public bool? unprofitable { get; set; } 40 | public List listsStates { get; set; } 41 | public bool? isMuted { get; set; } 42 | public bool? isRestricted { get; set; } 43 | public bool? canRestrict { get; set; } 44 | public bool? subscribedBy { get; set; } 45 | public bool? subscribedByExpire { get; set; } 46 | public DateTime? subscribedByExpireDate { get; set; } 47 | public bool? subscribedByAutoprolong { get; set; } 48 | public bool? subscribedIsExpiredNow { get; set; } 49 | public string? currentSubscribePrice { get; set; } 50 | public bool? subscribedOn { get; set; } 51 | public bool? subscribedOnExpiredNow { get; set; } 52 | public string subscribedOnDuration { get; set; } 53 | public DateTime? joinDate { get; set; } 54 | public bool? isReferrerAllowed { get; set; } 55 | public string about { get; set; } 56 | public string rawAbout { get; set; } 57 | public object website { get; set; } 58 | public object wishlist { get; set; } 59 | public object location { get; set; } 60 | public int? postsCount { get; set; } 61 | public int? archivedPostsCount { get; set; } 62 | public int? privateArchivedPostsCount { get; set; } 63 | public int? photosCount { get; set; } 64 | public int? videosCount { get; set; } 65 | public int? audiosCount { get; set; } 66 | public int? mediasCount { get; set; } 67 | public DateTime? lastSeen { get; set; } 68 | public int? favoritesCount { get; set; } 69 | public int? favoritedCount { get; set; } 70 | public bool? showPostsInFeed { get; set; } 71 | public bool? canReceiveChatMessage { get; set; } 72 | public bool? isPerformer { get; set; } 73 | public bool? isRealPerformer { get; set; } 74 | public bool? isSpotifyConnected { get; set; } 75 | public int? subscribersCount { get; set; } 76 | public bool? hasPinnedPosts { get; set; } 77 | public bool? hasLabels { get; set; } 78 | public bool? canChat { get; set; } 79 | public string? callPrice { get; set; } 80 | public bool? isPrivateRestriction { get; set; } 81 | public bool? showSubscribersCount { get; set; } 82 | public bool? showMediaCount { get; set; } 83 | public SubscribedByData subscribedByData { get; set; } 84 | public SubscribedOnData subscribedOnData { get; set; } 85 | public bool? canPromotion { get; set; } 86 | public bool? canCreatePromotion { get; set; } 87 | public bool? canCreateTrial { get; set; } 88 | public bool? isAdultContent { get; set; } 89 | public bool? canTrialSend { get; set; } 90 | public bool? hadEnoughLastPhotos { get; set; } 91 | public bool? hasLinks { get; set; } 92 | public DateTime? firstPublishedPostDate { get; set; } 93 | public bool? isSpringConnected { get; set; } 94 | public bool? isFriend { get; set; } 95 | public bool? isBlocked { get; set; } 96 | public bool? canReport { get; set; } 97 | public class AvatarThumbs 98 | { 99 | public string c50 { get; set; } 100 | public string c144 { get; set; } 101 | } 102 | 103 | public class HeaderSize 104 | { 105 | public int? width { get; set; } 106 | public int? height { get; set; } 107 | } 108 | 109 | public class HeaderThumbs 110 | { 111 | public string w480 { get; set; } 112 | public string w760 { get; set; } 113 | } 114 | 115 | public class ListsState 116 | { 117 | public string id { get; set; } 118 | public string type { get; set; } 119 | public string name { get; set; } 120 | public bool hasUser { get; set; } 121 | public bool canAddUser { get; set; } 122 | } 123 | 124 | public class Subscribe 125 | { 126 | public long? id { get; set; } 127 | public int? userId { get; set; } 128 | public int? subscriberId { get; set; } 129 | public DateTime? date { get; set; } 130 | public int? duration { get; set; } 131 | public DateTime? startDate { get; set; } 132 | public DateTime? expireDate { get; set; } 133 | public object cancelDate { get; set; } 134 | public string? price { get; set; } 135 | public string? regularPrice { get; set; } 136 | public int? discount { get; set; } 137 | public string action { get; set; } 138 | public string type { get; set; } 139 | public object offerStart { get; set; } 140 | public object offerEnd { get; set; } 141 | public bool? isCurrent { get; set; } 142 | } 143 | 144 | public class SubscribedByData 145 | { 146 | public string? price { get; set; } 147 | public string? newPrice { get; set; } 148 | public string? regularPrice { get; set; } 149 | public string? subscribePrice { get; set; } 150 | public int? discountPercent { get; set; } 151 | public int? discountPeriod { get; set; } 152 | public DateTime? subscribeAt { get; set; } 153 | public DateTime? expiredAt { get; set; } 154 | public object renewedAt { get; set; } 155 | public object discountFinishedAt { get; set; } 156 | public object discountStartedAt { get; set; } 157 | public string status { get; set; } 158 | public bool? isMuted { get; set; } 159 | public string unsubscribeReason { get; set; } 160 | public string duration { get; set; } 161 | public bool? showPostsInFeed { get; set; } 162 | public List subscribes { get; set; } 163 | } 164 | 165 | public class SubscribedOnData 166 | { 167 | public string? price { get; set; } 168 | public string? newPrice { get; set; } 169 | public string? regularPrice { get; set; } 170 | public string? subscribePrice { get; set; } 171 | public int? discountPercent { get; set; } 172 | public int? discountPeriod { get; set; } 173 | public DateTime? subscribeAt { get; set; } 174 | public DateTime? expiredAt { get; set; } 175 | public DateTime? renewedAt { get; set; } 176 | public object discountFinishedAt { get; set; } 177 | public object discountStartedAt { get; set; } 178 | public object status { get; set; } 179 | public bool? isMuted { get; set; } 180 | public string unsubscribeReason { get; set; } 181 | public string duration { get; set; } 182 | public int? tipsSumm { get; set; } 183 | public int? subscribesSumm { get; set; } 184 | public int? messagesSumm { get; set; } 185 | public int? postsSumm { get; set; } 186 | public int? streamsSumm { get; set; } 187 | public int? totalSumm { get; set; } 188 | public List subscribes { get; set; } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Helpers/DBHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.Sqlite; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace OF_DRM_Video_Downloader.Helpers 9 | { 10 | public class DBHelper : IDBHelper 11 | { 12 | public async Task CreateDB(string folder) 13 | { 14 | try 15 | { 16 | if (!Directory.Exists(folder + "/Metadata")) 17 | { 18 | Directory.CreateDirectory(folder + "/Metadata"); 19 | } 20 | 21 | string dbFilePath = $"{folder}/Metadata/user_data.db"; 22 | 23 | // connect to the new database file 24 | using (SqliteConnection connection = new SqliteConnection($"Data Source={dbFilePath}")) 25 | { 26 | // open the connection 27 | connection.Open(); 28 | 29 | // create the 'medias' table 30 | using (SqliteCommand cmd = new SqliteCommand("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, PRIMARY KEY(id), UNIQUE(media_id));", connection)) 31 | { 32 | cmd.ExecuteNonQuery(); 33 | } 34 | 35 | // create the 'messages' table 36 | using (SqliteCommand cmd = new SqliteCommand("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, PRIMARY KEY(id), UNIQUE(post_id));", connection)) 37 | { 38 | cmd.ExecuteNonQuery(); 39 | } 40 | 41 | // create the 'posts' table 42 | using (SqliteCommand cmd = new SqliteCommand("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, PRIMARY KEY(id), UNIQUE(post_id));", connection)) 43 | { 44 | cmd.ExecuteNonQuery(); 45 | } 46 | 47 | // create the 'stories' table 48 | using (SqliteCommand cmd = new SqliteCommand("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, PRIMARY KEY(id), UNIQUE(post_id));", connection)) 49 | { 50 | cmd.ExecuteNonQuery(); 51 | } 52 | 53 | // create the 'others' table 54 | using (SqliteCommand cmd = new SqliteCommand("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, PRIMARY KEY(id), UNIQUE(post_id));", connection)) 55 | { 56 | cmd.ExecuteNonQuery(); 57 | } 58 | 59 | // create the 'products' table 60 | using (SqliteCommand cmd = new SqliteCommand("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, PRIMARY KEY(id), UNIQUE(post_id));", connection)) 61 | { 62 | cmd.ExecuteNonQuery(); 63 | } 64 | 65 | // create the 'profiles' table 66 | using (SqliteCommand cmd = new SqliteCommand("CREATE TABLE IF NOT EXISTS profiles (id INTEGER NOT NULL, user_id INTEGER NOT NULL, username VARCHAR NOT NULL, PRIMARY KEY(id), UNIQUE(username));", connection)) 67 | { 68 | cmd.ExecuteNonQuery(); 69 | } 70 | } 71 | } 72 | catch (Exception ex) 73 | { 74 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 75 | 76 | if (ex.InnerException != null) 77 | { 78 | Console.WriteLine("\nInner Exception:"); 79 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 80 | } 81 | } 82 | } 83 | public async Task AddMessage(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at, int user_id) 84 | { 85 | try 86 | { 87 | using (SqliteConnection connection = new SqliteConnection($"Data Source={folder}/Metadata/user_data.db")) 88 | { 89 | connection.Open(); 90 | using (SqliteCommand cmd = new SqliteCommand($"SELECT COUNT(*) FROM messages WHERE post_id=@post_id", connection)) 91 | { 92 | cmd.Parameters.AddWithValue("@post_id", post_id); 93 | int count = Convert.ToInt32(cmd.ExecuteScalar()); 94 | if (count == 0) 95 | { 96 | // If the record doesn't exist, insert a new one 97 | using (SqliteCommand insertCmd = new SqliteCommand("INSERT INTO messages(post_id, text, price, paid, archived, created_at, user_id) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at, @user_id)", connection)) 98 | { 99 | insertCmd.Parameters.AddWithValue("@post_id", post_id); 100 | insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value); 101 | insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value); 102 | insertCmd.Parameters.AddWithValue("@is_paid", is_paid); 103 | insertCmd.Parameters.AddWithValue("@is_archived", is_archived); 104 | insertCmd.Parameters.AddWithValue("@created_at", created_at); 105 | insertCmd.Parameters.AddWithValue("@user_id", user_id); 106 | insertCmd.ExecuteNonQuery(); 107 | } 108 | } 109 | } 110 | } 111 | } 112 | catch (Exception ex) 113 | { 114 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 115 | 116 | if (ex.InnerException != null) 117 | { 118 | Console.WriteLine("\nInner Exception:"); 119 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 120 | } 121 | } 122 | } 123 | public async Task AddPost(string folder, long post_id, string message_text, string price, bool is_paid, bool is_archived, DateTime created_at) 124 | { 125 | try 126 | { 127 | using (SqliteConnection connection = new SqliteConnection($"Data Source={folder}/Metadata/user_data.db")) 128 | { 129 | connection.Open(); 130 | using (SqliteCommand cmd = new SqliteCommand($"SELECT COUNT(*) FROM posts WHERE post_id=@post_id", connection)) 131 | { 132 | cmd.Parameters.AddWithValue("@post_id", post_id); 133 | int count = Convert.ToInt32(cmd.ExecuteScalar()); 134 | if (count == 0) 135 | { 136 | // If the record doesn't exist, insert a new one 137 | using (SqliteCommand insertCmd = new SqliteCommand("INSERT INTO posts(post_id, text, price, paid, archived, created_at) VALUES(@post_id, @message_text, @price, @is_paid, @is_archived, @created_at)", connection)) 138 | { 139 | insertCmd.Parameters.AddWithValue("@post_id", post_id); 140 | insertCmd.Parameters.AddWithValue("@message_text", message_text ?? (object)DBNull.Value); 141 | insertCmd.Parameters.AddWithValue("@price", price ?? (object)DBNull.Value); 142 | insertCmd.Parameters.AddWithValue("@is_paid", is_paid); 143 | insertCmd.Parameters.AddWithValue("@is_archived", is_archived); 144 | insertCmd.Parameters.AddWithValue("@created_at", created_at); 145 | insertCmd.ExecuteNonQuery(); 146 | } 147 | } 148 | } 149 | } 150 | } 151 | catch (Exception ex) 152 | { 153 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 154 | 155 | if (ex.InnerException != null) 156 | { 157 | Console.WriteLine("\nInner Exception:"); 158 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 159 | } 160 | } 161 | } 162 | 163 | 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) 164 | { 165 | try 166 | { 167 | using (SqliteConnection connection = new SqliteConnection($"Data Source={folder}/Metadata/user_data.db")) 168 | { 169 | connection.Open(); 170 | using (SqliteCommand cmd = new SqliteCommand($"SELECT COUNT(*) FROM medias WHERE media_id={media_id}", connection)) 171 | { 172 | int count = Convert.ToInt32(cmd.ExecuteScalar()); 173 | if (count == 0) 174 | { 175 | // If the record doesn't exist, insert a new one 176 | using (SqliteCommand insertCmd = new SqliteCommand($"INSERT INTO medias(media_id, post_id, link, directory, filename, size, api_type, media_type, preview, downloaded, 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")}')", connection)) 177 | { 178 | insertCmd.ExecuteNonQuery(); 179 | } 180 | } 181 | } 182 | } 183 | } 184 | catch (Exception ex) 185 | { 186 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 187 | 188 | if (ex.InnerException != null) 189 | { 190 | Console.WriteLine("\nInner Exception:"); 191 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 192 | } 193 | } 194 | } 195 | 196 | public async Task CheckDownloaded(string folder, long media_id) 197 | { 198 | try 199 | { 200 | bool downloaded = false; 201 | using (SqliteConnection connection = new SqliteConnection($"Data Source={folder}/Metadata/user_data.db")) 202 | { 203 | connection.Open(); 204 | using (SqliteCommand cmd = new SqliteCommand($"SELECT downloaded FROM medias WHERE media_id={media_id}", connection)) 205 | { 206 | downloaded = Convert.ToBoolean(cmd.ExecuteScalar()); 207 | } 208 | } 209 | return downloaded; 210 | } 211 | catch (Exception ex) 212 | { 213 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 214 | 215 | if (ex.InnerException != null) 216 | { 217 | Console.WriteLine("\nInner Exception:"); 218 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 219 | } 220 | } 221 | return false; 222 | } 223 | public async Task UpdateMedia(string folder, long media_id, string directory, string filename, long size, bool downloaded, DateTime created_at) 224 | { 225 | using (SqliteConnection connection = new SqliteConnection($"Data Source={folder}/Metadata/user_data.db")) 226 | { 227 | connection.Open(); 228 | 229 | // Construct the update command 230 | string commandText = "UPDATE medias SET directory=@directory, filename=@filename, size=@size, downloaded=@downloaded, created_at=@created_at WHERE media_id=@media_id"; 231 | 232 | // Create a new command object 233 | using (SqliteCommand command = new SqliteCommand(commandText, connection)) 234 | { 235 | // Add parameters to the command object 236 | command.Parameters.AddWithValue("@directory", directory); 237 | command.Parameters.AddWithValue("@filename", filename); 238 | command.Parameters.AddWithValue("@size", size); 239 | command.Parameters.AddWithValue("@downloaded", downloaded ? 1 : 0); 240 | command.Parameters.AddWithValue("@created_at", created_at); 241 | command.Parameters.AddWithValue("@media_id", media_id); 242 | 243 | // Execute the command 244 | command.ExecuteNonQuery(); 245 | } 246 | } 247 | } 248 | public async Task GetFileSize(string folder, long media_id) 249 | { 250 | long size; 251 | using (SqliteConnection connection = new SqliteConnection($"Data Source={folder}/Metadata/user_data.db")) 252 | { 253 | connection.Open(); 254 | using (SqliteCommand cmd = new SqliteCommand($"SELECT size FROM medias WHERE media_id={media_id}", connection)) 255 | { 256 | size = Convert.ToInt64(cmd.ExecuteScalar()); 257 | } 258 | } 259 | return size; 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Widevine/CDM.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using WidevineClient.Crypto; 10 | 11 | namespace WidevineClient.Widevine 12 | { 13 | public class CDM 14 | { 15 | static Dictionary Devices { get; } = new Dictionary() 16 | { 17 | ["chrome_1610"] = new CDMDevice("chrome_1610", null, null, null) 18 | }; 19 | static Dictionary Sessions { get; set; } = new Dictionary(); 20 | 21 | static byte[] CheckPSSH(string psshB64) 22 | { 23 | byte[] systemID = new byte[] { 237, 239, 139, 169, 121, 214, 74, 206, 163, 200, 39, 220, 213, 29, 33, 237 }; 24 | 25 | if (psshB64.Length % 4 != 0) 26 | { 27 | psshB64 = psshB64.PadRight(psshB64.Length + (4 - (psshB64.Length % 4)), '='); 28 | } 29 | 30 | byte[] pssh = Convert.FromBase64String(psshB64); 31 | 32 | if (pssh.Length < 30) 33 | return pssh; 34 | 35 | if (!pssh[12..28].SequenceEqual(systemID)) 36 | { 37 | List newPssh = new List() { 0, 0, 0 }; 38 | newPssh.Add((byte)(32 + pssh.Length)); 39 | newPssh.AddRange(Encoding.UTF8.GetBytes("pssh")); 40 | newPssh.AddRange(new byte[] { 0, 0, 0, 0 }); 41 | newPssh.AddRange(systemID); 42 | newPssh.AddRange(new byte[] { 0, 0, 0, 0 }); 43 | newPssh[31] = (byte)(pssh.Length); 44 | newPssh.AddRange(pssh); 45 | 46 | return newPssh.ToArray(); 47 | } 48 | else 49 | { 50 | return pssh; 51 | } 52 | } 53 | 54 | public static string OpenSession(string initDataB64, string deviceName, bool offline = false, bool raw = false) 55 | { 56 | byte[] initData = CheckPSSH(initDataB64); 57 | 58 | var device = Devices[deviceName]; 59 | 60 | byte[] sessionId = new byte[16]; 61 | 62 | if (device.IsAndroid) 63 | { 64 | string randHex = ""; 65 | 66 | Random rand = new Random(); 67 | string choice = "ABCDEF0123456789"; 68 | for (int i = 0; i < 16; i++) 69 | randHex += choice[rand.Next(16)]; 70 | 71 | string counter = "01"; 72 | string rest = "00000000000000"; 73 | sessionId = Encoding.ASCII.GetBytes(randHex + counter + rest); 74 | } 75 | else 76 | { 77 | Random rand = new Random(); 78 | rand.NextBytes(sessionId); 79 | } 80 | 81 | Session session; 82 | dynamic parsedInitData = ParseInitData(initData); 83 | 84 | if (parsedInitData != null) 85 | { 86 | session = new Session(sessionId, parsedInitData, device, offline); 87 | } 88 | else if (raw) 89 | { 90 | session = new Session(sessionId, initData, device, offline); 91 | } 92 | else 93 | { 94 | return null; 95 | } 96 | 97 | Sessions.Add(Utils.BytesToHex(sessionId), session); 98 | 99 | return Utils.BytesToHex(sessionId); 100 | } 101 | 102 | static WidevineCencHeader ParseInitData(byte[] initData) 103 | { 104 | WidevineCencHeader cencHeader; 105 | 106 | try 107 | { 108 | cencHeader = Serializer.Deserialize(new MemoryStream(initData[32..])); 109 | } 110 | catch 111 | { 112 | try 113 | { 114 | //needed for HBO Max 115 | 116 | PSSHBox psshBox = PSSHBox.FromByteArray(initData); 117 | cencHeader = Serializer.Deserialize(new MemoryStream(psshBox.Data)); 118 | } 119 | catch 120 | { 121 | //Logger.Verbose("Unable to parse, unsupported init data format"); 122 | return null; 123 | } 124 | } 125 | 126 | return cencHeader; 127 | } 128 | 129 | public static bool CloseSession(string sessionId) 130 | { 131 | //Logger.Debug($"CloseSession(session_id={Utils.BytesToHex(sessionId)})"); 132 | //Logger.Verbose("Closing CDM session"); 133 | 134 | if (Sessions.ContainsKey(sessionId)) 135 | { 136 | Sessions.Remove(sessionId); 137 | //Logger.Verbose("CDM session closed"); 138 | return true; 139 | } 140 | else 141 | { 142 | //Logger.Info($"Session {sessionId} not found"); 143 | return false; 144 | } 145 | } 146 | 147 | public static bool SetServiceCertificate(string sessionId, byte[] certData) 148 | { 149 | //Logger.Debug($"SetServiceCertificate(sessionId={Utils.BytesToHex(sessionId)}, cert={certB64})"); 150 | //Logger.Verbose($"Setting service certificate"); 151 | 152 | if (!Sessions.ContainsKey(sessionId)) 153 | { 154 | //Logger.Error("Session ID doesn't exist"); 155 | return false; 156 | } 157 | 158 | SignedMessage signedMessage = new SignedMessage(); 159 | 160 | try 161 | { 162 | signedMessage = Serializer.Deserialize(new MemoryStream(certData)); 163 | } 164 | catch 165 | { 166 | //Logger.Warn("Failed to parse cert as SignedMessage"); 167 | } 168 | 169 | SignedDeviceCertificate serviceCertificate; 170 | try 171 | { 172 | try 173 | { 174 | //Logger.Debug("Service cert provided as signedmessage"); 175 | serviceCertificate = Serializer.Deserialize(new MemoryStream(signedMessage.Msg)); 176 | } 177 | catch 178 | { 179 | //Logger.Debug("Service cert provided as signeddevicecertificate"); 180 | serviceCertificate = Serializer.Deserialize(new MemoryStream(certData)); 181 | } 182 | } 183 | catch 184 | { 185 | //Logger.Error("Failed to parse service certificate"); 186 | return false; 187 | } 188 | 189 | Sessions[sessionId].ServiceCertificate = serviceCertificate; 190 | Sessions[sessionId].PrivacyMode = true; 191 | 192 | return true; 193 | } 194 | 195 | public static byte[] GetLicenseRequest(string sessionId) 196 | { 197 | //Logger.Debug($"GetLicenseRequest(sessionId={Utils.BytesToHex(sessionId)})"); 198 | //Logger.Verbose($"Getting license request"); 199 | 200 | if (!Sessions.ContainsKey(sessionId)) 201 | { 202 | //Logger.Error("Session ID doesn't exist"); 203 | return null; 204 | } 205 | 206 | var session = Sessions[sessionId]; 207 | 208 | //Logger.Debug("Building license request"); 209 | 210 | dynamic licenseRequest; 211 | 212 | if (session.InitData is WidevineCencHeader) 213 | { 214 | licenseRequest = new SignedLicenseRequest 215 | { 216 | Type = SignedLicenseRequest.MessageType.LicenseRequest, 217 | Msg = new LicenseRequest 218 | { 219 | Type = LicenseRequest.RequestType.New, 220 | KeyControlNonce = 1093602366, 221 | ProtocolVersion = ProtocolVersion.Current, 222 | ContentId = new LicenseRequest.ContentIdentification 223 | { 224 | CencId = new LicenseRequest.ContentIdentification.Cenc 225 | { 226 | LicenseType = session.Offline ? LicenseType.Offline : LicenseType.Default, 227 | RequestId = session.SessionId, 228 | Pssh = session.InitData 229 | } 230 | } 231 | } 232 | }; 233 | } 234 | else 235 | { 236 | licenseRequest = new SignedLicenseRequestRaw 237 | { 238 | Type = SignedLicenseRequestRaw.MessageType.LicenseRequest, 239 | Msg = new LicenseRequestRaw 240 | { 241 | Type = LicenseRequestRaw.RequestType.New, 242 | KeyControlNonce = 1093602366, 243 | ProtocolVersion = ProtocolVersion.Current, 244 | ContentId = new LicenseRequestRaw.ContentIdentification 245 | { 246 | CencId = new LicenseRequestRaw.ContentIdentification.Cenc 247 | { 248 | LicenseType = session.Offline ? LicenseType.Offline : LicenseType.Default, 249 | RequestId = session.SessionId, 250 | Pssh = session.InitData 251 | } 252 | } 253 | } 254 | }; 255 | } 256 | 257 | if (session.PrivacyMode) 258 | { 259 | //Logger.Debug("Privacy mode & serivce certificate loaded, encrypting client id"); 260 | 261 | EncryptedClientIdentification encryptedClientIdProto = new EncryptedClientIdentification(); 262 | 263 | //Logger.Debug("Unencrypted client id " + Utils.SerializeToString(clientId)); 264 | 265 | using var memoryStream = new MemoryStream(); 266 | Serializer.Serialize(memoryStream, session.Device.ClientID); 267 | byte[] data = Padding.AddPKCS7Padding(memoryStream.ToArray(), 16); 268 | 269 | using AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider 270 | { 271 | BlockSize = 128, 272 | Padding = PaddingMode.PKCS7, 273 | Mode = CipherMode.CBC 274 | }; 275 | aesProvider.GenerateKey(); 276 | aesProvider.GenerateIV(); 277 | 278 | using MemoryStream mstream = new MemoryStream(); 279 | using CryptoStream cryptoStream = new CryptoStream(mstream, aesProvider.CreateEncryptor(aesProvider.Key, aesProvider.IV), CryptoStreamMode.Write); 280 | cryptoStream.Write(data, 0, data.Length); 281 | encryptedClientIdProto.EncryptedClientId = mstream.ToArray(); 282 | 283 | using RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); 284 | RSA.ImportRSAPublicKey(session.ServiceCertificate.DeviceCertificate.PublicKey, out int bytesRead); 285 | encryptedClientIdProto.EncryptedPrivacyKey = RSA.Encrypt(aesProvider.Key, RSAEncryptionPadding.OaepSHA1); 286 | encryptedClientIdProto.EncryptedClientIdIv = aesProvider.IV; 287 | encryptedClientIdProto.ServiceId = Encoding.UTF8.GetString(session.ServiceCertificate.DeviceCertificate.ServiceId); 288 | encryptedClientIdProto.ServiceCertificateSerialNumber = session.ServiceCertificate.DeviceCertificate.SerialNumber; 289 | 290 | licenseRequest.Msg.EncryptedClientId = encryptedClientIdProto; 291 | } 292 | else 293 | { 294 | licenseRequest.Msg.ClientId = session.Device.ClientID; 295 | } 296 | 297 | //Logger.Debug("Signing license request"); 298 | 299 | using (var memoryStream = new MemoryStream()) 300 | { 301 | Serializer.Serialize(memoryStream, licenseRequest.Msg); 302 | byte[] data = memoryStream.ToArray(); 303 | session.LicenseRequest = data; 304 | 305 | licenseRequest.Signature = session.Device.Sign(data); 306 | } 307 | 308 | //Logger.Verbose("License request created"); 309 | 310 | byte[] requestBytes; 311 | using (var memoryStream = new MemoryStream()) 312 | { 313 | Serializer.Serialize(memoryStream, licenseRequest); 314 | requestBytes = memoryStream.ToArray(); 315 | } 316 | 317 | Sessions[sessionId] = session; 318 | 319 | //Logger.Debug($"license request b64: {Convert.ToBase64String(requestBytes)}"); 320 | return requestBytes; 321 | } 322 | 323 | public static void ProvideLicense(string sessionId, byte[] license) 324 | { 325 | //Logger.Debug($"ProvideLicense(sessionId={Utils.BytesToHex(sessionId)}, licenseB64={licenseB64})"); 326 | //Logger.Verbose("Decrypting provided license"); 327 | 328 | if (!Sessions.ContainsKey(sessionId)) 329 | { 330 | throw new Exception("Session ID doesn't exist"); 331 | } 332 | 333 | var session = Sessions[sessionId]; 334 | 335 | if (session.LicenseRequest == null) 336 | { 337 | throw new Exception("Generate a license request first"); 338 | } 339 | 340 | SignedLicense signedLicense; 341 | try 342 | { 343 | signedLicense = Serializer.Deserialize(new MemoryStream(license)); 344 | } 345 | catch 346 | { 347 | throw new Exception("Unable to parse license"); 348 | } 349 | 350 | //Logger.Debug("License: " + Utils.SerializeToString(signedLicense)); 351 | 352 | session.License = signedLicense; 353 | 354 | //Logger.Debug($"Deriving keys from session key"); 355 | 356 | try 357 | { 358 | var sessionKey = session.Device.Decrypt(session.License.SessionKey); 359 | 360 | if (sessionKey.Length != 16) 361 | { 362 | throw new Exception("Unable to decrypt session key"); 363 | } 364 | 365 | session.SessionKey = sessionKey; 366 | } 367 | catch 368 | { 369 | throw new Exception("Unable to decrypt session key"); 370 | } 371 | 372 | //Logger.Debug("Session key: " + Utils.BytesToHex(session.SessionKey)); 373 | 374 | session.DerivedKeys = DeriveKeys(session.LicenseRequest, session.SessionKey); 375 | 376 | //Logger.Debug("Verifying license signature"); 377 | 378 | byte[] licenseBytes; 379 | using (var memoryStream = new MemoryStream()) 380 | { 381 | Serializer.Serialize(memoryStream, signedLicense.Msg); 382 | licenseBytes = memoryStream.ToArray(); 383 | } 384 | byte[] hmacHash = CryptoUtils.GetHMACSHA256Digest(licenseBytes, session.DerivedKeys.Auth1); 385 | 386 | if (!hmacHash.SequenceEqual(signedLicense.Signature)) 387 | { 388 | throw new Exception("License signature mismatch"); 389 | } 390 | 391 | foreach (License.KeyContainer key in signedLicense.Msg.Keys) 392 | { 393 | string type = key.Type.ToString(); 394 | 395 | if (type == "Signing") 396 | continue; 397 | 398 | byte[] keyId; 399 | byte[] encryptedKey = key.Key; 400 | byte[] iv = key.Iv; 401 | keyId = key.Id; 402 | if (keyId == null) 403 | { 404 | keyId = Encoding.ASCII.GetBytes(key.Type.ToString()); 405 | } 406 | 407 | byte[] decryptedKey; 408 | 409 | using MemoryStream mstream = new MemoryStream(); 410 | using AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider 411 | { 412 | Mode = CipherMode.CBC, 413 | Padding = PaddingMode.PKCS7 414 | }; 415 | using CryptoStream cryptoStream = new CryptoStream(mstream, aesProvider.CreateDecryptor(session.DerivedKeys.Enc, iv), CryptoStreamMode.Write); 416 | cryptoStream.Write(encryptedKey, 0, encryptedKey.Length); 417 | decryptedKey = mstream.ToArray(); 418 | 419 | List permissions = new List(); 420 | if (type == "OperatorSession") 421 | { 422 | foreach (PropertyInfo perm in key._OperatorSessionKeyPermissions.GetType().GetProperties()) 423 | { 424 | if ((uint)perm.GetValue(key._OperatorSessionKeyPermissions) == 1) 425 | { 426 | permissions.Add(perm.Name); 427 | } 428 | } 429 | } 430 | session.ContentKeys.Add(new ContentKey 431 | { 432 | KeyID = keyId, 433 | Type = type, 434 | Bytes = decryptedKey, 435 | Permissions = permissions 436 | }); 437 | } 438 | 439 | //Logger.Debug($"Key count: {session.Keys.Count}"); 440 | 441 | Sessions[sessionId] = session; 442 | 443 | //Logger.Verbose("Decrypted all keys"); 444 | } 445 | 446 | public static DerivedKeys DeriveKeys(byte[] message, byte[] key) 447 | { 448 | byte[] encKeyBase = Encoding.UTF8.GetBytes("ENCRYPTION").Concat(new byte[] { 0x0, }).Concat(message).Concat(new byte[] { 0x0, 0x0, 0x0, 0x80 }).ToArray(); 449 | byte[] authKeyBase = Encoding.UTF8.GetBytes("AUTHENTICATION").Concat(new byte[] { 0x0, }).Concat(message).Concat(new byte[] { 0x0, 0x0, 0x2, 0x0 }).ToArray(); 450 | 451 | byte[] encKey = new byte[] { 0x01 }.Concat(encKeyBase).ToArray(); 452 | byte[] authKey1 = new byte[] { 0x01 }.Concat(authKeyBase).ToArray(); 453 | byte[] authKey2 = new byte[] { 0x02 }.Concat(authKeyBase).ToArray(); 454 | byte[] authKey3 = new byte[] { 0x03 }.Concat(authKeyBase).ToArray(); 455 | byte[] authKey4 = new byte[] { 0x04 }.Concat(authKeyBase).ToArray(); 456 | 457 | byte[] encCmacKey = CryptoUtils.GetCMACDigest(encKey, key); 458 | byte[] authCmacKey1 = CryptoUtils.GetCMACDigest(authKey1, key); 459 | byte[] authCmacKey2 = CryptoUtils.GetCMACDigest(authKey2, key); 460 | byte[] authCmacKey3 = CryptoUtils.GetCMACDigest(authKey3, key); 461 | byte[] authCmacKey4 = CryptoUtils.GetCMACDigest(authKey4, key); 462 | 463 | byte[] authCmacCombined1 = authCmacKey1.Concat(authCmacKey2).ToArray(); 464 | byte[] authCmacCombined2 = authCmacKey3.Concat(authCmacKey4).ToArray(); 465 | 466 | return new DerivedKeys 467 | { 468 | Auth1 = authCmacCombined1, 469 | Auth2 = authCmacCombined2, 470 | Enc = encCmacKey 471 | }; 472 | } 473 | 474 | public static List GetKeys(string sessionId) 475 | { 476 | if (Sessions.ContainsKey(sessionId)) 477 | return Sessions[sessionId].ContentKeys; 478 | else 479 | { 480 | throw new Exception("Session not found"); 481 | } 482 | } 483 | } 484 | } 485 | 486 | 487 | 488 | /* 489 | public static List ProvideLicense(string requestB64, string licenseB64) 490 | { 491 | byte[] licenseRequest; 492 | 493 | var request = Serializer.Deserialize(new MemoryStream(Convert.FromBase64String(requestB64))); 494 | 495 | using (var ms = new MemoryStream()) 496 | { 497 | Serializer.Serialize(ms, request.Msg); 498 | licenseRequest = ms.ToArray(); 499 | } 500 | 501 | SignedLicense signedLicense; 502 | try 503 | { 504 | signedLicense = Serializer.Deserialize(new MemoryStream(Convert.FromBase64String(licenseB64))); 505 | } 506 | catch 507 | { 508 | return null; 509 | } 510 | 511 | byte[] sessionKey; 512 | try 513 | { 514 | 515 | sessionKey = Controllers.Adapter.OaepDecrypt(Convert.ToBase64String(signedLicense.SessionKey)); 516 | 517 | if (sessionKey.Length != 16) 518 | { 519 | return null; 520 | } 521 | } 522 | catch 523 | { 524 | return null; 525 | } 526 | 527 | byte[] encKeyBase = Encoding.UTF8.GetBytes("ENCRYPTION").Concat(new byte[] { 0x0, }).Concat(licenseRequest).Concat(new byte[] { 0x0, 0x0, 0x0, 0x80 }).ToArray(); 528 | 529 | byte[] encKey = new byte[] { 0x01 }.Concat(encKeyBase).ToArray(); 530 | 531 | byte[] encCmacKey = GetCmacDigest(encKey, sessionKey); 532 | 533 | byte[] encryptionKey = encCmacKey; 534 | 535 | List keys = new List(); 536 | 537 | foreach (License.KeyContainer key in signedLicense.Msg.Keys) 538 | { 539 | string type = key.Type.ToString(); 540 | if (type == "Signing") 541 | { 542 | continue; 543 | } 544 | 545 | byte[] keyId; 546 | byte[] encryptedKey = key.Key; 547 | byte[] iv = key.Iv; 548 | keyId = key.Id; 549 | if (keyId == null) 550 | { 551 | keyId = Encoding.ASCII.GetBytes(key.Type.ToString()); 552 | } 553 | 554 | byte[] decryptedKey; 555 | 556 | using MemoryStream mstream = new MemoryStream(); 557 | using AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider 558 | { 559 | Mode = CipherMode.CBC, 560 | Padding = PaddingMode.PKCS7 561 | }; 562 | using CryptoStream cryptoStream = new CryptoStream(mstream, aesProvider.CreateDecryptor(encryptionKey, iv), CryptoStreamMode.Write); 563 | cryptoStream.Write(encryptedKey, 0, encryptedKey.Length); 564 | decryptedKey = mstream.ToArray(); 565 | 566 | List permissions = new List(); 567 | if (type == "OPERATOR_SESSION") 568 | { 569 | foreach (FieldInfo perm in key._OperatorSessionKeyPermissions.GetType().GetFields()) 570 | { 571 | if ((uint)perm.GetValue(key._OperatorSessionKeyPermissions) == 1) 572 | { 573 | permissions.Add(perm.Name); 574 | } 575 | } 576 | } 577 | keys.Add(BitConverter.ToString(keyId).Replace("-","").ToLower() + ":" + BitConverter.ToString(decryptedKey).Replace("-", "").ToLower()); 578 | } 579 | 580 | return keys; 581 | }*/ 582 | -------------------------------------------------------------------------------- /OF DRM Video Downloader/Helpers/DownloadHelper.cs: -------------------------------------------------------------------------------- 1 | using Entities; 2 | using Spectre.Console; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace OF_DRM_Video_Downloader.Helpers 11 | { 12 | public class DownloadHelper : IDownloadHelper 13 | { 14 | private static IDBHelper dBHelper; 15 | 16 | public DownloadHelper() 17 | { 18 | dBHelper = new DBHelper(); 19 | } 20 | public async Task DownloadPostDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task) 21 | { 22 | try 23 | { 24 | Uri uri = new Uri(url); 25 | string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; 26 | string path = "/Posts/Free/Videos"; 27 | if (!Directory.Exists(folder + path)) // check if the folder already exists 28 | { 29 | Directory.CreateDirectory(folder + path); // create the new folder 30 | } 31 | if (!await dBHelper.CheckDownloaded(folder, media_id)) 32 | { 33 | if (!File.Exists(folder + path + "/" + filename + "_source.mp4")) 34 | { 35 | //Use ytdl-p to download the MPD as a M4A and MP4 file 36 | ProcessStartInfo ytdlpstartInfo = new ProcessStartInfo(); 37 | ytdlpstartInfo.FileName = ytdlppath; 38 | ytdlpstartInfo.Arguments = $"--allow-u --no-part --restrict-filenames -N 4 --user-agent \"{user_agent}\" --add-header \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess}\" --referer \"https://onlyfans.com/\" -o \"{folder + path + "/"}%(title)s.%(ext)s\" --format \"bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best[ext=m4a]\" \"{url}\""; 39 | ytdlpstartInfo.CreateNoWindow = true; 40 | 41 | Process ytdlpprocess = new Process(); 42 | ytdlpprocess.StartInfo = ytdlpstartInfo; 43 | ytdlpprocess.Start(); 44 | ytdlpprocess.WaitForExit(); 45 | 46 | //Remove .fx from filenames 47 | if (File.Exists(folder + path + "/" + filename + ".f1.mp4")) 48 | { 49 | File.Move(folder + path + "/" + filename + ".f1.mp4", folder + path + "/" + filename + ".mp4"); 50 | } 51 | else if (File.Exists(folder + path + "/" + filename + ".f2.mp4")) 52 | { 53 | File.Move(folder + path + "/" + filename + ".f2.mp4", folder + path + "/" + filename + ".mp4"); 54 | } 55 | else if (File.Exists(folder + path + "/" + filename + ".f3.mp4")) 56 | { 57 | File.Move(folder + path + "/" + filename + ".f3.mp4", folder + path + "/" + filename + ".mp4"); 58 | } 59 | 60 | if (File.Exists(folder + path + "/" + filename + ".f3.m4a")) 61 | { 62 | File.Move(folder + path + "/" + filename + ".f3.m4a", folder + path + "/" + filename + ".m4a"); 63 | } 64 | else if (File.Exists(folder + path + "/" + filename + ".f4.m4a")) 65 | { 66 | File.Move(folder + path + "/" + filename + ".f4.m4a", folder + path + "/" + filename + ".m4a"); 67 | } 68 | 69 | //Use mp4decrypt to decrypt the MP4 and M4A files 70 | ProcessStartInfo mp4decryptStartInfoVideo = new ProcessStartInfo(); 71 | mp4decryptStartInfoVideo.FileName = mp4decryptpath; 72 | mp4decryptStartInfoVideo.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.mp4 {folder + path + "/" + filename}_vdec.mp4"; 73 | mp4decryptStartInfoVideo.CreateNoWindow = true; 74 | 75 | Process mp4decryptVideoProcess = new Process(); 76 | mp4decryptVideoProcess.StartInfo = mp4decryptStartInfoVideo; 77 | mp4decryptVideoProcess.Start(); 78 | mp4decryptVideoProcess.WaitForExit(); 79 | 80 | ProcessStartInfo mp4decryptStartInfoAudio = new ProcessStartInfo(); 81 | mp4decryptStartInfoAudio.FileName = mp4decryptpath; 82 | mp4decryptStartInfoAudio.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.m4a {folder + path + "/" + filename}_adec.mp4"; 83 | mp4decryptStartInfoAudio.CreateNoWindow = true; 84 | 85 | Process mp4decryptAudioProcess = new Process(); 86 | mp4decryptAudioProcess.StartInfo = mp4decryptStartInfoAudio; 87 | mp4decryptAudioProcess.Start(); 88 | mp4decryptAudioProcess.WaitForExit(); 89 | 90 | //Finally use FFMPEG to merge the 2 together 91 | ProcessStartInfo ffmpegStartInfo = new ProcessStartInfo(); 92 | ffmpegStartInfo.FileName = ffmpegpath; 93 | ffmpegStartInfo.Arguments = $"-i {folder + path + "/" + filename}_vdec.mp4 -i {folder + path + "/" + filename}_adec.mp4 -c copy {folder + path + "/" + filename}_source.mp4"; 94 | ffmpegStartInfo.CreateNoWindow = true; 95 | 96 | Process ffmpegProcess = new Process(); 97 | ffmpegProcess.StartInfo = ffmpegStartInfo; 98 | ffmpegProcess.Start(); 99 | ffmpegProcess.WaitForExit(); 100 | File.SetLastWriteTime($"{folder + path + "/" + filename}_source.mp4", lastModified); 101 | 102 | //Cleanup Files 103 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 104 | task.Increment(fileSizeInBytes); 105 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 106 | File.Delete($"{folder + path + "/" + filename}.mp4"); 107 | File.Delete($"{folder + path + "/" + filename}.m4a"); 108 | File.Delete($"{folder + path + "/" + filename}_adec.mp4"); 109 | File.Delete($"{folder + path + "/" + filename}_vdec.mp4"); 110 | 111 | return true; 112 | } 113 | else 114 | { 115 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 116 | task.Increment(fileSizeInBytes); 117 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 118 | } 119 | } 120 | else 121 | { 122 | long size = await dBHelper.GetFileSize(folder, media_id); 123 | task.Increment(size); 124 | } 125 | return false; 126 | } 127 | catch (Exception ex) 128 | { 129 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 130 | 131 | if (ex.InnerException != null) 132 | { 133 | Console.WriteLine("\nInner Exception:"); 134 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 135 | } 136 | } 137 | return false; 138 | } 139 | public async Task DownloadPurchasedPostDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task) 140 | { 141 | try 142 | { 143 | Uri uri = new Uri(url); 144 | string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; 145 | string path = "/Posts/Paid/Videos"; 146 | if (!Directory.Exists(folder + path)) // check if the folder already exists 147 | { 148 | Directory.CreateDirectory(folder + path); // create the new folder 149 | } 150 | if (!await dBHelper.CheckDownloaded(folder, media_id)) 151 | { 152 | if (!File.Exists(folder + path + "/" + filename + "_source.mp4")) 153 | { 154 | //Use ytdl-p to download the MPD as a M4A and MP4 file 155 | ProcessStartInfo ytdlpstartInfo = new ProcessStartInfo(); 156 | ytdlpstartInfo.FileName = ytdlppath; 157 | ytdlpstartInfo.Arguments = $"--allow-u --no-part --restrict-filenames -N 4 --user-agent \"{user_agent}\" --add-header \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess}\" --referer \"https://onlyfans.com/\" -o \"{folder + path + "/"}%(title)s.%(ext)s\" --format \"bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best[ext=m4a]\" \"{url}\""; 158 | ytdlpstartInfo.CreateNoWindow = true; 159 | 160 | Process ytdlpprocess = new Process(); 161 | ytdlpprocess.StartInfo = ytdlpstartInfo; 162 | ytdlpprocess.Start(); 163 | ytdlpprocess.WaitForExit(); 164 | 165 | //Remove .fx from filenames 166 | if (File.Exists(folder + path + "/" + filename + ".f1.mp4")) 167 | { 168 | File.Move(folder + path + "/" + filename + ".f1.mp4", folder + path + "/" + filename + ".mp4"); 169 | } 170 | else if (File.Exists(folder + path + "/" + filename + ".f2.mp4")) 171 | { 172 | File.Move(folder + path + "/" + filename + ".f2.mp4", folder + path + "/" + filename + ".mp4"); 173 | } 174 | else if (File.Exists(folder + path + "/" + filename + ".f3.mp4")) 175 | { 176 | File.Move(folder + path + "/" + filename + ".f3.mp4", folder + path + "/" + filename + ".mp4"); 177 | } 178 | 179 | if (File.Exists(folder + path + "/" + filename + ".f3.m4a")) 180 | { 181 | File.Move(folder + path + "/" + filename + ".f3.m4a", folder + path + "/" + filename + ".m4a"); 182 | } 183 | else if (File.Exists(folder + path + "/" + filename + ".f4.m4a")) 184 | { 185 | File.Move(folder + path + "/" + filename + ".f4.m4a", folder + path + "/" + filename + ".m4a"); 186 | } 187 | 188 | //Use mp4decrypt to decrypt the MP4 and M4A files 189 | ProcessStartInfo mp4decryptStartInfoVideo = new ProcessStartInfo(); 190 | mp4decryptStartInfoVideo.FileName = mp4decryptpath; 191 | mp4decryptStartInfoVideo.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.mp4 {folder + path + "/" + filename}_vdec.mp4"; 192 | mp4decryptStartInfoVideo.CreateNoWindow = true; 193 | 194 | Process mp4decryptVideoProcess = new Process(); 195 | mp4decryptVideoProcess.StartInfo = mp4decryptStartInfoVideo; 196 | mp4decryptVideoProcess.Start(); 197 | mp4decryptVideoProcess.WaitForExit(); 198 | 199 | ProcessStartInfo mp4decryptStartInfoAudio = new ProcessStartInfo(); 200 | mp4decryptStartInfoAudio.FileName = mp4decryptpath; 201 | mp4decryptStartInfoAudio.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.m4a {folder + path + "/" + filename}_adec.mp4"; 202 | mp4decryptStartInfoAudio.CreateNoWindow = true; 203 | 204 | Process mp4decryptAudioProcess = new Process(); 205 | mp4decryptAudioProcess.StartInfo = mp4decryptStartInfoAudio; 206 | mp4decryptAudioProcess.Start(); 207 | mp4decryptAudioProcess.WaitForExit(); 208 | 209 | //Finally use FFMPEG to merge the 2 together 210 | ProcessStartInfo ffmpegStartInfo = new ProcessStartInfo(); 211 | ffmpegStartInfo.FileName = ffmpegpath; 212 | ffmpegStartInfo.Arguments = $"-i {folder + path + "/" + filename}_vdec.mp4 -i {folder + path + "/" + filename}_adec.mp4 -c copy {folder + path + "/" + filename}_source.mp4"; 213 | ffmpegStartInfo.CreateNoWindow = true; 214 | 215 | Process ffmpegProcess = new Process(); 216 | ffmpegProcess.StartInfo = ffmpegStartInfo; 217 | ffmpegProcess.Start(); 218 | ffmpegProcess.WaitForExit(); 219 | File.SetLastWriteTime($"{folder + path + "/" + filename}_source.mp4", lastModified); 220 | 221 | //Cleanup Files 222 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 223 | task.Increment(fileSizeInBytes); 224 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 225 | File.Delete($"{folder + path + "/" + filename}.mp4"); 226 | File.Delete($"{folder + path + "/" + filename}.m4a"); 227 | File.Delete($"{folder + path + "/" + filename}_adec.mp4"); 228 | File.Delete($"{folder + path + "/" + filename}_vdec.mp4"); 229 | 230 | return true; 231 | } 232 | else 233 | { 234 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 235 | task.Increment(fileSizeInBytes); 236 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 237 | } 238 | } 239 | else 240 | { 241 | long size = await dBHelper.GetFileSize(folder, media_id); 242 | task.Increment(size); 243 | } 244 | return false; 245 | } 246 | catch (Exception ex) 247 | { 248 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 249 | 250 | if (ex.InnerException != null) 251 | { 252 | Console.WriteLine("\nInner Exception:"); 253 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 254 | } 255 | } 256 | return false; 257 | } 258 | public async Task DownloadArchivedDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task) 259 | { 260 | try 261 | { 262 | Uri uri = new Uri(url); 263 | string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; 264 | string path = "/Archived/Posts/Free/Videos"; 265 | if (!Directory.Exists(folder + path)) // check if the folder already exists 266 | { 267 | Directory.CreateDirectory(folder + path); // create the new folder 268 | } 269 | if (!await dBHelper.CheckDownloaded(folder, media_id)) 270 | { 271 | if (!File.Exists(folder + path + "/" + filename + "_source.mp4")) 272 | { 273 | //Use ytdl-p to download the MPD as a M4A and MP4 file 274 | ProcessStartInfo ytdlpstartInfo = new ProcessStartInfo(); 275 | ytdlpstartInfo.FileName = ytdlppath; 276 | ytdlpstartInfo.Arguments = $"--allow-u --no-part --restrict-filenames -N 4 --user-agent \"{user_agent}\" --add-header \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess}\" --referer \"https://onlyfans.com/\" -o \"{folder + path + "/"}%(title)s.%(ext)s\" --format \"bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best[ext=m4a]\" \"{url}\""; 277 | ytdlpstartInfo.CreateNoWindow = true; 278 | 279 | Process ytdlpprocess = new Process(); 280 | ytdlpprocess.StartInfo = ytdlpstartInfo; 281 | ytdlpprocess.Start(); 282 | ytdlpprocess.WaitForExit(); 283 | 284 | //Remove .fx from filenames 285 | if (File.Exists(folder + path + "/" + filename + ".f1.mp4")) 286 | { 287 | File.Move(folder + path + "/" + filename + ".f1.mp4", folder + path + "/" + filename + ".mp4"); 288 | } 289 | else if (File.Exists(folder + path + "/" + filename + ".f2.mp4")) 290 | { 291 | File.Move(folder + path + "/" + filename + ".f2.mp4", folder + path + "/" + filename + ".mp4"); 292 | } 293 | else if (File.Exists(folder + path + "/" + filename + ".f3.mp4")) 294 | { 295 | File.Move(folder + path + "/" + filename + ".f3.mp4", folder + path + "/" + filename + ".mp4"); 296 | } 297 | 298 | if (File.Exists(folder + path + "/" + filename + ".f3.m4a")) 299 | { 300 | File.Move(folder + path + "/" + filename + ".f3.m4a", folder + path + "/" + filename + ".m4a"); 301 | } 302 | else if (File.Exists(folder + path + "/" + filename + ".f4.m4a")) 303 | { 304 | File.Move(folder + path + "/" + filename + ".f4.m4a", folder + path + "/" + filename + ".m4a"); 305 | } 306 | 307 | //Use mp4decrypt to decrypt the MP4 and M4A files 308 | ProcessStartInfo mp4decryptStartInfoVideo = new ProcessStartInfo(); 309 | mp4decryptStartInfoVideo.FileName = mp4decryptpath; 310 | mp4decryptStartInfoVideo.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.mp4 {folder + path + "/" + filename}_vdec.mp4"; 311 | mp4decryptStartInfoVideo.CreateNoWindow = true; 312 | 313 | Process mp4decryptVideoProcess = new Process(); 314 | mp4decryptVideoProcess.StartInfo = mp4decryptStartInfoVideo; 315 | mp4decryptVideoProcess.Start(); 316 | mp4decryptVideoProcess.WaitForExit(); 317 | 318 | ProcessStartInfo mp4decryptStartInfoAudio = new ProcessStartInfo(); 319 | mp4decryptStartInfoAudio.FileName = mp4decryptpath; 320 | mp4decryptStartInfoAudio.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.m4a {folder + path + "/" + filename}_adec.mp4"; 321 | mp4decryptStartInfoAudio.CreateNoWindow = true; 322 | 323 | Process mp4decryptAudioProcess = new Process(); 324 | mp4decryptAudioProcess.StartInfo = mp4decryptStartInfoAudio; 325 | mp4decryptAudioProcess.Start(); 326 | mp4decryptAudioProcess.WaitForExit(); 327 | 328 | //Finally use FFMPEG to merge the 2 together 329 | ProcessStartInfo ffmpegStartInfo = new ProcessStartInfo(); 330 | ffmpegStartInfo.FileName = ffmpegpath; 331 | ffmpegStartInfo.Arguments = $"-i {folder + path + "/" + filename}_vdec.mp4 -i {folder + path + "/" + filename}_adec.mp4 -c copy {folder + path + "/" + filename}_source.mp4"; 332 | ffmpegStartInfo.CreateNoWindow = true; 333 | 334 | Process ffmpegProcess = new Process(); 335 | ffmpegProcess.StartInfo = ffmpegStartInfo; 336 | ffmpegProcess.Start(); 337 | ffmpegProcess.WaitForExit(); 338 | File.SetLastWriteTime($"{folder + path + "/" + filename}_source.mp4", lastModified); 339 | 340 | //Cleanup Files 341 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 342 | task.Increment(fileSizeInBytes); 343 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 344 | File.Delete($"{folder + path + "/" + filename}.mp4"); 345 | File.Delete($"{folder + path + "/" + filename}.m4a"); 346 | File.Delete($"{folder + path + "/" + filename}_adec.mp4"); 347 | File.Delete($"{folder + path + "/" + filename}_vdec.mp4"); 348 | 349 | return true; 350 | } 351 | else 352 | { 353 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 354 | task.Increment(fileSizeInBytes); 355 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 356 | } 357 | } 358 | else 359 | { 360 | long size = await dBHelper.GetFileSize(folder, media_id); 361 | task.Increment(size); 362 | } 363 | return false; 364 | } 365 | catch (Exception ex) 366 | { 367 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 368 | 369 | if (ex.InnerException != null) 370 | { 371 | Console.WriteLine("\nInner Exception:"); 372 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 373 | } 374 | } 375 | return false; 376 | } 377 | public async Task DownloadMessageDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task) 378 | { 379 | try 380 | { 381 | Uri uri = new Uri(url); 382 | string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; 383 | string path = "/Messages/Free/Videos"; 384 | if (!Directory.Exists(folder + path)) // check if the folder already exists 385 | { 386 | Directory.CreateDirectory(folder + path); // create the new folder 387 | } 388 | if (!await dBHelper.CheckDownloaded(folder, media_id)) 389 | { 390 | if (!File.Exists(folder + path + "/" + filename + "_source.mp4")) 391 | { 392 | //Use ytdl-p to download the MPD as a M4A and MP4 file 393 | ProcessStartInfo ytdlpstartInfo = new ProcessStartInfo(); 394 | ytdlpstartInfo.FileName = ytdlppath; 395 | ytdlpstartInfo.Arguments = $"--allow-u --no-part --restrict-filenames -N 4 --user-agent \"{user_agent}\" --add-header \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess}\" --referer \"https://onlyfans.com/\" -o \"{folder + path + "/"}%(title)s.%(ext)s\" --format \"bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best[ext=m4a]\" \"{url}\""; 396 | ytdlpstartInfo.CreateNoWindow = true; 397 | 398 | Process ytdlpprocess = new Process(); 399 | ytdlpprocess.StartInfo = ytdlpstartInfo; 400 | ytdlpprocess.Start(); 401 | ytdlpprocess.WaitForExit(); 402 | 403 | //Remove .fx from filenames 404 | if (File.Exists(folder + path + "/" + filename + ".f1.mp4")) 405 | { 406 | File.Move(folder + path + "/" + filename + ".f1.mp4", folder + path + "/" + filename + ".mp4"); 407 | } 408 | else if (File.Exists(folder + path + "/" + filename + ".f2.mp4")) 409 | { 410 | File.Move(folder + path + "/" + filename + ".f2.mp4", folder + path + "/" + filename + ".mp4"); 411 | } 412 | else if (File.Exists(folder + path + "/" + filename + ".f3.mp4")) 413 | { 414 | File.Move(folder + path + "/" + filename + ".f3.mp4", folder + path + "/" + filename + ".mp4"); 415 | } 416 | 417 | if (File.Exists(folder + path + "/" + filename + ".f3.m4a")) 418 | { 419 | File.Move(folder + path + "/" + filename + ".f3.m4a", folder + path + "/" + filename + ".m4a"); 420 | } 421 | else if (File.Exists(folder + path + "/" + filename + ".f4.m4a")) 422 | { 423 | File.Move(folder + path + "/" + filename + ".f4.m4a", folder + path + "/" + filename + ".m4a"); 424 | } 425 | 426 | //Use mp4decrypt to decrypt the MP4 and M4A files 427 | ProcessStartInfo mp4decryptStartInfoVideo = new ProcessStartInfo(); 428 | mp4decryptStartInfoVideo.FileName = mp4decryptpath; 429 | mp4decryptStartInfoVideo.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.mp4 {folder + path + "/" + filename}_vdec.mp4"; 430 | mp4decryptStartInfoVideo.CreateNoWindow = true; 431 | 432 | Process mp4decryptVideoProcess = new Process(); 433 | mp4decryptVideoProcess.StartInfo = mp4decryptStartInfoVideo; 434 | mp4decryptVideoProcess.Start(); 435 | mp4decryptVideoProcess.WaitForExit(); 436 | 437 | ProcessStartInfo mp4decryptStartInfoAudio = new ProcessStartInfo(); 438 | mp4decryptStartInfoAudio.FileName = mp4decryptpath; 439 | mp4decryptStartInfoAudio.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.m4a {folder + path + "/" + filename}_adec.mp4"; 440 | mp4decryptStartInfoAudio.CreateNoWindow = true; 441 | 442 | Process mp4decryptAudioProcess = new Process(); 443 | mp4decryptAudioProcess.StartInfo = mp4decryptStartInfoAudio; 444 | mp4decryptAudioProcess.Start(); 445 | mp4decryptAudioProcess.WaitForExit(); 446 | 447 | //Finally use FFMPEG to merge the 2 together 448 | ProcessStartInfo ffmpegStartInfo = new ProcessStartInfo(); 449 | ffmpegStartInfo.FileName = ffmpegpath; 450 | ffmpegStartInfo.Arguments = $"-i {folder + path + "/" + filename}_vdec.mp4 -i {folder + path + "/" + filename}_adec.mp4 -c copy {folder + path + "/" + filename}_source.mp4"; 451 | ffmpegStartInfo.CreateNoWindow = true; 452 | 453 | Process ffmpegProcess = new Process(); 454 | ffmpegProcess.StartInfo = ffmpegStartInfo; 455 | ffmpegProcess.Start(); 456 | ffmpegProcess.WaitForExit(); 457 | File.SetLastWriteTime($"{folder + path + "/" + filename}_source.mp4", lastModified); 458 | 459 | //Cleanup Files 460 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 461 | task.Increment(fileSizeInBytes); 462 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 463 | File.Delete($"{folder + path + "/" + filename}.mp4"); 464 | File.Delete($"{folder + path + "/" + filename}.m4a"); 465 | File.Delete($"{folder + path + "/" + filename}_adec.mp4"); 466 | File.Delete($"{folder + path + "/" + filename}_vdec.mp4"); 467 | 468 | return true; 469 | } 470 | else 471 | { 472 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 473 | task.Increment(fileSizeInBytes); 474 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 475 | } 476 | } 477 | else 478 | { 479 | long size = await dBHelper.GetFileSize(folder, media_id); 480 | task.Increment(size); 481 | } 482 | return false; 483 | } 484 | catch (Exception ex) 485 | { 486 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 487 | 488 | if (ex.InnerException != null) 489 | { 490 | Console.WriteLine("\nInner Exception:"); 491 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 492 | } 493 | } 494 | return false; 495 | } 496 | public async Task DownloadPaidMessageDRMVideo(string ytdlppath, string mp4decryptpath, string ffmpegpath, string user_agent, string policy, string signature, string kvp, string sess, string url, string decryptionKey, string folder, DateTime lastModified, long media_id, ProgressTask task) 497 | { 498 | try 499 | { 500 | Uri uri = new Uri(url); 501 | string filename = System.IO.Path.GetFileName(uri.LocalPath).Split(".")[0]; 502 | string path = "/Messages/Paid/Videos"; 503 | if (!Directory.Exists(folder + path)) // check if the folder already exists 504 | { 505 | Directory.CreateDirectory(folder + path); // create the new folder 506 | } 507 | if (!await dBHelper.CheckDownloaded(folder, media_id)) 508 | { 509 | if (!File.Exists(folder + path + "/" + filename + "_source.mp4")) 510 | { 511 | //Use ytdl-p to download the MPD as a M4A and MP4 file 512 | ProcessStartInfo ytdlpstartInfo = new ProcessStartInfo(); 513 | ytdlpstartInfo.FileName = ytdlppath; 514 | ytdlpstartInfo.Arguments = $"--allow-u --no-part --restrict-filenames -N 4 --user-agent \"{user_agent}\" --add-header \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess}\" --referer \"https://onlyfans.com/\" -o \"{folder + path + "/"}%(title)s.%(ext)s\" --format \"bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best[ext=m4a]\" \"{url}\""; 515 | ytdlpstartInfo.CreateNoWindow = true; 516 | 517 | Process ytdlpprocess = new Process(); 518 | ytdlpprocess.StartInfo = ytdlpstartInfo; 519 | ytdlpprocess.Start(); 520 | ytdlpprocess.WaitForExit(); 521 | 522 | //Remove .fx from filenames 523 | if (File.Exists(folder + path + "/" + filename + ".f1.mp4")) 524 | { 525 | File.Move(folder + path + "/" + filename + ".f1.mp4", folder + path + "/" + filename + ".mp4"); 526 | } 527 | else if (File.Exists(folder + path + "/" + filename + ".f2.mp4")) 528 | { 529 | File.Move(folder + path + "/" + filename + ".f2.mp4", folder + path + "/" + filename + ".mp4"); 530 | } 531 | else if (File.Exists(folder + path + "/" + filename + ".f3.mp4")) 532 | { 533 | File.Move(folder + path + "/" + filename + ".f3.mp4", folder + path + "/" + filename + ".mp4"); 534 | } 535 | 536 | if (File.Exists(folder + path + "/" + filename + ".f3.m4a")) 537 | { 538 | File.Move(folder + path + "/" + filename + ".f3.m4a", folder + path + "/" + filename + ".m4a"); 539 | } 540 | else if (File.Exists(folder + path + "/" + filename + ".f4.m4a")) 541 | { 542 | File.Move(folder + path + "/" + filename + ".f4.m4a", folder + path + "/" + filename + ".m4a"); 543 | } 544 | 545 | //Use mp4decrypt to decrypt the MP4 and M4A files 546 | ProcessStartInfo mp4decryptStartInfoVideo = new ProcessStartInfo(); 547 | mp4decryptStartInfoVideo.FileName = mp4decryptpath; 548 | mp4decryptStartInfoVideo.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.mp4 {folder + path + "/" + filename}_vdec.mp4"; 549 | mp4decryptStartInfoVideo.CreateNoWindow = true; 550 | 551 | Process mp4decryptVideoProcess = new Process(); 552 | mp4decryptVideoProcess.StartInfo = mp4decryptStartInfoVideo; 553 | mp4decryptVideoProcess.Start(); 554 | mp4decryptVideoProcess.WaitForExit(); 555 | 556 | ProcessStartInfo mp4decryptStartInfoAudio = new ProcessStartInfo(); 557 | mp4decryptStartInfoAudio.FileName = mp4decryptpath; 558 | mp4decryptStartInfoAudio.Arguments = $"--key {decryptionKey} {folder + path + "/" + filename}.m4a {folder + path + "/" + filename}_adec.mp4"; 559 | mp4decryptStartInfoAudio.CreateNoWindow = true; 560 | 561 | Process mp4decryptAudioProcess = new Process(); 562 | mp4decryptAudioProcess.StartInfo = mp4decryptStartInfoAudio; 563 | mp4decryptAudioProcess.Start(); 564 | mp4decryptAudioProcess.WaitForExit(); 565 | 566 | //Finally use FFMPEG to merge the 2 together 567 | ProcessStartInfo ffmpegStartInfo = new ProcessStartInfo(); 568 | ffmpegStartInfo.FileName = ffmpegpath; 569 | ffmpegStartInfo.Arguments = $"-i {folder + path + "/" + filename}_vdec.mp4 -i {folder + path + "/" + filename}_adec.mp4 -c copy {folder + path + "/" + filename}_source.mp4"; 570 | ffmpegStartInfo.CreateNoWindow = true; 571 | 572 | Process ffmpegProcess = new Process(); 573 | ffmpegProcess.StartInfo = ffmpegStartInfo; 574 | ffmpegProcess.Start(); 575 | ffmpegProcess.WaitForExit(); 576 | File.SetLastWriteTime($"{folder + path + "/" + filename}_source.mp4", lastModified); 577 | 578 | //Cleanup Files 579 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 580 | task.Increment(fileSizeInBytes); 581 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 582 | File.Delete($"{folder + path + "/" + filename}.mp4"); 583 | File.Delete($"{folder + path + "/" + filename}.m4a"); 584 | File.Delete($"{folder + path + "/" + filename}_adec.mp4"); 585 | File.Delete($"{folder + path + "/" + filename}_vdec.mp4"); 586 | 587 | return true; 588 | } 589 | else 590 | { 591 | long fileSizeInBytes = new FileInfo(folder + path + "/" + filename + "_source.mp4").Length; 592 | task.Increment(fileSizeInBytes); 593 | await dBHelper.UpdateMedia(folder, media_id, folder + path, filename + "_source.mp4", fileSizeInBytes, true, lastModified); 594 | } 595 | } 596 | else 597 | { 598 | long size = await dBHelper.GetFileSize(folder, media_id); 599 | task.Increment(size); 600 | } 601 | return false; 602 | } 603 | catch (Exception ex) 604 | { 605 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); 606 | 607 | if (ex.InnerException != null) 608 | { 609 | Console.WriteLine("\nInner Exception:"); 610 | Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); 611 | } 612 | } 613 | return false; 614 | } 615 | public async Task CalculateTotalFileSize(List urls, Auth auth) 616 | { 617 | long totalFileSize = 0; 618 | if (urls.Count > 250) 619 | { 620 | int batchSize = 250; 621 | 622 | var tasks = new List>(); 623 | 624 | for (int i = 0; i < urls.Count; i += batchSize) 625 | { 626 | var batchUrls = urls.Skip(i).Take(batchSize).ToList(); 627 | 628 | var batchTasks = batchUrls.Select(url => GetFileSizeAsync(url, auth)); 629 | tasks.AddRange(batchTasks); 630 | 631 | await Task.WhenAll(batchTasks); 632 | 633 | await Task.Delay(5000); 634 | } 635 | 636 | long[] fileSizes = await Task.WhenAll(tasks); 637 | foreach (long fileSize in fileSizes) 638 | { 639 | totalFileSize += fileSize; 640 | } 641 | } 642 | else 643 | { 644 | var tasks = new List>(); 645 | 646 | foreach (string url in urls) 647 | { 648 | tasks.Add(GetFileSizeAsync(url, auth)); 649 | } 650 | 651 | long[] fileSizes = await Task.WhenAll(tasks); 652 | foreach (long fileSize in fileSizes) 653 | { 654 | totalFileSize += fileSize; 655 | } 656 | } 657 | 658 | return totalFileSize; 659 | } 660 | 661 | private async Task GetFileSizeAsync(string url, Auth auth) 662 | { 663 | long fileSize = 0; 664 | 665 | try 666 | { 667 | Uri uri = new Uri(url); 668 | if (uri.Host == "cdn3.onlyfans.com" && uri.LocalPath.Contains("/dash/files")) 669 | { 670 | string[] messageUrlParsed = url.Split(','); 671 | string mpdURL = messageUrlParsed[0]; 672 | string policy = messageUrlParsed[1]; 673 | string signature = messageUrlParsed[2]; 674 | string kvp = messageUrlParsed[3]; 675 | 676 | mpdURL = mpdURL.Replace(".mpd", "_source.mp4"); 677 | 678 | using (HttpClient client = new HttpClient()) 679 | { 680 | client.DefaultRequestHeaders.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE}"); 681 | client.DefaultRequestHeaders.Add("User-Agent", auth.USER_AGENT); 682 | 683 | using (HttpResponseMessage response = await client.GetAsync(mpdURL, HttpCompletionOption.ResponseHeadersRead)) 684 | { 685 | if (response.IsSuccessStatusCode) 686 | { 687 | fileSize = response.Content.Headers.ContentLength ?? 0; 688 | } 689 | } 690 | } 691 | } 692 | } 693 | catch (Exception ex) 694 | { 695 | Console.WriteLine($"Error getting file size for URL '{url}': {ex.Message}"); 696 | } 697 | 698 | return fileSize; 699 | } 700 | } 701 | } 702 | --------------------------------------------------------------------------------