Major refactor #141

Merged
sim0n00ps merged 55 commits from whimsical-c4lic0/OF-DL:refactor-architecture into master 2026-02-13 00:21:58 +00:00
18 changed files with 457 additions and 306 deletions
Showing only changes of commit 40ccf7aa62 - Show all commits

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Archived; namespace OF_DL.Models.Dtos.Archived;

View File

@ -1,6 +1,6 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Archived; namespace OF_DL.Models.Dtos.Common;
public class CountersDto public class CountersDto
{ {

View File

@ -0,0 +1,11 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Streams;
public class InfoDto
{
[JsonProperty("source")] public SourceDto Source { get; set; } = new();
[JsonProperty("preview")] public PreviewDto Preview { get; set; } = new();
}

View File

@ -0,0 +1,101 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
using OF_DL.Utils;
namespace OF_DL.Models.Dtos.Streams;
public class ListItemDto
{
private string _rawText = "";
[JsonProperty("responseType")] public string ResponseType { get; set; } = "";
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("postedAt")] public DateTime PostedAt { get; set; }
[JsonProperty("postedAtPrecise")] public string PostedAtPrecise { get; set; } = "";
[JsonProperty("expiredAt")] public object ExpiredAt { get; set; } = new();
[JsonProperty("author")] public AuthorDto Author { get; set; } = new();
[JsonProperty("text")] public string Text { get; set; } = "";
[JsonProperty("rawText")]
public string RawText
{
get
{
if (string.IsNullOrEmpty(_rawText))
{
_rawText = XmlUtils.EvaluateInnerText(Text);
}
return _rawText;
}
set => _rawText = value;
}
[JsonProperty("lockedText")] public bool? LockedText { get; set; }
[JsonProperty("isFavorite")] public bool? IsFavorite { get; set; }
[JsonProperty("canReport")] public bool? CanReport { get; set; }
[JsonProperty("canDelete")] public bool? CanDelete { get; set; }
[JsonProperty("canComment")] public bool? CanComment { get; set; }
[JsonProperty("canEdit")] public bool? CanEdit { get; set; }
[JsonProperty("isPinned")] public bool? IsPinned { get; set; }
[JsonProperty("favoritesCount")] public int? FavoritesCount { get; set; }
[JsonProperty("mediaCount")] public int? MediaCount { get; set; }
[JsonProperty("isMediaReady")] public bool? IsMediaReady { get; set; }
[JsonProperty("voting")] public object Voting { get; set; } = new();
[JsonProperty("isOpened")] public bool? IsOpened { get; set; }
[JsonProperty("canToggleFavorite")] public bool? CanToggleFavorite { get; set; }
[JsonProperty("streamId")] public int? StreamId { get; set; }
[JsonProperty("price")] public string? Price { get; set; }
[JsonProperty("hasVoting")] public bool? HasVoting { get; set; }
[JsonProperty("isAddedToBookmarks")] public bool? IsAddedToBookmarks { get; set; }
[JsonProperty("isArchived")] public bool? IsArchived { get; set; }
[JsonProperty("isPrivateArchived")] public bool? IsPrivateArchived { get; set; }
[JsonProperty("isDeleted")] public bool? IsDeleted { get; set; }
[JsonProperty("hasUrl")] public bool? HasUrl { get; set; }
[JsonProperty("isCouplePeopleMedia")] public bool? IsCouplePeopleMedia { get; set; }
[JsonProperty("cantCommentReason")] public string CantCommentReason { get; set; } = "";
[JsonProperty("commentsCount")] public int? CommentsCount { get; set; }
[JsonProperty("mentionedUsers")] public List<object> MentionedUsers { get; set; } = [];
[JsonProperty("linkedUsers")] public List<object> LinkedUsers { get; set; } = [];
[JsonProperty("tipsAmount")] public string TipsAmount { get; set; } = "";
[JsonProperty("tipsAmountRaw")] public string TipsAmountRaw { get; set; } = "";
[JsonProperty("media")] public List<MediumDto> Media { get; set; } = [];
[JsonProperty("canViewMedia")] public bool? CanViewMedia { get; set; }
[JsonProperty("preview")] public List<object> Preview { get; set; } = [];
}

View File

@ -0,0 +1,37 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Streams;
public class MediumDto
{
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("type")] public string Type { get; set; } = "";
[JsonProperty("convertedToVideo")] public bool ConvertedToVideo { get; set; }
[JsonProperty("canView")] public bool CanView { get; set; }
[JsonProperty("hasError")] public bool HasError { get; set; }
[JsonProperty("createdAt")] public DateTime? CreatedAt { get; set; }
[JsonProperty("info")] public InfoDto Info { get; set; } = new();
[JsonProperty("source")] public SourceDto Source { get; set; } = new();
[JsonProperty("squarePreview")] public string SquarePreview { get; set; } = "";
[JsonProperty("full")] public string Full { get; set; } = "";
[JsonProperty("preview")] public string Preview { get; set; } = "";
[JsonProperty("thumb")] public string Thumb { get; set; } = "";
[JsonProperty("hasCustomPreview")] public bool HasCustomPreview { get; set; }
[JsonProperty("files")] public FilesDto Files { get; set; } = new();
[JsonProperty("videoSources")] public VideoSourcesDto VideoSources { get; set; } = new();
}

