Compare commits

..

No commits in common. "master" and "working_2025-05-13" have entirely different histories.

349 changed files with 14013 additions and 28634 deletions

View File

@ -1,186 +1,9 @@
# editorconfig.org
# top-most EditorConfig file
# Editor configuration, see https://editorconfig.org
root = true
# Default settings:
# A newline ending every file
# Use 4 spaces as indentation
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[project.json]
indent_size = 2
# Generated code
[*{_AssemblyInfo.cs,.notsupported.cs}]
generated_code = true
# C# files
[*.cs]
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
# Modifier preferences
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion
# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Types: use keywords instead of BCL types, and permit var only when the type is clear
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
dotnet_naming_style.static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
# Code style defaults
csharp_using_directive_placement = outside_namespace:suggestion
dotnet_sort_system_directives_first = true
csharp_prefer_braces = true:silent
csharp_preserve_single_line_blocks = true:none
csharp_preserve_single_line_statements = false:none
csharp_prefer_static_local_function = true:suggestion
csharp_prefer_simple_using_statement = false:none
csharp_style_prefer_switch_expression = true:suggestion
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
csharp_prefer_simple_default_expression = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_constructors = true:silent
csharp_style_expression_bodied_operators = true:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = true:silent
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Other features
csharp_style_prefer_index_operator = false:none
csharp_style_prefer_range_operator = false:none
csharp_style_pattern_local_over_anonymous_function = false:none
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
indent_size = 2
[*.{csproj,vbproj,proj,nativeproj,locproj}]
charset = utf-8
# Xml build files
[*.builds]
indent_size = 2
# Xml files
[*.{xml,stylecop,resx,ruleset}]
indent_size = 2
# Xml config files
[*.{props,targets,config,nuspec}]
indent_size = 2
# YAML config files
[*.{yml,yaml}]
indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf
[*.{cmd,bat}]
end_of_line = crlf

5
.gitattributes vendored
View File

@ -3,11 +3,6 @@
###############################################################################
* text=auto
###############################################################################
# Shell scripts should use LF line endings (avoid /bin/sh^M issues in containers)
###############################################################################
*.sh text eol=lf
###############################################################################
# Set default behavior for command prompt diff.
#

View File

@ -4,6 +4,10 @@ on:
push:
tags:
- 'OFDLV*'
paths:
- 'docs/**'
- '.gitea/workflows/publish-docs.yml'
workflow_dispatch:
jobs:
build-and-deploy:

View File

@ -29,54 +29,35 @@ jobs:
- name: Build for Windows and Linux
run: |
dotnet publish "OF DL.Cli/OF DL.Cli.csproj" -p:Version=${{ steps.version.outputs.version }} \
dotnet publish -p:Version=${{ steps.version.outputs.version }} \
-p:PackageVersion=${{ steps.version.outputs.version }} \
-p:WarningLevel=0 -c Release -r win-x86 \
--self-contained true -p:PublishSingleFile=true -o outwin-cli
--self-contained true -p:PublishSingleFile=true -o outwin
dotnet publish "OF DL.Gui/OF DL.Gui.csproj" -p:Version=${{ steps.version.outputs.version }} \
-p:PackageVersion=${{ steps.version.outputs.version }} \
-p:WarningLevel=0 -c Release -r win-x86 \
--self-contained true -p:PublishSingleFile=true -o outwin-gui
dotnet publish "OF DL.Cli/OF DL.Cli.csproj" -p:Version=${{ steps.version.outputs.version }} \
dotnet publish -p:Version=${{ steps.version.outputs.version }} \
-p:PackageVersion=${{ steps.version.outputs.version }} \
-p:WarningLevel=0 -c Release -r linux-x64 \
--self-contained true -p:PublishSingleFile=true -o outlin-cli
--self-contained true -p:PublishSingleFile=true -o outlin
- name: Copy and patch extra files
run: |
cd outlin-cli
chmod +x "OF DL.Cli"
cp ./OF\ DL/rules.json outwin/
chmod +x ./outlin/OF\ DL
cd outwin
echo "➤ Running OF DL binary (timeout)"
timeout --preserve-status --kill-after=5s 30s "./OF DL.Cli" --non-interactive || true
timeout --preserve-status --kill-after=5s 30s ../outlin/OF\ DL --non-interactive || true
echo "➤ Binary finished"
echo "➤ Combine OF DL.Cli and OF DL.Gui into a single release folder"
cd ..
mv outwin-gui outwin
cd outwin
mv "../outwin-cli/OF DL.Cli.exe" .
mv ../outlin-cli/config.conf .
rm *.pdb
mv "OF DL.Gui.exe" "OF DL.exe"
mv "OF DL.Cli.exe" "OF DL - Classic.exe"
echo "➤ Remove unneeded playwright binaries"
rm -rf .playwright/node/darwin*
rm -rf .playwright/node/linux*
echo "➤ Creating folder for CDM"
mkdir -p cdm/devices/chrome_1610
echo "➤ Copying ffmpeg and ffprobe from user folder"
echo "➤ Copying ffmpeg from user folder"
cp /home/rhys/ffmpeg/ffmpeg-7.1.1-essentials_build/bin/ffmpeg.exe .
cp /home/rhys/ffmpeg/ffmpeg-7.1.1-essentials_build/bin/ffprobe.exe .
cp /home/rhys/ffmpeg/ffmpeg-7.1.1-essentials_build/LICENSE LICENSE.ffmpeg
echo "➤ Creating release zip"
zip -r ../OFDLV${{ steps.version.outputs.version }}.zip .playwright *
zip ../OFDLV${{ steps.version.outputs.version }}.zip OF\ DL.exe e_sqlite3.dll rules.json config.conf cdm ffmpeg.exe LICENSE.ffmpeg
cd ..
- name: Create release and upload artifact

9
.gitignore vendored
View File

@ -371,12 +371,3 @@ FodyWeavers.xsd
# venv
venv/
# Generated docs
/site
# Builds
/outwin
/outwin-cli
/outwin-gui
/outlin-cli

123
AGENTS.md
View File

@ -1,123 +0,0 @@
# AGENTS.md
## Purpose
OF DL (OF-DL) is a C# (`net10.0`) app suite with:
- A modern Avalonia GUI
- A classic CLI
- Shared core services for auth, API calls, downloads, DRM handling, and metadata storage
## Architecture at a glance
1. `AuthService` loads `auth.json` or runs browser login and saves auth.
2. `ApiService` signs OnlyFans API requests with dynamic rules.
3. `DownloadOrchestrationService` selects creators/lists and coordinates download jobs.
4. `DownloadService` downloads/decrypts media and records metadata via `DBService`.
## Key directories
- `OF DL.Cli/`: CLI-specific UI/helpers
- `OF DL.Gui/`: Avalonia UI, view models, windows
- `OF DL.Core/Services/`: business logic (auth/api/download/config/startup/db/logging)
- `OF DL.Core/Models/`: DTOs, entities, config/auth/download/startup models, mappers
- `OF DL.Core/Widevine/`: Widevine CDM logic
- `docs/`: MkDocs source
- `docker/`: container entrypoint/runtime config
## Files and entry points to check first
- `OF DL.Gui/ViewModels/MainWindowViewModel.cs`
- `OF DL.Gui/Views/MainWindow.axaml`
- `OF DL.Core/Services/ApiService.cs`
- `OF DL.Core/Services/AuthService.cs`
- `OF DL.Core/Services/ConfigService.cs`
- `OF DL.Core/Services/DownloadOrchestrationService.cs`
- `OF DL.Core/Services/DownloadService.cs`
- `OF DL.Core/Services/StartupService.cs`
- `OF DL.Core/Services/DBService.cs`
- `OF DL.Core/Helpers/EnvironmentHelper.cs`
## Runtime files (relative to working directory)
- `config.conf` (primary config)
- `auth.json` (saved auth)
- `rules.json` (dynamic rules fallback)
- `users.db` (global user index)
- `chromium-data/` (browser profile for auth)
- `cdm/` (Widevine device files)
Default download root when `DownloadPath` is blank:
- `__user_data__/sites/OnlyFans/{username}`
## Commands
Build GUI:
```bash
dotnet build "OF DL.Gui/OF DL.Gui.csproj"
```
Build CLI:
```bash
dotnet build "OF DL.Cli/OF DL.Cli.csproj"
```
Tests:
```bash
dotnet test "OF DL.Tests/OF DL.Tests.csproj"
```
Coverage (optional):
```bash
dotnet test "OF DL.Tests/OF DL.Tests.csproj" --collect:"XPlat Code Coverage"
```
## High-impact technical details
Dynamic rules and request signing (`ApiService.GetDynamicHeaders`):
- Remote rules URL: `https://git.ofdl.tools/sim0n00ps/dynamic-rules/raw/branch/main/rules.json`
- Falls back to local `rules.json`
- Signed headers include `app-token`, `sign`, `time`, `user-id`, `user-agent`, `x-bc`, `cookie`
DRM decryption:
- Preferred: local CDM device files under `cdm/devices/chrome_1610/`
- Fallback path exists when CDM files are missing (`ofdl.tools/WV`)
- Primary flow is in `DownloadService` + DRM helpers in `ApiService`
## Documentation update rules
Update docs whenever user-facing behavior changes.
- Config shape/options changed: update
- `docs/config/configuration.md`
- `docs/config/all-configuration-options.md`
- `docs/config/custom-filename-formats.md` (if filename tokens/formats changed)
- Auth/login behavior changed: update `docs/config/auth.md`
- GUI/CLI workflow changed: update
- `docs/operation/modern-version.md`
- `docs/operation/classic-version.md` (if applicable)
- Docker runtime/paths changed: update `docs/installation/docker.md`
## Coding style essentials
Follow `.editorconfig`. Most important rules:
- 4 spaces (2 for XML/YAML/project files), no tabs
- C# braces on new lines
- Prefer predefined types (`int`, `string`)
- `using` directives outside namespace, `System` first
- Private fields `_camelCase`; private static `s_` prefix
## Agent guardrails
- Prefer small, targeted changes in the service/viewmodel responsible for the behavior.
- Keep GUI and CLI behavior aligned when changes affect both.
- Do not manually edit generated docs in `site/`.
- If you add new significant workflow/service structure, update this file.

View File

@ -1,72 +1,66 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
FROM alpine:3.20 AS build
ARG VERSION
RUN apk --no-cache --repository community add \
dotnet8-sdk
# Copy source code
COPY ["OF DL.sln", "/src/OF DL.sln"]
COPY ["OF DL.Cli", "/src/OF DL.Cli"]
COPY ["OF DL.Core", "/src/OF DL.Core"]
COPY ["OF DL.Gui", "/src/OF DL.Gui"]
COPY ["OF DL", "/src/OF DL"]
WORKDIR "/src"
# Build release
RUN dotnet publish "OF DL.Gui/OF DL.Gui.csproj" -p:WarningLevel=0 -p:Version=$VERSION -c Release -o outgui \
&& dotnet publish "OF DL.Cli/OF DL.Cli.csproj" -p:WarningLevel=0 -p:Version=$VERSION -c Release -o outcli \
RUN dotnet publish -p:WarningLevel=0 -p:Version=$VERSION -c Release --self-contained true -p:PublishSingleFile=true -o out
# Generate default config.conf files
&& /src/outcli/OF\ DL.Cli --non-interactive || true && \
RUN /src/out/OF\ DL --non-interactive || true && \
# Set download path in default config.conf to /data
sed -e 's/DownloadPath = ""/DownloadPath = "\/data"/' /src/config.conf > /src/updated_config.conf && \
mv /src/updated_config.conf /src/config.conf
FROM mcr.microsoft.com/dotnet/runtime:10.0 AS final
FROM alpine:3.20 AS final
# Install dependencies
RUN apt-get update \
&& apt-get install -y \
RUN apk --no-cache --repository community add \
bash \
tini \
dotnet8-runtime \
ffmpeg \
udev \
ttf-freefont \
chromium \
supervisor \
xvfb \
x11vnc \
novnc \
npm \
openbox \
xclip \
&& npx playwright install-deps \
&& apt-get remove --purge -y npm \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
novnc
# Redirect webroot to vnc.html instead of displaying directory listing
&& echo "<!DOCTYPE html><html><head><meta http-equiv=\"Refresh\" content=\"0; url='vnc.html'\" /></head><body></body></html>" > /usr/share/novnc/index.html \
RUN echo "<!DOCTYPE html><html><head><meta http-equiv=\"Refresh\" content=\"0; url='vnc.html'\" /></head><body></body></html>" > /usr/share/novnc/index.html
# Create directories for configuration and downloaded files
&& mkdir -p /data /config /config/logs /default-config
RUN mkdir /data /config /config/logs /default-config
# Copy release
COPY --from=build /src/outgui /app
ARG cli_src_dll="/src/outcli/*.dll"
ARG cli_src_app_files="/src/outcli/OF DL.Cli*"
ARG cli_target="/app/"
COPY --from=build ${cli_src_dll} ${cli_target}
COPY --from=build ${cli_src_app_files} ${cli_target}
COPY --from=build /src/out /app
# Copy default configuration files
COPY --from=build /src/config.conf /default-config
COPY --from=build ["/src/OF DL.Cli/rules.json", "/default-config"]
COPY --from=build ["/src/OF DL/rules.json", "/default-config"]
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY docker/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
ENV DEBIAN_FRONTEND="noninteractive" \
DISPLAY=:0.0 \
DISPLAY_WIDTH=1366 \
ENV DISPLAY=:0.0 \
DISPLAY_WIDTH=1024 \
DISPLAY_HEIGHT=768 \
OFDL_DOCKER=true \
PLAYWRIGHT_BROWSERS_PATH=/config/chromium
OFDL_PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \
OFDL_DOCKER=true
EXPOSE 8080
WORKDIR /config
ENTRYPOINT ["/usr/bin/tini", "--", "/app/entrypoint.sh"]
CMD []
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/app/entrypoint.sh"]

View File

