Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
9.0.x

- name: Restore dependencies
run: dotnet restore TiktokExplode.NET.sln
run: dotnet restore TiktokExplode.NET.slnx

- name: Build
run: dotnet build TiktokExplode.NET.sln --no-restore --configuration Release
run: dotnet build TiktokExplode.NET.slnx --no-restore --configuration Release

- name: Run tests
run: dotnet test TiktokExplode.Tests/TiktokExplode.Tests.csproj --no-build --configuration Release --logger "console;verbosity=normal"
Expand All @@ -50,10 +50,10 @@ jobs:
9.0.x

- name: Restore dependencies
run: dotnet restore TiktokExplode.NET.sln
run: dotnet restore TiktokExplode.NET.slnx

- name: Pack
run: dotnet pack TiktokExplode.NET.sln --no-restore --configuration Release
run: dotnet pack TiktokExplode.NET.slnx --no-restore --configuration Release

- name: Push to NuGet
run: dotnet nuget push "**/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
4 changes: 2 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"type": "process",
"args": [
"build",
"${workspaceFolder}/TiktokExplode.NET.sln",
"${workspaceFolder}/TiktokExplode.NET.slnx",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
Expand All @@ -19,7 +19,7 @@
"type": "process",
"args": [
"publish",
"${workspaceFolder}/TiktokExplode.NET.sln",
"${workspaceFolder}/TiktokExplode.NET.slnx",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
Expand Down
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![NuGet](https://img.shields.io/nuget/v/TiktokExplode.svg?label=TiktokExplode)](https://www.nuget.org/packages/TiktokExplode)
[![NuGet](https://img.shields.io/nuget/v/TiktokExplode.Infrastructure.svg?label=TiktokExplode.Infrastructure)](https://www.nuget.org/packages/TiktokExplode.Infrastructure)
[![NuGet](https://img.shields.io/nuget/v/TiktokExplode.All.svg?label=TiktokExplode.All)](https://www.nuget.org/packages/TiktokExplode.All)
[![NuGet](https://img.shields.io/nuget/v/TiktokExplode.Extensions.DependencyInjection.svg?label=TiktokExplode.Extensions.DependencyInjection)](https://www.nuget.org/packages/TiktokExplode.Extensions.DependencyInjection)
[![.NET](https://img.shields.io/badge/.NET-8.0%20%7C%209.0-512BD4)](https://dotnet.microsoft.com)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Expand Down Expand Up @@ -46,6 +47,14 @@ dotnet add package TiktokExplode.Infrastructure
> `TiktokExplode.Infrastructure` automatically brings in `TiktokExplode` (domain) as a transitive dependency.
> Install `TiktokExplode` alone only if you need the domain models/interfaces without the infrastructure.

**Using Microsoft.Extensions.DependencyInjection?**

```
dotnet add package TiktokExplode.Extensions.DependencyInjection
```

> Adds `AddTiktokExplode()` on `IServiceCollection`. See the [Dependency Injection](#dependency-injection) section.

> **Note:** `TiktokExplode.Infrastructure` depends on [Microsoft.Playwright](https://playwright.dev/dotnet/). After installation, run the following once to download the browser binaries:
>
> ```
Expand Down Expand Up @@ -205,6 +214,47 @@ Returned by `DownloadAsync` and `DownloadWatermarkedAsync`. Implements `IAsyncDi

---

## Dependency Injection

`TiktokExplode.Extensions.DependencyInjection` provides a fluent `AddTiktokExplode()` extension method for registering all TiktokExplode services into the .NET DI container.

```csharp
// Default — Playwright fetcher, all defaults
services.AddTiktokExplode();

// Custom — Playwright with visible browser window
services.AddTiktokExplode(b => b
.UsePlaywrightFetcher(o => o.Headless = false));

// HTTP fetcher — lighter, no browser dependency
services.AddTiktokExplode(b => b
.UseHttpFetcher(o => o.WarmupDelay = TimeSpan.Zero)
.ConfigureTiktok(o => o.MaxWafRetries = 5));
```

Registered services:

| Service | Implementation | Lifetime |
| --- | --- | --- |
| `IVideoClient` | `TiktokClient` | Singleton |
| `IPageFetcher` | `PlaywrightFetcher` or `HttpFetcher` | Singleton |
| `TikTokOptions` | — | Singleton |
| `PlaywrightFetcherOptions` or `HttpFetcherOptions` | — | Singleton |

```csharp
// Consume in your services via constructor injection
public class MyService(IVideoClient client)
{
public async Task<string> GetTitleAsync(string url)
{
var video = await client.GetVideoAsync(url);
return video.Description;
}
}
```

---

## Error Handling

```csharp
Expand Down Expand Up @@ -255,6 +305,8 @@ TiktokExplode.Infrastructure/ # HTTP + browser automation (Playwright + AngleSha
Common/ # StreamExtensions, TiktokClientExtensions

TiktokExplode.All/ # Meta-package — installs both packages above in one command

TiktokExplode.Extensions.DependencyInjection/ # AddTiktokExplode() for Microsoft.Extensions.DI
```

---
Expand Down
87 changes: 87 additions & 0 deletions TiktokExplode.Extensions.DependencyInjection/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# TiktokExplode.Extensions.DependencyInjection

[![NuGet](https://img.shields.io/nuget/v/TiktokExplode.Extensions.DependencyInjection.svg)](https://www.nuget.org/packages/TiktokExplode.Extensions.DependencyInjection)
[![.NET](https://img.shields.io/badge/.NET-8.0%20%7C%209.0-512BD4)](https://dotnet.microsoft.com)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Ts-Pytham/TiktokExplode/blob/master/LICENSE)

`Microsoft.Extensions.DependencyInjection` integration for [TiktokExplode](https://github.com/Ts-Pytham/TiktokExplode).

Provides `AddTiktokExplode()` — a fluent extension method on `IServiceCollection` that registers `IVideoClient` and lets you choose between the Playwright (default) or HTTP fetcher strategy.

---

## Installation

```
dotnet add package TiktokExplode.Extensions.DependencyInjection
```

---

## Usage

```csharp
// Default — Playwright fetcher, all defaults
services.AddTiktokExplode();

// Playwright with a visible browser window (useful for debugging)
services.AddTiktokExplode(b => b
.UsePlaywrightFetcher(o => o.Headless = false));

// HTTP fetcher — no browser dependency, lighter footprint
services.AddTiktokExplode(b => b
.UseHttpFetcher(o => o.WarmupDelay = TimeSpan.Zero)
.ConfigureTiktok(o => o.MaxWafRetries = 5));
```

Then inject `IVideoClient` normally:

```csharp
public class MyService(IVideoClient client)
{
public async Task<string> GetTitleAsync(string url)
{
var video = await client.GetVideoAsync(url);
return video.Description;
}
}
```

---

## Registered services

| Service | Implementation | Lifetime |
| --- | --- | --- |
| `IVideoClient` | `TiktokClient` | Singleton |
| `IPageFetcher` | `PlaywrightFetcher` or `HttpFetcher` | Singleton |
| `TikTokOptions` | — | Singleton |
| `PlaywrightFetcherOptions` or `HttpFetcherOptions` | — | Singleton |

---

## Builder API

| Method | Description |
| --- | --- |
| `ConfigureTiktok(Action<TikTokOptions>?)` | Configures WAF retry count and base delay |
| `UsePlaywrightFetcher(Action<PlaywrightFetcherOptions>?)` | Uses a real Chromium browser via Playwright (default) |
| `UseHttpFetcher(Action<HttpFetcherOptions>?)` | Uses a plain `HttpClient` — faster start, may be WAF-blocked |

> **Note:** `UsePlaywrightFetcher` and `UseHttpFetcher` are mutually exclusive — the last one called wins.

---

## Related packages

| Package | Description |
| --- | --- |
| [`TiktokExplode`](https://www.nuget.org/packages/TiktokExplode) | Domain layer — models, interfaces, exceptions |
| [`TiktokExplode.Infrastructure`](https://www.nuget.org/packages/TiktokExplode.Infrastructure) | HTTP + Playwright fetchers, parser, download client |
| [`TiktokExplode.All`](https://www.nuget.org/packages/TiktokExplode.All) | Meta-package — installs domain + infrastructure together |

---

## License

MIT — see [LICENSE](https://github.com/Ts-Pytham/TiktokExplode/blob/master/LICENSE) for details.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<RootNamespace>TiktokExplode.Extensions.DependencyInjection</RootNamespace>
<AssemblyName>TiktokExplode.Extensions.DependencyInjection</AssemblyName>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup>
<PackageId>TiktokExplode.Extensions.DependencyInjection</PackageId>
<Version>1.0.0</Version>
<Authors>Ts-Pytham</Authors>
<Owners>Ts-Pytham</Owners>
<Description>Microsoft.Extensions.DependencyInjection integration for TiktokExplode. Provides AddTiktokExplode() with a fluent builder to register IVideoClient and choose between Playwright or HTTP fetcher strategies.</Description>
<PackageTags>tiktok;downloader;dependency-injection;di;extensions;dotnet</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/Ts-Pytham/TiktokExplode</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ts-Pytham/TiktokExplode</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\"/>
<None Include="..\assets\icon.png" Pack="true" PackagePath="\"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.5" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\TiktokExplode\TiktokExplode.csproj" />
<ProjectReference Include="..\TiktokExplode.Infrastructure\TiktokExplode.Infrastructure.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Microsoft.Extensions.DependencyInjection;
using TiktokExplode.Domain.Abstractions;
using TiktokExplode.Infrastructure.Clients;
using TiktokExplode.Infrastructure.Fetchers;
using TiktokExplode.Infrastructure.Options;

namespace TiktokExplode.Extensions.DependencyInjection;

/// <summary>
/// Fluent builder for configuring TiktokExplode services before they are registered
/// into an <see cref="IServiceCollection"/>.
/// Obtain an instance through <see cref="TiktokExplodeServiceCollectionExtensions.AddTiktokExplode"/>.
/// </summary>
public sealed class TiktokExplodeBuilder(IServiceCollection services)
{
private readonly TikTokOptions _tiktokOptions = new();
private Action<IServiceCollection> _fetcherRegistration = RegisterPlaywright(new());

/// <summary>
/// Configures the WAF-retry behaviour of <c>TiktokClient</c>.
/// </summary>
/// <param name="options">Delegate that mutates a <see cref="TikTokOptions"/> instance.</param>
/// <returns>The same builder for chaining.</returns>
public TiktokExplodeBuilder ConfigureTiktok(Action<TikTokOptions>? options = null)
{
options?.Invoke(_tiktokOptions);
return this;
}

/// <summary>
/// Configures the Playwright-based page fetcher as the active <see cref="IPageFetcher"/>.
/// This is the default strategy — call this only when you need to customise the options.
/// </summary>
/// <param name="options">Delegate that mutates a <see cref="PlaywrightFetcherOptions"/> instance.</param>
/// <returns>The same builder for chaining.</returns>
public TiktokExplodeBuilder UsePlaywrightFetcher(Action<PlaywrightFetcherOptions>? options = null)
{
var playwrightOptions = new PlaywrightFetcherOptions();
options?.Invoke(playwrightOptions);
_fetcherRegistration = RegisterPlaywright(playwrightOptions);
return this;
}

/// <summary>
/// Configures the lightweight HTTP-based page fetcher as the active <see cref="IPageFetcher"/>.
/// This strategy avoids the Playwright browser dependency but may be blocked by TikTok's WAF.
/// </summary>
/// <param name="options">Delegate that mutates an <see cref="HttpFetcherOptions"/> instance.</param>
/// <returns>The same builder for chaining.</returns>
public TiktokExplodeBuilder UseHttpFetcher(Action<HttpFetcherOptions>? options = null)
{
var httpFetcherOptions = new HttpFetcherOptions();
options?.Invoke(httpFetcherOptions);
_fetcherRegistration = RegisterHttp(httpFetcherOptions);
return this;
}

/// <summary>
/// Applies all pending registrations to the underlying <see cref="IServiceCollection"/>.
/// Registers <see cref="TikTokOptions"/>, the chosen <see cref="IPageFetcher"/>,
/// and <see cref="IVideoClient"/> as singletons.
/// </summary>
/// <returns>The service collection for further chaining.</returns>
public IServiceCollection Build()
{
services.AddSingleton(_tiktokOptions);
_fetcherRegistration(services);
services.AddSingleton<IVideoClient, TiktokClient>();
return services;
}

private static Action<IServiceCollection> RegisterPlaywright(PlaywrightFetcherOptions options)
{
return services =>
{
services.AddSingleton(options);
services.AddSingleton<IPageFetcher, PlaywrightFetcher>();
};
}

private static Action<IServiceCollection> RegisterHttp(HttpFetcherOptions options)
{
return services =>
{
services.AddSingleton(options);
services.AddSingleton<IPageFetcher, HttpFetcher>();
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.Extensions.DependencyInjection;

namespace TiktokExplode.Extensions.DependencyInjection;

/// <summary>
/// Extension methods on <see cref="IServiceCollection"/> for registering TiktokExplode services.
/// </summary>
public static class TiktokExplodeServiceCollectionExtensions
{
extension(IServiceCollection services)
{
/// <summary>
/// Registers TiktokExplode services into the service collection.
/// By default, uses the Playwright-based fetcher with default options.
/// </summary>
/// <param name="configure">
/// Optional delegate to customise the fetcher strategy and options via <see cref="TiktokExplodeBuilder"/>.
/// </param>
/// <returns>The same <see cref="IServiceCollection"/> for chaining.</returns>
/// <example>
/// <code>
/// // Default — Playwright fetcher, all defaults
/// services.AddTiktokExplode();
///
/// // Custom — HTTP fetcher, no warmup delay
/// services.AddTiktokExplode(b => b
/// .UseHttpFetcher(o => o.WarmupDelay = TimeSpan.Zero)
/// .ConfigureTiktok(o => o.MaxWafRetries = 5));
/// </code>
/// </example>
public IServiceCollection AddTiktokExplode(
Action<TiktokExplodeBuilder>? configure = null)
{
var builder = new TiktokExplodeBuilder(services);
configure?.Invoke(builder);

return builder.Build();
}
}
}
4 changes: 2 additions & 2 deletions TiktokExplode.Infrastructure/Options/HttpFetcherOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ public sealed class HttpFetcherOptions
/// cookies before they are needed.
/// Defaults to <c>1 200 ms</c>.
/// </summary>
public TimeSpan WarmupDelay { get; init; } = TimeSpan.FromMilliseconds(1200);
public TimeSpan WarmupDelay { get; set; } = TimeSpan.FromMilliseconds(1200);

/// <summary>
/// The <c>User-Agent</c> header value sent with all HTTP requests.
/// Defaults to a recent Chrome on Windows UA string.
/// </summary>
public string UserAgent { get; init; } = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
public string UserAgent { get; set; } = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
}
Loading
Loading