View File

@ -0,0 +1,17 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Streams;
public class StreamsDto
{
[JsonProperty("list")] public List<ListItemDto> List { get; set; } = [];
[JsonProperty("hasMore")] public bool HasMore { get; set; }
[JsonProperty("headMarker")] public string HeadMarker { get; set; } = "";
[JsonProperty("tailMarker")] public string TailMarker { get; set; } = "";
[JsonProperty("counters")] public CountersDto Counters { get; set; } = new();
}

View File

@ -0,0 +1,41 @@
using OF_DL.Models.Entities.Common;
using OF_DL.Utils;
namespace OF_DL.Models.Entities.Streams;
public class ListItem
{
private string _rawText = "";
public long Id { get; set; }
public DateTime PostedAt { get; set; }
public Author? Author { get; set; }
public string Text { get; set; } = "";
public string RawText
{
get
{
if (string.IsNullOrEmpty(_rawText))
{
_rawText = XmlUtils.EvaluateInnerText(Text);
}
return _rawText;
}
set => _rawText = value;
}
public bool IsOpened { get; set; }
public string? Price { get; set; }
public bool IsArchived { get; set; }
public List<Medium>? Media { get; set; }
public List<object>? Preview { get; set; }
}

View File

@ -0,0 +1,14 @@
using OF_DL.Models.Entities.Common;
namespace OF_DL.Models.Entities.Streams;
public class Medium
{
public long Id { get; set; }
public string? Type { get; set; }
public bool CanView { get; set; }
public Files? Files { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace OF_DL.Models.Entities.Streams;
public class Streams
{
public List<ListItem> List { get; set; } = [];
public bool HasMore { get; set; }
public string? TailMarker { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace OF_DL.Models.Entities.Streams;
public class StreamsCollection
{
public List<Medium> StreamMedia { get; set; } = [];
public List<ListItem> StreamObjects { get; set; } = [];
public Dictionary<long, string> Streams { get; set; } = new();
}

View File

@ -0,0 +1,108 @@
using OF_DL.Models.Dtos.Common;
using OF_DL.Models.Dtos.Streams;
using OF_DL.Models.Entities.Common;
using OF_DL.Models.Entities.Streams;
namespace OF_DL.Models.Mappers;
public static class StreamsMapper
{
public static Streams FromDto(StreamsDto? dto)
{
Streams mapped = new() { HasMore = dto?.HasMore ?? false, TailMarker = dto?.TailMarker };
if (dto?.List == null)
{
return mapped;
}
foreach (ListItemDto entry in dto.List)
{
mapped.List.Add(MapList(entry));
}
return mapped;
}
private static ListItem MapList(ListItemDto dto) =>
new()
{
Id = dto.Id,
PostedAt = dto.PostedAt,
Author = MapAuthor(dto.Author),
Text = dto.Text,
RawText = dto.RawText,
Price = dto.Price,
IsOpened = dto.IsOpened ?? false,
IsArchived = dto.IsArchived ?? false,
Media = MapMedia(dto.Media),
Preview = dto.Preview
};
private static Author? MapAuthor(AuthorDto? dto) =>
dto == null ? null : new Author { Id = dto.Id };
private static List<Medium>? MapMedia(List<MediumDto>? media) =>
media?.Select(MapMedium).ToList();
private static Medium MapMedium(MediumDto dto) =>
new() { Id = dto.Id, Type = dto.Type, CanView = dto.CanView, Files = MapFiles(dto.Files) };
private static Files? MapFiles(FilesDto? dto)
{
if (dto == null)
{
return null;
}
Full? full = MapFull(dto.Full);
Drm? drm = MapDrm(dto.Drm);
if (full == null && drm == null)
{
return null;
}
return new Files { Full = full, Drm = drm };
}
private static Full? MapFull(FullDto? dto) =>
dto == null || string.IsNullOrEmpty(dto.Url) ? null : new Full { Url = dto.Url };
private static Drm? MapDrm(DrmDto? dto)
{
if (dto?.Manifest == null || string.IsNullOrEmpty(dto.Manifest.Dash))
{
return null;
}
Dash? dash = MapDash(dto.Signature.Dash);
if (dash == null)
{
return null;
}
return new Drm
{
Manifest = new Manifest { Dash = dto.Manifest.Dash }, Signature = new Signature { Dash = dash }
};
}
private static Dash? MapDash(DashDto? dto)
{
if (dto == null ||
string.IsNullOrEmpty(dto.CloudFrontPolicy) ||
string.IsNullOrEmpty(dto.CloudFrontSignature) ||
string.IsNullOrEmpty(dto.CloudFrontKeyPairId))
{
return null;
}
return new Dash
{
CloudFrontPolicy = dto.CloudFrontPolicy,
CloudFrontSignature = dto.CloudFrontSignature,
CloudFrontKeyPairId = dto.CloudFrontKeyPairId
};
}
}

View File

@ -1,209 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Utils;
namespace OF_DL.Models.Streams;
public class Streams
{
public List<List> list { get; set; }
public bool hasMore { get; set; }
public string headMarker { get; set; }
public string tailMarker { get; set; }
public Counters counters { get; set; }
public class Author
{
public long id { get; set; }
public string _view { get; set; }
}
public class Counters
{
public int audiosCount { get; set; }
public int photosCount { get; set; }
public int videosCount { get; set; }
public int mediasCount { get; set; }
public int postsCount { get; set; }
public int streamsCount { get; set; }
public int archivedPostsCount { get; set; }
}
public class Files
{
public Full full { get; set; }
public Thumb thumb { get; set; }
public Preview preview { get; set; }
public SquarePreview squarePreview { get; set; }
public Drm drm { get; set; }
}
public class Full
{
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public long size { get; set; }
public List<object> sources { get; set; }
}
public class SquarePreview
{
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public long size { get; set; }
}
public class Thumb
{
public string url { get; set; }
public int width { get; set; }
public int height { get; set; }
public long size { get; set; }
}
public class Info
{
public Source source { get; set; }
public Preview preview { get; set; }
}
public class List
{
private string _rawText;
public string responseType { get; set; }
public long id { get; set; }
public DateTime postedAt { get; set; }
public string postedAtPrecise { get; set; }
public object expiredAt { get; set; }
public Author author { get; set; }
public string text { get; set; }
public string rawText
{
get
{
if (string.IsNullOrEmpty(_rawText))
{
_rawText = XmlUtils.EvaluateInnerText(text);
}
return _rawText;
}
set => _rawText = value;
}
public bool lockedText { get; set; }
public bool isFavorite { get; set; }
public bool canReport { get; set; }
public bool canDelete { get; set; }
public bool canComment { get; set; }
public bool canEdit { get; set; }
public bool isPinned { get; set; }
public int favoritesCount { get; set; }
public int mediaCount { get; set; }
public bool isMediaReady { get; set; }
public object voting { get; set; }
public bool isOpened { get; set; }
public bool canToggleFavorite { get; set; }
public int streamId { get; set; }
public string price { get; set; }
public bool hasVoting { get; set; }
public bool isAddedToBookmarks { get; set; }
public bool isArchived { get; set; }
public bool isPrivateArchived { get; set; }
public bool isDeleted { get; set; }
public bool hasUrl { get; set; }
public bool isCouplePeopleMedia { get; set; }
public string cantCommentReason { get; set; }
public int commentsCount { get; set; }
public List<object> mentionedUsers { get; set; }
public List<object> linkedUsers { get; set; }
public string tipsAmount { get; set; }
public string tipsAmountRaw { get; set; }
public List<Medium> media { get; set; }
public bool canViewMedia { get; set; }
public List<object> preview { get; set; }
}
public class Medium
{
public long id { get; set; }
public string type { get; set; }
public bool convertedToVideo { get; set; }
public bool canView { get; set; }
public bool hasError { get; set; }
public DateTime? createdAt { get; set; }
public Info info { get; set; }
public Source source { get; set; }
public string squarePreview { get; set; }
public string full { get; set; }
public string preview { get; set; }
public string thumb { get; set; }
public bool hasCustomPreview { get; set; }
public Files files { get; set; }
public VideoSources videoSources { get; set; }
}
public class Preview
{
public int width { get; set; }
public int height { get; set; }
public long size { get; set; }
public string url { get; set; }
}
public class Source
{
public string source { get; set; }
public int width { get; set; }
public int height { get; set; }
public long size { get; set; }
public int duration { get; set; }
}
public class VideoSources
{
[JsonProperty("720")] public object _720 { get; set; }
[JsonProperty("240")] public object _240 { get; set; }
}
public class Drm
{
public Manifest manifest { get; set; }
public Signature signature { get; set; }
}
public class Manifest
{
public string? hls { get; set; }
public string? dash { get; set; }
}
public class Signature
{
public Hls hls { get; set; }
public Dash dash { get; set; }
}
public class Hls
{
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
[JsonProperty("CloudFront-Key-Pair-Id")]
public string CloudFrontKeyPairId { get; set; }
}
public class Dash
{
[JsonProperty("CloudFront-Policy")] public string CloudFrontPolicy { get; set; }
[JsonProperty("CloudFront-Signature")] public string CloudFrontSignature { get; set; }
[JsonProperty("CloudFront-Key-Pair-Id")]
public string CloudFrontKeyPairId { get; set; }
}
}

View File

@ -1,8 +0,0 @@
namespace OF_DL.Models.Streams;
public class StreamsCollection
{
public List<Streams.Medium> StreamMedia = new();
public List<Streams.List> StreamObjects = new();
public Dictionary<long, string> Streams = new();
}

View File

@ -7,13 +7,13 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OF_DL.CLI; using OF_DL.CLI;
using OF_DL.Models; using OF_DL.Models;
using OF_DL.Models.Streams;
using OF_DL.Enumerations; using OF_DL.Enumerations;
using OF_DL.Helpers; using OF_DL.Helpers;
using ArchivedEntities = OF_DL.Models.Entities.Archived; using ArchivedEntities = OF_DL.Models.Entities.Archived;
using MessageEntities = OF_DL.Models.Entities.Messages; using MessageEntities = OF_DL.Models.Entities.Messages;
using PostEntities = OF_DL.Models.Entities.Posts; using PostEntities = OF_DL.Models.Entities.Posts;
using PurchasedEntities = OF_DL.Models.Entities.Purchased; using PurchasedEntities = OF_DL.Models.Entities.Purchased;
using StreamEntities = OF_DL.Models.Entities.Streams;
using OF_DL.Services; using OF_DL.Services;
using Serilog; using Serilog;
using Spectre.Console; using Spectre.Console;
@ -1764,7 +1764,7 @@ public class Program(IServiceProvider serviceProvider)
IAPIService apiService = serviceProvider.GetRequiredService<IAPIService>(); IAPIService apiService = serviceProvider.GetRequiredService<IAPIService>();
IDownloadService downloadService = serviceProvider.GetRequiredService<IDownloadService>(); IDownloadService downloadService = serviceProvider.GetRequiredService<IDownloadService>();
StreamsCollection streams = new(); StreamEntities.StreamsCollection streams = new();
await AnsiConsole.Status() await AnsiConsole.Status()
.StartAsync("[red]Getting Streams[/]", .StartAsync("[red]Getting Streams[/]",

View File

@ -6,7 +6,6 @@ using System.Xml.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OF_DL.Models; using OF_DL.Models;
using OF_DL.Models.Streams;
using OF_DL.Enumerations; using OF_DL.Enumerations;
using ArchivedDtos = OF_DL.Models.Dtos.Archived; using ArchivedDtos = OF_DL.Models.Dtos.Archived;
using HighlightDtos = OF_DL.Models.Dtos.Highlights; using HighlightDtos = OF_DL.Models.Dtos.Highlights;
@ -15,13 +14,15 @@ using MessageDtos = OF_DL.Models.Dtos.Messages;
using PostDtos = OF_DL.Models.Dtos.Posts; using PostDtos = OF_DL.Models.Dtos.Posts;
using PurchasedDtos = OF_DL.Models.Dtos.Purchased; using PurchasedDtos = OF_DL.Models.Dtos.Purchased;
using StoriesDtos = OF_DL.Models.Dtos.Stories; using StoriesDtos = OF_DL.Models.Dtos.Stories;
using StreamsDtos = OF_DL.Models.Dtos.Streams;
using ArchivedEntities = OF_DL.Models.Entities.Archived; using ArchivedEntities = OF_DL.Models.Entities.Archived;
using HighlightEntities = OF_DL.Models.Entities.Highlights; using HighlightEntities = OF_DL.Models.Entities.Highlights;
using ListEntities = OF_DL.Models.Entities.Lists; using ListEntities = OF_DL.Models.Entities.Lists;
using MessageEntities = OF_DL.Models.Entities.Messages; using MessageEntities = OF_DL.Models.Entities.Messages;
using PostEntities = OF_DL.Models.Entities.Posts; using PostEntities = OF_DL.Models.Entities.Posts;
using PurchasedEntities = OF_DL.Models.Entities.Purchased; using PurchasedEntities = OF_DL.Models.Entities.Purchased;
using StoriesEntities = OF_DL.Models.Entities.Stories; using StoryEntities = OF_DL.Models.Entities.Stories;
using StreamEntities = OF_DL.Models.Entities.Streams;
using OF_DL.Models.Mappers; using OF_DL.Models.Mappers;
using OF_DL.Widevine; using OF_DL.Widevine;
using Serilog; using Serilog;
@ -396,9 +397,9 @@ public class APIService(IAuthService authService, IConfigService configService,
List<StoriesDtos.StoryDto>? storiesDto = List<StoriesDtos.StoryDto>? storiesDto =
JsonConvert.DeserializeObject<List<StoriesDtos.StoryDto>>(body, m_JsonSerializerSettings); JsonConvert.DeserializeObject<List<StoriesDtos.StoryDto>>(body, m_JsonSerializerSettings);
List<StoriesEntities.Stories> stories = StoriesMapper.FromDto(storiesDto); List<StoryEntities.Stories> stories = StoriesMapper.FromDto(storiesDto);
foreach (StoriesEntities.Stories story in stories) foreach (StoryEntities.Stories story in stories)
{ {
if (story.Media[0].CreatedAt.HasValue) if (story.Media[0].CreatedAt.HasValue)
{ {
@ -417,7 +418,7 @@ public class APIService(IAuthService authService, IConfigService configService,
if (story.Media != null && story.Media.Count > 0) if (story.Media != null && story.Media.Count > 0)
{ {
foreach (StoriesEntities.Medium medium in story.Media) foreach (StoryEntities.Medium medium in story.Media)
{ {
await dbService.AddMedia(folder, medium.Id, story.Id, medium.Files.Full.Url, null, null, await dbService.AddMedia(folder, medium.Id, story.Id, medium.Files.Full.Url, null, null,
null, "Stories", null, "Stories",
@ -1208,15 +1209,16 @@ public class APIService(IAuthService authService, IConfigService configService,
return null; return null;
} }
public async Task<StreamsCollection> GetStreams(string endpoint, string folder, List<long> paid_post_ids, public async Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder,
List<long> paid_post_ids,
StatusContext ctx) StatusContext ctx)
{ {
Log.Debug($"Calling GetStreams - {endpoint}"); Log.Debug($"Calling GetStreams - {endpoint}");
try try
{ {
Streams streams = new(); StreamEntities.Streams streams = new();
StreamsCollection streamsCollection = new(); StreamEntities.StreamsCollection streamsCollection = new();
int post_limit = 50; int post_limit = 50;
Dictionary<string, string> getParams = new() Dictionary<string, string> getParams = new()
{ {
@ -1239,29 +1241,33 @@ public class APIService(IAuthService authService, IConfigService configService,
configService.CurrentConfig.CustomDate); configService.CurrentConfig.CustomDate);
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient()); string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());
streams = JsonConvert.DeserializeObject<Streams>(body, m_JsonSerializerSettings); StreamsDtos.StreamsDto? streamsDto =
ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]"); JsonConvert.DeserializeObject<StreamsDtos.StreamsDto>(body, m_JsonSerializerSettings);
streams = StreamsMapper.FromDto(streamsDto);
ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.List.Count}[/]");
ctx.Spinner(Spinner.Known.Dots); ctx.Spinner(Spinner.Known.Dots);
ctx.SpinnerStyle(Style.Parse("blue")); ctx.SpinnerStyle(Style.Parse("blue"));
if (streams != null && streams.hasMore) if (streams != null && streams.HasMore)
{ {
UpdateGetParamsForDateSelection( UpdateGetParamsForDateSelection(
downloadDateSelection, downloadDateSelection,
ref getParams, ref getParams,
streams.tailMarker); streams.TailMarker);
while (true) while (true)
{ {
Streams newstreams = new(); StreamEntities.Streams newstreams = new();
string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient()); string? loopbody = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient());
newstreams = JsonConvert.DeserializeObject<Streams>(loopbody, m_JsonSerializerSettings); StreamsDtos.StreamsDto? newStreamsDto =
JsonConvert.DeserializeObject<StreamsDtos.StreamsDto>(loopbody, m_JsonSerializerSettings);
newstreams = StreamsMapper.FromDto(newStreamsDto);
streams.list.AddRange(newstreams.list); streams.List.AddRange(newstreams.List);
ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.list.Count}[/]"); ctx.Status($"[red]Getting Streams\n[/] [red]Found {streams.List.Count}[/]");
ctx.Spinner(Spinner.Known.Dots); ctx.Spinner(Spinner.Known.Dots);
ctx.SpinnerStyle(Style.Parse("blue")); ctx.SpinnerStyle(Style.Parse("blue"));
if (!newstreams.hasMore) if (!newstreams.HasMore)
{ {
break; break;
} }
@ -1269,18 +1275,18 @@ public class APIService(IAuthService authService, IConfigService configService,
UpdateGetParamsForDateSelection( UpdateGetParamsForDateSelection(
downloadDateSelection, downloadDateSelection,
ref getParams, ref getParams,
newstreams.tailMarker); newstreams.TailMarker);
} }
} }
foreach (Streams.List stream in streams.list) foreach (StreamEntities.ListItem stream in streams.List)
{ {
List<long> streamPreviewIds = new(); List<long> streamPreviewIds = new();
if (stream.preview != null && stream.preview.Count > 0) if (stream.Preview != null && stream.Preview.Count > 0)
{ {
for (int i = 0; i < stream.preview.Count; i++) for (int i = 0; i < stream.Preview.Count; i++)
{ {
if (stream.preview[i] is long previewId) if (stream.Preview[i] is long previewId)
{ {
if (!streamPreviewIds.Contains(previewId)) if (!streamPreviewIds.Contains(previewId))
{ {
@ -1290,68 +1296,68 @@ public class APIService(IAuthService authService, IConfigService configService,
} }
} }
await dbService.AddPost(folder, stream.id, stream.text != null ? stream.text : string.Empty, await dbService.AddPost(folder, stream.Id, stream.Text != null ? stream.Text : string.Empty,
stream.price != null ? stream.price : "0", stream.price != null && stream.isOpened ? true : false, stream.Price != null ? stream.Price : "0", stream.Price != null && stream.IsOpened ? true : false,
stream.isArchived, stream.postedAt); stream.IsArchived, stream.PostedAt);
streamsCollection.StreamObjects.Add(stream); streamsCollection.StreamObjects.Add(stream);
if (stream.media != null && stream.media.Count > 0) if (stream.Media != null && stream.Media.Count > 0)
{ {
foreach (Streams.Medium medium in stream.media) foreach (StreamEntities.Medium medium in stream.Media)
{ {
if (medium.type == "photo" && !configService.CurrentConfig.DownloadImages) if (medium.Type == "photo" && !configService.CurrentConfig.DownloadImages)
{ {
continue; continue;
} }
if (medium.type == "video" && !configService.CurrentConfig.DownloadVideos) if (medium.Type == "video" && !configService.CurrentConfig.DownloadVideos)
{ {
continue; continue;
} }
if (medium.type == "gif" && !configService.CurrentConfig.DownloadVideos) if (medium.Type == "gif" && !configService.CurrentConfig.DownloadVideos)
{ {
continue; continue;
} }
if (medium.type == "audio" && !configService.CurrentConfig.DownloadAudios) if (medium.Type == "audio" && !configService.CurrentConfig.DownloadAudios)
{ {
continue; continue;
} }
if (medium.canView && medium.files?.drm == null) if (medium.CanView && medium.Files?.Drm == null)
{ {
bool has = paid_post_ids.Any(cus => cus.Equals(medium.id)); bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id));
if (!has && medium.canView && medium.files != null && medium.files.full != null && if (!has && medium.CanView && medium.Files != null && medium.Files.Full != null &&
!string.IsNullOrEmpty(medium.files.full.url)) !string.IsNullOrEmpty(medium.Files.Full.Url))
{ {
if (!streamsCollection.Streams.ContainsKey(medium.id)) if (!streamsCollection.Streams.ContainsKey(medium.Id))
{ {
await dbService.AddMedia(folder, medium.id, stream.id, medium.files.full.url, null, await dbService.AddMedia(folder, medium.Id, stream.Id, medium.Files.Full.Url, null,
null, null, "Posts", null, null, "Posts",
medium.type == "photo" ? "Images" : medium.Type == "photo" ? "Images" :
medium.type == "video" || medium.type == "gif" ? "Videos" : medium.Type == "video" || medium.Type == "gif" ? "Videos" :
medium.type == "audio" ? "Audios" : null, medium.Type == "audio" ? "Audios" : null,
streamPreviewIds.Contains(medium.id) ? true : false, false, null); streamPreviewIds.Contains(medium.Id) ? true : false, false, null);
streamsCollection.Streams.Add(medium.id, medium.files.full.url); streamsCollection.Streams.Add(medium.Id, medium.Files.Full.Url);
streamsCollection.StreamMedia.Add(medium); streamsCollection.StreamMedia.Add(medium);
} }
} }
} }
else if (medium.canView && medium.files != null && medium.files.drm != null) else if (medium.CanView && medium.Files != null && medium.Files.Drm != null)
{ {
bool has = paid_post_ids.Any(cus => cus.Equals(medium.id)); bool has = paid_post_ids.Any(cus => cus.Equals(medium.Id));
if (!has && medium.files != null && medium.files.drm != null) if (!has && medium.Files != null && medium.Files.Drm != null)
{ {
if (!streamsCollection.Streams.ContainsKey(medium.id)) if (!streamsCollection.Streams.ContainsKey(medium.Id))
{ {
await dbService.AddMedia(folder, medium.id, stream.id, await dbService.AddMedia(folder, medium.Id, stream.Id,
medium.files.drm.manifest.dash, null, null, null, "Posts", medium.Files.Drm.Manifest.Dash, null, null, null, "Posts",
medium.type == "photo" ? "Images" : medium.Type == "photo" ? "Images" :
medium.type == "video" || medium.type == "gif" ? "Videos" : medium.Type == "video" || medium.Type == "gif" ? "Videos" :
medium.type == "audio" ? "Audios" : null, medium.Type == "audio" ? "Audios" : null,
streamPreviewIds.Contains(medium.id) ? true : false, false, null); streamPreviewIds.Contains(medium.Id) ? true : false, false, null);
streamsCollection.Streams.Add(medium.id, streamsCollection.Streams.Add(medium.Id,
$"{medium.files.drm.manifest.dash},{medium.files.drm.signature.dash.CloudFrontPolicy},{medium.files.drm.signature.dash.CloudFrontSignature},{medium.files.drm.signature.dash.CloudFrontKeyPairId},{medium.id},{stream.id}"); $"{medium.Files.Drm.Manifest.Dash},{medium.Files.Drm.Signature.Dash.CloudFrontPolicy},{medium.Files.Drm.Signature.Dash.CloudFrontSignature},{medium.Files.Drm.Signature.Dash.CloudFrontKeyPairId},{medium.Id},{stream.Id}");
streamsCollection.StreamMedia.Add(medium); streamsCollection.StreamMedia.Add(medium);
} }
} }

View File

@ -4,13 +4,13 @@ using System.Xml.Linq;
using FFmpeg.NET; using FFmpeg.NET;
using FFmpeg.NET.Events; using FFmpeg.NET.Events;
using OF_DL.Models; using OF_DL.Models;
using OF_DL.Models.Streams;
using OF_DL.Enumerations; using OF_DL.Enumerations;
using ArchivedEntities = OF_DL.Models.Entities.Archived; using ArchivedEntities = OF_DL.Models.Entities.Archived;
using CommonEntities = OF_DL.Models.Entities.Common; using CommonEntities = OF_DL.Models.Entities.Common;
using MessageEntities = OF_DL.Models.Entities.Messages; using MessageEntities = OF_DL.Models.Entities.Messages;
using PostEntities = OF_DL.Models.Entities.Posts; using PostEntities = OF_DL.Models.Entities.Posts;
using PurchasedEntities = OF_DL.Models.Entities.Purchased; using PurchasedEntities = OF_DL.Models.Entities.Purchased;
using StreamEntities = OF_DL.Models.Entities.Streams;
using OF_DL.Utils; using OF_DL.Utils;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
@ -1012,14 +1012,15 @@ public class DownloadService(
} }
public async Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, public async Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type,
IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, IProgressReporter progressReporter, string? filenameFormat, StreamEntities.ListItem? streamInfo,
Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users) StreamEntities.Medium? streamMedia, CommonEntities.Author? author,
Dictionary<string, long> users)
{ {
string path; string path;
if (configService.CurrentConfig.FolderPerPost && streamInfo != null && streamInfo?.id is not null && if (configService.CurrentConfig.FolderPerPost && streamInfo != null && streamInfo?.Id is not null &&
streamInfo?.postedAt is not null) streamInfo?.PostedAt is not null)
{ {
path = $"/Posts/Free/{streamInfo.id} {streamInfo.postedAt:yyyy-MM-dd HH-mm-ss}"; path = $"/Posts/Free/{streamInfo.Id} {streamInfo.PostedAt:yyyy-MM-dd HH-mm-ss}";
} }
else else
{ {
@ -1967,8 +1968,9 @@ public class DownloadService(
public async Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, public async Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url,
string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type, string decryptionKey, string folder, DateTime lastModified, long media_id, string api_type,
IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, IProgressReporter progressReporter, string? filenameFormat, StreamEntities.ListItem? streamInfo,
Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users) StreamEntities.Medium? streamMedia, CommonEntities.Author? author,
Dictionary<string, long> users)
{ {
try try
{ {
@ -1976,10 +1978,10 @@ public class DownloadService(
string path; string path;
Uri uri = new(url); Uri uri = new(url);
string filename = Path.GetFileName(uri.LocalPath).Split(".")[0]; string filename = Path.GetFileName(uri.LocalPath).Split(".")[0];
if (configService.CurrentConfig.FolderPerPost && streamInfo != null && streamInfo?.id is not null && if (configService.CurrentConfig.FolderPerPost && streamInfo != null && streamInfo?.Id is not null &&
streamInfo?.postedAt is not null) streamInfo?.PostedAt is not null)
{ {
path = $"/Posts/Free/{streamInfo.id} {streamInfo.postedAt:yyyy-MM-dd HH-mm-ss}/Videos"; path = $"/Posts/Free/{streamInfo.Id} {streamInfo.PostedAt:yyyy-MM-dd HH-mm-ss}/Videos";
} }
else else
{ {
@ -2809,7 +2811,7 @@ public class DownloadService(
public async Task<DownloadResult> DownloadStreams(string username, long userId, string path, public async Task<DownloadResult> DownloadStreams(string username, long userId, string path,
Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing, Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
StreamsCollection streams, IProgressReporter progressReporter) StreamEntities.StreamsCollection streams, IProgressReporter progressReporter)
{ {
Log.Debug($"Calling DownloadStreams - {username}"); Log.Debug($"Calling DownloadStreams - {username}");
@ -2853,14 +2855,14 @@ public class DownloadService(
$"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}?type=widevine", $"https://onlyfans.com/api2/v2/users/media/{parsed[4]}/drm/post/{parsed[5]}?type=widevine",
pssh); pssh);
Streams.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.id == kvp.Key); StreamEntities.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.Id == kvp.Key);
Streams.List? streamInfo = StreamEntities.ListItem? streamInfo =
streams.StreamObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); streams.StreamObjects.FirstOrDefault(p => p?.Media?.Contains(mediaInfo) == true);
isNew = await DownloadStreamsDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], decryptionKey, isNew = await DownloadStreamsDRMVideo(parsed[1], parsed[2], parsed[3], parsed[0], decryptionKey,
path, lastModified, kvp.Key, "Streams", progressReporter, path, lastModified, kvp.Key, "Streams", progressReporter,
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ??
string.Empty, streamInfo, mediaInfo, streamInfo?.author, users); string.Empty, streamInfo, mediaInfo, streamInfo?.Author, users);
} }
else else
{ {
@ -2869,12 +2871,12 @@ public class DownloadService(
} }
else else
{ {
Streams.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.id == kvp.Key); StreamEntities.Medium? mediaInfo = streams.StreamMedia.FirstOrDefault(m => m.Id == kvp.Key);
Streams.List? streamInfo = StreamEntities.ListItem? streamInfo =
streams.StreamObjects.FirstOrDefault(p => p?.media?.Contains(mediaInfo) == true); streams.StreamObjects.FirstOrDefault(p => p?.Media?.Contains(mediaInfo) == true);
isNew = await DownloadStreamMedia(kvp.Value, path, kvp.Key, "Streams", progressReporter, isNew = await DownloadStreamMedia(kvp.Value, path, kvp.Key, "Streams", progressReporter,
configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ?? configService.CurrentConfig.GetCreatorFileNameFormatConfig(username).PostFileNameFormat ??
string.Empty, streamInfo, mediaInfo, streamInfo?.author, users); string.Empty, streamInfo, mediaInfo, streamInfo?.Author, users);
} }
if (isNew) if (isNew)

View File

@ -1,11 +1,11 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OF_DL.Models; using OF_DL.Models;
using OF_DL.Models.Streams;
using OF_DL.Enumerations; using OF_DL.Enumerations;
using OF_DL.Models.Entities.Purchased;
using ArchivedEntities = OF_DL.Models.Entities.Archived; using ArchivedEntities = OF_DL.Models.Entities.Archived;
using MessageEntities = OF_DL.Models.Entities.Messages; using MessageEntities = OF_DL.Models.Entities.Messages;
using PostEntities = OF_DL.Models.Entities.Posts; using PostEntities = OF_DL.Models.Entities.Posts;
using PurchasedEntities = OF_DL.Models.Entities.Purchased;
using StreamEntities = OF_DL.Models.Entities.Streams;
using Spectre.Console; using Spectre.Console;
namespace OF_DL.Services; namespace OF_DL.Services;
@ -22,20 +22,30 @@ public interface IAPIService
Task<Dictionary<long, string>> GetMedia(MediaType mediatype, string endpoint, string? username, string folder, Task<Dictionary<long, string>> GetMedia(MediaType mediatype, string endpoint, string? username, string folder,
List<long> paid_post_ids); List<long> paid_post_ids);
Task<PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username, List<long> paid_post_ids, Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username,
List<long> paid_post_ids,
StatusContext ctx); StatusContext ctx);
Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paid_post_ids, Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paid_post_ids,
StatusContext ctx); StatusContext ctx);
Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder); Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder);
Task<StreamsCollection> GetStreams(string endpoint, string folder, List<long> paid_post_ids, StatusContext ctx);
Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder, List<long> paid_post_ids,
StatusContext ctx);
Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder, StatusContext ctx); Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder, StatusContext ctx);
Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder, StatusContext ctx); Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder, StatusContext ctx);
Task<PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username, StatusContext ctx);
Task<SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder); Task<PurchasedEntities.PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username,
StatusContext ctx);
Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder);
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users); Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users);
Task<List<PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder, Dictionary<string, long> users);
Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder,
Dictionary<string, long> users);
Task<User> GetUserInfo(string endpoint); Task<User> GetUserInfo(string endpoint);
Task<JObject> GetUserInfoById(string endpoint); Task<JObject> GetUserInfoById(string endpoint);
Dictionary<string, string> GetDynamicHeaders(string path, string queryParam); Dictionary<string, string> GetDynamicHeaders(string path, string queryParam);

View File

@ -1,10 +1,10 @@
using OF_DL.Models; using OF_DL.Models;
using OF_DL.Models.Streams;
using ArchivedEntities = OF_DL.Models.Entities.Archived; using ArchivedEntities = OF_DL.Models.Entities.Archived;
using CommonEntities = OF_DL.Models.Entities.Common; using CommonEntities = OF_DL.Models.Entities.Common;
using MessageEntities = OF_DL.Models.Entities.Messages; using MessageEntities = OF_DL.Models.Entities.Messages;
using PostEntities = OF_DL.Models.Entities.Posts; using PostEntities = OF_DL.Models.Entities.Posts;
using PurchasedEntities = OF_DL.Models.Entities.Purchased; using PurchasedEntities = OF_DL.Models.Entities.Purchased;
using StreamEntities = OF_DL.Models.Entities.Streams;
namespace OF_DL.Services; namespace OF_DL.Services;
@ -95,12 +95,14 @@ public interface IDownloadService
IProgressReporter progressReporter); IProgressReporter progressReporter);
Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type, Task<bool> DownloadStreamMedia(string url, string folder, long media_id, string api_type,
IProgressReporter progressReporter, string? filenameFormat, Streams.List? streamInfo, IProgressReporter progressReporter, string? filenameFormat, StreamEntities.ListItem? streamInfo,
Streams.Medium? streamMedia, Streams.Author? author, Dictionary<string, long> users); StreamEntities.Medium? streamMedia, CommonEntities.Author? author,
Dictionary<string, long> users);
Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey, Task<bool> DownloadStreamsDRMVideo(string policy, string signature, string kvp, string url, string decryptionKey,
string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter, string folder, DateTime lastModified, long media_id, string api_type, IProgressReporter progressReporter,
string? filenameFormat, Streams.List? streamInfo, Streams.Medium? streamMedia, Streams.Author? author, string? filenameFormat, StreamEntities.ListItem? streamInfo, StreamEntities.Medium? streamMedia,
CommonEntities.Author? author,
Dictionary<string, long> users); Dictionary<string, long> users);
Task<bool> DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url, Task<bool> DownloadSingleMessagePreviewDRMVideo(string policy, string signature, string kvp, string url,
@ -132,7 +134,7 @@ public interface IDownloadService
IProgressReporter progressReporter); IProgressReporter progressReporter);
Task<DownloadResult> DownloadStreams(string username, long userId, string path, Dictionary<string, long> users, Task<DownloadResult> DownloadStreams(string username, long userId, string path, Dictionary<string, long> users,
bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamsCollection streams, bool clientIdBlobMissing, bool devicePrivateKeyMissing, StreamEntities.StreamsCollection streams,
IProgressReporter progressReporter); IProgressReporter progressReporter);
Task<DownloadResult> DownloadFreePosts(string username, long userId, string path, Dictionary<string, long> users, Task<DownloadResult> DownloadFreePosts(string username, long userId, string path, Dictionary<string, long> users,