@ -1,136 +0,0 @@
using OF_DL.Models.Downloads;
using OF_DL.Services;
using Spectre.Console;
namespace OF_DL.CLI;
/// <summary>
/// Spectre.Console implementation of IDownloadEventHandler.
/// Handles all CLI-specific display logic for downloads.
/// </summary>
public class SpectreDownloadEventHandler : IDownloadEventHandler
{
public CancellationToken CancellationToken { get; } = CancellationToken.None;
public async Task<T> WithStatusAsync<T>(string statusMessage, Func<IStatusReporter, Task<T>> work)
{
TaskCompletionSource<T> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
await AnsiConsole.Status()
.StartAsync($"[red]{Markup.Escape(statusMessage)}[/]",
async ctx =>
{
try
{
SpectreStatusReporter reporter = new(ctx);
T result = await work(reporter);
tcs.TrySetResult(result);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
});
return await tcs.Task;
}
public async Task<T> WithProgressAsync<T>(string description, long maxValue, bool showSize,
Func<IProgressReporter, Task<T>> work)
{
TaskCompletionSource<T> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
await AnsiConsole.Progress()
.Columns(GetProgressColumns(showSize))
.StartAsync(async ctx =>
{
try
{
ProgressTask task = ctx.AddTask($"[red]{Markup.Escape(description)}[/]", false);
task.MaxValue = maxValue;
task.StartTask();
SpectreProgressReporter progressReporter = new(task);
T result = await work(progressReporter);
tcs.TrySetResult(result);
task.StopTask();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
});
return await tcs.Task;
}
public void OnContentFound(string contentType, int mediaCount, int objectCount) =>
AnsiConsole.Markup($"[red]Found {mediaCount} Media from {objectCount} {Markup.Escape(contentType)}\n[/]");
public void OnNoContentFound(string contentType) =>
AnsiConsole.Markup($"[red]Found 0 {Markup.Escape(contentType)}\n[/]");
public void OnDownloadComplete(string contentType, DownloadResult result) =>
AnsiConsole.Markup(
$"[red]{Markup.Escape(contentType)} Media Already Downloaded: {result.ExistingDownloads} New {Markup.Escape(contentType)} Media Downloaded: {result.NewDownloads}[/]\n");
public void OnUserStarting(string username) =>
AnsiConsole.Markup($"[red]\nScraping Data for {Markup.Escape(username)}\n[/]");
public void OnUserComplete(string username, CreatorDownloadResult result)
{
AnsiConsole.Markup("\n");
AnsiConsole.Write(new BreakdownChart()
.FullSize()
.AddItem("Paid Posts", result.PaidPostCount, Color.Red)
.AddItem("Posts", result.PostCount, Color.Blue)
.AddItem("Archived", result.ArchivedCount, Color.Green)
.AddItem("Streams", result.StreamsCount, Color.Purple)
.AddItem("Stories", result.StoriesCount, Color.Yellow)
.AddItem("Highlights", result.HighlightsCount, Color.Orange1)
.AddItem("Messages", result.MessagesCount, Color.LightGreen)
.AddItem("Paid Messages", result.PaidMessagesCount, Color.Aqua));
AnsiConsole.Markup("\n");
}
public void OnPurchasedTabUserComplete(string username, int paidPostCount, int paidMessagesCount)
{
AnsiConsole.Markup("\n");
AnsiConsole.Write(new BreakdownChart()
.FullSize()
.AddItem("Paid Posts", paidPostCount, Color.Red)
.AddItem("Paid Messages", paidMessagesCount, Color.Aqua));
AnsiConsole.Markup("\n");
}
public void OnScrapeComplete(TimeSpan elapsed) =>
AnsiConsole.Markup($"[green]Scrape Completed in {elapsed.TotalMinutes:0.00} minutes\n[/]");
public void OnMessage(string message) => AnsiConsole.Markup($"[red]{Markup.Escape(message)}\n[/]");
private static ProgressColumn[] GetProgressColumns(bool showScrapeSize)
{
List<ProgressColumn> progressColumns;
if (showScrapeSize)
{
progressColumns =
[
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
new DownloadedColumn(),
new RemainingTimeColumn()
];
}
else
{
progressColumns =
[
new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn()
];
}
return progressColumns.ToArray();
}
}

View File

@ -1,16 +0,0 @@
using OF_DL.Services;
using Spectre.Console;
namespace OF_DL.CLI;
/// <summary>
/// Implementation of IProgressReporter that uses Spectre.Console's ProgressTask for CLI output.
/// </summary>
public class SpectreProgressReporter(ProgressTask task, CancellationToken cancellationToken = default) : IProgressReporter
{
private readonly ProgressTask _task = task ?? throw new ArgumentNullException(nameof(task));
public CancellationToken CancellationToken { get; } = cancellationToken;
public void ReportProgress(long increment) => _task.Increment(increment);
}

View File

@ -1,17 +0,0 @@
using OF_DL.Services;
using Spectre.Console;
namespace OF_DL.CLI;
/// <summary>
/// Implementation of IStatusReporter that uses Spectre.Console's StatusContext for CLI output.
/// </summary>
public class SpectreStatusReporter(StatusContext ctx) : IStatusReporter
{
public void ReportStatus(string message)
{
ctx.Status($"[red]{message}[/]");
ctx.Spinner(Spinner.Known.Dots);
ctx.SpinnerStyle(Style.Parse("blue"));
}
}

View File

@ -1,51 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>OF_DL</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ApplicationIcon>Icon\download.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="Icon\download.ico"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OF DL.Core\OF DL.Core.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Akka" Version="1.5.61" />
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1"/>
<PackageReference Include="HtmlAgilityPack" Version="1.12.4"/>
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3"/>
<PackageReference Include="Microsoft.Playwright" Version="1.58.0"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
<PackageReference Include="protobuf-net" Version="3.2.56"/>
<PackageReference Include="Serilog" Version="4.3.1"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1"/>
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0"/>
<PackageReference Include="System.Reactive" Version="6.1.0"/>
<PackageReference Include="xFFmpeg.NET" Version="7.2.0"/>
</ItemGroup>
<ItemGroup>
<Reference Include="Spectre.Console">
<HintPath>References\Spectre.Console.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Update="rules.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="chromium-scripts/stealth.min.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -1,911 +0,0 @@
using System.Text.RegularExpressions;
using Microsoft.Extensions.DependencyInjection;
using OF_DL.CLI;
using OF_DL.Models;
using OF_DL.Enumerations;
using OF_DL.Helpers;
using OF_DL.Models.Config;
using OF_DL.Models.Downloads;
using OF_DL.Models.Entities.Users;
using OF_DL.Services;
using Serilog;
using Spectre.Console;
namespace OF_DL;
public class Program(IServiceProvider serviceProvider)
{
private async Task LoadAuthFromBrowser()
{
IAuthService authService = serviceProvider.GetRequiredService<IAuthService>();
// Show the initial message
AnsiConsole.MarkupLine("[yellow]Downloading dependencies. Please wait ...[/]");
// Show instructions based on the environment
await Task.Delay(5000);
if (EnvironmentHelper.IsRunningInDocker())
{
AnsiConsole.MarkupLine(
"[yellow]In your web browser, navigate to the port forwarded from your docker container.[/]");
AnsiConsole.MarkupLine(
"[yellow]For instance, if your docker run command included \"-p 8080:8080\", open your web browser to \"http://localhost:8080\".[/]");
AnsiConsole.MarkupLine(
"[yellow]Once on that webpage, please use it to log in to your OF account. Do not navigate away from the page.[/]");
}
else
{
AnsiConsole.MarkupLine(
"[yellow]In the new window that has opened, please log in to your OF account. Do not close the window or tab. Do not navigate away from the page.[/]\n");
AnsiConsole.MarkupLine(
"[yellow]If you use this method or encounter other issues while logging in, use one of the legacy authentication methods documented here:[/]");
AnsiConsole.MarkupLine("[link]https://docs.ofdl.tools/config/auth/#legacy-methods[/]");
}
// Load auth from browser using the service
bool success = await authService.LoadFromBrowserAsync();
if (!success || authService.CurrentAuth == null)
{
AnsiConsole.MarkupLine(
"\n[red]Authentication failed. Be sure to log into to OF using the new window that opened automatically.[/]");
AnsiConsole.MarkupLine(
"[red]The window will close automatically when the authentication process is finished.[/]");
AnsiConsole.MarkupLine(
"[red]If the problem persists, you may want to try using a legacy authentication method documented here:[/]\n");
AnsiConsole.MarkupLine("[link]https://docs.ofdl.tools/config/auth/#legacy-methods[/]");
AnsiConsole.MarkupLine("[red]Press any key to exit.[/]");
Log.Error("auth invalid after attempt to get auth from browser");
Environment.Exit(2);
}
await authService.SaveToFileAsync();
}
public static async Task Main(string[] args)
{
AnsiConsole.Write(new FigletText("Welcome to OF-DL").Color(Color.Red));
AnsiConsole.Markup("Documentation: [link]https://docs.ofdl.tools/[/]\n");
AnsiConsole.Markup("Discord server: [link]https://discord.com/invite/6bUW8EJ53j[/]\n\n");
ServiceCollection services = await ConfigureServices(args);
ServiceProvider serviceProvider = services.BuildServiceProvider();
Program program = serviceProvider.GetRequiredService<Program>();
await program.RunAsync();
}
private static async Task<ServiceCollection> ConfigureServices(string[] args)
{
// Set up dependency injection with LoggingService and ConfigService
ServiceCollection services = new();
services.AddSingleton<ILoggingService, LoggingService>();
services.AddSingleton<IConfigService, ConfigService>();
ServiceProvider tempServiceProvider = services.BuildServiceProvider();
ILoggingService loggingService = tempServiceProvider.GetRequiredService<ILoggingService>();
IConfigService configService = tempServiceProvider.GetRequiredService<IConfigService>();
if (!await configService.LoadConfigurationAsync(args))
{
AnsiConsole.MarkupLine("\n[red]config.conf is not valid, check your syntax![/]\n");
if (!configService.IsCliNonInteractive)
{
AnsiConsole.MarkupLine("[red]Press any key to exit.[/]");
Console.ReadKey();
}
Environment.Exit(3);
}
AnsiConsole.Markup("[green]config.conf located successfully!\n[/]");
// Set up full dependency injection with loaded config
services = [];
services.AddSingleton(loggingService);
services.AddSingleton(configService);
services.AddSingleton<IAuthService, AuthService>();
services.AddSingleton<IApiService, ApiService>();
services.AddSingleton<IDbService, DbService>();
services.AddSingleton<IDownloadService, DownloadService>();
services.AddSingleton<IFileNameService, FileNameService>();
services.AddSingleton<IStartupService, StartupService>();
services.AddSingleton<IDownloadOrchestrationService, DownloadOrchestrationService>();
services.AddSingleton<Program>();
return services;
}
private async Task RunAsync()
{
IConfigService configService = serviceProvider.GetRequiredService<IConfigService>();
IAuthService authService = serviceProvider.GetRequiredService<IAuthService>();
IStartupService startupService = serviceProvider.GetRequiredService<IStartupService>();
IDownloadOrchestrationService orchestrationService =
serviceProvider.GetRequiredService<IDownloadOrchestrationService>();
try
{
// Version check
VersionCheckResult versionResult = await startupService.CheckVersionAsync();
DisplayVersionResult(versionResult);
// Environment validation
StartupResult startupResult = await startupService.ValidateEnvironmentAsync();
DisplayStartupResult(startupResult);
if (!startupResult.IsWindowsVersionValid)
{
Console.Write(
"This appears to be running on an older version of Windows which is not supported.\n\n");
Console.Write(
"OF-DL requires Windows 10 or higher when being run on Windows. Your reported version is: {0}\n\n",
startupResult.OsVersionString);
if (!configService.CurrentConfig.NonInteractiveMode)
{
Console.Write("Press any key to continue.\n");
Console.ReadKey();
}
Environment.Exit(1);
}
if (!startupResult.FfmpegFound)
{
if (!configService.CurrentConfig.NonInteractiveMode)
{
AnsiConsole.Markup(
"[red]Cannot locate FFmpeg; please modify config.conf with the correct path. Press any key to exit.[/]");
Console.ReadKey();
}
else
{
AnsiConsole.Markup(
"[red]Cannot locate FFmpeg; please modify config.conf with the correct path.[/]");
}
Environment.Exit(4);
}
if (!startupResult.FfprobeFound)
{
if (!configService.CurrentConfig.NonInteractiveMode)
{
AnsiConsole.Markup(
"[red]Cannot locate FFprobe; please modify config.conf with the correct path. Press any key to exit.[/]");
Console.ReadKey();
}
else
{
AnsiConsole.Markup(
"[red]Cannot locate FFprobe; please modify config.conf with the correct path.[/]");
}
Environment.Exit(4);
}
// Auth flow
await HandleAuthFlow(authService, configService);
// Validate cookie string
authService.ValidateCookieString();
// rules.json validation
DisplayRulesJsonResult(startupResult, configService);
// NonInteractiveMode
if (configService.CurrentConfig.NonInteractiveMode)
{
configService.CurrentConfig.NonInteractiveMode = true;
Log.Debug("NonInteractiveMode = true");
}
// Validate auth via API
User? validate = await authService.ValidateAuthAsync();
if (validate == null || (validate.Name == null && validate.Username == null))
{
Log.Error("Auth failed");
authService.CurrentAuth = null;
if (!configService.CurrentConfig.NonInteractiveMode &&
!configService.CurrentConfig.DisableBrowserAuth)
{
await LoadAuthFromBrowser();
}
if (authService.CurrentAuth == null)
{
AnsiConsole.MarkupLine(
"\n[red]Auth failed. Please try again or use other authentication methods detailed here:[/]\n");
AnsiConsole.MarkupLine("[link]https://docs.ofdl.tools/config/auth[/]\n");
if (!configService.CurrentConfig.NonInteractiveMode)
{
Console.WriteLine("\nPress any key to exit.");
Console.ReadKey();
}
Environment.Exit(2);
}
}
AnsiConsole.Markup(
$"[green]Logged In successfully as {(!string.IsNullOrEmpty(validate?.Name) ? validate.Name : "Unknown Name")} {(!string.IsNullOrEmpty(validate?.Username) ? validate.Username : "Unknown Username")}\n[/]");
// Main download loop
await DownloadAllData(orchestrationService, configService, startupResult);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
if (ex.InnerException != null)
{
Console.WriteLine("\nInner Exception:");
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
ex.InnerException.StackTrace);
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
ex.InnerException.StackTrace);
}
if (!configService.CurrentConfig.NonInteractiveMode)
{
Console.WriteLine("\nPress any key to exit.");
Console.ReadKey();
}
Environment.Exit(5);
}
}
private async Task DownloadAllData(
IDownloadOrchestrationService orchestrationService,
IConfigService configService,
StartupResult startupResult)
{
Config config = configService.CurrentConfig;
SpectreDownloadEventHandler eventHandler = new();
Log.Debug("Calling DownloadAllData");
do
{
DateTime startTime = DateTime.Now;
UserListResult userListResult = await orchestrationService.GetAvailableUsersAsync();
Dictionary<string, long> users = userListResult.Users;
Dictionary<string, long> lists = userListResult.Lists;
if (userListResult.IgnoredListError != null)
{
AnsiConsole.Markup($"[red]{Markup.Escape(userListResult.IgnoredListError)}\n[/]");
}
KeyValuePair<bool, Dictionary<string, long>> hasSelectedUsersKVP;
if (config.NonInteractiveMode && config.NonInteractiveModePurchasedTab)
{
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, long>>(true,
new Dictionary<string, long> { { "PurchasedTab", 0 } });
}
else if (config.NonInteractiveMode && string.IsNullOrEmpty(config.NonInteractiveModeListName))
{
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, long>>(true, users);
}
else if (config.NonInteractiveMode && !string.IsNullOrEmpty(config.NonInteractiveModeListName))
{
ListUserSelectionResult listSelectionResult =
await orchestrationService.GetUsersForListAsync(config.NonInteractiveModeListName, users, lists);
Dictionary<string, long> selectedUsers = listSelectionResult.SelectedUsers;
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, long>>(true, selectedUsers);
}
else
{
(bool IsExit, Dictionary<string, long>? selectedUsers) userSelectionResult =
await HandleUserSelection(users, lists);
config = configService.CurrentConfig;
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, long>>(userSelectionResult.IsExit,
userSelectionResult.selectedUsers ?? []);
}
if (hasSelectedUsersKVP.Key &&
hasSelectedUsersKVP.Value.ContainsKey("SinglePost"))
{
await HandleSinglePostDownload(orchestrationService, users, startupResult, eventHandler);
}
else if (hasSelectedUsersKVP.Key &&
hasSelectedUsersKVP.Value.ContainsKey("PurchasedTab"))
{
await orchestrationService.DownloadPurchasedTabAsync(users,
startupResult.ClientIdBlobMissing, startupResult.DevicePrivateKeyMissing, eventHandler);
DateTime endTime = DateTime.Now;
eventHandler.OnScrapeComplete(endTime - startTime);
}
else if (hasSelectedUsersKVP.Key &&
hasSelectedUsersKVP.Value.ContainsKey("SingleMessage"))
{
await HandleSingleMessageDownload(orchestrationService, users, startupResult, eventHandler);
}
else if (hasSelectedUsersKVP.Key &&
!hasSelectedUsersKVP.Value.ContainsKey("ConfigChanged"))
{
foreach (KeyValuePair<string, long> user in hasSelectedUsersKVP.Value)
{
string path = orchestrationService.ResolveDownloadPath(user.Key);
Log.Debug($"Download path: {path}");
await orchestrationService.DownloadCreatorContentAsync(
user.Key, user.Value, path, users,
startupResult.ClientIdBlobMissing, startupResult.DevicePrivateKeyMissing,
eventHandler);
}
DateTime endTime = DateTime.Now;
eventHandler.OnScrapeComplete(endTime - startTime);
}
else if (hasSelectedUsersKVP.Key &&
hasSelectedUsersKVP.Value.ContainsKey("ConfigChanged"))
{
// Config was changed, loop will re-read
}
else
{
break;
}
} while (!config.NonInteractiveMode);
}
private async Task HandleSinglePostDownload(
IDownloadOrchestrationService orchestrationService,
Dictionary<string, long> users,
StartupResult startupResult,
IDownloadEventHandler eventHandler)
{
AnsiConsole.Markup(
"[red]To find an individual post URL, click on the ... at the top right corner of the post and select 'Copy link to post'.\n\nTo return to the main menu, enter 'back' or 'exit' when prompted for the URL.\n\n[/]");
string postUrl = AnsiConsole.Prompt(
new TextPrompt<string>("[red]Please enter a post URL: [/]")
.ValidationErrorMessage("[red]Please enter a valid post URL[/]")
.Validate(url =>
{
Log.Debug($"Single Post URL: {url}");
Regex regex = new("https://onlyfans\\.com/[0-9]+/[A-Za-z0-9]+", RegexOptions.IgnoreCase);
if (regex.IsMatch(url))
{
return ValidationResult.Success();
}
if (url == "" || url == "exit" || url == "back")
{
return ValidationResult.Success();
}
Log.Error("Post URL invalid");
return ValidationResult.Error("[red]Please enter a valid post URL[/]");
}));
if (postUrl != "" && postUrl != "exit" && postUrl != "back")
{
long postId = Convert.ToInt64(postUrl.Split("/")[3]);
string username = postUrl.Split("/")[4];
Log.Debug($"Single Post ID: {postId}");
Log.Debug($"Single Post Creator: {username}");
if (users.ContainsKey(username))
{
string path = orchestrationService.ResolveDownloadPath(username);
Log.Debug($"Download path: {path}");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
AnsiConsole.Markup($"[red]Created folder for {Markup.Escape(username)}\n[/]");
Log.Debug($"Created folder for {username}");
}
else
{
AnsiConsole.Markup($"[red]Folder for {Markup.Escape(username)} already created\n[/]");
}
IDbService dbService = serviceProvider.GetRequiredService<IDbService>();
await dbService.CreateDb(path);
await orchestrationService.DownloadSinglePostAsync(username, postId, path, users,
startupResult.ClientIdBlobMissing, startupResult.DevicePrivateKeyMissing, eventHandler);
}
}
}
private async Task HandleSingleMessageDownload(
IDownloadOrchestrationService orchestrationService,
Dictionary<string, long> users,
StartupResult startupResult,
IDownloadEventHandler eventHandler)
{
AnsiConsole.Markup(
"[red]To find an individual message URL, note that you can only do so for PPV messages that you have unlocked. Go the main OnlyFans timeline, click on the Purchased tab, find the relevant message, click on the ... at the top right corner of the message, and select 'Copy link to message'. For all other messages, you cannot scrape them individually, you must scrape all messages from that creator.\n\nTo return to the main menu, enter 'back' or 'exit' when prompted for the URL.\n\n[/]");
string messageUrl = AnsiConsole.Prompt(
new TextPrompt<string>("[red]Please enter a message URL: [/]")
.ValidationErrorMessage("[red]Please enter a valid message URL[/]")
.Validate(url =>
{
Log.Debug($"Single Paid Message URL: {url}");
Regex regex = new("https://onlyfans\\.com/my/chats/chat/[0-9]+/\\?firstId=[0-9]+$",
RegexOptions.IgnoreCase);
if (regex.IsMatch(url))
{
return ValidationResult.Success();
}
if (url == "" || url == "back" || url == "exit")
{
return ValidationResult.Success();
}
Log.Error("Message URL invalid");
return ValidationResult.Error("[red]Please enter a valid message URL[/]");
}));
if (messageUrl != "" && messageUrl != "exit" && messageUrl != "back")
{
long messageId = Convert.ToInt64(messageUrl.Split("?firstId=")[1]);
long userId = Convert.ToInt64(messageUrl.Split("/")[6]);
Log.Debug($"Message ID: {messageId}");
Log.Debug($"User ID: {userId}");
string? username = await orchestrationService.ResolveUsernameAsync(userId);
Log.Debug("Content creator: {Username}", username);
if (username == null)
{
Log.Error("Could not resolve username for user ID: {userId}", userId);
AnsiConsole.MarkupLine("[red]Could not resolve username for user ID[/]");
return;
}
string path = orchestrationService.ResolveDownloadPath(username);
Log.Debug($"Download path: {path}");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
AnsiConsole.Markup($"[red]Created folder for {Markup.Escape(username)}\n[/]");
Log.Debug($"Created folder for {username}");
}
else
{
AnsiConsole.Markup($"[red]Folder for {Markup.Escape(username)} already created\n[/]");
Log.Debug($"Folder for {username} already created");
}
IDbService dbService = serviceProvider.GetRequiredService<IDbService>();
await dbService.CreateDb(path);
await orchestrationService.DownloadSinglePaidMessageAsync(username, messageId, path, users,
startupResult.ClientIdBlobMissing, startupResult.DevicePrivateKeyMissing, eventHandler);
}
}
public async Task<(bool IsExit, Dictionary<string, long>? selectedUsers)> HandleUserSelection(
Dictionary<string, long> users, Dictionary<string, long> lists)
{
IConfigService configService = serviceProvider.GetRequiredService<IConfigService>();
IAuthService authService = serviceProvider.GetRequiredService<IAuthService>();
IApiService apiService = serviceProvider.GetRequiredService<IApiService>();
ILoggingService loggingService = serviceProvider.GetRequiredService<ILoggingService>();
bool hasSelectedUsers = false;
Dictionary<string, long> selectedUsers = new();
Config currentConfig = configService.CurrentConfig;
while (!hasSelectedUsers)
{
List<string> mainMenuOptions = GetMainMenuOptions(users, lists);
string mainMenuSelection = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title(
"[red]Select Accounts to Scrape | Select All = All Accounts | List = Download content from users on List | Custom = Specific Account(s)[/]")
.AddChoices(mainMenuOptions)
);
switch (mainMenuSelection)
{
case "[red]Select All[/]":
selectedUsers = users;
hasSelectedUsers = true;
break;
case "[red]List[/]":
while (true)
{
MultiSelectionPrompt<string> listSelectionPrompt = new();
listSelectionPrompt.Title = "[red]Select List[/]";
listSelectionPrompt.PageSize = 10;
listSelectionPrompt.AddChoice("[red]Go Back[/]");
foreach (string key in lists.Keys.Select(k => $"[red]{k}[/]").ToList())
{
listSelectionPrompt.AddChoice(key);
}
List<string> listSelection = AnsiConsole.Prompt(listSelectionPrompt);
if (listSelection.Contains("[red]Go Back[/]"))
{
break;
}
hasSelectedUsers = true;
List<string> listUsernames = new();
foreach (string item in listSelection)
{
long listId = lists[item.Replace("[red]", "").Replace("[/]", "")];
List<string> usernames = await apiService.GetListUsers($"/lists/{listId}/users") ?? [];
foreach (string user in usernames)
{
listUsernames.Add(user);
}
}
selectedUsers = users.Where(x => listUsernames.Contains($"{x.Key}"))
.ToDictionary(x => x.Key, x => x.Value);
AnsiConsole.Markup(string.Format("[red]Downloading from List(s): {0}[/]",
string.Join(", ", listSelection)));
break;
}
break;
case "[red]Custom[/]":
while (true)
{
MultiSelectionPrompt<string> selectedNamesPrompt = new();
selectedNamesPrompt.MoreChoicesText("[grey](Move up and down to reveal more choices)[/]");
selectedNamesPrompt.InstructionsText(
"[grey](Press <space> to select, <enter> to accept)[/]\n[grey](Press A-Z to easily navigate the list)[/]");
selectedNamesPrompt.Title("[red]Select users[/]");
selectedNamesPrompt.PageSize(10);
selectedNamesPrompt.AddChoice("[red]Go Back[/]");
foreach (string key in users.Keys.OrderBy(k => k).Select(k => $"[red]{k}[/]").ToList())
{
selectedNamesPrompt.AddChoice(key);
}
List<string> userSelection = AnsiConsole.Prompt(selectedNamesPrompt);
if (userSelection.Contains("[red]Go Back[/]"))
{
break;
}
hasSelectedUsers = true;
selectedUsers = users.Where(x => userSelection.Contains($"[red]{x.Key}[/]"))
.ToDictionary(x => x.Key, x => x.Value);
break;
}
break;
case "[red]Download Single Post[/]":
return (true, new Dictionary<string, long> { { "SinglePost", 0 } });
case "[red]Download Single Paid Message[/]":
return (true, new Dictionary<string, long> { { "SingleMessage", 0 } });
case "[red]Download Purchased Tab[/]":
return (true, new Dictionary<string, long> { { "PurchasedTab", 0 } });
case "[red]Edit config.conf[/]":
while (true)
{
List<(string Name, bool Value)> toggleableProps = configService.GetToggleableProperties();
List<(string choice, bool isSelected)> choices = new() { ("[red]Go Back[/]", false) };
foreach ((string Name, bool Value) prop in toggleableProps)
{
choices.Add(($"[red]{prop.Name}[/]", prop.Value));
}
MultiSelectionPrompt<string> multiSelectionPrompt = new MultiSelectionPrompt<string>()
.Title("[red]Edit config.conf[/]")
.PageSize(25);
foreach ((string choice, bool isSelected) choice in choices)
{
multiSelectionPrompt.AddChoices(choice.choice, selectionItem =>
{
if (choice.isSelected)
{
selectionItem.Select();
}
});
}
List<string> configOptions = AnsiConsole.Prompt(multiSelectionPrompt);
if (configOptions.Contains("[red]Go Back[/]"))
{
break;
}
// Extract plain names from selections
List<string> selectedNames = configOptions
.Select(o => o.Replace("[red]", "").Replace("[/]", ""))
.ToList();
bool configChanged = configService.ApplyToggleableSelections(selectedNames);
await configService.SaveConfigurationAsync();
currentConfig = configService.CurrentConfig;
if (configChanged)
{
return (true, new Dictionary<string, long> { { "ConfigChanged", 0 } });
}
break;
}
break;
case "[red]Change logging level[/]":
while (true)
{
List<(string choice, bool isSelected)> choices = [("[red]Go Back[/]", false)];
foreach (string name in typeof(LoggingLevel).GetEnumNames())
{
string itemLabel = $"[red]{name}[/]";
choices.Add(new ValueTuple<string, bool>(itemLabel,
name == loggingService.GetCurrentLoggingLevel().ToString()));
}
SelectionPrompt<string> selectionPrompt = new SelectionPrompt<string>()
.Title("[red]Select logging level[/]")
.PageSize(25);
foreach ((string choice, bool isSelected) choice in choices)
{
selectionPrompt.AddChoice(choice.choice);
}
string levelOption = AnsiConsole.Prompt(selectionPrompt);
if (levelOption.Contains("[red]Go Back[/]"))
{
break;
}
levelOption = levelOption.Replace("[red]", "").Replace("[/]", "");
LoggingLevel newLogLevel =
(LoggingLevel)Enum.Parse(typeof(LoggingLevel), levelOption, true);
Log.Debug($"Logging level changed to: {levelOption}");
Config newConfig = currentConfig;
newConfig.LoggingLevel = newLogLevel;
currentConfig = newConfig;
configService.UpdateConfig(newConfig);
await configService.SaveConfigurationAsync();
break;
}
break;
case "[red]Logout and Exit[/]":
authService.Logout();
return (false, null);
case "[red]Exit[/]":
return (false, null);
}
}
return (true, selectedUsers);
}
public static List<string> GetMainMenuOptions(Dictionary<string, long> users, Dictionary<string, long> lists)
{
if (lists.Count > 0)
{
return new List<string>
{
"[red]Select All[/]",
"[red]List[/]",
"[red]Custom[/]",
"[red]Download Single Post[/]",
"[red]Download Single Paid Message[/]",
"[red]Download Purchased Tab[/]",
"[red]Edit config.conf[/]",
"[red]Change logging level[/]",
"[red]Logout and Exit[/]",
"[red]Exit[/]"
};
}
return new List<string>
{
"[red]Select All[/]",
"[red]Custom[/]",
"[red]Download Single Post[/]",
"[red]Download Single Paid Message[/]",
"[red]Download Purchased Tab[/]",
"[red]Edit config.conf[/]",
"[red]Change logging level[/]",
"[red]Logout and Exit[/]",
"[red]Exit[/]"
};
}
private async Task HandleAuthFlow(IAuthService authService, IConfigService configService)
{
if (await authService.LoadFromFileAsync())
{
AnsiConsole.Markup("[green]auth.json located successfully!\n[/]");
}
else if (File.Exists("auth.json"))
{
Log.Information("Auth file found but could not be deserialized");
if (configService.CurrentConfig.NonInteractiveMode)
{
AnsiConsole.MarkupLine(
"\n[red]auth.json has invalid JSON syntax. The file can be generated automatically when OF-DL is run in the standard, interactive mode.[/]\n");
AnsiConsole.MarkupLine(
"[red]You may also want to try using the browser extension which is documented here:[/]\n");
AnsiConsole.MarkupLine("[link]https://docs.ofdl.tools/config/auth/#legacy-methods[/]");
Environment.Exit(2);
}
if (!configService.CurrentConfig.DisableBrowserAuth)
{
await LoadAuthFromBrowser();
}
else
{
ShowAuthMissingError(configService.CurrentConfig.NonInteractiveMode);
}
}
else
{
if (configService.CurrentConfig.NonInteractiveMode)
{
ShowAuthMissingError(configService.CurrentConfig.NonInteractiveMode);
}
else if (!configService.CurrentConfig.DisableBrowserAuth)
{
await LoadAuthFromBrowser();
}
else
{
ShowAuthMissingError(configService.CurrentConfig.NonInteractiveMode);
}
}
}
private static void ShowAuthMissingError(bool nonInteractiveMode)
{
AnsiConsole.MarkupLine(
"\n[red]auth.json is missing. The file can be generated automatically when OF-DL is run in the standard, interactive mode.[/]\n");
AnsiConsole.MarkupLine(
"[red]You may also want to try using the browser extension which is documented here:[/]\n");
AnsiConsole.MarkupLine("[link]https://docs.ofdl.tools/config/auth/#legacy-methods[/]");
if (!nonInteractiveMode)
{
AnsiConsole.MarkupLine("[red]Press any key to exit.[/]");
Console.ReadKey();
}
Environment.Exit(2);
}
private static void DisplayVersionResult(VersionCheckResult result)
{
if (result.TimedOut)
{
AnsiConsole.Markup("[yellow]Version check timed out after 30 seconds.\n[/]");
return;
}
if (result.CheckFailed)
{
AnsiConsole.Markup("[yellow]Failed to verify that OF-DL is up-to-date.\n[/]");
return;
}
if (result.LocalVersion == null || result.LatestVersion == null)
{
// Debug mode or no version info
AnsiConsole.Markup("[yellow]Running in Debug/Local mode. Version check skipped.\n[/]");
return;
}
if (result.IsUpToDate)
{
AnsiConsole.Markup("[green]You are running OF-DL version " +
$"{result.LocalVersion.Major}.{result.LocalVersion.Minor}.{result.LocalVersion.Build}\n[/]");
AnsiConsole.Markup("[green]Latest Release version: " +
$"{result.LatestVersion.Major}.{result.LatestVersion.Minor}.{result.LatestVersion.Build}\n[/]");
}
else
{
AnsiConsole.Markup("[red]You are running OF-DL version " +
$"{result.LocalVersion.Major}.{result.LocalVersion.Minor}.{result.LocalVersion.Build}\n[/]");
AnsiConsole.Markup("[red]Please update to the current release, " +
$"{result.LatestVersion.Major}.{result.LatestVersion.Minor}.{result.LatestVersion.Build}: [link=https://git.ofdl.tools/sim0n00ps/OF-DL/releases]https://git.ofdl.tools/sim0n00ps/OF-DL/releases[/]\n[/]");
}
}
private static void DisplayStartupResult(StartupResult result)
{
// OS
if (result is { IsWindowsVersionValid: true, OsVersionString: not null } &&
EnvironmentHelper.IsRunningOnWindows())
{
AnsiConsole.Markup("[green]Valid version of Windows found.\n[/]");
}
// FFmpeg
if (result.FfmpegFound)
{
AnsiConsole.Markup(
result is { FfmpegPathAutoDetected: true, FfmpegPath: not null }
? $"[green]FFmpeg located successfully. Path auto-detected: {Markup.Escape(result.FfmpegPath)}\n[/]"
: "[green]FFmpeg located successfully\n[/]");
AnsiConsole.Markup(result.FfmpegVersion != null
? $"[green]ffmpeg version detected as {Markup.Escape(result.FfmpegVersion)}[/]\n"
: "[yellow]ffmpeg version could not be parsed[/]\n");
}
// FFprobe
if (result.FfprobeFound)
{
AnsiConsole.Markup(
result is { FfprobePathAutoDetected: true, FfprobePath: not null }
? $"[green]FFprobe located successfully. Path auto-detected: {Markup.Escape(result.FfprobePath)}\n[/]"
: "[green]FFprobe located successfully\n[/]");
AnsiConsole.Markup(result.FfprobeVersion != null
? $"[green]FFprobe version detected as {Markup.Escape(result.FfprobeVersion)}[/]\n"
: "[yellow]FFprobe version could not be parsed[/]\n");
}
// Widevine
if (!result.ClientIdBlobMissing)
{
AnsiConsole.Markup("[green]device_client_id_blob located successfully![/]\n");
}
if (!result.DevicePrivateKeyMissing)
{
AnsiConsole.Markup("[green]device_private_key located successfully![/]\n");
}
if (result.ClientIdBlobMissing || result.DevicePrivateKeyMissing)
{
AnsiConsole.Markup(
"[yellow]device_client_id_blob and/or device_private_key missing, https://ofdl.tools/ will be used instead for DRM protected videos\n[/]");
}
}
private static void DisplayRulesJsonResult(StartupResult result, IConfigService configService)
{
if (result.RulesJsonExists)
{
if (result.RulesJsonValid)
{
AnsiConsole.Markup("[green]rules.json located successfully!\n[/]");
}
else
{
AnsiConsole.MarkupLine("\n[red]rules.json is not valid, check your JSON syntax![/]\n");
AnsiConsole.MarkupLine("[red]Please ensure you are using the latest version of the software.[/]\n");
Log.Error("rules.json processing failed: {Error}", result.RulesJsonError);
if (!configService.CurrentConfig.NonInteractiveMode)
{
AnsiConsole.MarkupLine("[red]Press any key to exit.[/]");
Console.ReadKey();
}
Environment.Exit(2);
}
}
}
}

View File

@ -1,112 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v7.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v7.0": {
"Spectre.Console/0.0.0-preview.0": {
"dependencies": {
"Microsoft.SourceLink.GitHub": "1.1.1",
"MinVer": "4.2.0",
"Roslynator.Analyzers": "4.1.2",
"StyleCop.Analyzers": "1.2.0-beta.435",
"System.Memory": "4.5.5",
"Wcwidth.Sources": "1.0.0"
},
"runtime": {
"Spectre.Console.dll": {}
}
},
"Microsoft.Build.Tasks.Git/1.1.1": {},
"Microsoft.SourceLink.Common/1.1.1": {},
"Microsoft.SourceLink.GitHub/1.1.1": {
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"MinVer/4.2.0": {},
"Roslynator.Analyzers/4.1.2": {},
"StyleCop.Analyzers/1.2.0-beta.435": {
"dependencies": {
"StyleCop.Analyzers.Unstable": "1.2.0.435"
}
},
"StyleCop.Analyzers.Unstable/1.2.0.435": {},
"System.Memory/4.5.5": {},
"Wcwidth.Sources/1.0.0": {}
}
},
"libraries": {
"Spectre.Console/0.0.0-preview.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.Build.Tasks.Git/1.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-AT3HlgTjsqHnWpBHSNeR0KxbLZD7bztlZVj7I8vgeYG9SYqbeFGh0TM/KVtC6fg53nrWHl3VfZFvb5BiQFcY6Q==",
"path": "microsoft.build.tasks.git/1.1.1",
"hashPath": "microsoft.build.tasks.git.1.1.1.nupkg.sha512"
},
"Microsoft.SourceLink.Common/1.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg==",
"path": "microsoft.sourcelink.common/1.1.1",
"hashPath": "microsoft.sourcelink.common.1.1.1.nupkg.sha512"
},
"Microsoft.SourceLink.GitHub/1.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==",
"path": "microsoft.sourcelink.github/1.1.1",
"hashPath": "microsoft.sourcelink.github.1.1.1.nupkg.sha512"
},
"MinVer/4.2.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Po4tv+sri1jsaebQ8F6+yD5ru9Gas5mR111F6HR2ULqwflvjjZXMstpeOc1GHMJeQa3g4E3b8MX8K2cShkuUAg==",
"path": "minver/4.2.0",
"hashPath": "minver.4.2.0.nupkg.sha512"
},
"Roslynator.Analyzers/4.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bNl3GRSBFjJymYnwq/IRDD9MOSZz9VKdGk9RsN0MWIXoSRnVKQv84f6s9nLE13y20lZgMZKlDqGw2uInBH4JgA==",
"path": "roslynator.analyzers/4.1.2",
"hashPath": "roslynator.analyzers.4.1.2.nupkg.sha512"
},
"StyleCop.Analyzers/1.2.0-beta.435": {
"type": "package",
"serviceable": true,
"sha512": "sha512-TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==",
"path": "stylecop.analyzers/1.2.0-beta.435",
"hashPath": "stylecop.analyzers.1.2.0-beta.435.nupkg.sha512"
},
"StyleCop.Analyzers.Unstable/1.2.0.435": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==",
"path": "stylecop.analyzers.unstable/1.2.0.435",
"hashPath": "stylecop.analyzers.unstable.1.2.0.435.nupkg.sha512"
},
"System.Memory/4.5.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"path": "system.memory/4.5.5",
"hashPath": "system.memory.4.5.5.nupkg.sha512"
},
"Wcwidth.Sources/1.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-86tmwfGXRz7GJQXBnoTFoMvqSqd6irfkEkRzQFR54W/nweaR8cUvzY8x++z+B/+eUPSuqD2Ah1iPJHgthy4pzg==",
"path": "wcwidth.sources/1.0.0",
"hashPath": "wcwidth.sources.1.0.0.nupkg.sha512"
}
}
}

View File

@ -1,16 +0,0 @@
# Stealth Script Creation
## Requirements
- NodeJS (with npx CLI tool)
## Instructions
- Open a terminal in this directory (OF DL.Cli/chromium-scripts)
- Run `npx -y extract-stealth-evasions`
- Copy the `stealth.js` file into the other chromium-scripts directory as well (`OF DL.Gui/chromium-scripts/`)
## References
See the readme.md file and source code for the stealth
script [here](https://github.com/berstend/puppeteer-extra/tree/master/packages/extract-stealth-evasions).

File diff suppressed because one or more lines are too long

View File

@ -1,41 +0,0 @@
{
"app-token": "33d57ade8c02dbc5a333db99ff9ae26a",
"static_param": "RyY8GpixStP90t68HWIJ8Qzo745n0hy0",
"prefix": "30586",
"suffix": "67000213",
"checksum_constant": 521,
"checksum_indexes": [
0,
2,
3,
7,
7,
8,
8,
10,
11,
13,
14,
16,
17,
17,
17,
19,
19,
20,
21,
21,
23,
23,
24,
24,
27,
27,
29,
30,
31,
34,
35,
39
]
}

View File

@ -1,29 +0,0 @@
using System.Security.Cryptography;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Crypto.Parameters;
namespace OF_DL.Crypto;
public class CryptoUtils
{
public static byte[] GetHMACSHA256Digest(byte[] data, byte[] key) => new HMACSHA256(key).ComputeHash(data);
public static byte[] GetCMACDigest(byte[] data, byte[] key)
{
IBlockCipher cipher = new AesEngine();
IMac mac = new CMac(cipher, 128);
KeyParameter keyParam = new(key);
mac.Init(keyParam);
mac.BlockUpdate(data, 0, data.Length);
byte[] outBytes = new byte[16];
mac.DoFinal(outBytes, 0);
return outBytes;
}
}

View File

@ -1,127 +0,0 @@
using System.Security.Cryptography;
namespace OF_DL.Crypto;
public class Padding
{
public static byte[] AddPKCS7Padding(byte[] data, int k)
{
int m = k - data.Length % k;
byte[] padding = new byte[m];
Array.Fill(padding, (byte)m);
byte[] paddedBytes = new byte[data.Length + padding.Length];
Buffer.BlockCopy(data, 0, paddedBytes, 0, data.Length);
Buffer.BlockCopy(padding, 0, paddedBytes, data.Length, padding.Length);
return paddedBytes;
}
public static byte[] RemovePKCS7Padding(byte[] paddedByteArray)
{
byte last = paddedByteArray[^1];
if (paddedByteArray.Length <= last)
{
return paddedByteArray;
}
return SubArray(paddedByteArray, 0, paddedByteArray.Length - last);
}
public static T[] SubArray<T>(T[] arr, int start, int length)
{
T[] result = new T[length];
Buffer.BlockCopy(arr, start, result, 0, length);
return result;
}
public static byte[] AddPssPadding(byte[] hash)
{
int modBits = 2048;
int hLen = 20;
int emLen = 256;
int lmask = 0;
for (int i = 0; i < 8 * emLen - (modBits - 1); i++)
{
lmask = (lmask >> 1) | 0x80;
}
// Commented out since the condition will always be false while emLen = 256 and hLen = 20
// if (emLen < hLen + hLen + 2)
// {
// return null;
// }
byte[] salt = new byte[hLen];
new Random().NextBytes(salt);
byte[] m_prime = Enumerable.Repeat((byte)0, 8).ToArray().Concat(hash).Concat(salt).ToArray();
byte[] h = SHA1.Create().ComputeHash(m_prime);
byte[] ps = Enumerable.Repeat((byte)0, emLen - hLen - hLen - 2).ToArray();
byte[] db = ps.Concat(new byte[] { 0x01 }).Concat(salt).ToArray();
byte[] dbMask = MGF1(h, emLen - hLen - 1);
byte[] maskedDb = new byte[dbMask.Length];
for (int i = 0; i < dbMask.Length; i++)
{
maskedDb[i] = (byte)(db[i] ^ dbMask[i]);
}
maskedDb[0] = (byte)(maskedDb[0] & ~lmask);
byte[] padded = maskedDb.Concat(h).Concat(new byte[] { 0xBC }).ToArray();
return padded;
}
public static byte[] RemoveOAEPPadding(byte[] data)
{
int k = 256;
int hLen = 20;
byte[] maskedSeed = data[1..(hLen + 1)];
byte[] maskedDB = data[(hLen + 1)..];
byte[] seedMask = MGF1(maskedDB, hLen);
byte[] seed = new byte[maskedSeed.Length];
for (int i = 0; i < maskedSeed.Length; i++)
{
seed[i] = (byte)(maskedSeed[i] ^ seedMask[i]);
}
byte[] dbMask = MGF1(seed, k - hLen - 1);
byte[] db = new byte[maskedDB.Length];
for (int i = 0; i < maskedDB.Length; i++)
{
db[i] = (byte)(maskedDB[i] ^ dbMask[i]);
}
int onePos = BitConverter.ToString(db[hLen..]).Replace("-", "").IndexOf("01", StringComparison.Ordinal) / 2;
byte[] unpadded = db[(hLen + onePos + 1)..];
return unpadded;
}
private static byte[] MGF1(byte[] seed, int maskLen)
{
SHA1 hobj = SHA1.Create();
int hLen = hobj.HashSize / 8;
List<byte> T = new();
for (int i = 0; i < (int)Math.Ceiling(maskLen / (double)hLen); i++)
{
byte[] c = BitConverter.GetBytes(i);
Array.Reverse(c);
byte[] digest = hobj.ComputeHash(seed.Concat(c).ToArray());
T.AddRange(digest);
}
return T.GetRange(0, maskLen).ToArray();
}
}

View File

@ -1,7 +0,0 @@
namespace OF_DL.Enumerations;
public enum CustomFileNameOption
{
ReturnOriginal,
ReturnEmpty
}

View File

@ -1,7 +0,0 @@
namespace OF_DL.Enumerations;
public enum DownloadDateSelection
{
before,
after
}

View File

@ -1,34 +0,0 @@
namespace OF_DL.Enumerations;
public enum LoggingLevel
{
//
// Summary:
// Anything and everything you might want to know about a running block of code.
Verbose,
//
// Summary:
// Internal system events that aren't necessarily observable from the outside.
Debug,
//
// Summary:
// The lifeblood of operational intelligence - things happen.
Information,
//
// Summary:
// Service is degraded or endangered.
Warning,
//
// Summary:
// Functionality is unavailable, invariants are broken or data is lost.
Error,
//
// Summary:
// If you have a pager, it goes off when one of these occurs.
Fatal
}

View File

@ -1,12 +0,0 @@
namespace OF_DL.Enumerations;
public enum MediaType
{
PaidPosts = 10,
Posts = 20,
Archived = 30,
Stories = 40,
Highlights = 50,
Messages = 60,
PaidMessages = 70
}

View File

@ -1,9 +0,0 @@
// ReSharper disable InconsistentNaming
namespace OF_DL.Enumerations;
public enum Theme
{
light,
dark
}

View File

@ -1,8 +0,0 @@
namespace OF_DL.Enumerations;
public enum VideoResolution
{
_240,
_720,
source
}

View File

@ -1,32 +0,0 @@
namespace OF_DL.Helpers;
public static class Constants
{
public const string DiscordInviteUrl = "https://discord.com/invite/6bUW8EJ53j";
public const string DocumentationUrl = "https://docs.ofdl.tools/";
public const string LegacyAuthDocumentationUrl = "https://docs.ofdl.tools/config/auth/#legacy-methods";
public const string ApiUrl = "https://onlyfans.com/api2/v2";
public const int ApiPageSize = 50;
public const int WidevineRetryDelay = 10;
public const int WidevineMaxRetries = 3;
public const int DrmDownloadMaxRetries = 3;
public const int ApiRetryMaxAttempts = 5;
public const int ApiRetryBaseDelayMs = 500;
public const int ApiRetryMaxDelayMs = 8000;
public const int DownloadRetryMaxAttempts = 4;
public const int DownloadRetryBaseDelayMs = 1000;
public const int DownloadRetryMaxDelayMs = 15000;
}

View File

@ -1,14 +0,0 @@
namespace OF_DL.Helpers;
public static class EnvironmentHelper
{
private const string DockerEnvironmentVariableName = "OFDL_DOCKER";
public static bool IsRunningInDocker()
{
string? dockerValue = Environment.GetEnvironmentVariable(DockerEnvironmentVariableName);
return string.Equals(dockerValue, "true", StringComparison.OrdinalIgnoreCase);
}
public static bool IsRunningOnWindows() => OperatingSystem.IsWindows();
}

View File

@ -1,26 +0,0 @@
using Serilog;
namespace OF_DL.Helpers;
internal static class ExceptionLoggerHelper
{
/// <summary>
/// Logs an exception to the console and Serilog with inner exception details.
/// </summary>
/// <param name="ex">The exception to log.</param>
public static void LogException(Exception ex)
{
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
if (ex.InnerException == null)
{
return;
}
Console.WriteLine("\nInner Exception:");
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
ex.InnerException.StackTrace);
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
ex.InnerException.StackTrace);
}
}

View File

@ -1,60 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.OfdlApi;
using Serilog;
namespace OF_DL.Helpers;
public static class VersionHelper
{
private const string Url = "https://git.ofdl.tools/api/v1/repos/sim0n00ps/OF-DL/releases/latest";
private static readonly HttpClient s_httpClient = new();
public static async Task<string?> GetLatestReleaseTag(CancellationToken cancellationToken = default)
{
Log.Debug("Calling GetLatestReleaseTag");
try
{
HttpResponseMessage response = await s_httpClient.GetAsync(Url, cancellationToken);
if (!response.IsSuccessStatusCode)
{
Log.Debug("GetLatestReleaseTag did not return a Success Status Code");
return null;
}
string body = await response.Content.ReadAsStringAsync(cancellationToken);
Log.Debug("GetLatestReleaseTag API Response: {Body}", body);
LatestReleaseApiResponse? versionCheckResponse =
JsonConvert.DeserializeObject<LatestReleaseApiResponse>(body);
if (versionCheckResponse != null && versionCheckResponse.TagName != "")
{
return versionCheckResponse.TagName;
}
Log.Debug("GetLatestReleaseTag did not return a valid tag name");
return null;
}
catch (OperationCanceledException)
{
throw; // Rethrow timeout exceptions to be handled by the caller
}
catch (Exception ex)
{
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
if (ex.InnerException != null)
{
Console.WriteLine("\nInner Exception:");
Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
ex.InnerException.StackTrace);
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message,
ex.InnerException.StackTrace);
}
}
return null;
}
}

View File

@ -1,21 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models;
public class Auth
{
[JsonProperty(PropertyName = "USER_ID")]
public string? UserId { get; set; } = "";
[JsonProperty(PropertyName = "USER_AGENT")]
public string? UserAgent { get; set; } = "";
[JsonProperty(PropertyName = "X_BC")] public string? XBc { get; set; } = "";
[JsonProperty(PropertyName = "COOKIE")]
public string? Cookie { get; set; } = "";
[JsonIgnore]
[JsonProperty(PropertyName = "FFMPEG_PATH")]
public string? FfmpegPath { get; set; } = "";
}

View File

@ -1,168 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using OF_DL.Enumerations;
using Serilog;
namespace OF_DL.Models.Config;
public class Config : IFileNameFormatConfig
{
[ToggleableConfig] public bool DownloadAvatarHeaderPhoto { get; set; } = true;
[ToggleableConfig] public bool DownloadPaidPosts { get; set; } = true;
[ToggleableConfig] public bool DownloadPosts { get; set; } = true;
[ToggleableConfig] public bool DownloadArchived { get; set; } = true;
[ToggleableConfig] public bool DownloadStreams { get; set; } = true;
[ToggleableConfig] public bool DownloadStories { get; set; } = true;
[ToggleableConfig] public bool DownloadHighlights { get; set; } = true;
[ToggleableConfig] public bool DownloadMessages { get; set; } = true;
[ToggleableConfig] public bool DownloadPaidMessages { get; set; } = true;
[ToggleableConfig] public bool DownloadImages { get; set; } = true;
[ToggleableConfig] public bool DownloadVideos { get; set; } = true;
[ToggleableConfig] public bool DownloadAudios { get; set; } = true;
[ToggleableConfig] public bool IncludeExpiredSubscriptions { get; set; }
[ToggleableConfig] public bool IncludeRestrictedSubscriptions { get; set; }
[ToggleableConfig] public bool SkipAds { get; set; }
public string? DownloadPath { get; set; } = "";
[ToggleableConfig] public bool RenameExistingFilesWhenCustomFormatIsSelected { get; set; }
public int? Timeout { get; set; } = -1;
[ToggleableConfig] public bool FolderPerPaidPost { get; set; }
[ToggleableConfig] public bool FolderPerPost { get; set; }
[ToggleableConfig] public bool FolderPerPaidMessage { get; set; }
[ToggleableConfig] public bool FolderPerMessage { get; set; }
[ToggleableConfig] public bool LimitDownloadRate { get; set; }
public int DownloadLimitInMbPerSec { get; set; } = 4;
// Indicates if you want to download only on specific dates.
[ToggleableConfig] public bool DownloadOnlySpecificDates { get; set; }
// This enum will define if we want data from before or after the CustomDate.
[JsonConverter(typeof(StringEnumConverter))]
public DownloadDateSelection DownloadDateSelection { get; set; } = DownloadDateSelection.before;
// This is the specific date used in combination with the above enum.
[JsonConverter(typeof(ShortDateConverter))]
public DateTime? CustomDate { get; set; } = null;
[ToggleableConfig] public bool ShowScrapeSize { get; set; }
[ToggleableConfig] public bool DownloadPostsIncrementally { get; set; }
public bool NonInteractiveMode { get; set; }
public string NonInteractiveModeListName { get; set; } = "";
[ToggleableConfig] public bool NonInteractiveModePurchasedTab { get; set; }
public string? FFmpegPath { get; set; } = "";
public string? FFprobePath { get; set; } = "";
[ToggleableConfig] public bool BypassContentForCreatorsWhoNoLongerExist { get; set; }
public Dictionary<string, CreatorConfig> CreatorConfigs { get; set; } = new();
[ToggleableConfig] public bool DownloadDuplicatedMedia { get; set; }
public string IgnoredUsersListName { get; set; } = "";
[JsonConverter(typeof(StringEnumConverter))]
public LoggingLevel LoggingLevel { get; set; } = LoggingLevel.Error;
[JsonConverter(typeof(StringEnumConverter))]
public Theme Theme { get; set; } = Theme.dark;
[ToggleableConfig] public bool HideMissingCdmKeysWarning { get; set; }
[ToggleableConfig] public bool HideShowScrapeSizeWarning { get; set; }
[ToggleableConfig] public bool IgnoreOwnMessages { get; set; }
[ToggleableConfig] public bool DisableBrowserAuth { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public VideoResolution DownloadVideoResolution { get; set; } = VideoResolution.source;
public double DrmVideoDurationMatchThreshold { get; set; } = 0.98;
// When enabled, post/message text is stored as-is without XML stripping.
[ToggleableConfig] public bool DisableTextSanitization { get; set; }
public string? PaidPostFileNameFormat { get; set; } = "";
public string? PostFileNameFormat { get; set; } = "";
public string? PaidMessageFileNameFormat { get; set; } = "";
public string? MessageFileNameFormat { get; set; } = "";
public IFileNameFormatConfig GetCreatorFileNameFormatConfig(string username)
{
FileNameFormatConfig combinedFilenameFormatConfig = new()
{
PaidPostFileNameFormat = PaidPostFileNameFormat,
PostFileNameFormat = PostFileNameFormat,
PaidMessageFileNameFormat = PaidMessageFileNameFormat,
MessageFileNameFormat = MessageFileNameFormat
};
if (CreatorConfigs.TryGetValue(username, out CreatorConfig? creatorConfig))
{
if (!string.IsNullOrEmpty(creatorConfig.PaidPostFileNameFormat))
{
combinedFilenameFormatConfig.PaidPostFileNameFormat = creatorConfig.PaidPostFileNameFormat;
}
if (!string.IsNullOrEmpty(creatorConfig.PostFileNameFormat))
{
combinedFilenameFormatConfig.PostFileNameFormat = creatorConfig.PostFileNameFormat;
}
if (!string.IsNullOrEmpty(creatorConfig.PaidMessageFileNameFormat))
{
combinedFilenameFormatConfig.PaidMessageFileNameFormat = creatorConfig.PaidMessageFileNameFormat;
}
if (!string.IsNullOrEmpty(creatorConfig.MessageFileNameFormat))
{
combinedFilenameFormatConfig.MessageFileNameFormat = creatorConfig.MessageFileNameFormat;
}
}
Log.Debug("PaidMessageFilenameFormat: {CombinedConfigPaidMessageFileNameFormat}",
combinedFilenameFormatConfig.PaidMessageFileNameFormat);
Log.Debug("PostFileNameFormat: {CombinedConfigPostFileNameFormat}",
combinedFilenameFormatConfig.PostFileNameFormat);
Log.Debug("MessageFileNameFormat: {CombinedConfigMessageFileNameFormat}",
combinedFilenameFormatConfig.MessageFileNameFormat);
Log.Debug("PaidPostFileNameFormat: {CombinedConfigPaidPostFileNameFormat}",
combinedFilenameFormatConfig.PaidPostFileNameFormat);
return combinedFilenameFormatConfig;
}
private class ShortDateConverter : IsoDateTimeConverter
{
public ShortDateConverter() => DateTimeFormat = "yyyy-MM-dd";
}
}

View File

@ -1,12 +0,0 @@
namespace OF_DL.Models.Config;
public class CreatorConfig : IFileNameFormatConfig
{
public string? PaidPostFileNameFormat { get; set; }
public string? PostFileNameFormat { get; set; }
public string? PaidMessageFileNameFormat { get; set; }
public string? MessageFileNameFormat { get; set; }
}

View File

@ -1,12 +0,0 @@
namespace OF_DL.Models.Config;
public class FileNameFormatConfig : IFileNameFormatConfig
{
public string? PaidPostFileNameFormat { get; set; }
public string? PostFileNameFormat { get; set; }
public string? PaidMessageFileNameFormat { get; set; }
public string? MessageFileNameFormat { get; set; }
}

View File

@ -1,12 +0,0 @@
namespace OF_DL.Models.Config;
public interface IFileNameFormatConfig
{
string? PaidPostFileNameFormat { get; set; }
string? PostFileNameFormat { get; set; }
string? PaidMessageFileNameFormat { get; set; }
string? MessageFileNameFormat { get; set; }
}

View File

@ -1,4 +0,0 @@
namespace OF_DL.Models.Config;
[AttributeUsage(AttributeTargets.Property)]
internal class ToggleableConfigAttribute : Attribute;

View File

@ -1,36 +0,0 @@
namespace OF_DL.Models.Downloads;
public class CreatorDownloadResult
{
public int PaidPostCount { get; set; }
public int PostCount { get; set; }
public int ArchivedCount { get; set; }
public int StreamsCount { get; set; }
public int StoriesCount { get; set; }
public int HighlightsCount { get; set; }
public int MessagesCount { get; set; }
public int PaidMessagesCount { get; set; }
}
public class UserListResult
{
public Dictionary<string, long> Users { get; set; } = new();
public Dictionary<string, long> Lists { get; set; } = new();
public string? IgnoredListError { get; set; }
}
public class ListUserSelectionResult
{
public Dictionary<string, long> SelectedUsers { get; set; } = new();
public List<string> UnavailableUsernames { get; set; } = [];
}

View File

@ -1,37 +0,0 @@
namespace OF_DL.Models.Downloads;
/// <summary>
/// Represents the result of a download operation.
/// </summary>
public class DownloadResult
{
/// <summary>
/// Total number of media items processed.
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// Number of newly downloaded media items.
/// </summary>
public int NewDownloads { get; set; }
/// <summary>
/// Number of media items that were already downloaded.
/// </summary>
public int ExistingDownloads { get; set; }
/// <summary>
/// The type of media downloaded (e.g., "Posts", "Messages", "Stories", etc.).
/// </summary>
public string MediaType { get; set; } = string.Empty;
/// <summary>
/// Indicates whether the download operation was successful.
/// </summary>
public bool Success { get; set; } = true;
/// <summary>
/// Optional error message if the download failed.
/// </summary>
public string? ErrorMessage { get; set; }
}

View File

@ -1,17 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Archived;
public class ArchivedDto
{
[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

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

View File

@ -1,96 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
using OF_DL.Utils;
namespace OF_DL.Models.Dtos.Archived;
public class LinkedPostDto
{
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 object StreamId { get; set; } = new();
[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("media")] public List<MediumDto> Media { get; set; } = [];
[JsonProperty("canViewMedia")] public bool? CanViewMedia { get; set; }
[JsonProperty("preview")] public List<object> Preview { get; set; } = [];
}

View File

@ -1,97 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
using OF_DL.Utils;
namespace OF_DL.Models.Dtos.Archived;
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 object StreamId { get; set; } = new();
[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("commentsCount")] public int? CommentsCount { get; set; }
[JsonProperty("mentionedUsers")] public List<object> MentionedUsers { get; set; } = [];
[JsonProperty("linkedUsers")] public List<object> LinkedUsers { 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; } = [];
[JsonProperty("cantCommentReason")] public string CantCommentReason { get; set; } = "";
}

View File

@ -1,35 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Archived;
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; } = new();
[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("files")] public FilesDto Files { get; set; } = new();
[JsonProperty("videoSources")] public VideoSourcesDto VideoSources { get; set; } = new();
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class AuthorDto
{
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("_view")] public string View { get; set; } = "";
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class AvatarThumbsDto
{
[JsonProperty("c50")] public string C50 { get; set; } = "";
[JsonProperty("c144")] public string C144 { get; set; } = "";
}

View File

@ -1,20 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class CountersDto
{
[JsonProperty("audiosCount")] public int? AudiosCount { get; set; }
[JsonProperty("photosCount")] public int? PhotosCount { get; set; }
[JsonProperty("videosCount")] public int? VideosCount { get; set; }
[JsonProperty("mediasCount")] public int? MediasCount { get; set; }
[JsonProperty("postsCount")] public int? PostsCount { get; set; }
[JsonProperty("streamsCount")] public int? StreamsCount { get; set; }
[JsonProperty("archivedPostsCount")] public int? ArchivedPostsCount { get; set; }
}

View File

@ -1,13 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class DashDto
{
[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,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class DrmDto
{
[JsonProperty("manifest")] public ManifestDto Manifest { get; set; } = new();
[JsonProperty("signature")] public SignatureDto Signature { get; set; } = new();
}

View File

@ -1,17 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class FilesDto
{
[JsonProperty("full")] public FullDto Full { get; set; } = new();
[JsonProperty("thumb")] public ThumbDto Thumb { get; set; } = new();
[JsonProperty("preview")] public PreviewDto Preview { get; set; } = new();
[JsonProperty("squarePreview")] public SquarePreviewDto SquarePreview { get; set; } = new();
[JsonProperty("drm")] public DrmDto? Drm { get; set; }
}

View File

@ -1,16 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class FullDto
{
[JsonProperty("url")] public string Url { get; set; } = "";
[JsonProperty("width")] public int Width { get; set; }
[JsonProperty("height")] public int Height { get; set; }
[JsonProperty("size")] public long Size { get; set; }
[JsonProperty("sources")] public List<object> Sources { get; set; } = [];
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class HeaderSizeDto
{
[JsonProperty("width")] public int Width { get; set; }
[JsonProperty("height")] public int Height { get; set; }
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class HeaderThumbsDto
{
[JsonProperty("w480")] public string W480 { get; set; } = "";
[JsonProperty("w760")] public string W760 { get; set; } = "";
}

View File

@ -1,13 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class HlsDto
{
[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,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class ManifestDto
{
[JsonProperty("hls")] public string Hls { get; set; } = "";
[JsonProperty("dash")] public string Dash { get; set; } = "";
}

View File

@ -1,16 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class PreviewDto
{
[JsonProperty("width")] public int? Width { get; set; }
[JsonProperty("height")] public int? Height { get; set; }
[JsonProperty("size")] public int? Size { get; set; }
[JsonProperty("url")] public string Url { get; set; } = "";
[JsonProperty("sources")] public SourcesDto Sources { get; set; } = new();
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class SignatureDto
{
[JsonProperty("hls")] public HlsDto Hls { get; set; } = new();
[JsonProperty("dash")] public DashDto Dash { get; set; } = new();
}

View File

@ -1,18 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class SourceDto
{
[JsonProperty("url")] public string Url { get; set; } = "";
[JsonProperty("width")] public int Width { get; set; }
[JsonProperty("height")] public int Height { get; set; }
[JsonProperty("duration")] public int Duration { get; set; }
[JsonProperty("size")] public long Size { get; set; }
[JsonProperty("sources")] public SourcesDto Sources { get; set; } = new();
}

View File

@ -1,14 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class SourcesDto
{
[JsonProperty("720")] public string _720 { get; set; } = "";
[JsonProperty("240")] public string _240 { get; set; } = "";
[JsonProperty("w150")] public string W150 { get; set; } = "";
[JsonProperty("w480")] public string W480 { get; set; } = "";
}

View File

@ -1,16 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class SquarePreviewDto
{
[JsonProperty("url")] public string Url { get; set; } = "";
[JsonProperty("width")] public int Width { get; set; }
[JsonProperty("height")] public int Height { get; set; }
[JsonProperty("size")] public long Size { get; set; }
[JsonProperty("sources")] public SourcesDto Sources { get; set; } = new();
}

View File

@ -1,44 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Subscriptions;
namespace OF_DL.Models.Dtos.Common;
public class SubscribedByDataDto
{
[JsonProperty("price")] public string? Price { get; set; }
[JsonProperty("newPrice")] public string? NewPrice { get; set; }
[JsonProperty("regularPrice")] public string? RegularPrice { get; set; }
[JsonProperty("subscribePrice")] public string? SubscribePrice { get; set; }
[JsonProperty("discountPercent")] public int? DiscountPercent { get; set; }
[JsonProperty("discountPeriod")] public int? DiscountPeriod { get; set; }
[JsonProperty("subscribeAt")] public DateTime? SubscribeAt { get; set; }
[JsonProperty("expiredAt")] public DateTime? ExpiredAt { get; set; }
[JsonProperty("renewedAt")] public DateTime? RenewedAt { get; set; }
[JsonProperty("discountFinishedAt")] public object? DiscountFinishedAt { get; set; } = new();
[JsonProperty("discountStartedAt")] public object? DiscountStartedAt { get; set; } = new();
[JsonProperty("status")] public string Status { get; set; } = "";
[JsonProperty("isMuted")] public bool? IsMuted { get; set; }
[JsonProperty("unsubscribeReason")] public string UnsubscribeReason { get; set; } = "";
[JsonProperty("duration")] public string Duration { get; set; } = "";
[JsonProperty("showPostsInFeed")] public bool? ShowPostsInFeed { get; set; }
[JsonProperty("subscribes")] public List<SubscribeDto> Subscribes { get; set; } = [];
[JsonProperty("hasActivePaidSubscriptions")]
public bool? HasActivePaidSubscriptions { get; set; }
}

View File

@ -1,54 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Subscriptions;
namespace OF_DL.Models.Dtos.Common;
public class SubscribedOnDataDto
{
[JsonProperty("price")] public string? Price { get; set; }
[JsonProperty("newPrice")] public string? NewPrice { get; set; }
[JsonProperty("regularPrice")] public string? RegularPrice { get; set; }
[JsonProperty("subscribePrice")] public string? SubscribePrice { get; set; }
[JsonProperty("discountPercent")] public int? DiscountPercent { get; set; }
[JsonProperty("discountPeriod")] public int? DiscountPeriod { get; set; }
[JsonProperty("subscribeAt")] public DateTime? SubscribeAt { get; set; }
[JsonProperty("expiredAt")] public DateTime? ExpiredAt { get; set; }
[JsonProperty("renewedAt")] public DateTime? RenewedAt { get; set; }
[JsonProperty("discountFinishedAt")] public object? DiscountFinishedAt { get; set; } = new();
[JsonProperty("discountStartedAt")] public object? DiscountStartedAt { get; set; } = new();
[JsonProperty("status")] public object? Status { get; set; }
[JsonProperty("isMuted")] public bool? IsMuted { get; set; }
[JsonProperty("unsubscribeReason")] public string? UnsubscribeReason { get; set; } = "";
[JsonProperty("duration")] public string Duration { get; set; } = "";
[JsonProperty("tipsSumm")] public string? TipsSumm { get; set; }
[JsonProperty("subscribesSumm")] public string? SubscribesSumm { get; set; }
[JsonProperty("messagesSumm")] public string? MessagesSumm { get; set; }
[JsonProperty("postsSumm")] public string? PostsSumm { get; set; }
[JsonProperty("streamsSumm")] public string? StreamsSumm { get; set; }
[JsonProperty("totalSumm")] public string? TotalSumm { get; set; }
[JsonProperty("subscribes")] public List<SubscribeDto> Subscribes { get; set; } = [];
[JsonProperty("hasActivePaidSubscriptions")]
public bool? HasActivePaidSubscriptions { get; set; }
}

View File

@ -1,14 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class ThumbDto
{
[JsonProperty("url")] public string Url { get; set; } = "";
[JsonProperty("width")] public int Width { get; set; }
[JsonProperty("height")] public int Height { get; set; }
[JsonProperty("size")] public long Size { get; set; }
}

View File

@ -1,8 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class VideoDto
{
[JsonProperty("mp4")] public string Mp4 { get; set; } = "";
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Common;
public class VideoSourcesDto
{
[JsonProperty("720")] public string _720 { get; set; } = "";
[JsonProperty("240")] public string _240 { get; set; } = "";
}

View File

@ -1,22 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Highlights;
public class HighlightMediaDto
{
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("userId")] public long UserId { get; set; }
[JsonProperty("title")] public string Title { get; set; } = "";
[JsonProperty("coverStoryId")] public long CoverStoryId { get; set; }
[JsonProperty("cover")] public string Cover { get; set; } = "";
[JsonProperty("storiesCount")] public int StoriesCount { get; set; }
[JsonProperty("createdAt")] public DateTime? CreatedAt { get; set; }
[JsonProperty("stories")] public List<StoryDto> Stories { get; set; } = [];
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Highlights;
public class HighlightsDto
{
[JsonProperty("list")] public List<ListItemDto> List { get; set; } = [];
[JsonProperty("hasMore")] public bool HasMore { get; set; }
}

View File

@ -1,20 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Highlights;
public class ListItemDto
{
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("userId")] public long UserId { get; set; }
[JsonProperty("title")] public string Title { get; set; } = "";
[JsonProperty("coverStoryId")] public long CoverStoryId { get; set; }
[JsonProperty("cover")] public string Cover { get; set; } = "";
[JsonProperty("storiesCount")] public int StoriesCount { get; set; }
[JsonProperty("createdAt")] public DateTime? CreatedAt { get; set; }
}

View File

@ -1,21 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Highlights;
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("files")] public FilesDto Files { get; set; } = new();
}

View File

@ -1,24 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Highlights;
public class StoryDto
{
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("userId")] public long UserId { get; set; }
[JsonProperty("isWatched")] public bool IsWatched { get; set; }
[JsonProperty("isReady")] public bool IsReady { get; set; }
[JsonProperty("media")] public List<MediumDto> Media { get; set; } = [];
[JsonProperty("createdAt")] public DateTime? CreatedAt { get; set; }
[JsonProperty("question")] public object Question { get; set; } = new();
[JsonProperty("canLike")] public bool CanLike { get; set; }
[JsonProperty("isLiked")] public bool IsLiked { get; set; }
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class HeaderSizeDto
{
[JsonProperty("width")] public int? Width { get; set; }
[JsonProperty("height")] public int? Height { get; set; }
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class HeaderThumbsDto
{
[JsonProperty("w480")] public string W480 { get; set; } = "";
[JsonProperty("w760")] public string W760 { get; set; } = "";
}

View File

@ -1,16 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class ListsStateDto
{
[JsonProperty("id")] public string Id { get; set; } = "";
[JsonProperty("type")] public string Type { get; set; } = "";
[JsonProperty("name")] public string Name { get; set; } = "";
[JsonProperty("hasUser")] public bool HasUser { get; set; }
[JsonProperty("canAddUser")] public bool CanAddUser { get; set; }
}

View File

@ -1,38 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class SubscribeDto
{
[JsonProperty("id")] public object Id { get; set; } = new();
[JsonProperty("userId")] public long? UserId { get; set; }
[JsonProperty("subscriberId")] public int? SubscriberId { get; set; }
[JsonProperty("date")] public DateTime? Date { get; set; }
[JsonProperty("duration")] public int? Duration { get; set; }
[JsonProperty("startDate")] public DateTime? StartDate { get; set; }
[JsonProperty("expireDate")] public DateTime? ExpireDate { get; set; }
[JsonProperty("cancelDate")] public object CancelDate { get; set; } = new();
[JsonProperty("price")] public string? Price { get; set; }
[JsonProperty("regularPrice")] public string? RegularPrice { get; set; }
[JsonProperty("discount")] public string? Discount { get; set; }
[JsonProperty("action")] public string Action { get; set; } = "";
[JsonProperty("type")] public string Type { get; set; } = "";
[JsonProperty("offerStart")] public object OfferStart { get; set; } = new();
[JsonProperty("offerEnd")] public object OfferEnd { get; set; } = new();
[JsonProperty("isCurrent")] public bool? IsCurrent { get; set; }
}

View File

@ -1,40 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class SubscribedByDataDto
{
[JsonProperty("price")] public string? Price { get; set; }
[JsonProperty("newPrice")] public string? NewPrice { get; set; }
[JsonProperty("regularPrice")] public string? RegularPrice { get; set; }
[JsonProperty("subscribePrice")] public string? SubscribePrice { get; set; }
[JsonProperty("discountPercent")] public string? DiscountPercent { get; set; }
[JsonProperty("discountPeriod")] public string? DiscountPeriod { get; set; }
[JsonProperty("subscribeAt")] public DateTime? SubscribeAt { get; set; }
[JsonProperty("expiredAt")] public DateTime? ExpiredAt { get; set; }
[JsonProperty("renewedAt")] public object RenewedAt { get; set; } = new();
[JsonProperty("discountFinishedAt")] public object DiscountFinishedAt { get; set; } = new();
[JsonProperty("discountStartedAt")] public object DiscountStartedAt { get; set; } = new();
[JsonProperty("status")] public string Status { get; set; } = "";
[JsonProperty("isMuted")] public bool? IsMuted { get; set; }
[JsonProperty("unsubscribeReason")] public string UnsubscribeReason { get; set; } = "";
[JsonProperty("duration")] public string Duration { get; set; } = "";
[JsonProperty("showPostsInFeed")] public bool? ShowPostsInFeed { get; set; }
[JsonProperty("subscribes")] public List<SubscribeDto> Subscribes { get; set; } = [];
}

View File

@ -1,54 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class SubscribedOnDataDto
{
[JsonProperty("price")] public string? Price { get; set; }
[JsonProperty("newPrice")] public string? NewPrice { get; set; }
[JsonProperty("regularPrice")] public string? RegularPrice { get; set; }
[JsonProperty("subscribePrice")] public string? SubscribePrice { get; set; }
[JsonProperty("discountPercent")] public string? DiscountPercent { get; set; }
[JsonProperty("discountPeriod")] public string? DiscountPeriod { get; set; }
[JsonProperty("subscribeAt")] public DateTime? SubscribeAt { get; set; }
[JsonProperty("expiredAt")] public DateTime? ExpiredAt { get; set; }
[JsonProperty("renewedAt")] public object RenewedAt { get; set; } = new();
[JsonProperty("discountFinishedAt")] public object DiscountFinishedAt { get; set; } = new();
[JsonProperty("discountStartedAt")] public object DiscountStartedAt { get; set; } = new();
[JsonProperty("status")] public object Status { get; set; } = new();
[JsonProperty("isMuted")] public bool? IsMuted { get; set; }
[JsonProperty("unsubscribeReason")] public string UnsubscribeReason { get; set; } = "";
[JsonProperty("duration")] public string Duration { get; set; } = "";
[JsonProperty("tipsSumm")] public string? TipsSumm { get; set; }
[JsonProperty("subscribesSumm")] public string? SubscribesSumm { get; set; }
[JsonProperty("messagesSumm")] public string? MessagesSumm { get; set; }
[JsonProperty("postsSumm")] public string? PostsSumm { get; set; }
[JsonProperty("streamsSumm")] public string? StreamsSumm { get; set; }
[JsonProperty("totalSumm")] public string? TotalSumm { get; set; }
[JsonProperty("lastActivity")] public DateTime? LastActivity { get; set; }
[JsonProperty("recommendations")] public int? Recommendations { get; set; }
[JsonProperty("subscribes")] public List<object> Subscribes { get; set; } = [];
}

View File

@ -1,16 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class SubscriptionBundleDto
{
[JsonProperty("id")] public long? Id { get; set; }
[JsonProperty("discount")] public string? Discount { get; set; }
[JsonProperty("duration")] public string? Duration { get; set; }
[JsonProperty("price")] public string? Price { get; set; }
[JsonProperty("canBuy")] public bool? CanBuy { get; set; }
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class UserListDto
{
[JsonProperty("list")] public List<UserListItemDto> List { get; set; } = [];
[JsonProperty("hasMore")] public bool? HasMore { get; set; }
}

View File

@ -1,42 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class UserListItemDto
{
[JsonProperty("id")] public string Id { get; set; } = "";
[JsonProperty("type")] public string Type { get; set; } = "";
[JsonProperty("name")] public string Name { get; set; } = "";
[JsonProperty("usersCount")] public int? UsersCount { get; set; }
[JsonProperty("postsCount")] public int? PostsCount { get; set; }
[JsonProperty("canUpdate")] public bool? CanUpdate { get; set; }
[JsonProperty("canDelete")] public bool? CanDelete { get; set; }
[JsonProperty("canManageUsers")] public bool? CanManageUsers { get; set; }
[JsonProperty("canAddUsers")] public bool? CanAddUsers { get; set; }
[JsonProperty("canPinnedToFeed")] public bool? CanPinnedToFeed { get; set; }
[JsonProperty("isPinnedToFeed")] public bool? IsPinnedToFeed { get; set; }
[JsonProperty("canPinnedToChat")] public bool? CanPinnedToChat { get; set; }
[JsonProperty("isPinnedToChat")] public bool? IsPinnedToChat { get; set; }
[JsonProperty("order")] public string Order { get; set; } = "";
[JsonProperty("direction")] public string Direction { get; set; } = "";
[JsonProperty("users")] public List<UserListUserDto> Users { get; set; } = [];
[JsonProperty("customOrderUsersIds")] public List<object> CustomOrderUsersIds { get; set; } = [];
[JsonProperty("posts")] public List<object> Posts { get; set; } = [];
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Lists;
public class UserListUserDto
{
[JsonProperty("id")] public long? Id { get; set; }
[JsonProperty("_view")] public string View { get; set; } = "";
}

View File

@ -1,121 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Lists;
public class UsersListDto
{
[JsonProperty("view")] public string View { get; set; } = "";
[JsonProperty("avatar")] public string Avatar { get; set; } = "";
[JsonProperty("avatarThumbs")] public AvatarThumbsDto AvatarThumbs { get; set; } = new();
[JsonProperty("header")] public string Header { get; set; } = "";
[JsonProperty("headerSize")] public HeaderSizeDto HeaderSize { get; set; } = new();
[JsonProperty("headerThumbs")] public HeaderThumbsDto HeaderThumbs { get; set; } = new();
[JsonProperty("id")] public long? Id { get; set; }
[JsonProperty("name")] public string Name { get; set; } = "";
[JsonProperty("username")] public string Username { get; set; } = "";
[JsonProperty("canLookStory")] public bool? CanLookStory { get; set; }
[JsonProperty("canCommentStory")] public bool? CanCommentStory { get; set; }
[JsonProperty("hasNotViewedStory")] public bool? HasNotViewedStory { get; set; }
[JsonProperty("isVerified")] public bool? IsVerified { get; set; }
[JsonProperty("canPayInternal")] public bool? CanPayInternal { get; set; }
[JsonProperty("hasScheduledStream")] public bool? HasScheduledStream { get; set; }
[JsonProperty("hasStream")] public bool? HasStream { get; set; }
[JsonProperty("hasStories")] public bool? HasStories { get; set; }
[JsonProperty("tipsEnabled")] public bool? TipsEnabled { get; set; }
[JsonProperty("tipsTextEnabled")] public bool? TipsTextEnabled { get; set; }
[JsonProperty("tipsMin")] public int? TipsMin { get; set; }
[JsonProperty("tipsMinInternal")] public int? TipsMinInternal { get; set; }
[JsonProperty("tipsMax")] public int? TipsMax { get; set; }
[JsonProperty("canEarn")] public bool? CanEarn { get; set; }
[JsonProperty("canAddSubscriber")] public bool? CanAddSubscriber { get; set; }
[JsonProperty("subscribePrice")] public string? SubscribePrice { get; set; }
[JsonProperty("subscriptionBundles")] public List<SubscriptionBundleDto> SubscriptionBundles { get; set; } = [];
[JsonProperty("displayName")] public string DisplayName { get; set; } = "";
[JsonProperty("notice")] public string Notice { get; set; } = "";
[JsonProperty("isPaywallRequired")] public bool? IsPaywallRequired { get; set; }
[JsonProperty("unprofitable")] public bool? Unprofitable { get; set; }
[JsonProperty("listsStates")] public List<ListsStateDto> ListsStates { get; set; } = [];
[JsonProperty("isMuted")] public bool? IsMuted { get; set; }
[JsonProperty("isRestricted")] public bool? IsRestricted { get; set; }
[JsonProperty("canRestrict")] public bool? CanRestrict { get; set; }
[JsonProperty("subscribedBy")] public bool? SubscribedBy { get; set; }
[JsonProperty("subscribedByExpire")] public bool? SubscribedByExpire { get; set; }
[JsonProperty("subscribedByExpireDate")]
public DateTime? SubscribedByExpireDate { get; set; }
[JsonProperty("subscribedByAutoprolong")]
public bool? SubscribedByAutoprolong { get; set; }
[JsonProperty("subscribedIsExpiredNow")]
public bool? SubscribedIsExpiredNow { get; set; }
[JsonProperty("currentSubscribePrice")]
public string? CurrentSubscribePrice { get; set; }
[JsonProperty("subscribedOn")] public bool? SubscribedOn { get; set; }
[JsonProperty("subscribedOnExpiredNow")]
public bool? SubscribedOnExpiredNow { get; set; }
[JsonProperty("subscribedOnDuration")] public string SubscribedOnDuration { get; set; } = "";
[JsonProperty("canReport")] public bool? CanReport { get; set; }
[JsonProperty("canReceiveChatMessage")]
public bool? CanReceiveChatMessage { get; set; }
[JsonProperty("hideChat")] public bool? HideChat { get; set; }
[JsonProperty("lastSeen")] public DateTime? LastSeen { get; set; }
[JsonProperty("isPerformer")] public bool? IsPerformer { get; set; }
[JsonProperty("isRealPerformer")] public bool? IsRealPerformer { get; set; }
[JsonProperty("subscribedByData")] public SubscribedByDataDto SubscribedByData { get; set; } = new();
[JsonProperty("subscribedOnData")] public SubscribedOnDataDto SubscribedOnData { get; set; } = new();
[JsonProperty("canTrialSend")] public bool? CanTrialSend { get; set; }
[JsonProperty("isBlocked")] public bool? IsBlocked { get; set; }
[JsonProperty("promoOffers")] public List<object> PromoOffers { get; set; } = [];
}

View File

@ -1,98 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Messages;
public class FromUserDto
{
[JsonProperty("_view")] public string ViewRaw { get; set; } = "";
[JsonProperty("view")] public string View { get; set; } = "";
[JsonProperty("avatar")] public string Avatar { get; set; } = "";
[JsonProperty("avatarThumbs")] public AvatarThumbsDto AvatarThumbs { get; set; } = new();
[JsonProperty("header")] public string Header { get; set; } = "";
[JsonProperty("headerSize")] public HeaderSizeDto HeaderSize { get; set; } = new();
[JsonProperty("headerThumbs")] public HeaderThumbsDto HeaderThumbs { get; set; } = new();
[JsonProperty("id")] public long? Id { get; set; }
[JsonProperty("name")] public string Name { get; set; } = "";
[JsonProperty("username")] public string Username { get; set; } = "";
[JsonProperty("canLookStory")] public bool CanLookStory { get; set; }
[JsonProperty("canCommentStory")] public bool CanCommentStory { get; set; }
[JsonProperty("hasNotViewedStory")] public bool HasNotViewedStory { get; set; }
[JsonProperty("isVerified")] public bool IsVerified { get; set; }
[JsonProperty("canPayInternal")] public bool CanPayInternal { get; set; }
[JsonProperty("hasScheduledStream")] public bool HasScheduledStream { get; set; }
[JsonProperty("hasStream")] public bool HasStream { get; set; }
[JsonProperty("hasStories")] public bool HasStories { get; set; }
[JsonProperty("tipsEnabled")] public bool TipsEnabled { get; set; }
[JsonProperty("tipsTextEnabled")] public bool TipsTextEnabled { get; set; }
[JsonProperty("tipsMin")] public int TipsMin { get; set; }
[JsonProperty("tipsMinInternal")] public int TipsMinInternal { get; set; }
[JsonProperty("tipsMax")] public int TipsMax { get; set; }
[JsonProperty("canEarn")] public bool CanEarn { get; set; }
[JsonProperty("canAddSubscriber")] public bool CanAddSubscriber { get; set; }
[JsonProperty("subscribePrice")] public string SubscribePrice { get; set; } = "";
[JsonProperty("subscriptionBundles")] public List<object> SubscriptionBundles { get; set; } = [];
[JsonProperty("isPaywallRequired")] public bool IsPaywallRequired { get; set; }
[JsonProperty("listsStates")] public List<ListsStateDto> ListsStates { get; set; } = [];
[JsonProperty("isRestricted")] public bool IsRestricted { get; set; }
[JsonProperty("canRestrict")] public bool CanRestrict { get; set; }
[JsonProperty("subscribedBy")] public object SubscribedBy { get; set; } = new();
[JsonProperty("subscribedByExpire")] public object SubscribedByExpire { get; set; } = new();
[JsonProperty("subscribedByExpireDate")]
public DateTime? SubscribedByExpireDate { get; set; }
[JsonProperty("subscribedByAutoprolong")]
public object SubscribedByAutoprolong { get; set; } = new();
[JsonProperty("subscribedIsExpiredNow")]
public bool SubscribedIsExpiredNow { get; set; }
[JsonProperty("currentSubscribePrice")]
public object CurrentSubscribePrice { get; set; } = new();
[JsonProperty("subscribedOn")] public object SubscribedOn { get; set; } = new();
[JsonProperty("subscribedOnExpiredNow")]
public object SubscribedOnExpiredNow { get; set; } = new();
[JsonProperty("subscribedOnDuration")] public object SubscribedOnDuration { get; set; } = new();
[JsonProperty("callPrice")] public int CallPrice { get; set; }
[JsonProperty("lastSeen")] public DateTime? LastSeen { get; set; }
[JsonProperty("canReport")] public bool CanReport { get; set; }
}

View File

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

View File

@ -1,66 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Messages;
public class ListItemDto
{
[JsonProperty("responseType")] public string ResponseType { get; set; } = "";
[JsonProperty("text")] public string Text { get; set; } = "";
[JsonProperty("giphyId")] public object GiphyId { get; set; } = new();
[JsonProperty("lockedText")] public bool? LockedText { get; set; }
[JsonProperty("isFree")] public bool? IsFree { get; set; }
[JsonProperty("price")] public string Price { get; set; } = "";
[JsonProperty("isMediaReady")] public bool? IsMediaReady { get; set; }
[JsonProperty("mediaCount")] public int? MediaCount { get; set; }
[JsonProperty("media")] public List<MediumDto> Media { get; set; } = [];
[JsonProperty("previews")] public List<object> Previews { get; set; } = [];
[JsonProperty("isTip")] public bool? IsTip { get; set; }
[JsonProperty("isReportedByMe")] public bool? IsReportedByMe { get; set; }
[JsonProperty("isCouplePeopleMedia")] public bool? IsCouplePeopleMedia { get; set; }
[JsonProperty("queueId")] public object QueueId { get; set; } = new();
[JsonProperty("fromUser")] public FromUserDto FromUser { get; set; } = new();
[JsonProperty("isFromQueue")] public bool? IsFromQueue { get; set; }
[JsonProperty("canUnsendQueue")] public bool? CanUnsendQueue { get; set; }
[JsonProperty("unsendSecondsQueue")] public int? UnsendSecondsQueue { get; set; }
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("isOpened")] public bool? IsOpened { get; set; }
[JsonProperty("isNew")] public bool? IsNew { get; set; }
[JsonProperty("createdAt")] public DateTime? CreatedAt { get; set; }
[JsonProperty("changedAt")] public DateTime? ChangedAt { get; set; }
[JsonProperty("cancelSeconds")] public int? CancelSeconds { get; set; }
[JsonProperty("isLiked")] public bool? IsLiked { get; set; }
[JsonProperty("canPurchase")] public bool? CanPurchase { get; set; }
[JsonProperty("canPurchaseReason")] public string CanPurchaseReason { get; set; } = "";
[JsonProperty("canReport")] public bool? CanReport { get; set; }
[JsonProperty("canBePinned")] public bool? CanBePinned { get; set; }
[JsonProperty("isPinned")] public bool? IsPinned { get; set; }
}

View File

@ -1,18 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Messages;
public class ListsStateDto
{
[JsonProperty("id")] public string Id { get; set; } = "";
[JsonProperty("type")] public string Type { get; set; } = "";
[JsonProperty("name")] public string Name { get; set; } = "";
[JsonProperty("hasUser")] public bool HasUser { get; set; }
[JsonProperty("canAddUser")] public bool CanAddUser { get; set; }
[JsonProperty("cannotAddUserReason")] public string CannotAddUserReason { get; set; } = "";
}

View File

@ -1,37 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Messages;
public class MediumDto
{
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("canView")] public bool CanView { get; set; }
[JsonProperty("type")] public string Type { get; set; } = "";
[JsonProperty("src")] public string Src { get; set; } = "";
[JsonProperty("preview")] public string Preview { get; set; } = "";
[JsonProperty("thumb")] public string Thumb { get; set; } = "";
[JsonProperty("locked")] public object Locked { get; set; } = new();
[JsonProperty("duration")] public int? Duration { get; set; }
[JsonProperty("hasError")] public bool? HasError { get; set; }
[JsonProperty("squarePreview")] public string SquarePreview { get; set; } = "";
[JsonProperty("video")] public VideoDto Video { get; set; } = new();
[JsonProperty("videoSources")] public VideoSourcesDto VideoSources { get; set; } = new();
[JsonProperty("source")] public SourceDto Source { get; set; } = new();
[JsonProperty("info")] public InfoDto Info { get; set; } = new();
[JsonProperty("files")] public FilesDto Files { get; set; } = new();
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Messages;
public class MessagesDto
{
[JsonProperty("list")] public List<ListItemDto> List { get; set; } = [];
[JsonProperty("hasMore")] public bool HasMore { get; set; }
}

View File

@ -1,60 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Messages;
public class SingleMessageDto
{
[JsonProperty("responseType")] public string ResponseType { get; set; } = "";
[JsonProperty("text")] public string Text { get; set; } = "";
[JsonProperty("giphyId")] public object GiphyId { get; set; } = new();
[JsonProperty("lockedText")] public bool LockedText { get; set; }
[JsonProperty("isFree")] public bool IsFree { get; set; }
[JsonProperty("price")] public double Price { get; set; }
[JsonProperty("isMediaReady")] public bool IsMediaReady { get; set; }
[JsonProperty("mediaCount")] public int MediaCount { get; set; }
[JsonProperty("media")] public List<MediumDto> Media { get; set; } = [];
[JsonProperty("previews")] public List<object> Previews { get; set; } = [];
[JsonProperty("isTip")] public bool IsTip { get; set; }
[JsonProperty("isReportedByMe")] public bool IsReportedByMe { get; set; }
[JsonProperty("isCouplePeopleMedia")] public bool IsCouplePeopleMedia { get; set; }
[JsonProperty("queueId")] public long QueueId { get; set; }
[JsonProperty("fromUser")] public FromUserDto FromUser { get; set; } = new();
[JsonProperty("isFromQueue")] public bool IsFromQueue { get; set; }
[JsonProperty("canUnsendQueue")] public bool CanUnsendQueue { get; set; }
[JsonProperty("unsendSecondsQueue")] public int UnsendSecondsQueue { get; set; }
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("isOpened")] public bool IsOpened { get; set; }
[JsonProperty("isNew")] public bool IsNew { get; set; }
[JsonProperty("createdAt")] public DateTime? CreatedAt { get; set; }
[JsonProperty("changedAt")] public DateTime? ChangedAt { get; set; }
[JsonProperty("cancelSeconds")] public int CancelSeconds { get; set; }
[JsonProperty("isLiked")] public bool IsLiked { get; set; }
[JsonProperty("canPurchase")] public bool CanPurchase { get; set; }
[JsonProperty("canReport")] public bool CanReport { get; set; }
}

View File

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

View File

@ -1,101 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
using OF_DL.Utils;
namespace OF_DL.Models.Dtos.Posts;
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 object StreamId { get; set; } = new();
[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("votingType")] public int? VotingType { 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("canVote")] public bool? CanVote { 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

@ -1,37 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Posts;
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; }
[JsonProperty("source")] public SourceDto? Source { get; set; }
[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

@ -1,14 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Posts;
public class PostDto
{
[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; } = "";
}

View File

@ -1,99 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
using OF_DL.Utils;
namespace OF_DL.Models.Dtos.Posts;
public class SinglePostDto
{
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 string 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("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

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Purchased;
public class FromUserDto
{
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("_view")] public string View { get; set; } = "";
}

View File

@ -1,72 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
using MessageDtos = OF_DL.Models.Dtos.Messages;
namespace OF_DL.Models.Dtos.Purchased;
public class ListItemDto
{
[JsonProperty("responseType")] public string ResponseType { get; set; } = "";
[JsonProperty("text")] public string Text { get; set; } = "";
[JsonProperty("giphyId")] public object GiphyId { get; set; } = new();
[JsonProperty("lockedText")] public bool? LockedText { get; set; }
[JsonProperty("isFree")] public bool? IsFree { get; set; }
[JsonProperty("price")] public string? Price { get; set; }
[JsonProperty("isMediaReady")] public bool? IsMediaReady { get; set; }
[JsonProperty("mediaCount")] public int? MediaCount { get; set; }
[JsonProperty("media")] public List<MessageDtos.MediumDto>? Media { get; set; }
[JsonProperty("previews")] public List<object>? Previews { get; set; }
[JsonProperty("preview")] public List<object>? Preview { get; set; }
[JsonProperty("isTip")] public bool? IsTip { get; set; }
[JsonProperty("isReportedByMe")] public bool? IsReportedByMe { get; set; }
[JsonProperty("isCouplePeopleMedia")] public bool? IsCouplePeopleMedia { get; set; }
[JsonProperty("queueId")] public object QueueId { get; set; } = new();
[JsonProperty("fromUser")] public FromUserDto? FromUser { get; set; }
[JsonProperty("author")] public AuthorDto? Author { get; set; }
[JsonProperty("isFromQueue")] public bool? IsFromQueue { get; set; }
[JsonProperty("canUnsendQueue")] public bool? CanUnsendQueue { get; set; }
[JsonProperty("unsendSecondsQueue")] public int? UnsendSecondsQueue { get; set; }
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("isOpened")] public bool IsOpened { get; set; }
[JsonProperty("isNew")] public bool? IsNew { get; set; }
[JsonProperty("createdAt")] public DateTime? CreatedAt { get; set; }
[JsonProperty("postedAt")] public DateTime? PostedAt { get; set; }
[JsonProperty("changedAt")] public DateTime? ChangedAt { get; set; }
[JsonProperty("cancelSeconds")] public int? CancelSeconds { get; set; }
[JsonProperty("isLiked")] public bool? IsLiked { get; set; }
[JsonProperty("canPurchase")] public bool? CanPurchase { get; set; }
[JsonProperty("canReport")] public bool? CanReport { get; set; }
[JsonProperty("isCanceled")] public bool? IsCanceled { get; set; }
[JsonProperty("isArchived")] public bool? IsArchived { get; set; }
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Purchased;
public class PurchasedDto
{
[JsonProperty("list")] public List<ListItemDto> List { get; set; } = [];
[JsonProperty("hasMore")] public bool HasMore { get; set; }
}

View File

@ -1,21 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Stories;
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("files")] public FilesDto Files { get; set; } = new();
}

View File

@ -1,24 +0,0 @@
using Newtonsoft.Json;
namespace OF_DL.Models.Dtos.Stories;
public class StoryDto
{
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("userId")] public long UserId { get; set; }
[JsonProperty("isWatched")] public bool IsWatched { get; set; }
[JsonProperty("isReady")] public bool IsReady { get; set; }
[JsonProperty("media")] public List<MediumDto> Media { get; set; } = [];
[JsonProperty("createdAt")] public DateTime? CreatedAt { get; set; }
[JsonProperty("question")] public object Question { get; set; } = new();
[JsonProperty("canLike")] public bool CanLike { get; set; }
[JsonProperty("isLiked")] public bool IsLiked { get; set; }
}

View File

@ -1,11 +0,0 @@
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

@ -1,101 +0,0 @@
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

@ -1,37 +0,0 @@
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

@ -1,17 +0,0 @@
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

@ -1,111 +0,0 @@
using Newtonsoft.Json;
using OF_DL.Models.Dtos.Common;
namespace OF_DL.Models.Dtos.Subscriptions;
public class ListItemDto
{
[JsonProperty("view")] public string View { get; set; } = "";
[JsonProperty("avatar")] public string? Avatar { get; set; }
[JsonProperty("avatarThumbs")] public AvatarThumbsDto AvatarThumbs { get; set; } = new();
[JsonProperty("header")] public string? Header { get; set; }
[JsonProperty("headerSize")] public HeaderSizeDto HeaderSize { get; set; } = new();
[JsonProperty("headerThumbs")] public HeaderThumbsDto HeaderThumbs { get; set; } = new();
[JsonProperty("id")] public long Id { get; set; }
[JsonProperty("name")] public string Name { get; set; } = "";
[JsonProperty("username")] public string? Username { get; set; }
[JsonProperty("canLookStory")] public bool? CanLookStory { get; set; }
[JsonProperty("canCommentStory")] public bool? CanCommentStory { get; set; }
[JsonProperty("hasNotViewedStory")] public bool? HasNotViewedStory { get; set; }
[JsonProperty("isVerified")] public bool? IsVerified { get; set; }
[JsonProperty("canPayInternal")] public bool? CanPayInternal { get; set; }
[JsonProperty("hasScheduledStream")] public bool? HasScheduledStream { get; set; }
[JsonProperty("hasStream")] public bool? HasStream { get; set; }
[JsonProperty("hasStories")] public bool? HasStories { get; set; }
[JsonProperty("tipsEnabled")] public bool? TipsEnabled { get; set; }
[JsonProperty("tipsTextEnabled")] public bool? TipsTextEnabled { get; set; }
[JsonProperty("tipsMin")] public int? TipsMin { get; set; }
[JsonProperty("tipsMinInternal")] public int? TipsMinInternal { get; set; }
[JsonProperty("tipsMax")] public int? TipsMax { get; set; }
[JsonProperty("canEarn")] public bool? CanEarn { get; set; }
[JsonProperty("canAddSubscriber")] public bool? CanAddSubscriber { get; set; }
[JsonProperty("subscribePrice")] public string? SubscribePrice { get; set; }
[JsonProperty("isPaywallRequired")] public bool? IsPaywallRequired { get; set; }
[JsonProperty("unprofitable")] public bool? Unprofitable { get; set; }
[JsonProperty("listsStates")] public List<ListsStateDto> ListsStates { get; set; } = [];
[JsonProperty("isMuted")] public bool? IsMuted { get; set; }
[JsonProperty("isRestricted")] public bool? IsRestricted { get; set; }
[JsonProperty("canRestrict")] public bool? CanRestrict { get; set; }
[JsonProperty("subscribedBy")] public bool? SubscribedBy { get; set; }
[JsonProperty("subscribedByExpire")] public bool? SubscribedByExpire { get; set; }
[JsonProperty("subscribedByExpireDate")] public DateTime? SubscribedByExpireDate { get; set; }
[JsonProperty("subscribedByAutoprolong")] public bool? SubscribedByAutoprolong { get; set; }
[JsonProperty("subscribedIsExpiredNow")] public bool? SubscribedIsExpiredNow { get; set; }
[JsonProperty("currentSubscribePrice")] public string? CurrentSubscribePrice { get; set; }
[JsonProperty("subscribedOn")] public bool? SubscribedOn { get; set; }
[JsonProperty("subscribedOnExpiredNow")] public bool? SubscribedOnExpiredNow { get; set; }
[JsonProperty("subscribedOnDuration")] public string SubscribedOnDuration { get; set; } = "";
[JsonProperty("canReport")] public bool? CanReport { get; set; }
[JsonProperty("canReceiveChatMessage")] public bool? CanReceiveChatMessage { get; set; }
[JsonProperty("hideChat")] public bool? HideChat { get; set; }
[JsonProperty("lastSeen")] public DateTime? LastSeen { get; set; }
[JsonProperty("isPerformer")] public bool? IsPerformer { get; set; }
[JsonProperty("isRealPerformer")] public bool? IsRealPerformer { get; set; }
[JsonProperty("subscribedByData")] public SubscribedByDataDto SubscribedByData { get; set; } = new();
[JsonProperty("subscribedOnData")] public SubscribedOnDataDto SubscribedOnData { get; set; } = new();
[JsonProperty("canTrialSend")] public bool? CanTrialSend { get; set; }
[JsonProperty("isBlocked")] public bool? IsBlocked { get; set; }
[JsonProperty("displayName")] public string DisplayName { get; set; } = "";
[JsonProperty("notice")] public string Notice { get; set; } = "";
}

Some files were not shown because too many files have changed in this diff Show More